You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3167010 on 2016/10/19 by Dmitriy.Dyomin Fix for LevelStreaming getting stuck, and World->PersistentLevel null assert Contributed by Funcom: https://udn.unrealengine.com/questions/312900/fix-for-levelstreaming-getting-stuck-and-world-per.html #jira UE-36397 Change 3166824 on 2016/10/18 by Alexis.Matte Fix re-import of mesh material assignment regression #jira UE-37479 Change 3166821 on 2016/10/18 by Alexis.Matte Make sure the old asset are build correctly #jira UE-37461 Change 3166740 on 2016/10/18 by Lina.Halper Fix crash with hide name when bone buffer doesn't exist due to slave component #jira: UE-37467 Change 3166737 on 2016/10/18 by Jeff.Campeau Fix double release in PhysX if there's an exception during shutdown #jira UE-37058 Change 3166733 on 2016/10/18 by Chris.Babcock Add missing MultiviewOVR functions for Android deferred #jira UE-37401 #ue4 #android Change 3166719 on 2016/10/18 by Richard.TalbotWatkin Fixed crash when setting a closed loop on a SplineComponent with no spline points defined. #jira UE-37449 - Editor Crashes When Enabling Closed Loop on Spline in Blueprint Change 3166701 on 2016/10/18 by Daniel.Wright Call HandleLegacyMapBuildData before saving a cooked package - fixes cases where map packages get loaded in the cooker via FRedirectCollector::ResolveStringAssetReference in Core, where there is no opportunity to do post-load legacy map fixups. #jira UE-37317 Change 3166549 on 2016/10/18 by Alexis.Matte Make sure the vertex paint circle brush is display when painting #jira UE-37462 Change 3166459 on 2016/10/18 by Mitchell.Wilson Removed duplicate mapping of Gamepad_RightY for thrust in BP Flying template #jira UE-37471 Change 3166435 on 2016/10/18 by Mitchell.Wilson Cleared preview mesh on skydome materials used in ShooterGame to resolve UDK_ProceduralSky_4UVChannel warnings #jira UE-29718 Change 3166363 on 2016/10/18 by Mitchell.Wilson copied EditorPlane to UI folder of ShooterGame and named it MenuPlane. Replaced EditorPlane in ShooterEntry with MenuPlane. Rebuilt lighting for shootergame levels and added build data for each level. #jira UE-37435 Change 3166342 on 2016/10/18 by Rolando.Caloca UE4.14 - Fix for crash on splines with decals #jira UE-36864 Change 3166315 on 2016/10/18 by Ori.Cohen Implement copy and assignment operator for convexElem struct and aggregate geometry to avoid shallow copy of raw pointers. #JIRA UE-21572, UE-37429 Change 3166274 on 2016/10/18 by Peter.Sauerbrei deprecate iOS 7 and provide better messaging for the IOS versions we no longer directly support #jira UE-37034 Change 3166238 on 2016/10/18 by Max.Preussner MediaPlayerEditor: Failure opening media, even though it opened successfully (UE-37470) Copied from Dev-Sequencer CL# 3166218 #jira UE-37470 Change 3166209 on 2016/10/18 by Daniel.Lamb Fixed issue with launch on marking packages as dirty. #test Cook shooter game #jira UE-37455 Change 3165901 on 2016/10/18 by Jamie.Dale Fixed Clang error caused by missing return type #jira UE-37421 Change3165774on 2016/10/18 by Steve.Robb Fix for FMallocBinned::GetAllocationSize() for aligned allocations. Copied from CL# 3165739. #jira UE-37249 #jira UE-37243 Change 3165457 on 2016/10/17 by Max.Chen Sequencer: Default number frame handles to 0 so that there's no change in behavior when rendering out a master sequence of shots. Handle frames need to enabled explicitly by the user. #jira UE-37420 Change3165444on 2016/10/17 by Max.Chen Sequencer: Fix reset view range so that it's a no-op when the range is negative. #jira UE-37412 Change 3165257 on 2016/10/17 by Keli.Hlodversson Fix crash when exiting using SteamVR if using VR controllers and launched from steam. #jira UE-37432 Change 3165225 on 2016/10/17 by Nick.Darnell Strategy Game - No longer overrides the engine default iOS build machine to use. Shooter Game - Has been upgraded to user the newer location of the DPI scale curve. #jira UE-37001 Change 3165110 on 2016/10/17 by Michael.Trepka Fixed GetPlatformLibExtension for iOS and tvOS in PhysX build script #jira UE-36939 Change 3165107 on 2016/10/17 by Mitchell.Wilson Resolve CIS content warnings in Shooter Game. #jira UE-30791 Change 3165001 on 2016/10/17 by Alexis.Matte Bump the static mesh build version to force a rebuild. #jira UE-37262 Change 3164928 on 2016/10/17 by Ben.Marsh GitHub: Allow files in /Engine/Plugins/.../Binaries/... to be distributed on GitHub. This was a hold over from the days that editor binaries used to be checked in to P4, but it's excluding plugin ThirdParty folders too. Change 3164914 on 2016/10/17 by Max.Chen Sequencer: Fix case where restoring the last view target was getting skipped. It should always restore if the camera object and the unlock if camera actor object is null. Copy from Dev-Sequencer #jira UE-35285 Change 3164896 on 2016/10/17 by Ben.Marsh UBT: Include all public headers and libraries when running UBT with -generateexternalfilelist, not just those referenced for external modules. This is a change in behavior, since it will cause public engine headers to be included in the target receipt, but it's reasonable behavior since they are required to build against the precompiled binaries anyway. Change 3164892 on 2016/10/17 by Jamie.Dale Fixed BPs passing a null parent when copying data, as this prevented some fix-up from happening correctly in ImportText #jira UE-36977 Change 3164886 on 2016/10/17 by Jamie.Dale Fixed issues propagating property changes when editing Blueprints - FText property changes that kept the same identity didn't used to propagate due to the CDO and instance sharing the same display string. We now import the previous value into a temporary property and use UProperty::Identical to compare the values, rather than rely on the exported text. - TMap/TSet property changes weren't being propagated at all since UI support was added. They needed to be changed to use the parent node like TArray property changes do. - TSet property exporting would produce "()" for both an empty set, and a set containing a single empty FString/FName. This confused the property import, and caused the UProperty::Identical to fail. We now make sure that FString/FName properties correctly honor the PPF_Delimited when dealing with empty values. #jira UE-36977 Change 3164884 on 2016/10/17 by Jamie.Dale We now re-key text when it moves into a new package (with a valid package localization ID) to avoid identity conflicts when instancing in the editor #jira UE-36977 Change 3164843 on 2016/10/17 by Ben.Marsh UBT: Restore support for generating multiple project file types at once. Move the XML config setting for overwriting the type of project files to generate into a an option specific to Visual Studio, under <VCProjectFileGenerator>/<Version>. #jira UE-37386 Change 3164823 on 2016/10/17 by Daniel.Lamb Fixed warning about shader compiler stalling. #test Cook Shootergame #jira UE-37393 Change 3164805 on 2016/10/17 by Alexis.Matte Remove the skinxx import workflow for static mesh #jira UE-37262 Change 3164803 on 2016/10/17 by Mitchell.Wilson Rebuilt lighting on all template projects #jira UE-37317 Change 3164775 on 2016/10/17 by Matt.Kuhlenschmidt Fixed Editing InlineEditConditionToggle variable inside a blueprint causing the editor to crash #jira UE-37029 Change 3164737 on 2016/10/17 by Matt.Kuhlenschmidt Fixed crash debugging blueprints in a networked game session - Eliminated pointless assert that in no way was necessary to crash the users game and moved the disabling of realtime viewports until after routing BeginPlay is complete. This ensures any debugger windows that appear during BeginPlay have realtime disabled by default. #jira UE-37360 Change 3164711 on 2016/10/17 by Chris.Wood Added char limit to user description in Crash Report Client [UE-37377] - Limit description field size in Crash Report Client #jira UE-37377 Change 3164706 on 2016/10/17 by Alexis.Matte Fix fbx scene re-import of staticmesh loosing there materials #jira UE-37032 Change 3164688 on 2016/10/17 by Matt.Kuhlenschmidt Fixed incorrect selection borders appearing if an object was duplicated and then undone and redone #jira UE-37300 Change 3164686 on 2016/10/17 by Alexis.Matte Remove unwanted re-import setting option in both mesh editor. #jira UE-36640 Change 3164622 on 2016/10/17 by Dan.Oconnor Duplicating 3153975 and 3155758 in 4.14 #jira UE-36938 Change 3164620 on 2016/10/17 by Robert.Manuszewski UE4 - Critical fix for task graph memory leak. (re-implementing CL #3159689 by Gil.Gribb) #jira UE-37382 #fyi Gil.Gribb Change 3164557 on 2016/10/17 by Mitchell.Wilson Rebuilt lighting in code and bp first person template. #jira UE-37317 Change 3164370 on 2016/10/17 by Chris.Wood Fix broken application path in Crash Report Client app restart code. [UE-36429] - Send and Restart from Crash Reporter does nothing #jira UE-36429 Change 3164329 on 2016/10/17 by Dmitriy.Dyomin Fixed: Shader compile error using Point Lights Nvidia Shield #jira UE-25671 Change 3164219 on 2016/10/16 by Max.Chen Fix CDO Constructor errors Copy from Dev-Sequencer #jira UE-36787 Change 3164173 on 2016/10/16 by Zachary.Wilson Submitting testing content for Reduced Lightmap Mixing and updating TM-Reflections #jira UE-29618 Change 3164171 on 2016/10/16 by Benjamin.Hyder Re-Saving TM-Noise to update camera position #jira UE-29618 Change 3164169 on 2016/10/16 by Benjamin.Hyder Updating TM-Noise map to include VectorVoronoi noise material #jira UE-29618 Change 3164022 on 2016/10/15 by zachary.wilson Adding testing content for material optimizations, attribute blend node, and preskinned loc pos node #jira UE-29618 Change 3163970 on 2016/10/15 by Benjamin.Hyder updating shading model for skeletal mesh example in TM-SSS_Fullres #jira UE-29618 Change 3163961 on 2016/10/15 by Benjamin.Hyder adding animated skeletal mesh example to TM-SSS_Fullres #jira UE-29618 Change 3163958 on 2016/10/15 by Benjamin.Hyder adding diffuse example to TM-SSS_Fullres #jira UE-29618 Change 3163728 on 2016/10/14 by Tyler.Cole Prep build scripts for WEX MCP in UE4 Release-4.14 stream. #jira NONE-0 Change 3163718 on 2016/10/14 by Tyler.Cole Prep build scripts for Ocean MCP in UE4 Release-4.14 stream. #jira NONE-0 Change 3163715 on 2016/10/14 by Tyler.Cole Prep build scripts for Fortnite MCP in UE4 Release-4.14 stream. #jira NONE-0 Change 3163711 on 2016/10/14 by Tyler.Cole Prep build scripts for Orion MCP in UE4 Release-4.14 stream. #jira NONE-0 Change3163698on 2016/10/14 by Michael.Trepka Fixed a CoreAudio crash on unpause #jira UE-37126 Change 3163534 on 2016/10/14 by Matt.Kuhlenschmidt Disable versioning info in project badge by default #jira UE-37335 Change 3163485 on 2016/10/14 by Michael.Trepka Add one more RPATH entry on Mac to support launching staged builds #jira UE-36799 Change 3163479 on 2016/10/14 by Michael.Trepka A couple of changes to prevent Mac app bundles from modifying their content for compatibility with the Mac App Store #jira UE-25742 Change 3163465 on 2016/10/14 by Daniel.Lamb Fix issue with cook command from editor was trying to package also. #test Cook command in editor #jira UE-36796 Change 3163455 on 2016/10/14 by Matt.Kuhlenschmidt Fixed selected section highlight and vertex color view modes not working. #jira UE-37308 Change 3163450 on 2016/10/14 by Mike.Beach Fixing up more Fortnite CIS warnings that were exposed by recent Dev-BP fixes - removing unnecessary cast operation in Mission_Outpost. #jira UE-37055 Change 3163400 on 2016/10/14 by Benjamin.Hyder Updating Lighting setttings inTM-SSS_Fullres #jira UE-29618 Change 3163392 on 2016/10/14 by Benjamin.Hyder Creating TM-SSS_Fullres test map and importing 4k skin diffuse and bump materials #jira UE-29618 Change 3163336 on 2016/10/14 by Mike.Beach Fixing up more Fortnite CIS warnings that we're exposed by a recent change from Dev-BP - replacing deprecated ClearTimerByHandle calls with ClearAndInvalidateTimerByHandle. #jira UE-37055 Change 3163335 on 2016/10/14 by Max.Chen Sequence Recorder: Disable auto possess player for recorded pawns. This fixes a bug where if you record a third person template character, when you open the sequence, the recorded character will possess the viewport. Copy from Dev-Sequencer #jira UE-35342 Change 3163230 on 2016/10/14 by Richard.TalbotWatkin Duplicated from //UE4/Release-4.13, CL 3111897 When doing a full geometry rebuild, force the level model's poly linkage to be reinitialized, so that coplanar surfaces can be correctly linked. This fixes a regression following the separation of FPoly::iLink and FPoly::iLinkSurface. #jira UE-35482 - Selecting a brush surface that is flush with other brush surfaces selects all of them after Building Geometry #jira UE-35999 - Additive BSP Selections Acting Grouped after Building Geometry #jira UE-37340 - Selecting Geometry objects selects multiple brushes after Building Change 3163155 on 2016/10/14 by Benn.Gallagher Fixed crash when changing scene settings an hitting undo in Persona with complex cloth active #jira UE-37332 Change 3163146 on 2016/10/14 by Marc.Audy Properly add/remove wind sources when activated/deactivated #jira UE-37289 Change 3163135 on 2016/10/14 by Phillip.Kavan [UE-35259] Fix AddComponent node-instanced Blueprint components finding the wrong archetype (template basis) in some scenarios. Mirrored from CL# 3160052 (//UE4/Dev-Blueprints). Additional changes (for release branch): - Bumped ReleaseObjectVersion to limit fixup code to only BP assets that existed prior to this change. #jira UE-35259 Change 3162999 on 2016/10/14 by Ben.Marsh QFE: Fix writing output files if they don't already exist, and bump version number to 4.14. Change 3162988 on 2016/10/14 by Thomas.Sarkanen Fix socket editing on meshes Feature was inadvertently removed with the Persona refactor. #jira UE-37313 - Create Mesh Socket is missing Change 3162938 on 2016/10/14 by Ben.Zeigler Merging CL 3162934 to //UE4/Release-4.14 #jira UE-37044 Fix crash when loading map that has null actors in the actor list Change 3162900 on 2016/10/14 by Dmitriy.Dyomin Fixed: Android should use single channel texture for ShadowMaps instead of 4 channel #jira UE-37312 Change 3162864 on 2016/10/14 by Yannick.Lange VR Editor: - Fix Landscape Undo/Redo functions step-by-step #jira UE-37050 - Fix Landscape is sculpting twice if both Motioncontrollers are aimed at landscape #jira UE-37272 #jira UE-37050 #jira UE-37272 Change 3162761 on 2016/10/14 by Jack.Porter Fixed landscape Undo crash after using Move-To-Level tool and then selecting a landscape proxy in the sublevel #jira UE-36863 Change 3162724 on 2016/10/14 by Max.Chen Sequencer: Fix time dilation in level sequence player Copy from Dev-Sequencer #jira UE-37277 Change 3162617 on 2016/10/13 by Jeff.Campeau Fix Windows XP compilation issues. - Block Win10 SDK includes - Remove unused Win10 SDK path collecting - Fix extraneous includes - Add 32b atomic option for certain stats #jira UE-36909 Change 3162503 on 2016/10/13 by Max.Preussner MediaPlayerEditor: Fixed Media player selection is ignored if media specifies player overrides (UE-37248) Merged from Dev-Sequencer CL#3160995#jira UE-37248 Change 3162470 on 2016/10/13 by Mitchell.Wilson Rebuilt lighting and saved levels in StrategyGame. #jira UE-36913 Change 3162466 on 2016/10/13 by Michael.Trepka By default, do not compile Mac OpenGL and Metal SM4 shaders while cooking #jira UE-37088 Change 3162458 on 2016/10/13 by Keli.Hlodversson When quitting from the SteamVR menu overlay, in addition to shutting down the VR subsystem, also exit the game (and preview when running inside the editor) #jira UE-37292 Game does not exit when exited through SteamVR UI Change 3162421 on 2016/10/13 by Mitchell.Wilson Rebuilt lighting and resaved levels in SunTemple, Zen Garden, and Mobile Starter content. #jira UE-36913 Change 3162420 on 2016/10/13 by Marc.Audy Clear BlueprintCreatedComponents in PostEditUndo since it is non transactional and expected to be empty #jira UE-37071 Change 3162406 on 2016/10/13 by Ben.Marsh Simplygon: Disable compiling Simplygon for installed builds if the required headers aren't there. Fixes creating installed builds for licensees. #jira UE-37269 Change 3162382 on 2016/10/13 by Ben.Marsh UAT: Fix accessing uninitialized UBT variable at startup, causing build warnings. Change 3162314 on 2016/10/13 by Ben.Marsh Add PhysX build job to 4.14 branch. Change 3162311 on 2016/10/13 by Matt.Kuhlenschmidt Ensure FBX scene import data object is not garbage collected during import #jira UE-35606 Change 3162270 on 2016/10/13 by Ben.Marsh UBT/Editor: Improved integration for Visual Studio "15". * Added editor source code accessors for specific Visual Studio versions (2013, 2015, "15"), which call through to the default Visual Studio accessor but allow specifying a more specific setting for which IDE to use. * UBT reads this setting, and will generate project files for the preferred Visual Studio version if set. * Added a BuildConfiguration.xml setting to set which type of project files to generate - under the <ProjectFileGenerator> element, add a <Type> property taking values of Make/CMake/QMake/KDevelop/CodeLite/VisualStudio/VisualStudio2012/VisualStudio2013/VisualStudio2015/VisualStudio2017/XCode. * Added an separate editor setting for the Windows target platform controlling which compiler to use, separately from the IDE. This setting can also be set via <WindowsPlatform> <Compiler> element in BuildConfiguration.xml. * Removed compiler argument from generated project files (this should now persist via INI files or XML config) * Removed compiler argument being appended to UBT when doing hot reload (should pick this up from the build environment instead) * Added support for enums in UBT XML configs * Fixed a few version checks against exact compiler versions - MS plan is give VS '15' updates version numbers 1911, 1912, etc... #jira UE-37176 #jira UE-36872 Change 3162236 on 2016/10/13 by Mike.Beach Fixing a GLEO that could occur after compiling a component Blueprint (preventing you from saving other Blueprints that use that component) - using the authratative class in AddComponent node reconstruction (for the output pin) so that it doesn't end up referencing the REINST class. #jira UE-37224 Change 3162225 on 2016/10/13 by Alex.Delesky #jira UE-36995 - Precise click should now work correctly with buttons that are children of a scroll list. Also addresses #jira UE-37250 and PR #2859 (#git author aarmbruster andrew.armbruster@gmail.com) Change 3162107 on 2016/10/13 by Matt.Kuhlenschmidt Prevent perforce branch stuff from appearing in edtior project badge. This feature was meant for those compiling on perforce builds. #jira UE-37278 Change 3162060 on 2016/10/13 by Matt.Kuhlenschmidt Fix mac build #jira UE-36885 Change 3162025 on 2016/10/13 by Daniel.Wright Compile fix #jira UE-37246 Change 3162009 on 2016/10/13 by Daniel.Wright [Copy] Fixed movable lights getting assigned a shadowmap channel #jira UE-37246 Change 3161963 on 2016/10/13 by Jon.Nabozny Enable PrimaryActorTick.bCanEverTick in necessary Samples. The seperation of GameMode caused different behavior, since GameModeBase has tick disabled by default (which is set in AInfo). #jira UE-36888 Change 3161896 on 2016/10/13 by Matt.Kuhlenschmidt Added guard and more logging for crash when reimporting textures #jira UE-37263 Change 3161865 on 2016/10/13 by mason.seay Making the name more user friendly for test asset #jira UE-29618 Change 3161855 on 2016/10/13 by Matt.Kuhlenschmidt Fixed keybindings not working with editor settings search and regressions where you could not import,export,or reset keybindings to default - Made the keybindings setting a proper developer settings object #jira UE-36885 Change 3161854 on 2016/10/13 by Daniel.Wright [Copy] Fixed level getting added to the dirty list twice when legacy lightmaps are present #jira UE-37204 Change 3161743 on 2016/10/13 by Lauren.Ridge Setting RenderTarget in OculusRiftRender.cpp to fix ensure when entering VR editing mode on Oculus Rift #jira UE-37245 Change 3161694 on 2016/10/13 by Michael.Dupuis #jira UE-37001 Perform manual migration of UICurve to proper config category Change 3161689 on 2016/10/13 by Thomas.Sarkanen Fixed failing detachment automation test The issue was not the attachment itself, but rather the test setup conditions being different to what was expected, give that setting an actor's rotation with a quaternion doesnt always return the same value as the one that is set. #jira UE-37160 - Detachment automation tests failing Change 3161685 on 2016/10/13 by mason.seay Test content for retargeting animation #jira UE-29618 Change 3161423 on 2016/10/13 by Jamie.Dale Split localized package redirection out of FCoreDelegates::PackageNameResolvers They're different enough in behavior that the delegate resolution was breaking the localized package resolution by resolving in too many places and causing the localized package to be loaded with its real localized name as well as the fake non-localized name. #jira UE-37119 Change 3161394 on 2016/10/13 by Mitchell.Wilson Replaced deprecated blueprint node with GrabComponentAtLocation to resolve warnings. #jira UE-37256 Change 3161363 on 2016/10/13 by Jamie.Dale Fixing mangled English translations #jira UE-36128 Change 3161319 on 2016/10/13 by Benn.Gallagher Fixed crash when forcing ref pose during anim blueprint initialization when using sub anim instances #jira UE-37254 Change 3161310 on 2016/10/13 by Martin.Wilson Fix crash retargetting AnimBP with "Allow remapping to existing assets" enabled #jira UE-35149 Change 3161303 on 2016/10/13 by Jurre.deBaare Crash when using merge actor on static meshes that have been affected by simplygon #fix Set whether or not a LOD is eligible for exporting by determining if it contains valid LOD data #jira UE-36880 Change 3161166 on 2016/10/13 by Jack.Porter Prevent Launch On prompting to save a freshly-opened, non-modified map while still prompting the user to save never-saved maps #jira UE-37131 Change 3161161 on 2016/10/13 by Thomas.Sarkanen Fixed override materials hanging around when setting skeletal meshes #jira UE-37102 - On switching an anim blueprints preview mesh the skeletal mesh is not switched but the materials are Change 3161160 on 2016/10/13 by Thomas.Sarkanen Fix crash changing preview skeletal mesh with bone selected Make sure to keep BonesOfInterest and the preview scene selected bone in sync. Also make sure to only use one so we wont suffer this again if we inteodiuce any inconsistencies in the future. #jira UE-37081 - If a bone is selected when switching preview skeletons the editor will crash Change 3160882 on 2016/10/12 by Mike.Beach Mirroring CL 3158790 from Dev-BP Fixing an issue with ctrl pin dragging, where marco nodes' connections would disappear - the SGraph drag operation now references pins by handles (so it can account for reconstructed nodes during the operation). #jira UE-37033 Change 3160863 on 2016/10/12 by Lauren.Ridge Changing VR Editor delete function to go through the standard delete pathway so FEdModeMeshPaint has a chance to handle it #jira UE-35685 Change 3160855 on 2016/10/12 by Jeff.Campeau Fix WinXP compile issues caused by WebSocket, SteamVR, and Oculus changes. #jira UE-36909 Change 3160844 on 2016/10/12 by Marcus.Wassmer Duplicate PR #2855: Ansel plugin fixes (Contributed by adamnv) #jira UE-37162 Change 3160749 on 2016/10/12 by Daniel.Wright [Copy] Legacy lightmaps are renamed with the world that uses them. Fixes 'graph linked to external object' when renaming a map in the content browser. #jira UE-37231 Change 3160748 on 2016/10/12 by Daniel.Wright [Copy] Lightmap textures are now outered to UMapBuildDataRegistry so that the UMapBuildDataRegistry can be moved in the content browser #jira UE-37231 Change 3160747 on 2016/10/12 by Daniel.Wright [Copy] Legacy MapBuildDataRegistry objects are no longer public so they don't get shown in the content browser, prevents users trying to move them. #jira UE-37231 Change 3160727 on 2016/10/12 by Mitchell.Wilson Updating minimum iOS version to iOS 8 for all samples and templates #jira UE-37022 Change 3160655 on 2016/10/12 by Chad.Taylor Merging VR loading screen fixes from Dev-VR #jira UE-36741 Change 3160643 on 2016/10/12 by Keli.Hlodversson Ensure that the DebugConsoleObject's width is reset to the width of the left eye view before rendering the console when doing stereoscopic rendering. #jira UE-36440 Change3160641on 2016/10/12 by Mike.Beach Fixing up Fortnite CIS warnings that we're exposed by a recent change from Dev-BP - replacing deprecated ClearTimerByHandle calls with ClearAndInvalidateTimerByHandle. #jira UE-37055 Change 3160572 on 2016/10/12 by Matt.Kuhlenschmidt Missed change from CL 3159889 #jira UE-35503 Change 3160518 on 2016/10/12 by Ryan.Gerleve Fix assert when adding a new sublevel. #jira UE-37148 Change 3160439 on 2016/10/12 by Ben.Marsh Fix support for Visual Studio "15" preview 5. #jira UE-37227 Change 3160363 on 2016/10/12 by Daniel.Lamb Fix for skip editor content flag being passed throught o UAT. #jira UE-37223 Change 3160277 on 2016/10/12 by Mieszko.Zielinski Manually recreated CL#3159909 #UE4 Original comment: --- Added sanity-checkes to access to UNavigationSystem::NavDataSet elements #UE4 Lack of those test has been reported licencees as source of some crashes #jira UE-37209 Change 3160120 on 2016/10/12 by Chris.Babcock Fix x86 and x86_64 libpng libraries for Android #jira UE-37192 #ue4 #android Change 3160080 on 2016/10/12 by Matthew.Griffin PR #2840: UE-36945: Set exe icon properly in packaged Windows games (Contributed by projectgheist) #jira UE-36945 Change 3160063 on 2016/10/12 by Gareth.Martin Fixed crash trying to edit landscape with r.LightPropagationVolume=1 enabled #jira UE-36933 Change 3160045 on 2016/10/12 by Ryan.Gerleve Fix some issues with manipulating sublevels in the editor. #jira UE-36901, UE-36932 Change 3160044 on 2016/10/12 by Gareth.Martin Fix condition on BuildTree in UHierarchicalInstancedStaticMeshComponent::Serialize - This should fix the crash that caused it to be commented out #jira UE-37152 Change 3160032 on 2016/10/12 by Matt.Kuhlenschmidt Fixed arrays inside structs, inside arrays not refreshing when an element is added or removed refresh #jira UE-36985 Change 3159965 on 2016/10/12 by Ben.Zeigler #jira UE-37170 Fix crash when spawning PlayerController with a null CheatClass, this now just skips spawning the cheat manager Change 3159957 on 2016/10/12 by Robert.Manuszewski Making FindShaderResourceById and FindShaderById return a raw pointer instead of TRefCountPtr (basically undoing CL #2538774) to prevent creating temporary TRefCountPtrs. It's no longer necessary to use TRefCountPtrs as shader serialization has changed (CL #2989898) and shaders are no longer registered on the async loading thread. #jira UE-35570, UE-35511, UE-35570, UE-35924 Change 3159921 on 2016/10/12 by Matthew.Griffin Duplicating CL#3153485 from Dev-Build Removed GUBP from Automation Tool Mono solution Change 3159919 on 2016/10/12 by Matthew.Griffin Duplicating CL#3150017 from Dev-Build Fixes for PS4 deployment suggested in UDN post: https://udn.unrealengine.com/questions/314055/issues-with-ps4platformautomationcs.html Change 3159904 on 2016/10/12 by Matt.Kuhlenschmidt Fix regression where toggling Play/Pause in PIE would not give the mouse back to the game #jira UE-37112 Change 3159903 on 2016/10/12 by Ben.Zeigler #jira UE-37163 Activate auto activate components immediately when registering in an editor world, the initialize call will never actually happen Change 3159890 on 2016/10/12 by Matt.Kuhlenschmidt Prevent crashes when a keybinding to start and stop PIE is toggled repeatedly. #jira UE-36814 Change 3159889 on 2016/10/12 by Matt.Kuhlenschmidt Fixed part of the details panel UI showing up when there is nothing in the details panel. This was causing crashes when clicking on those parts of the UI #jira UE-35503 Change 3159888 on 2016/10/12 by Ben.Zeigler #jira UE-36849 DataTable::LoadStructData allocating wrong size Fix cases that were using the PropertiesSize to malloc a UStruct to instead use GetStructureSize(). There is a difference because of alignment, so on some platforms it was corrupting memory Change 3159887 on 2016/10/12 by Matt.Kuhlenschmidt Guard against crash in Fcanvas drawing #jira UE-36496 Change 3159886 on 2016/10/12 by Ben.Zeigler #jira UE-36884 Stop savepackage from making dependencies on PendingKill objects, because those will not actually be saved #jira UE-36876 Fix it so savepackage doesn't try to strip ClientOnly objects when cooking for a client+server config, we only want to strip if both flags are set. Re-enable warning I disabled Fix crashes when running EDL cooked builds. This is not a new bug, but fortnite content changes exposed it Fix it so pending kill component templates do not end up in import table, they will fail to import Fix it so when marking an inheritable component template as unnecessary it also marks it pending kill. This fixes it so if GetArchetype will not return an orphaned, non-saving component as the archetype for a grandchild blueprint Change 3159885 on 2016/10/12 by Matthew.Griffin Duplicating CL#3149950 from Dev-Build to include .tps files in the installed build Including source for additional programs to Installed Build #jira UE-36668 #jira UE-37072 Change 3159853 on 2016/10/12 by Matthew.Griffin Duplicating CL#3148611 from Dev-Build Added list of Dependant modules to EULA check #jira UE-29432 Change 3159385 on 2016/10/11 by Nick.Shin make emscripten for physx use gMask like windows and xbox does emsdk doesn't like: union { U32 u; F32 f; } bla; it seems, floats are 64 bits on browsers... stream: release-4.14 #jira UE-36916 //UE4/Main: Step 'Compile UE4Game HTML5' - 50 Warnings Change 3159384 on 2016/10/11 by Nick.Shin manually submitting HTML5 PhysX libs recompiled to fix the NaN warnings stream: Release-4.14 #jira UE-36916 //UE4/Main: Step 'Compile UE4Game HTML5' - 50 Warnings Change 3159210 on 2016/10/11 by Ben.Marsh Set the default for the BRANCH_NAME macro to the escaped 4.14 branch. [CL 3175266 by Matthew Griffin in Main branch]
2905 lines
94 KiB
C++
2905 lines
94 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "SkeletonEditorPrivatePCH.h"
|
|
#include "SSkeletonTree.h"
|
|
|
|
#include "ScopedTransaction.h"
|
|
#include "BoneDragDropOp.h"
|
|
#include "SocketDragDropOp.h"
|
|
#include "SkeletonTreeCommands.h"
|
|
#include "DragAndDrop/AssetDragDropOp.h"
|
|
|
|
#include "AssetSelection.h"
|
|
|
|
#include "Editor/ContentBrowser/Public/ContentBrowserModule.h"
|
|
#include "ComponentAssetBroker.h"
|
|
|
|
#include "SlateIconFinder.h"
|
|
|
|
#include "Editor/UnrealEd/Public/AssetNotifications.h"
|
|
|
|
#include "Animation/PreviewAssetAttachComponent.h"
|
|
#include "AnimPreviewInstance.h"
|
|
|
|
#include "Factories.h"
|
|
#include "Developer/MeshUtilities/Public/MeshUtilities.h"
|
|
#include "UnrealExporter.h"
|
|
#include "SSearchBox.h"
|
|
#include "SInlineEditableTextBlock.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
#include "GenericCommands.h"
|
|
#include "Animation/BlendProfile.h"
|
|
#include "SBlendProfilePicker.h"
|
|
#include "IPersonaPreviewScene.h"
|
|
#include "IDocumentation.h"
|
|
#include "PersonaUtils.h"
|
|
|
|
#include "BoneSelectionWidget.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "SSkeletonTree"
|
|
|
|
static const FName ColumnID_BoneLabel( "BoneName" );
|
|
static const FName ColumnID_RetargetingLabel( "TranslationRetargeting" );
|
|
static const FName ColumnID_BlendProfileLabel( "BlendProfile" );
|
|
|
|
DECLARE_DELEGATE_RetVal_TwoParams(FReply, FOnDraggingBoneItem, const FGeometry&, const FPointerEvent&);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SSkeletonTreeRow
|
|
|
|
DECLARE_DELEGATE_RetVal_TwoParams(FReply, FOnDraggingTreeItem, const FGeometry&, const FPointerEvent&);
|
|
|
|
typedef TSharedPtr< FDisplayedTreeRowInfo > FDisplayedTreeRowInfoPtr;
|
|
|
|
class SSkeletonTreeRow
|
|
: public SMultiColumnTableRow< FDisplayedTreeRowInfoPtr >
|
|
{
|
|
public:
|
|
|
|
SLATE_BEGIN_ARGS(SSkeletonTreeRow) {}
|
|
|
|
/** The item for this row **/
|
|
SLATE_ARGUMENT(FDisplayedTreeRowInfoPtr, Item)
|
|
|
|
/** Pointer to the owning skeleton tree so it can be used to copy sockets, tree refresh when needed etc */
|
|
SLATE_ARGUMENT(TWeakPtr< SSkeletonTree >, SkeletonTree);
|
|
|
|
/** Pointer to the owning skeleton tree so it can be used to copy sockets, tree refresh when needed etc */
|
|
SLATE_ARGUMENT(TWeakPtr< FEditableSkeleton >, EditableSkeleton);
|
|
|
|
/** Filter text typed by the user into the parent tree's search widget */
|
|
SLATE_ARGUMENT(FText, FilterText);
|
|
|
|
/** Delegate for dragging items **/
|
|
SLATE_EVENT(FOnDraggingTreeItem, OnDraggingItem);
|
|
|
|
SLATE_END_ARGS()
|
|
|
|
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView);
|
|
|
|
/** Overridden from SMultiColumnTableRow. Generates a widget for this column of the tree row. */
|
|
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override;
|
|
|
|
/** Overridden from STableRow. Allows us to generate inline edit widgets. */
|
|
virtual void ConstructChildren(ETableViewMode::Type InOwnerTableMode, const TAttribute<FMargin>& InPadding, const TSharedRef<SWidget>& InContent) override;
|
|
|
|
/** Override OnDragEnter for drag and drop of sockets onto bones */
|
|
virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
|
|
|
|
/** Override OnDragLeave for drag and drop of sockets onto bones */
|
|
virtual void OnDragLeave(const FDragDropEvent& DragDropEvent) override;
|
|
|
|
/** Override OnDrop for drag and drop of sockets and meshes onto bones */
|
|
virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
|
|
|
|
/** Override DoesItemHaveChildren to add expander to in-line editors */
|
|
virtual int32 DoesItemHaveChildren() const override;
|
|
|
|
/** @return true if the corresponding item is expanded; false otherwise */
|
|
virtual bool IsItemExpanded() const override;
|
|
|
|
/** Toggle the expansion of the item associated with this row */
|
|
virtual void ToggleExpansion() override;
|
|
|
|
protected:
|
|
|
|
/** Handler for starting a drag/drop action */
|
|
virtual FReply OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
|
|
|
/** Get the editable skeleton we are editing */
|
|
TSharedRef<FEditableSkeleton> GetEditableSkeleton() const { return EditableSkeleton.Pin().ToSharedRef(); }
|
|
|
|
/** Get the skeleton tree we are embedded in */
|
|
TSharedRef<SSkeletonTree> GetSkeletonTree() const { return SkeletonTree.Pin().ToSharedRef(); }
|
|
|
|
private:
|
|
|
|
/** The item this row is holding */
|
|
FDisplayedTreeRowInfoPtr Item;
|
|
|
|
/** Text the user typed into the search box - used for text highlighting */
|
|
FText FilterText;
|
|
|
|
/** Weak pointer to the owning skeleton tree */
|
|
TWeakPtr<SSkeletonTree> SkeletonTree;
|
|
|
|
/** Weak pointer to the owning editable skeleton */
|
|
TWeakPtr<FEditableSkeleton> EditableSkeleton;
|
|
|
|
/** Item that we're dragging */
|
|
FOnDraggingTreeItem OnDraggingItem;
|
|
|
|
/** Was the user pressing "Alt" when the drag was started? */
|
|
bool bIsAltDrag;
|
|
};
|
|
|
|
|
|
void SSkeletonTreeRow::Construct( const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView )
|
|
{
|
|
Item = InArgs._Item;
|
|
OnDraggingItem = InArgs._OnDraggingItem;
|
|
FilterText = InArgs._FilterText;
|
|
EditableSkeleton = InArgs._EditableSkeleton;
|
|
SkeletonTree = InArgs._SkeletonTree;
|
|
|
|
check( Item.IsValid() );
|
|
|
|
SMultiColumnTableRow< FDisplayedTreeRowInfoPtr >::Construct( FSuperRowType::FArguments(), InOwnerTableView );
|
|
}
|
|
|
|
void SSkeletonTreeRow::ConstructChildren(ETableViewMode::Type InOwnerTableMode, const TAttribute<FMargin>& InPadding, const TSharedRef<SWidget>& InContent)
|
|
{
|
|
STableRow<FDisplayedTreeRowInfoPtr>::Content = InContent;
|
|
|
|
TSharedRef<SWidget> InlineEditWidget = Item->GenerateInlineEditWidget(FilterText, FIsSelected::CreateSP(this, &STableRow::IsSelected));
|
|
|
|
// MultiColumnRows let the user decide which column should contain the expander/indenter item.
|
|
this->ChildSlot
|
|
.Padding(InPadding)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
InContent
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
InlineEditWidget
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef< SWidget > SSkeletonTreeRow::GenerateWidgetForColumn( const FName& ColumnName )
|
|
{
|
|
if ( ColumnName == ColumnID_BoneLabel )
|
|
{
|
|
TSharedPtr< SHorizontalBox > RowBox;
|
|
|
|
SAssignNew( RowBox, SHorizontalBox );
|
|
|
|
RowBox->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
SNew( SExpanderArrow, SharedThis(this) )
|
|
];
|
|
|
|
Item->GenerateWidgetForNameColumn( RowBox, FilterText, FIsSelected::CreateSP(this, &STableRow::IsSelectedExclusively ) );
|
|
|
|
return RowBox.ToSharedRef();
|
|
}
|
|
else
|
|
{
|
|
return Item->GenerateWidgetForDataColumn(ColumnName);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTreeRow::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FSocketDragDropOp> DragConnectionOp = DragDropEvent.GetOperationAs<FSocketDragDropOp>();
|
|
|
|
// Is someone dragging a socket onto a bone?
|
|
if (DragConnectionOp.IsValid())
|
|
{
|
|
if ( Item->GetType() == ESkeletonTreeRowType::Bone &&
|
|
( *static_cast<FName*>( Item->GetData() ) != DragConnectionOp->GetSocketInfo().Socket->BoneName ) )
|
|
{
|
|
// The socket can be dropped here if we're a bone and NOT the socket's existing parent
|
|
DragConnectionOp->SetIcon( FEditorStyle::GetBrush( TEXT( "Graph.ConnectorFeedback.Ok" ) ) );
|
|
}
|
|
else if ( Item->GetType() == ESkeletonTreeRowType::Bone && DragConnectionOp->IsAltDrag() )
|
|
{
|
|
// For Alt-Drag, dropping onto the existing parent is fine, as we're going to copy, not move the socket
|
|
DragConnectionOp->SetIcon( FEditorStyle::GetBrush( TEXT( "Graph.ConnectorFeedback.Ok" ) ) );
|
|
}
|
|
else
|
|
{
|
|
DragConnectionOp->SetIcon( FEditorStyle::GetBrush( TEXT( "Graph.ConnectorFeedback.Error" ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSkeletonTreeRow::OnDragLeave( const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FSocketDragDropOp> DragConnectionOp = DragDropEvent.GetOperationAs<FSocketDragDropOp>();
|
|
if (DragConnectionOp.IsValid())
|
|
{
|
|
// Reset the drag/drop icon when leaving this row
|
|
DragConnectionOp->SetIcon( FEditorStyle::GetBrush( TEXT( "Graph.ConnectorFeedback.Error" ) ) );
|
|
}
|
|
}
|
|
|
|
FReply SSkeletonTreeRow::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
TSharedPtr<FSocketDragDropOp> DragConnectionOp = DragDropEvent.GetOperationAs<FSocketDragDropOp>();
|
|
if (DragConnectionOp.IsValid())
|
|
{
|
|
FSelectedSocketInfo SocketInfo = DragConnectionOp->GetSocketInfo();
|
|
|
|
if ( DragConnectionOp->IsAltDrag() && Item->GetType() == ESkeletonTreeRowType::Bone )
|
|
{
|
|
// In an alt-drag, the socket can be dropped on any bone
|
|
// (including its existing parent) to create a uniquely named copy
|
|
GetSkeletonTree()->DuplicateAndSelectSocket( SocketInfo, *static_cast<FName*>( Item->GetData() ) );
|
|
}
|
|
else if ( Item->GetType() == ESkeletonTreeRowType::Bone &&
|
|
*static_cast<FName*>( Item->GetData() ) != SocketInfo.Socket->BoneName )
|
|
{
|
|
// The socket can be dropped here if we're a bone and NOT the socket's existing parent
|
|
USkeletalMesh* SkeletalMesh = GetSkeletonTree()->GetPreviewScene().IsValid() ? GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
GetEditableSkeleton()->SetSocketParent(SocketInfo.Socket->SocketName, *static_cast<FName*>(Item->GetData()), SkeletalMesh);
|
|
|
|
GetSkeletonTree()->CreateFromSkeleton();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DragDropEvent.GetOperationAs<FAssetDragDropOp>().IsValid())
|
|
{
|
|
GetSkeletonTree()->OnDropAssetToSkeletonTree( Item, DragDropEvent );
|
|
}
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FReply SSkeletonTreeRow::OnDragDetected( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
|
|
{
|
|
if ( OnDraggingItem.IsBound() )
|
|
{
|
|
return OnDraggingItem.Execute( MyGeometry, MouseEvent );
|
|
}
|
|
else
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
}
|
|
|
|
int32 SSkeletonTreeRow::DoesItemHaveChildren() const
|
|
{
|
|
if(Item->HasInlineEditor())
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return SMultiColumnTableRow<FDisplayedTreeRowInfoPtr>::DoesItemHaveChildren();
|
|
}
|
|
|
|
bool SSkeletonTreeRow::IsItemExpanded() const
|
|
{
|
|
return SMultiColumnTableRow<FDisplayedTreeRowInfoPtr>::IsItemExpanded() || Item->IsInlineEditorExpanded();
|
|
}
|
|
|
|
void SSkeletonTreeRow::ToggleExpansion()
|
|
{
|
|
SMultiColumnTableRow<FDisplayedTreeRowInfoPtr>::ToggleExpansion();
|
|
|
|
if (Item->HasInlineEditor())
|
|
{
|
|
Item->ToggleInlineEditorExpansion();
|
|
OwnerTablePtr.Pin()->Private_SetItemExpansion(Item, Item->IsInlineEditorExpanded());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FDisplayedTreeRowInfo
|
|
|
|
TSharedRef<class FEditableSkeleton> FDisplayedTreeRowInfo::GetEditableSkeleton() const
|
|
{
|
|
return SkeletonTree.Pin()->GetEditableSkeletonInternal();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FDisplayedMeshBoneInfo
|
|
|
|
TSharedRef<ITableRow> FDisplayedMeshBoneInfo::MakeTreeRowWidget(const TSharedRef<STableViewBase>& OwnerTable, FText FilterText )
|
|
{
|
|
return
|
|
SNew( SSkeletonTreeRow, OwnerTable )
|
|
.Item( SharedThis(this) )
|
|
.FilterText( FilterText )
|
|
.SkeletonTree(SkeletonTree)
|
|
.EditableSkeleton(GetEditableSkeleton())
|
|
.OnDraggingItem( this, &FDisplayedMeshBoneInfo::OnDragDetected );
|
|
}
|
|
|
|
EVisibility FDisplayedMeshBoneInfo::GetLODIconVisibility() const
|
|
{
|
|
if (bRequiredBone)
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
return EVisibility::Hidden;
|
|
}
|
|
|
|
void FDisplayedMeshBoneInfo::GenerateWidgetForNameColumn( TSharedPtr< SHorizontalBox > Box, FText& FilterText, FIsSelected InIsSelected )
|
|
{
|
|
const FSlateBrush* LODIcon = FEditorStyle::GetBrush("SkeletonTree.LODBone");
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(0.0f, 1.0f))
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(LODIcon)
|
|
.Visibility(this, &FDisplayedMeshBoneInfo::GetLODIconVisibility)
|
|
];
|
|
|
|
if (GetSkeletonTree()->GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent();
|
|
CacheLODChange(PreviewComponent);
|
|
}
|
|
|
|
FText ToolTip = GetBoneToolTip();
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2, 0, 0, 0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew( STextBlock )
|
|
.ColorAndOpacity(this, &FDisplayedMeshBoneInfo::GetBoneTextColor)
|
|
.Text( FText::FromName(BoneName) )
|
|
.HighlightText( FilterText )
|
|
.Font(this, &FDisplayedMeshBoneInfo::GetBoneTextFont)
|
|
.ToolTipText( ToolTip )
|
|
];
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedMeshBoneInfo::GenerateWidgetForDataColumn(const FName& DataColumnName)
|
|
{
|
|
if(DataColumnName == ColumnID_RetargetingLabel)
|
|
{
|
|
return
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SAssignNew(RetargetingComboButton, SComboButton)
|
|
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
|
|
.ForegroundColor(this, &FDisplayedMeshBoneInfo::GetRetargetingComboButtonForegroundColor)
|
|
.ContentPadding(0)
|
|
.OnGetMenuContent(this, &FDisplayedMeshBoneInfo::CreateBoneTranslationRetargetingModeMenu)
|
|
.ToolTip(IDocumentation::Get()->CreateToolTip(
|
|
LOCTEXT("RetargetingToolTip", "Set bone translation retargeting mode"),
|
|
nullptr,
|
|
TEXT("Shared/Editors/Persona"),
|
|
TEXT("TranslationRetargeting")))
|
|
.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &FDisplayedMeshBoneInfo::GetTranslationRetargetingModeMenuTitle)
|
|
]
|
|
]
|
|
];
|
|
}
|
|
else if(DataColumnName == ColumnID_BlendProfileLabel)
|
|
{
|
|
bool bWritable = true;
|
|
|
|
UBlendProfile* CurrentProfile = GetSkeletonTree()->GetSelectedBlendProfile();
|
|
|
|
// We should never have this column if we don't have a profile
|
|
check(CurrentProfile);
|
|
|
|
return SNew(SBox)
|
|
.Padding(0.0f)
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(SSpinBox<float>)
|
|
.Style(&FEditorStyle::GetWidgetStyle<FSpinBoxStyle>("SkeletonTree.HyperlinkSpinBox"))
|
|
.ContentPadding(0.0f)
|
|
.MinValue(0.0f)
|
|
.MaxValue(1000.0f)
|
|
.Value(CurrentProfile->GetBoneBlendScale(BoneName))
|
|
.OnValueCommitted(this, &FDisplayedMeshBoneInfo::OnBlendSliderCommitted)
|
|
];
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
FSlateColor FDisplayedMeshBoneInfo::GetRetargetingComboButtonForegroundColor() const
|
|
{
|
|
static const FName InvertedForegroundName("InvertedForeground");
|
|
static const FName DefaultForegroundName("DefaultForeground");
|
|
|
|
if (RetargetingComboButton.IsValid())
|
|
{
|
|
return RetargetingComboButton->IsHovered() ? FEditorStyle::GetSlateColor(InvertedForegroundName) : FEditorStyle::GetSlateColor(DefaultForegroundName);
|
|
}
|
|
return FSlateColor::UseForeground();
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedMeshBoneInfo::CreateBoneTranslationRetargetingModeMenu()
|
|
{
|
|
FMenuBuilder MenuBuilder(true, nullptr);
|
|
|
|
MenuBuilder.BeginSection("BoneTranslationRetargetingMode", LOCTEXT( "BoneTranslationRetargetingModeMenuHeading", "Bone Translation Retargeting Mode" ) );
|
|
{
|
|
UEnum* const Enum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EBoneTranslationRetargetingMode"), true);
|
|
check(Enum);
|
|
|
|
FUIAction ActionRetargetingAnimation = FUIAction(FExecuteAction::CreateSP(this, &FDisplayedMeshBoneInfo::SetBoneTranslationRetargetingMode, EBoneTranslationRetargetingMode::Animation));
|
|
MenuBuilder.AddMenuEntry( Enum->GetDisplayNameText(EBoneTranslationRetargetingMode::Animation), LOCTEXT( "BoneTranslationRetargetingAnimationToolTip", "Use translation from animation." ), FSlateIcon(), ActionRetargetingAnimation);
|
|
|
|
FUIAction ActionRetargetingSkeleton = FUIAction(FExecuteAction::CreateSP(this, &FDisplayedMeshBoneInfo::SetBoneTranslationRetargetingMode, EBoneTranslationRetargetingMode::Skeleton));
|
|
MenuBuilder.AddMenuEntry( Enum->GetDisplayNameText(EBoneTranslationRetargetingMode::Skeleton), LOCTEXT( "BoneTranslationRetargetingSkeletonToolTip", "Use translation from Skeleton." ), FSlateIcon(), ActionRetargetingSkeleton);
|
|
|
|
FUIAction ActionRetargetingLengthScale = FUIAction(FExecuteAction::CreateSP(this, &FDisplayedMeshBoneInfo::SetBoneTranslationRetargetingMode, EBoneTranslationRetargetingMode::AnimationScaled));
|
|
MenuBuilder.AddMenuEntry( Enum->GetDisplayNameText(EBoneTranslationRetargetingMode::AnimationScaled), LOCTEXT( "BoneTranslationRetargetingAnimationScaledToolTip", "Use translation from animation, scale length by Skeleton's proportions." ), FSlateIcon(), ActionRetargetingLengthScale);
|
|
|
|
FUIAction ActionRetargetingAnimationRelative = FUIAction(FExecuteAction::CreateSP(this, &FDisplayedMeshBoneInfo::SetBoneTranslationRetargetingMode, EBoneTranslationRetargetingMode::AnimationRelative));
|
|
MenuBuilder.AddMenuEntry( Enum->GetDisplayNameText(EBoneTranslationRetargetingMode::AnimationRelative), LOCTEXT("BoneTranslationRetargetingAnimationRelativeToolTip", "Use relative translation from animation similar to an additive animation."), FSlateIcon(), ActionRetargetingAnimationRelative);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
FText FDisplayedMeshBoneInfo::GetTranslationRetargetingModeMenuTitle() const
|
|
{
|
|
const USkeleton& Skeleton = GetEditableSkeleton()->GetSkeleton();
|
|
|
|
const int32 BoneIndex = Skeleton.GetReferenceSkeleton().FindBoneIndex( BoneName );
|
|
if( BoneIndex != INDEX_NONE )
|
|
{
|
|
const EBoneTranslationRetargetingMode::Type RetargetingMode = Skeleton.GetBoneTranslationRetargetingMode(BoneIndex);
|
|
UEnum* const Enum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EBoneTranslationRetargetingMode"), true);
|
|
if (Enum)
|
|
{
|
|
return Enum->GetDisplayNameText(RetargetingMode);
|
|
}
|
|
}
|
|
|
|
return LOCTEXT("None", "None");
|
|
}
|
|
|
|
void FDisplayedMeshBoneInfo::SetBoneTranslationRetargetingMode(EBoneTranslationRetargetingMode::Type NewRetargetingMode)
|
|
{
|
|
GetEditableSkeleton()->SetBoneTranslationRetargetingMode(BoneName, NewRetargetingMode);
|
|
}
|
|
|
|
void FDisplayedMeshBoneInfo::SetBoneBlendProfileScale(float NewScale, bool bRecurse)
|
|
{
|
|
FName BlendProfileName = GetSkeletonTree()->GetSelectedBlendProfileName();
|
|
GetEditableSkeleton()->SetBlendProfileScale(BlendProfileName, BoneName, NewScale, bRecurse);
|
|
}
|
|
|
|
FSlateFontInfo FDisplayedMeshBoneInfo::GetBoneTextFont() const
|
|
{
|
|
if (bWeightedBone)
|
|
{
|
|
return FEditorStyle::GetWidgetStyle<FTextBlockStyle>("SkeletonTree.BoldFont").Font;
|
|
}
|
|
else
|
|
{
|
|
return FEditorStyle::GetWidgetStyle<FTextBlockStyle>("SkeletonTree.NormalFont").Font;
|
|
}
|
|
}
|
|
|
|
void FDisplayedMeshBoneInfo::CacheLODChange(UDebugSkelMeshComponent* PreviewComponent)
|
|
{
|
|
bWeightedBone = false;
|
|
bRequiredBone = false;
|
|
if (PreviewComponent)
|
|
{
|
|
int32 BoneIndex = PreviewComponent->GetBoneIndex(BoneName);
|
|
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
if (GetSkeletonTree()->IsBoneWeighted(BoneIndex, PreviewComponent))
|
|
{
|
|
//Bone is vertex weighted
|
|
bWeightedBone = true;
|
|
}
|
|
if (GetSkeletonTree()->IsBoneRequired(BoneIndex, PreviewComponent))
|
|
{
|
|
bRequiredBone = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FSlateColor FDisplayedMeshBoneInfo::GetBoneTextColor() const
|
|
{
|
|
if (bRequiredBone)
|
|
{
|
|
return FSlateColor(FLinearColor::White);
|
|
}
|
|
else
|
|
{
|
|
return FSlateColor(FLinearColor::Gray);
|
|
}
|
|
}
|
|
|
|
FReply FDisplayedMeshBoneInfo::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) )
|
|
{
|
|
return FReply::Handled().BeginDragDrop( FBoneDragDropOp::New(GetEditableSkeleton(), BoneName ) );
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FText FDisplayedMeshBoneInfo::GetBoneToolTip()
|
|
{
|
|
bool bIsMeshBone = false;
|
|
bool bIsWeightedBone = false;
|
|
bool bMeshExists = false;
|
|
|
|
FText ToolTip;
|
|
|
|
if (GetSkeletonTree()->GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent();
|
|
|
|
if (PreviewComponent)
|
|
{
|
|
bMeshExists = true;
|
|
|
|
int32 BoneIndex = PreviewComponent->GetBoneIndex(BoneName);
|
|
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
bIsMeshBone = true;
|
|
|
|
bIsWeightedBone = GetSkeletonTree()->IsBoneWeighted(BoneIndex, PreviewComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !bMeshExists )
|
|
{
|
|
ToolTip = LOCTEXT( "BoneToolTipNoMeshAvailable", "This bone exists only on the skeleton as there is no current mesh set" );
|
|
}
|
|
else
|
|
{
|
|
if ( !bIsMeshBone )
|
|
{
|
|
ToolTip = LOCTEXT( "BoneToolTipSkeletonOnly", "This bone exists only on the skeleton, but not on the current mesh" );
|
|
}
|
|
else
|
|
{
|
|
if ( !bIsWeightedBone )
|
|
{
|
|
ToolTip = LOCTEXT( "BoneToolTipSkeletonAndMesh", "This bone is used by the current mesh, but has no vertices weighted against it" );
|
|
}
|
|
else
|
|
{
|
|
ToolTip = LOCTEXT( "BoneToolTipWeighted", "This bone has vertices weighted against it" );
|
|
}
|
|
}
|
|
}
|
|
|
|
return ToolTip;
|
|
}
|
|
|
|
void FDisplayedMeshBoneInfo::OnBlendSliderCommitted(float NewValue, ETextCommit::Type CommitType)
|
|
{
|
|
if(CommitType == ETextCommit::OnEnter)
|
|
{
|
|
SetBoneBlendProfileScale(NewValue, false);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FDisplayedSocketInfo
|
|
|
|
TSharedRef<ITableRow> FDisplayedSocketInfo::MakeTreeRowWidget(
|
|
const TSharedRef<STableViewBase>& InOwnerTable,
|
|
FText InFilterText )
|
|
{
|
|
return
|
|
SNew( SSkeletonTreeRow, InOwnerTable )
|
|
.Item( SharedThis(this) )
|
|
.FilterText( InFilterText )
|
|
.SkeletonTree(SkeletonTree)
|
|
.EditableSkeleton(GetEditableSkeleton())
|
|
.OnDraggingItem( this, &FDisplayedSocketInfo::OnDragDetected );
|
|
}
|
|
|
|
void FDisplayedSocketInfo::GenerateWidgetForNameColumn( TSharedPtr< SHorizontalBox > Box, FText& FilterText, FIsSelected InIsSelected )
|
|
{
|
|
const FSlateBrush* SocketIcon = ( ParentType == ESocketParentType::Mesh ) ?
|
|
FEditorStyle::GetBrush( "SkeletonTree.MeshSocket" ) :
|
|
FEditorStyle::GetBrush( "SkeletonTree.SkeletonSocket" );
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(0.0f, 1.0f))
|
|
[
|
|
SNew( SImage )
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image( SocketIcon )
|
|
];
|
|
|
|
const FSlateFontInfo TextFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf" ), 10 );
|
|
|
|
FText ToolTip = GetSocketToolTip();
|
|
|
|
TAttribute<FText> SocketNameAttr = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &FDisplayedSocketInfo::GetSocketNameAsText));
|
|
TSharedPtr< SInlineEditableTextBlock > InlineWidget;
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2, 0, 0, 0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew( InlineWidget, SInlineEditableTextBlock )
|
|
.ColorAndOpacity( this, &FDisplayedSocketInfo::GetTextColor )
|
|
.Text( SocketNameAttr )
|
|
.HighlightText( FilterText )
|
|
.Font( TextFont )
|
|
.ToolTipText( ToolTip )
|
|
.OnVerifyTextChanged( this, &FDisplayedSocketInfo::OnVerifySocketNameChanged )
|
|
.OnTextCommitted( this, &FDisplayedSocketInfo::OnCommitSocketName )
|
|
.IsSelected( InIsSelected )
|
|
];
|
|
|
|
OnRenameRequested.BindSP( InlineWidget.Get(), &SInlineEditableTextBlock::EnterEditingMode);
|
|
|
|
if ( ParentType == ESocketParentType::Mesh )
|
|
{
|
|
FText SocketSuffix = IsSocketCustomized() ?
|
|
LOCTEXT( "CustomizedSuffix", " [Mesh]" ) :
|
|
LOCTEXT( "MeshSuffix", " [Mesh Only]" );
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew( STextBlock )
|
|
.ColorAndOpacity( FLinearColor::Gray )
|
|
.Text( SocketSuffix )
|
|
.Font(TextFont)
|
|
.ToolTipText( ToolTip )
|
|
];
|
|
}
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedSocketInfo::GenerateInlineEditWidget(FText& FilterText, FIsSelected InIsSelected)
|
|
{
|
|
if (GetDefault<UPersonaOptions>()->bUseInlineSocketEditor)
|
|
{
|
|
FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true);
|
|
DetailsViewArgs.bAllowFavoriteSystem = false;
|
|
DetailsViewArgs.bShowScrollBar = false;
|
|
TSharedRef<IDetailsView> DetailsView = EditModule.CreateDetailView(DetailsViewArgs);
|
|
DetailsView->SetObject(SocketData);
|
|
|
|
return SNew(SOverlay)
|
|
.Visibility_Lambda([this]() { return bInlineEditorExpanded ? EVisibility::Visible : EVisibility::Collapsed; })
|
|
+ SOverlay::Slot()
|
|
.Padding(2.0f, 4.0f, 2.0f, 4.0f)
|
|
[
|
|
DetailsView
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.Image(FEditorStyle::GetBrush("SkeletonTree.InlineEditorShadowTop"))
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Bottom)
|
|
[
|
|
SNew(SImage)
|
|
.Visibility(EVisibility::HitTestInvisible)
|
|
.Image(FEditorStyle::GetBrush("SkeletonTree.InlineEditorShadowBottom"))
|
|
];
|
|
}
|
|
else
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
}
|
|
|
|
FSlateColor FDisplayedSocketInfo::GetTextColor() const
|
|
{
|
|
if (ParentType == ESocketParentType::Skeleton && bIsCustomized)
|
|
{
|
|
return FSlateColor::UseSubduedForeground();
|
|
}
|
|
else
|
|
{
|
|
return FSlateColor::UseForeground();
|
|
}
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedSocketInfo::GenerateWidgetForDataColumn(const FName& DataColumnName)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
void FDisplayedSocketInfo::OnItemDoubleClicked()
|
|
{
|
|
OnRenameRequested.ExecuteIfBound();
|
|
}
|
|
|
|
bool FDisplayedSocketInfo::CanCustomizeSocket() const
|
|
{
|
|
// If the socket is on the skeleton, we have a valid mesh
|
|
// and there isn't one of the same name on the mesh, we can customize it
|
|
USkeletalMesh* SkeletalMesh = GetSkeletonTree()->GetPreviewScene().IsValid() ? GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
return (SkeletalMesh && !IsSocketCustomized());
|
|
}
|
|
|
|
void FDisplayedSocketInfo::RequestRename()
|
|
{
|
|
OnRenameRequested.ExecuteIfBound();
|
|
}
|
|
|
|
bool FDisplayedSocketInfo::OnVerifySocketNameChanged( const FText& InText, FText& OutErrorMessage )
|
|
{
|
|
// You can't have two sockets with the same name on the mesh, nor on the skeleton,
|
|
// but you can have a socket with the same name on the mesh *and* the skeleton.
|
|
bool bVerifyName = true;
|
|
|
|
FText NewText = FText::TrimPrecedingAndTrailing(InText);
|
|
|
|
if (NewText.IsEmpty())
|
|
{
|
|
OutErrorMessage = LOCTEXT( "EmptySocketName_Error", "Sockets must have a name!");
|
|
bVerifyName = false;
|
|
}
|
|
else
|
|
{
|
|
USkeletalMesh* SkeletalMesh = GetSkeletonTree()->GetPreviewScene().IsValid() ? GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
bVerifyName = !GetEditableSkeleton()->DoesSocketAlreadyExist( SocketData, NewText, ParentType, SkeletalMesh );
|
|
|
|
// Needs to be checked on verify.
|
|
if ( !bVerifyName )
|
|
{
|
|
|
|
// Tell the user that the socket is a duplicate
|
|
OutErrorMessage = LOCTEXT( "DuplicateSocket_Error", "Socket name in use!");
|
|
}
|
|
}
|
|
|
|
return bVerifyName;
|
|
}
|
|
|
|
void FDisplayedSocketInfo::OnCommitSocketName( const FText& InText, ETextCommit::Type CommitInfo )
|
|
{
|
|
FText NewText = FText::TrimPrecedingAndTrailing(InText);
|
|
|
|
// Notify skeleton tree of socket rename
|
|
USkeletalMesh* SkeletalMesh = GetSkeletonTree()->GetPreviewScene().IsValid() ? GetSkeletonTree()->GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
GetEditableSkeleton()->RenameSocket(SocketData->SocketName, FName(*NewText.ToString()), SkeletalMesh);
|
|
}
|
|
|
|
FReply FDisplayedSocketInfo::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
|
{
|
|
if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) )
|
|
{
|
|
FSelectedSocketInfo SocketInfo( SocketData, ParentType == ESocketParentType::Skeleton );
|
|
|
|
return FReply::Handled().BeginDragDrop( FSocketDragDropOp::New( SocketInfo, MouseEvent.IsAltDown() ) );
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
FText FDisplayedSocketInfo::GetSocketToolTip()
|
|
{
|
|
FText ToolTip;
|
|
|
|
if ( ParentType == ESocketParentType::Skeleton && bIsCustomized == false )
|
|
{
|
|
ToolTip = LOCTEXT( "SocketToolTipSkeletonOnly", "This socket is on the skeleton only. It is shared with all meshes that use this skeleton" );
|
|
}
|
|
else if ( ParentType == ESocketParentType::Mesh && bIsCustomized == false )
|
|
{
|
|
ToolTip = LOCTEXT( "SocketToolTipMeshOnly", "This socket is on the current mesh only" );
|
|
}
|
|
else if ( ParentType == ESocketParentType::Skeleton )
|
|
{
|
|
ToolTip = LOCTEXT( "SocketToolTipSkeleton", "This socket is on the skeleton (shared with all meshes that use the skeleton), and the current mesh has duplciated version of it" );
|
|
}
|
|
else
|
|
{
|
|
ToolTip = LOCTEXT( "SocketToolTipCustomized", "This socket is on the current mesh, customizing the socket of the same name on the skeleton" );
|
|
}
|
|
|
|
return ToolTip;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FDisplayedAttachedAssetInfo
|
|
|
|
TSharedRef<FDisplayedAttachedAssetInfo> FDisplayedAttachedAssetInfo::Make(const FName& InAttachedTo, UObject* InAsset, TWeakPtr<SSkeletonTree> InSkeletonTree)
|
|
{
|
|
FDisplayedAttachedAssetInfo* Info = new FDisplayedAttachedAssetInfo(InAttachedTo, InAsset);
|
|
Info->SkeletonTree = InSkeletonTree;
|
|
|
|
Info->AssetComponent = PersonaUtils::GetComponentForAttachedObject(InSkeletonTree.Pin()->GetPreviewScene()->GetPreviewMeshComponent(), InAsset, InAttachedTo);
|
|
|
|
return MakeShareable(Info);
|
|
}
|
|
|
|
TSharedRef<ITableRow> FDisplayedAttachedAssetInfo::MakeTreeRowWidget(
|
|
const TSharedRef<STableViewBase>& InOwnerTable,
|
|
FText InFilterText )
|
|
{
|
|
return
|
|
SNew(SSkeletonTreeRow, InOwnerTable)
|
|
.Item(SharedThis(this))
|
|
.FilterText(InFilterText)
|
|
.SkeletonTree(SkeletonTree)
|
|
.EditableSkeleton(GetEditableSkeleton());
|
|
}
|
|
|
|
void FDisplayedAttachedAssetInfo::GenerateWidgetForNameColumn( TSharedPtr< SHorizontalBox > Box, FText& FilterText, FIsSelected InIsSelected )
|
|
{
|
|
UActorFactory* ActorFactory = FActorFactoryAssetProxy::GetFactoryForAssetObject( Asset );
|
|
const FSlateBrush* IconBrush = FSlateIconFinder::FindIconBrushForClass(ActorFactory->GetDefaultActorClass(FAssetData()));
|
|
|
|
Box->AddSlot()
|
|
.Padding(FMargin(0.0f, 1.0f))
|
|
.AutoWidth()
|
|
[
|
|
SNew( SImage )
|
|
.Image( IconBrush )
|
|
];
|
|
|
|
const FSlateFontInfo TextFont( FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 10 );
|
|
const FLinearColor TextColor(FLinearColor::White);
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
[
|
|
SNew( STextBlock )
|
|
.ColorAndOpacity(TextColor)
|
|
.Text( FText::FromString(Asset->GetName()) )
|
|
.HighlightText( FilterText )
|
|
.Font(TextFont)
|
|
];
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(5.0f,0.0f)
|
|
[
|
|
SNew( STextBlock )
|
|
.ColorAndOpacity( FLinearColor::Gray )
|
|
.Text(LOCTEXT( "AttachedAssetPreviewText", "[Preview Only]" ) )
|
|
.Font(TextFont)
|
|
.ToolTipText( LOCTEXT( "AttachedAssetPreviewText_ToolTip", "Attached assets in Persona are preview only and do not carry through to the game." ) )
|
|
];
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedAttachedAssetInfo::GenerateWidgetForDataColumn(const FName& DataColumnName)
|
|
{
|
|
if(DataColumnName == ColumnID_RetargetingLabel)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(SCheckBox)
|
|
.ToolTipText(LOCTEXT("TranslationCheckBoxToolTip", "Click to toggle visibility of this asset"))
|
|
.OnCheckStateChanged(this, &FDisplayedAttachedAssetInfo::OnToggleAssetDisplayed)
|
|
.IsChecked(this, &FDisplayedAttachedAssetInfo::IsAssetDisplayed)
|
|
.Style(FEditorStyle::Get(), "CheckboxLookToggleButtonCheckbox")
|
|
[
|
|
SNew(SImage)
|
|
.Image(this, &FDisplayedAttachedAssetInfo::OnGetAssetDisplayedButtonImage)
|
|
.ColorAndOpacity(FLinearColor::Black)
|
|
]
|
|
];
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
ECheckBoxState FDisplayedAttachedAssetInfo::IsAssetDisplayed() const
|
|
{
|
|
if(AssetComponent.IsValid())
|
|
{
|
|
return AssetComponent->IsVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
return ECheckBoxState::Undetermined;
|
|
}
|
|
|
|
void FDisplayedAttachedAssetInfo::OnToggleAssetDisplayed( ECheckBoxState InCheckboxState )
|
|
{
|
|
if(AssetComponent.IsValid())
|
|
{
|
|
AssetComponent->SetVisibility(InCheckboxState == ECheckBoxState::Checked);
|
|
}
|
|
}
|
|
|
|
const FSlateBrush* FDisplayedAttachedAssetInfo::OnGetAssetDisplayedButtonImage() const
|
|
{
|
|
return IsAssetDisplayed() == ECheckBoxState::Checked ?
|
|
FEditorStyle::GetBrush( "Kismet.VariableList.ExposeForInstance" ) :
|
|
FEditorStyle::GetBrush( "Kismet.VariableList.HideForInstance" );
|
|
}
|
|
|
|
void FDisplayedAttachedAssetInfo::OnItemDoubleClicked()
|
|
{
|
|
TArray<UObject*> AssetsToSync;
|
|
AssetsToSync.Add(Asset);
|
|
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
ContentBrowserModule.Get().SyncBrowserToAssets( AssetsToSync );
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FDisplayedVirtualBoneInfo
|
|
|
|
TSharedRef<ITableRow> FDisplayedVirtualBoneInfo::MakeTreeRowWidget(
|
|
const TSharedRef<STableViewBase>& OwnerTable,
|
|
FText FilterText)
|
|
{
|
|
return
|
|
SNew(SSkeletonTreeRow, OwnerTable)
|
|
.Item(SharedThis(this))
|
|
.FilterText(FilterText)
|
|
.SkeletonTree(SkeletonTree);
|
|
}
|
|
|
|
EVisibility FDisplayedVirtualBoneInfo::GetLODIconVisibility() const
|
|
{
|
|
return EVisibility::Visible;
|
|
}
|
|
|
|
void FDisplayedVirtualBoneInfo::GenerateWidgetForNameColumn(TSharedPtr< SHorizontalBox > Box, FText& FilterText, FIsSelected InIsSelected)
|
|
{
|
|
const FSlateBrush* LODIcon = FEditorStyle::GetBrush("SkeletonTree.LODBone");
|
|
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(FMargin(0.0f, 1.0f))
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SImage)
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
.Image(LODIcon)
|
|
.Visibility(this, &FDisplayedVirtualBoneInfo::GetLODIconVisibility)
|
|
];
|
|
|
|
FText ToolTip = GetBoneToolTip();
|
|
Box->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2, 0, 0, 0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(this, &FDisplayedVirtualBoneInfo::GetBoneTextColor)
|
|
.Text(FText::FromName(BoneName))
|
|
.HighlightText(FilterText)
|
|
.Font(this, &FDisplayedVirtualBoneInfo::GetBoneTextFont)
|
|
.ToolTipText(ToolTip)
|
|
];
|
|
}
|
|
|
|
TSharedRef< SWidget > FDisplayedVirtualBoneInfo::GenerateWidgetForDataColumn(const FName& DataColumnName)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
FSlateFontInfo FDisplayedVirtualBoneInfo::GetBoneTextFont() const
|
|
{
|
|
return FEditorStyle::GetWidgetStyle<FTextBlockStyle>("SkeletonTree.BoldFont").Font;
|
|
}
|
|
|
|
FSlateColor FDisplayedVirtualBoneInfo::GetBoneTextColor() const
|
|
{
|
|
return FSlateColor(FLinearColor(0.4f, 0.4f, 1.f));
|
|
}
|
|
|
|
FText FDisplayedVirtualBoneInfo::GetBoneToolTip()
|
|
{
|
|
return LOCTEXT("VirtualBone_ToolTip", "Virtual Bones are added in editor and allow space switching between two different bones in the skeleton.");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// SSkeletonTree
|
|
|
|
void SSkeletonTree::Construct(const FArguments& InArgs, const TSharedRef<FEditableSkeleton>& InEditableSkeleton, const FSkeletonTreeArgs& InSkeletonTreeArgs)
|
|
{
|
|
BoneFilter = EBoneFilter::All;
|
|
SocketFilter = ESocketFilter::Active;
|
|
bShowingAdvancedOptions = false;
|
|
bSelectingSocket = false;
|
|
bSelectingBone = false;
|
|
bDeselectingAll = false;
|
|
|
|
EditableSkeleton = InEditableSkeleton;
|
|
PreviewScene = InSkeletonTreeArgs.PreviewScene;
|
|
IsEditable = InArgs._IsEditable;
|
|
|
|
SetPreviewComponentSocketFilter();
|
|
|
|
// Register delegates
|
|
InSkeletonTreeArgs.OnPostUndo.Add(FSimpleDelegate::CreateSP(this, &SSkeletonTree::PostUndo));
|
|
|
|
PreviewScene->RegisterOnLODChanged(FSimpleDelegate::CreateSP(this, &SSkeletonTree::OnLODSwitched));
|
|
|
|
InEditableSkeleton->RegisterOnSkeletonHierarchyChanged(USkeleton::FOnSkeletonHierarchyChanged::CreateSP(this, &SSkeletonTree::PostUndo));
|
|
|
|
RegisterOnObjectSelected(InSkeletonTreeArgs.OnObjectSelected);
|
|
|
|
// Register and bind all our menu commands
|
|
FSkeletonTreeCommands::Register();
|
|
BindCommands();
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew( SVerticalBox )
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( FMargin( 0.0f, 0.0f, 0.0f, 4.0f ) )
|
|
[
|
|
SAssignNew( NameFilterBox, SSearchBox )
|
|
.SelectAllTextWhenFocused( true )
|
|
.OnTextChanged( this, &SSkeletonTree::OnFilterTextChanged )
|
|
.HintText( LOCTEXT( "SearchBoxHint", "Search Skeleton Tree...") )
|
|
.AddMetaData<FTagMetaData>(TEXT("SkelTree.Search"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding( FMargin( 0.0f, 4.0f, 0.0f, 0.0f ) )
|
|
[
|
|
SAssignNew(TreeHolder, SOverlay)
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(0.0f, 0.0f, 2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(0.0f, 0.0f, 2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(BlendProfilePicker, SBlendProfilePicker, GetEditableSkeleton())
|
|
.Standalone(true)
|
|
.OnBlendProfileSelected(this, &SSkeletonTree::OnBlendProfileSelected)
|
|
]
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.Padding( 0.0f, 0.0f, 2.0f, 0.0f )
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.AutoWidth()
|
|
[
|
|
SAssignNew( FilterComboButton, SComboButton )
|
|
.ButtonStyle(FEditorStyle::Get(), "ToggleButton")
|
|
.ForegroundColor(this, &SSkeletonTree::GetFilterComboButtonForegroundColor)
|
|
.ContentPadding(0.0f)
|
|
.OnGetMenuContent( this, &SSkeletonTree::CreateFilterMenu )
|
|
.ToolTipText( LOCTEXT( "BoneFilterToolTip", "Change which types of bones and sockets are shown, retargeting options" ) )
|
|
.AddMetaData<FTagMetaData>(TEXT("SkelTree.Bones"))
|
|
.ButtonContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SImage).Image( FEditorStyle::GetBrush("GenericViewButton") )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2, 0, 0, 0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( this, &SSkeletonTree::GetFilterMenuTitle )
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
CreateTreeColumns();
|
|
|
|
OnLODSwitched();
|
|
}
|
|
|
|
SSkeletonTree::~SSkeletonTree()
|
|
{
|
|
if (EditableSkeleton.IsValid())
|
|
{
|
|
EditableSkeleton.Pin()->UnregisterOnSkeletonHierarchyChanged(this);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::BindCommands()
|
|
{
|
|
// This should not be called twice on the same instance
|
|
check( !UICommandList.IsValid() );
|
|
|
|
UICommandList = MakeShareable( new FUICommandList );
|
|
|
|
FUICommandList& CommandList = *UICommandList;
|
|
|
|
// Grab the list of menu commands to bind...
|
|
const FSkeletonTreeCommands& MenuActions = FSkeletonTreeCommands::Get();
|
|
|
|
// ...and bind them all
|
|
|
|
// Bone Filter commands
|
|
CommandList.MapAction(
|
|
MenuActions.ShowAllBones,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::All ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::All ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowMeshBones,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::Mesh ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::Mesh ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowLODBones,
|
|
FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneFilter, EBoneFilter::LOD),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsBoneFilter, EBoneFilter::LOD));
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowWeightedBones,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::Weighted ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::Weighted ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.HideBones,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetBoneFilter, EBoneFilter::None ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsBoneFilter, EBoneFilter::None ) );
|
|
|
|
// Socket filter commands
|
|
CommandList.MapAction(
|
|
MenuActions.ShowActiveSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Active ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Active ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowMeshSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Mesh ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Mesh ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowSkeletonSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::Skeleton ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::Skeleton ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowAllSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::All ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::All ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.HideSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::SetSocketFilter, ESocketFilter::None ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SSkeletonTree::IsSocketFilter, ESocketFilter::None ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ShowRetargeting,
|
|
FExecuteAction::CreateSP(this, &SSkeletonTree::OnChangeShowingAdvancedOptions),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SSkeletonTree::IsShowingAdvancedOptions));
|
|
|
|
// Socket manipulation commands
|
|
CommandList.MapAction(
|
|
MenuActions.AddSocket,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnAddSocket ),
|
|
FCanExecuteAction::CreateSP( this, &SSkeletonTree::IsAddingSocketsAllowed ) );
|
|
|
|
CommandList.MapAction(
|
|
FGenericCommands::Get().Rename,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnRenameSocket ),
|
|
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanRenameSelected ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.CreateMeshSocket,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCustomizeSocket ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.RemoveMeshSocket,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnDeleteSelectedRows ) ); // Removing customization just deletes the mesh socket
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.PromoteSocketToSkeleton,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnPromoteSocket ) ); // Adding customization just deletes the mesh socket
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.DeleteSelectedRows,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnDeleteSelectedRows ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.CopyBoneNames,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCopyBoneNames ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.ResetBoneTransforms,
|
|
FExecuteAction::CreateSP(this, &SSkeletonTree::OnResetBoneTransforms ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.CopySockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnCopySockets ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.PasteSockets,
|
|
FExecuteAction::CreateSP( this, &SSkeletonTree::OnPasteSockets, false ) );
|
|
|
|
CommandList.MapAction(
|
|
MenuActions.PasteSocketsToSelectedBone,
|
|
FExecuteAction::CreateSP(this, &SSkeletonTree::OnPasteSockets, true));
|
|
}
|
|
|
|
TSharedRef<ITableRow> SSkeletonTree::MakeTreeRowWidget(TSharedPtr<FDisplayedTreeRowInfo> InInfo, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
check( InInfo.IsValid() );
|
|
|
|
return InInfo->MakeTreeRowWidget( OwnerTable, FilterText );
|
|
}
|
|
|
|
void SSkeletonTree::GetChildrenForInfo(TSharedPtr<FDisplayedTreeRowInfo> InInfo, TArray< TSharedPtr<FDisplayedTreeRowInfo> >& OutChildren)
|
|
{
|
|
check(InInfo.IsValid());
|
|
OutChildren = InInfo->Children;
|
|
}
|
|
|
|
bool SSkeletonTree::AttachToParent( TSharedRef<FDisplayedTreeRowInfo> ItemToAttach, FName ParentName, int32 ItemsToInclude)
|
|
{
|
|
// Find the parent info
|
|
for (int32 BoneIndex = 0; BoneIndex < DisplayMirror.Num(); ++BoneIndex)
|
|
{
|
|
TSharedRef<FDisplayedTreeRowInfo> CurrentItem = DisplayMirror[BoneIndex];
|
|
|
|
// does the item match our filter
|
|
if ( (ItemsToInclude & (int32)CurrentItem->GetType()) != 0 )
|
|
{
|
|
if ( CurrentItem->GetRowItemName() == ParentName )
|
|
{
|
|
CurrentItem->Children.Insert( ItemToAttach, 0 );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SSkeletonTree::CreateTreeColumns()
|
|
{
|
|
TSharedRef<SHeaderRow> TreeHeaderRow = SNew(SHeaderRow)
|
|
+ SHeaderRow::Column(ColumnID_BoneLabel)
|
|
.DefaultLabel(LOCTEXT("SkeletonBoneNameLabel", "Name"))
|
|
.FillWidth(0.5f);
|
|
|
|
if (bShowingAdvancedOptions)
|
|
{
|
|
TreeHeaderRow->AddColumn(
|
|
SHeaderRow::Column(ColumnID_RetargetingLabel)
|
|
.DefaultLabel(LOCTEXT("SkeletonBoneTranslationRetargetingLabel", "Translation Retargeting"))
|
|
.FillWidth(0.25f)
|
|
);
|
|
}
|
|
|
|
if(BlendProfilePicker->GetSelectedBlendProfileName() != NAME_None)
|
|
{
|
|
TreeHeaderRow->AddColumn(
|
|
SHeaderRow::Column(ColumnID_BlendProfileLabel)
|
|
.DefaultLabel(LOCTEXT("BlendProfileLabel", "Blend Profile Scale"))
|
|
.FillWidth(0.25f));
|
|
}
|
|
|
|
SAssignNew(SkeletonTreeView, SMeshSkeletonTreeRowType)
|
|
.TreeItemsSource(&SkeletonRowList)
|
|
.OnGenerateRow(this, &SSkeletonTree::MakeTreeRowWidget)
|
|
.OnGetChildren(this, &SSkeletonTree::GetChildrenForInfo)
|
|
.OnContextMenuOpening(this, &SSkeletonTree::CreateContextMenu)
|
|
.OnSelectionChanged(this, &SSkeletonTree::OnSelectionChanged)
|
|
.OnItemScrolledIntoView(this, &SSkeletonTree::OnItemScrolledIntoView)
|
|
.OnMouseButtonDoubleClick(this, &SSkeletonTree::OnTreeDoubleClick)
|
|
.OnSetExpansionRecursive(this, &SSkeletonTree::SetTreeItemExpansionRecursive)
|
|
.ItemHeight(24)
|
|
.HeaderRow
|
|
(
|
|
TreeHeaderRow
|
|
);
|
|
|
|
TreeHolder->ClearChildren();
|
|
TreeHolder->AddSlot()
|
|
[
|
|
SNew(SScrollBorder, SkeletonTreeView.ToSharedRef())
|
|
[
|
|
SkeletonTreeView.ToSharedRef()
|
|
]
|
|
];
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
|
|
void SSkeletonTree::CreateFromSkeleton(USkeletalMeshSocket* SocketToRename /*= nullptr*/)
|
|
{
|
|
const USkeleton& Skeleton = GetEditableSkeletonInternal()->GetSkeleton();
|
|
|
|
SkeletonRowList.Empty();
|
|
|
|
const FReferenceSkeleton& RefSkeleton = Skeleton.GetReferenceSkeleton();
|
|
|
|
DisplayMirror.Empty( RefSkeleton.GetNum() );
|
|
|
|
if( BoneFilter != EBoneFilter::None )
|
|
{
|
|
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetRawBoneNum(); ++BoneIndex)
|
|
{
|
|
const FName& BoneName = RefSkeleton.GetBoneName(BoneIndex);
|
|
if ( !FilterText.IsEmpty() && !BoneName.ToString().Contains( FilterText.ToString()) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
if (PreviewMeshComponent)
|
|
{
|
|
int32 BoneMeshIndex = PreviewMeshComponent->GetBoneIndex(BoneName);
|
|
|
|
// Remove non-mesh bones if we're filtering
|
|
if ((BoneFilter == EBoneFilter::Mesh || BoneFilter == EBoneFilter::Weighted || BoneFilter == EBoneFilter::LOD) &&
|
|
BoneMeshIndex == INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Remove non-vertex-weighted bones if we're filtering
|
|
if (BoneFilter == EBoneFilter::Weighted && !IsBoneWeighted(BoneMeshIndex, PreviewMeshComponent))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Remove non-vertex-weighted bones if we're filtering
|
|
if (BoneFilter == EBoneFilter::LOD && !IsBoneRequired(BoneMeshIndex, PreviewMeshComponent))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
|
|
|
TSharedRef<FDisplayedMeshBoneInfo> DisplayBone = FDisplayedMeshBoneInfo::Make( RefSkeleton.GetBoneName(BoneIndex), SharedThis(this) );
|
|
|
|
if ( BoneIndex > 0 && FilterText.IsEmpty() && DisplayMirror.Num() != 0 ) // No hierarchy when filtertext is non-empty
|
|
{
|
|
check(ParentIndex < BoneIndex);
|
|
|
|
// We support filtering the list, so ParentIndex isn't necessarily correct in the DisplayMirror any more, so we need to search for it by name
|
|
FName ParentName = RefSkeleton.GetBoneName(ParentIndex);
|
|
bool bFoundRemappedParentIndex = false;
|
|
|
|
for ( int32 i = 0; i < DisplayMirror.Num(); ++i )
|
|
{
|
|
FDisplayedTreeRowInfoPtr TreeRowInfo = DisplayMirror[i];
|
|
|
|
// At this point, we can assume that *all* of DisplayMirror contains bones, not sockets
|
|
check( TreeRowInfo->GetType() == ESkeletonTreeRowType::Bone );
|
|
|
|
if ( *static_cast<FName*>( TreeRowInfo->GetData() ) == ParentName )
|
|
{
|
|
ParentIndex = i;
|
|
bFoundRemappedParentIndex = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bFoundRemappedParentIndex )
|
|
{
|
|
DisplayMirror[ParentIndex]->Children.Add(DisplayBone);
|
|
}
|
|
else
|
|
{
|
|
// The parent bone didn't pass the filter, so just add this bone to the base of the tree
|
|
SkeletonRowList.Add(DisplayBone);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SkeletonRowList.Add(DisplayBone);
|
|
}
|
|
|
|
DisplayMirror.Add(DisplayBone);
|
|
SkeletonTreeView->SetItemExpansion(DisplayBone, true);
|
|
}
|
|
}
|
|
|
|
// Add the sockets for the skeleton
|
|
if ( SocketFilter == ESocketFilter::Active || SocketFilter == ESocketFilter::All || SocketFilter == ESocketFilter::Skeleton )
|
|
{
|
|
AddSocketsFromData( Skeleton.Sockets, ESocketParentType::Skeleton, SocketToRename );
|
|
}
|
|
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
if ( SocketFilter == ESocketFilter::Active || SocketFilter == ESocketFilter::All || SocketFilter == ESocketFilter::Mesh )
|
|
{
|
|
// Add the sockets for the mesh
|
|
if (SkeletalMesh)
|
|
{
|
|
AddSocketsFromData( SkeletalMesh->GetMeshOnlySocketList(), ESocketParentType::Mesh, SocketToRename );
|
|
}
|
|
}
|
|
|
|
//Add the attached mesh items last, these are the most child like of all the items that can go in the skeleton tree
|
|
|
|
// Mesh attached items...
|
|
if (SkeletalMesh)
|
|
{
|
|
AddAttachedAssets( SkeletalMesh->PreviewAttachedAssetContainer );
|
|
}
|
|
|
|
if (BoneFilter != EBoneFilter::None)
|
|
{
|
|
AddVirtualBones(Skeleton.GetVirtualBones());
|
|
}
|
|
|
|
// ...skeleton attached items
|
|
AddAttachedAssets( Skeleton.PreviewAttachedAssetContainer );
|
|
|
|
SkeletonTreeView->RequestTreeRefresh();
|
|
}
|
|
|
|
void SSkeletonTree::AddSocketsFromData(const TArray< USkeletalMeshSocket* >& SocketArray, ESocketParentType ParentType, USkeletalMeshSocket* SocketToRename)
|
|
{
|
|
const USkeleton& Skeleton = GetEditableSkeletonInternal()->GetSkeleton();
|
|
|
|
for (auto SocketIt = SocketArray.CreateConstIterator(); SocketIt; ++SocketIt)
|
|
{
|
|
USkeletalMeshSocket* Socket = *(SocketIt);
|
|
|
|
if (!FilterText.IsEmpty() && !Socket->SocketName.ToString().Contains(FilterText.ToString()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool bIsCustomized = false;
|
|
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
if (ParentType == ESocketParentType::Mesh)
|
|
{
|
|
bIsCustomized = GetEditableSkeletonInternal()->DoesSocketAlreadyExist(NULL, FText::FromName(Socket->SocketName), ESocketParentType::Skeleton, SkeletalMesh);
|
|
}
|
|
else
|
|
{
|
|
if (SkeletalMesh)
|
|
{
|
|
bIsCustomized = GetEditableSkeletonInternal()->DoesSocketAlreadyExist(NULL, FText::FromName(Socket->SocketName), ESocketParentType::Mesh, SkeletalMesh);
|
|
|
|
if (SocketFilter == ESocketFilter::Active && bIsCustomized)
|
|
{
|
|
// Don't add the skeleton socket if it's already added for the mesh
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<FDisplayedSocketInfo> DisplaySocket = FDisplayedSocketInfo::Make(Socket, ParentType, SharedThis(this), bIsCustomized);
|
|
|
|
if (Socket == SocketToRename)
|
|
{
|
|
SkeletonTreeView->SetSelection(DisplaySocket);
|
|
OnRenameSocket();
|
|
}
|
|
DisplayMirror.Add(DisplaySocket);
|
|
|
|
if (!AttachToParent(DisplaySocket, Socket->BoneName, (int32)ESkeletonTreeRowType::Bone))
|
|
{
|
|
// Just add it to the list if the parent bone isn't currently displayed
|
|
SkeletonRowList.Add(DisplaySocket);
|
|
}
|
|
|
|
SkeletonTreeView->SetItemExpansion(DisplaySocket, true);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::AddVirtualBones(const TArray<FVirtualBone>& VirtualBones)
|
|
{
|
|
for (const FVirtualBone& VirtualBone: VirtualBones)
|
|
{
|
|
if (!FilterText.IsEmpty() && !VirtualBone.VirtualBoneName.ToString().Contains(FilterText.ToString()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FDisplayedVirtualBoneInfo> DisplayBone = FDisplayedVirtualBoneInfo::Make(VirtualBone.VirtualBoneName, SharedThis(this));
|
|
|
|
DisplayMirror.Add(DisplayBone);
|
|
|
|
if (!AttachToParent(DisplayBone, VirtualBone.SourceBoneName, (int32)ESkeletonTreeRowType::Bone))
|
|
{
|
|
// Just add it to the list if the parent bone isn't currently displayed
|
|
SkeletonRowList.Add(DisplayBone);
|
|
}
|
|
|
|
SkeletonTreeView->SetItemExpansion(DisplayBone, true);
|
|
}
|
|
}
|
|
|
|
class FBoneTreeSelection
|
|
{
|
|
public:
|
|
TArray<TSharedPtr<FDisplayedTreeRowInfo>> SelectedItems;
|
|
|
|
TArray<TSharedPtr<FDisplayedMeshBoneInfo>> SelectedBones;
|
|
TArray<TSharedPtr<FDisplayedSocketInfo>> SelectedSockets;
|
|
TArray<TSharedPtr<FDisplayedAttachedAssetInfo>> SelectedAssets;
|
|
TArray<TSharedPtr<FDisplayedVirtualBoneInfo>> SelectedVirtualBones;
|
|
|
|
FBoneTreeSelection(TArray<TSharedPtr<FDisplayedTreeRowInfo>> InSelectedItems) : SelectedItems(InSelectedItems)
|
|
{
|
|
for ( auto ItemIt = SelectedItems.CreateConstIterator(); ItemIt; ++ItemIt )
|
|
{
|
|
FDisplayedTreeRowInfoPtr Item = *(ItemIt);
|
|
|
|
switch(Item->GetType())
|
|
{
|
|
case ESkeletonTreeRowType::Bone:
|
|
{
|
|
SelectedBones.Add( StaticCastSharedPtr< FDisplayedMeshBoneInfo >(Item) );
|
|
break;
|
|
}
|
|
case ESkeletonTreeRowType::Socket:
|
|
{
|
|
SelectedSockets.Add( StaticCastSharedPtr< FDisplayedSocketInfo >(Item) );
|
|
break;
|
|
}
|
|
case ESkeletonTreeRowType::AttachedAsset:
|
|
{
|
|
SelectedAssets.Add( StaticCastSharedPtr< FDisplayedAttachedAssetInfo >(Item) );
|
|
break;
|
|
}
|
|
case ESkeletonTreeRowType::VirtualBone:
|
|
{
|
|
SelectedVirtualBones.Add(StaticCastSharedPtr< FDisplayedVirtualBoneInfo >(Item));
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
check(false); // Unknown row type!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsMultipleItemsSelected() const
|
|
{
|
|
return SelectedItems.Num() > 1;
|
|
}
|
|
|
|
bool IsSingleItemSelected() const
|
|
{
|
|
return SelectedItems.Num() == 1;
|
|
}
|
|
|
|
bool IsSingleOfTypeSelected(ESkeletonTreeRowType ItemType) const
|
|
{
|
|
if(IsSingleItemSelected())
|
|
{
|
|
return GetNumSelected(ItemType) == 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FDisplayedTreeRowInfo> GetSingleSelectedItem()
|
|
{
|
|
check(IsSingleItemSelected());
|
|
return SelectedItems[0];
|
|
}
|
|
|
|
bool HasSelectedOfType(ESkeletonTreeRowType ItemType) const
|
|
{
|
|
return GetNumSelected(ItemType) > 0;
|
|
}
|
|
|
|
int32 GetNumSelected(ESkeletonTreeRowType ItemType) const
|
|
{
|
|
switch (ItemType)
|
|
{
|
|
case ESkeletonTreeRowType::Bone:
|
|
{
|
|
return SelectedBones.Num();
|
|
}
|
|
case ESkeletonTreeRowType::Socket:
|
|
{
|
|
return SelectedSockets.Num();
|
|
}
|
|
case ESkeletonTreeRowType::AttachedAsset:
|
|
{
|
|
return SelectedAssets.Num();
|
|
}
|
|
case ESkeletonTreeRowType::VirtualBone:
|
|
{
|
|
return SelectedVirtualBones.Num();
|
|
}
|
|
default:
|
|
{
|
|
check(false); // Unknown type
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
TSharedPtr< SWidget > SSkeletonTree::CreateContextMenu()
|
|
{
|
|
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
|
|
|
|
FBoneTreeSelection BoneTreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
const bool CloseAfterSelection = true;
|
|
FMenuBuilder MenuBuilder( CloseAfterSelection, UICommandList );
|
|
{
|
|
if(BoneTreeSelection.HasSelectedOfType(ESkeletonTreeRowType::AttachedAsset) || BoneTreeSelection.HasSelectedOfType(ESkeletonTreeRowType::Socket) || BoneTreeSelection.HasSelectedOfType(ESkeletonTreeRowType::VirtualBone))
|
|
{
|
|
MenuBuilder.BeginSection("SkeletonTreeSelectedItemsActions", LOCTEXT( "SelectedActions", "Selected Item Actions" ) );
|
|
MenuBuilder.AddMenuEntry( Actions.DeleteSelectedRows );
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if(BoneTreeSelection.HasSelectedOfType(ESkeletonTreeRowType::Bone))
|
|
{
|
|
MenuBuilder.BeginSection("SkeletonTreeBonesAction", LOCTEXT( "BoneActions", "Selected Bone Actions" ) );
|
|
MenuBuilder.AddMenuEntry( Actions.CopyBoneNames );
|
|
MenuBuilder.AddMenuEntry( Actions.ResetBoneTransforms );
|
|
|
|
if(BoneTreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Bone))
|
|
{
|
|
MenuBuilder.AddMenuEntry( Actions.AddSocket );
|
|
MenuBuilder.AddMenuEntry( Actions.PasteSockets );
|
|
MenuBuilder.AddMenuEntry( Actions.PasteSocketsToSelectedBone );
|
|
}
|
|
|
|
MenuBuilder.AddSubMenu(LOCTEXT("AddVirtualBone", "Add Virtual Bone"),
|
|
LOCTEXT("AddVirtualBone_ToolTip", "Adds a virtual bone to the skeleton."),
|
|
FNewMenuDelegate::CreateSP(this, &SSkeletonTree::FillVirtualBoneSubmenu, BoneTreeSelection.SelectedBones));
|
|
|
|
MenuBuilder.EndSection();
|
|
|
|
UBlendProfile* const SelectedBlendProfile = BlendProfilePicker->GetSelectedBlendProfile();
|
|
if(SelectedBlendProfile && BoneTreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Bone))
|
|
{
|
|
TSharedPtr<FDisplayedMeshBoneInfo> BoneInfo = BoneTreeSelection.SelectedBones[0];
|
|
|
|
FName BoneName = *static_cast<FName*>(BoneInfo->GetData());
|
|
const USkeleton& Skeleton = GetEditableSkeletonInternal()->GetSkeleton();
|
|
int32 BoneIndex = Skeleton.GetReferenceSkeleton().FindBoneIndex(BoneName);
|
|
|
|
float CurrentBlendScale = SelectedBlendProfile->GetBoneBlendScale(BoneIndex);
|
|
|
|
MenuBuilder.BeginSection("SkeletonTreeBlendProfileScales", LOCTEXT("BlendProfileContextOptions", "Blend Profile"));
|
|
{
|
|
FUIAction RecursiveSetScales;
|
|
RecursiveSetScales.ExecuteAction = FExecuteAction::CreateSP(this, &SSkeletonTree::RecursiveSetBlendProfileScales, CurrentBlendScale);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(
|
|
FText::Format(LOCTEXT("RecursiveSetBlendScales_Label", "Recursively Set Blend Scales To {0}"), FText::AsNumber(CurrentBlendScale)),
|
|
LOCTEXT("RecursiveSetBlendScales_ToolTip", "Sets all child bones to use the same blend profile scale as the selected bone"),
|
|
FSlateIcon(),
|
|
RecursiveSetScales
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if(bShowingAdvancedOptions)
|
|
{
|
|
MenuBuilder.BeginSection("SkeletonTreeBoneTranslationRetargeting", LOCTEXT("BoneTranslationRetargetingHeader", "Bone Translation Retargeting"));
|
|
{
|
|
FUIAction RecursiveRetargetingSkeletonAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::Skeleton));
|
|
FUIAction RecursiveRetargetingAnimationAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::Animation));
|
|
FUIAction RecursiveRetargetingAnimationScaledAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::AnimationScaled));
|
|
FUIAction RecursiveRetargetingAnimationRelativeAction = FUIAction(FExecuteAction::CreateSP(this, &SSkeletonTree::SetBoneTranslationRetargetingModeRecursive, EBoneTranslationRetargetingMode::AnimationRelative));
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(LOCTEXT("SetTranslationRetargetingSkeletonChildrenAction", "Recursively Set Translation Retargeting Skeleton")
|
|
, LOCTEXT("BoneTranslationRetargetingSkeletonToolTip", "Use translation from Skeleton.")
|
|
, FSlateIcon()
|
|
, RecursiveRetargetingSkeletonAction
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(LOCTEXT("SetTranslationRetargetingAnimationChildrenAction", "Recursively Set Translation Retargeting Animation")
|
|
, LOCTEXT("BoneTranslationRetargetingAnimationToolTip", "Use translation from animation.")
|
|
, FSlateIcon()
|
|
, RecursiveRetargetingAnimationAction
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(LOCTEXT("SetTranslationRetargetingAnimationScaledChildrenAction", "Recursively Set Translation Retargeting AnimationScaled")
|
|
, LOCTEXT("BoneTranslationRetargetingAnimationScaledToolTip", "Use translation from animation, scale length by Skeleton's proportions.")
|
|
, FSlateIcon()
|
|
, RecursiveRetargetingAnimationScaledAction
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(LOCTEXT("SetTranslationRetargetingAnimationRelativeChildrenAction", "Recursively Set Translation Retargeting AnimationRelative")
|
|
, LOCTEXT("BoneTranslationRetargetingAnimationRelativeToolTip", "Use relative translation from animation similar to an additive animation.")
|
|
, FSlateIcon()
|
|
, RecursiveRetargetingAnimationRelativeAction
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
MenuBuilder.BeginSection("SkeletonTreeBoneReductionForLOD", LOCTEXT("BoneReductionHeader", "LOD Bone Reduction"));
|
|
{
|
|
MenuBuilder.AddSubMenu(
|
|
LOCTEXT("SkeletonTreeBoneReductionForLOD_RemoveSelectedFromLOD", "Remove Selected..."),
|
|
FText::GetEmpty(),
|
|
FNewMenuDelegate::CreateStatic(&SSkeletonTree::CreateMenuForBoneReduction, this, LastCachedLODForPreviewMeshComponent, true)
|
|
);
|
|
|
|
MenuBuilder.AddSubMenu(
|
|
LOCTEXT("SkeletonTreeBoneReductionForLOD_RemoveChildrenFromLOD", "Remove Children..."),
|
|
FText::GetEmpty(),
|
|
FNewMenuDelegate::CreateStatic(&SSkeletonTree::CreateMenuForBoneReduction, this, LastCachedLODForPreviewMeshComponent, false)
|
|
);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
if(BoneTreeSelection.HasSelectedOfType(ESkeletonTreeRowType::Socket))
|
|
{
|
|
MenuBuilder.BeginSection("SkeletonTreeSocketsActions", LOCTEXT( "SocketActions", "Selected Socket Actions" ) );
|
|
|
|
MenuBuilder.AddMenuEntry( Actions.CopySockets );
|
|
|
|
if(BoneTreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Socket))
|
|
{
|
|
MenuBuilder.AddMenuEntry( FGenericCommands::Get().Rename, NAME_None, LOCTEXT("RenameSocket_Label", "Rename Socket"), LOCTEXT("RenameSocket_Tooltip", "Rename this socket") );
|
|
|
|
FDisplayedSocketInfo* DisplayedSocketInfo = static_cast< FDisplayedSocketInfo* >( BoneTreeSelection.GetSingleSelectedItem().Get() );
|
|
|
|
if ( DisplayedSocketInfo->IsSocketCustomized() && DisplayedSocketInfo->GetParentType() == ESocketParentType::Mesh )
|
|
{
|
|
MenuBuilder.AddMenuEntry( Actions.RemoveMeshSocket );
|
|
}
|
|
|
|
USkeletalMeshSocket* SelectedSocket = static_cast< USkeletalMeshSocket* >( DisplayedSocketInfo->GetData() );
|
|
|
|
// If the socket is on the skeleton, we have a valid mesh
|
|
// and there isn't one of the same name on the mesh, we can customize it
|
|
if ( DisplayedSocketInfo->CanCustomizeSocket() )
|
|
{
|
|
if ( DisplayedSocketInfo->GetParentType() == ESocketParentType::Skeleton )
|
|
{
|
|
MenuBuilder.AddMenuEntry( Actions.CreateMeshSocket );
|
|
}
|
|
else if ( DisplayedSocketInfo->GetParentType() == ESocketParentType::Mesh )
|
|
{
|
|
// If a socket is on the mesh only, then offer to promote it to the skeleton
|
|
MenuBuilder.AddMenuEntry( Actions.PromoteSocketToSkeleton );
|
|
}
|
|
}
|
|
}
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
MenuBuilder.BeginSection("SkeletonTreeAttachedAssets", LOCTEXT( "AttachedAssetsActionsHeader", "Attached Assets Actions" ) );
|
|
|
|
if ( BoneTreeSelection.IsSingleItemSelected() )
|
|
{
|
|
MenuBuilder.AddSubMenu( LOCTEXT( "AttachNewAsset", "Add Preview Asset" ),
|
|
LOCTEXT ( "AttachNewAsset_ToolTip", "Attaches an asset to this part of the skeleton. Assets can also be dragged onto the skeleton from a content browser to attach" ),
|
|
FNewMenuDelegate::CreateSP( this, &SSkeletonTree::FillAttachAssetSubmenu, BoneTreeSelection.GetSingleSelectedItem() ) );
|
|
}
|
|
|
|
FUIAction RemoveAllAttachedAssets = FUIAction( FExecuteAction::CreateSP( this, &SSkeletonTree::OnRemoveAllAssets ),
|
|
FCanExecuteAction::CreateSP( this, &SSkeletonTree::CanRemoveAllAssets ));
|
|
|
|
MenuBuilder.AddMenuEntry( LOCTEXT( "RemoveAllAttachedAssets", "Remove All Attached Assets" ),
|
|
LOCTEXT ( "RemoveAllAttachedAssets_ToolTip", "Removes all the attached assets from the skeleton and mesh." ),
|
|
FSlateIcon(), RemoveAllAttachedAssets );
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SSkeletonTree::FillVirtualBoneSubmenu(FMenuBuilder& MenuBuilder, TArray<TSharedPtr<FDisplayedMeshBoneInfo>> SourceBones)
|
|
{
|
|
const bool bShowVirtualBones = false;
|
|
TSharedRef<SWidget> MenuContent = SNew(SBoneTreeMenu, GetEditableSkeletonInternal())
|
|
.bShowVirtualBones(false)
|
|
.Title(LOCTEXT("TargetBonePickerTitle", "Pick Target Bone..."))
|
|
.OnBoneSelectionChanged(this, &SSkeletonTree::OnVirtualTargetBonePicked, SourceBones);
|
|
MenuBuilder.AddWidget(MenuContent, FText::GetEmpty(), true);
|
|
}
|
|
|
|
void SSkeletonTree::OnVirtualTargetBonePicked(FName TargetBoneName, TArray<TSharedPtr<FDisplayedMeshBoneInfo>> SourceBones)
|
|
{
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
|
|
for (const TSharedPtr<FDisplayedMeshBoneInfo>& SourceBone : SourceBones)
|
|
{
|
|
if(SourceBone.IsValid())
|
|
{
|
|
FName SourceBoneName = SourceBone->GetRowItemName();
|
|
if(!GetEditableSkeletonInternal()->HandleAddVirtualBone(SourceBoneName, TargetBoneName))
|
|
{
|
|
UE_LOG(LogAnimation, Log, TEXT("Could not create space switch bone from %s to %s, it already exists"), *SourceBoneName.ToString(), *TargetBoneName.ToString());
|
|
}
|
|
else
|
|
{
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SSkeletonTree::CreateMenuForBoneReduction(FMenuBuilder& MenuBuilder, SSkeletonTree * Widget, int32 LODIndex, bool bIncludeSelected)
|
|
{
|
|
MenuBuilder.AddMenuEntry
|
|
(FText::FromString(FString::Printf(TEXT("From LOD %d and below"), LODIndex))
|
|
, FText::FromString(FString::Printf(TEXT("Remove Selected %s from current LOD %d and all lower LODs"), (bIncludeSelected) ? TEXT("bones") : TEXT("children"), LODIndex))
|
|
, FSlateIcon()
|
|
, FUIAction(FExecuteAction::CreateSP(Widget, &SSkeletonTree::RemoveFromLOD, LODIndex, bIncludeSelected, true))
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry
|
|
(FText::FromString(FString::Printf(TEXT("From LOD %d only"), LODIndex))
|
|
, FText::FromString(FString::Printf(TEXT("Remove selected %s from current LOD %d only"), (bIncludeSelected) ? TEXT("bones") : TEXT("children"), LODIndex))
|
|
, FSlateIcon()
|
|
, FUIAction(FExecuteAction::CreateSP(Widget, &SSkeletonTree::RemoveFromLOD, LODIndex, bIncludeSelected, false))
|
|
);
|
|
}
|
|
|
|
|
|
void SSkeletonTree::SetBoneTranslationRetargetingModeRecursive(EBoneTranslationRetargetingMode::Type NewRetargetingMode)
|
|
{
|
|
TArray<FName> BoneNames;
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
for ( auto ItemIt = TreeSelection.SelectedBones.CreateConstIterator(); ItemIt; ++ItemIt )
|
|
{
|
|
BoneNames.Add(*static_cast<FName*>( (*ItemIt)->GetData() ));
|
|
}
|
|
|
|
GetEditableSkeletonInternal()->SetBoneTranslationRetargetingModeRecursive(BoneNames, NewRetargetingMode);
|
|
}
|
|
|
|
void SSkeletonTree::RemoveFromLOD(int32 LODIndex, bool bIncludeSelected, bool bIncludeBelowLODs)
|
|
{
|
|
// we cant do this without a preview scene
|
|
if (!GetPreviewScene().IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
if (!PreviewMeshComponent->SkeletalMesh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// ask users you can't undo this change, and warn them
|
|
const FText Message(LOCTEXT("RemoveBonesFromLODWarning", "This action can't be undone. Would you like to continue?"));
|
|
if (FMessageDialog::Open(EAppMsgType::YesNo, Message) == EAppReturnType::Yes)
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
const FReferenceSkeleton& RefSkeleton = GetEditableSkeletonInternal()->GetSkeleton().GetReferenceSkeleton();
|
|
|
|
TArray<FName> BonesToRemove;
|
|
|
|
for (auto ItemIt = TreeSelection.SelectedBones.CreateConstIterator(); ItemIt; ++ItemIt)
|
|
{
|
|
FName BoneName = *static_cast<FName*>((*ItemIt)->GetData());
|
|
int32 BoneIndex = RefSkeleton.FindBoneIndex(BoneName);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
if (bIncludeSelected)
|
|
{
|
|
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(LODIndex, BoneName);
|
|
BonesToRemove.AddUnique(BoneName);
|
|
}
|
|
else
|
|
{
|
|
for (int32 ChildIndex = BoneIndex + 1; ChildIndex < RefSkeleton.GetRawBoneNum(); ++ChildIndex)
|
|
{
|
|
if (RefSkeleton.GetParentIndex(ChildIndex) == BoneIndex)
|
|
{
|
|
FName ChildBoneName = RefSkeleton.GetBoneName(ChildIndex);
|
|
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(LODIndex, ChildBoneName);
|
|
BonesToRemove.AddUnique(ChildBoneName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 TotalLOD = PreviewMeshComponent->SkeletalMesh->LODInfo.Num();
|
|
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
|
|
|
|
if (bIncludeBelowLODs)
|
|
{
|
|
for (int32 Index = LODIndex + 1; Index < TotalLOD; ++Index)
|
|
{
|
|
MeshUtilities.RemoveBonesFromMesh(PreviewMeshComponent->SkeletalMesh, Index, &BonesToRemove);
|
|
PreviewMeshComponent->SkeletalMesh->AddBoneToReductionSetting(Index, BonesToRemove);
|
|
}
|
|
}
|
|
|
|
// remove from current LOD
|
|
MeshUtilities.RemoveBonesFromMesh(PreviewMeshComponent->SkeletalMesh, LODIndex, &BonesToRemove);
|
|
// update UI to reflect the change
|
|
OnLODSwitched();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnCopyBoneNames()
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if( TreeSelection.SelectedBones.Num() > 0 )
|
|
{
|
|
FString BoneNames;
|
|
for( auto ItemIt = TreeSelection.SelectedBones.CreateConstIterator(); ItemIt; ++ItemIt )
|
|
{
|
|
FName* BoneName = static_cast<FName*>( (*ItemIt)->GetData() );
|
|
|
|
BoneNames += BoneName->ToString();
|
|
BoneNames += "\r\n";
|
|
}
|
|
FPlatformMisc::ClipboardCopy( *BoneNames );
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnResetBoneTransforms()
|
|
{
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
check(PreviewComponent);
|
|
UAnimPreviewInstance* PreviewInstance = PreviewComponent->PreviewInstance;
|
|
check(PreviewInstance);
|
|
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if (TreeSelection.SelectedBones.Num() > 0)
|
|
{
|
|
bool bModified = false;
|
|
GEditor->BeginTransaction(LOCTEXT("SkeletonTree_ResetBoneTransforms", "Reset Bone Transforms"));
|
|
|
|
for (auto ItemIt = TreeSelection.SelectedBones.CreateConstIterator(); ItemIt; ++ItemIt)
|
|
{
|
|
const FName* BoneName = static_cast<FName*>((*ItemIt)->GetData());
|
|
check(BoneName);
|
|
const FAnimNode_ModifyBone* ModifiedBone = PreviewInstance->FindModifiedBone(*BoneName);
|
|
if (ModifiedBone != nullptr)
|
|
{
|
|
if (!bModified)
|
|
{
|
|
PreviewInstance->SetFlags(RF_Transactional);
|
|
PreviewInstance->Modify();
|
|
bModified = true;
|
|
}
|
|
|
|
PreviewInstance->RemoveBoneModification(*BoneName);
|
|
}
|
|
}
|
|
|
|
GEditor->EndTransaction();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnCopySockets() const
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
int32 NumSocketsToCopy = TreeSelection.SelectedSockets.Num();
|
|
if ( NumSocketsToCopy > 0 )
|
|
{
|
|
FString SocketsDataString;
|
|
|
|
for ( auto ItemIt = TreeSelection.SelectedSockets.CreateConstIterator(); ItemIt; ++ItemIt )
|
|
{
|
|
FDisplayedSocketInfo* DisplayedSocketInfo = (*ItemIt).Get();
|
|
USkeletalMeshSocket* Socket = static_cast<USkeletalMeshSocket*>( DisplayedSocketInfo->GetData() );
|
|
|
|
SocketsDataString += SerializeSocketToString( Socket, DisplayedSocketInfo );
|
|
}
|
|
|
|
FString CopyString = FString::Printf( TEXT("%s\nNumSockets=%d\n%s"), *FEditableSkeleton::SocketCopyPasteHeader, NumSocketsToCopy, *SocketsDataString );
|
|
|
|
FPlatformMisc::ClipboardCopy( *CopyString );
|
|
}
|
|
}
|
|
|
|
FString SSkeletonTree::SerializeSocketToString( USkeletalMeshSocket* Socket, const FDisplayedSocketInfo* DisplayedSocketInfo ) const
|
|
{
|
|
FString SocketString;
|
|
|
|
SocketString += FString::Printf( TEXT( "IsOnSkeleton=%s\n" ), DisplayedSocketInfo->GetParentType() == ESocketParentType::Skeleton ? TEXT( "1" ) : TEXT( "0" ) );
|
|
|
|
FStringOutputDevice Buffer;
|
|
const FExportObjectInnerContext Context;
|
|
UExporter::ExportToOutputDevice( &Context, Socket, nullptr, Buffer, TEXT( "copy" ), 0, PPF_Copy, false );
|
|
SocketString += Buffer;
|
|
|
|
return SocketString;
|
|
}
|
|
|
|
void SSkeletonTree::OnPasteSockets(bool bPasteToSelectedBone)
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
// Pasting sockets should only work if there is just one bone selected
|
|
if ( TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Bone) )
|
|
{
|
|
FName DestBoneName = bPasteToSelectedBone ? *static_cast<FName*>( TreeSelection.GetSingleSelectedItem()->GetData() ) : NAME_None;
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
GetEditableSkeletonInternal()->HandlePasteSockets(DestBoneName, SkeletalMesh);
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnAddSocket()
|
|
{
|
|
// This adds a socket to the currently selected bone in the SKELETON, not the MESH.
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
// Can only add a socket to one bone
|
|
if (TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Bone))
|
|
{
|
|
FName BoneName = *static_cast<FName*>(TreeSelection.GetSingleSelectedItem()->GetData());
|
|
USkeletalMeshSocket* NewSocket = GetEditableSkeletonInternal()->HandleAddSocket(BoneName);
|
|
FSelectedSocketInfo SocketInfo(NewSocket, true);
|
|
SetSelectedSocket(SocketInfo);
|
|
CreateFromSkeleton(NewSocket);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnCustomizeSocket()
|
|
{
|
|
// This should only be called on a skeleton socket, it copies the
|
|
// socket to the mesh so the user can edit it separately
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if(TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Socket))
|
|
{
|
|
USkeletalMeshSocket* SocketToCustomize = static_cast<USkeletalMeshSocket*>( TreeSelection.GetSingleSelectedItem()->GetData() );
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
GetEditableSkeletonInternal()->HandleCustomizeSocket(SocketToCustomize, SkeletalMesh);
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnPromoteSocket()
|
|
{
|
|
// This should only be called on a mesh socket, it copies the
|
|
// socket to the skeleton so all meshes can use it
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
// Can only customize one socket (CreateContextMenu() should prevent this firing!)
|
|
if(TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Socket))
|
|
{
|
|
USkeletalMeshSocket* SocketToPromote = static_cast<USkeletalMeshSocket*>( TreeSelection.GetSingleSelectedItem()->GetData() );
|
|
GetEditableSkeletonInternal()->HandlePromoteSocket(SocketToPromote);
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::FillAttachAssetSubmenu(FMenuBuilder& MenuBuilder, const FDisplayedTreeRowInfoPtr TargetItem)
|
|
{
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
|
|
|
TArray<UClass*> FilterClasses = FComponentAssetBrokerage::GetSupportedAssets(USceneComponent::StaticClass());
|
|
|
|
//Clean up the selection so it is relevant to Persona
|
|
FilterClasses.RemoveSingleSwap(UBlueprint::StaticClass(), false); //Child actor components broker gives us blueprints which isn't wanted
|
|
FilterClasses.RemoveSingleSwap(USoundBase::StaticClass(), false); //No sounds wanted
|
|
|
|
FAssetPickerConfig AssetPickerConfig;
|
|
AssetPickerConfig.Filter.bRecursiveClasses = true;
|
|
|
|
for(int i = 0; i < FilterClasses.Num(); ++i)
|
|
{
|
|
AssetPickerConfig.Filter.ClassNames.Add(FilterClasses[i]->GetFName());
|
|
}
|
|
|
|
|
|
AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SSkeletonTree::OnAssetSelectedFromPicker, TargetItem);
|
|
|
|
TSharedRef<SWidget> MenuContent = SNew(SBox)
|
|
.WidthOverride(384)
|
|
.HeightOverride(500)
|
|
[
|
|
ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)
|
|
];
|
|
MenuBuilder.AddWidget( MenuContent, FText::GetEmpty(), true);
|
|
}
|
|
|
|
void SSkeletonTree::OnAssetSelectedFromPicker(const FAssetData& AssetData, const FDisplayedTreeRowInfoPtr TargetItem)
|
|
{
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
TArray<FAssetData> Assets;
|
|
Assets.Add(AssetData);
|
|
|
|
AttachAssetsToSkeletonTree(TargetItem, Assets);
|
|
}
|
|
|
|
void SSkeletonTree::OnRemoveAllAssets()
|
|
{
|
|
GetEditableSkeletonInternal()->HandleRemoveAllAssets(GetPreviewScene());
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
bool SSkeletonTree::CanRemoveAllAssets() const
|
|
{
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
|
|
const bool bHasPreviewAttachedObjects = GetEditableSkeletonInternal()->GetSkeleton().PreviewAttachedAssetContainer.Num() > 0;
|
|
const bool bHasMeshPreviewAttachedObjects = ( SkeletalMesh && SkeletalMesh->PreviewAttachedAssetContainer.Num() );
|
|
|
|
return bHasPreviewAttachedObjects || bHasMeshPreviewAttachedObjects;
|
|
}
|
|
|
|
bool SSkeletonTree::CanRenameSelected() const
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
return TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Socket);
|
|
}
|
|
|
|
void SSkeletonTree::OnRenameSocket()
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if(TreeSelection.IsSingleOfTypeSelected(ESkeletonTreeRowType::Socket))
|
|
{
|
|
SkeletonTreeView->RequestScrollIntoView(TreeSelection.GetSingleSelectedItem());
|
|
DeferredRenameRequest = TreeSelection.GetSingleSelectedItem();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnSelectionChanged(TSharedPtr<FDisplayedTreeRowInfo> Selection, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if( Selection.IsValid() )
|
|
{
|
|
//Get all the selected items
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
if (TreeSelection.SelectedItems.Num() > 0 && PreviewComponent)
|
|
{
|
|
// pick the first settable bone from the selection
|
|
for (auto ItemIt = TreeSelection.SelectedItems.CreateConstIterator(); ItemIt; ++ItemIt)
|
|
{
|
|
FDisplayedTreeRowInfoPtr Item = *(ItemIt);
|
|
|
|
// Test SelectInfo so we don't end up in an infinite loop due to delegates calling each other
|
|
if (SelectInfo != ESelectInfo::Direct && (Item->GetType() == ESkeletonTreeRowType::Bone || Item->GetType() == ESkeletonTreeRowType::VirtualBone))
|
|
{
|
|
FName BoneName = *static_cast<FName *>(Item->GetData());
|
|
|
|
// Get bone index
|
|
int32 BoneIndex = PreviewComponent->GetBoneIndex(BoneName);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
GetPreviewScene()->SetSelectedBone(BoneName);
|
|
OnObjectSelectedMulticast.Broadcast(nullptr);
|
|
break;
|
|
}
|
|
}
|
|
// Test SelectInfo so we don't end up in an infinite loop due to delegates calling each other
|
|
else if (SelectInfo != ESelectInfo::Direct && Item->GetType() == ESkeletonTreeRowType::Socket)
|
|
{
|
|
FDisplayedSocketInfo* DisplayedSocketInfo = static_cast<FDisplayedSocketInfo*>(Item.Get());
|
|
USkeletalMeshSocket* Socket = static_cast<USkeletalMeshSocket*>(Item->GetData());
|
|
FSelectedSocketInfo SocketInfo(Socket, DisplayedSocketInfo->GetParentType() == ESocketParentType::Skeleton);
|
|
GetPreviewScene()->SetSelectedSocket(SocketInfo);
|
|
|
|
OnObjectSelectedMulticast.Broadcast(SocketInfo.Socket);
|
|
}
|
|
else if (Item->GetType() == ESkeletonTreeRowType::AttachedAsset)
|
|
{
|
|
GetPreviewScene()->DeselectAll();
|
|
|
|
OnObjectSelectedMulticast.Broadcast(nullptr);
|
|
}
|
|
}
|
|
PreviewComponent->PostInitMeshObject(PreviewComponent->MeshObject);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
// Tell the preview scene if the user ctrl-clicked the selected bone/socket to de-select it
|
|
GetPreviewScene()->DeselectAll();
|
|
}
|
|
|
|
OnObjectSelectedMulticast.Broadcast(nullptr);
|
|
}
|
|
}
|
|
|
|
FReply SSkeletonTree::OnDropAssetToSkeletonTree(const FDisplayedTreeRowInfoPtr TargetItem, const FDragDropEvent& DragDropEvent)
|
|
{
|
|
TSharedPtr<FAssetDragDropOp> DragDropOp = DragDropEvent.GetOperationAs<FAssetDragDropOp>();
|
|
if (DragDropOp.IsValid())
|
|
{
|
|
//Do we have some assets to attach?
|
|
if(DragDropOp->AssetData.Num() > 0)
|
|
{
|
|
AttachAssetsToSkeletonTree(TargetItem, DragDropOp->AssetData);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SSkeletonTree::AttachAssetsToSkeletonTree(const FDisplayedTreeRowInfoPtr TargetItem, const TArray<FAssetData>& AssetData)
|
|
{
|
|
bool bAllAssetWereLoaded = true;
|
|
TArray<UObject*> DroppedObjects;
|
|
for (int32 AssetIdx = 0; AssetIdx < AssetData.Num(); ++AssetIdx)
|
|
{
|
|
UObject* Object = AssetData[AssetIdx].GetAsset();
|
|
if ( Object != NULL )
|
|
{
|
|
DroppedObjects.Add( Object );
|
|
}
|
|
else
|
|
{
|
|
bAllAssetWereLoaded = false;
|
|
}
|
|
}
|
|
|
|
if(bAllAssetWereLoaded)
|
|
{
|
|
FName AttachToName = TargetItem->GetAttachName();
|
|
bool bAttachToMesh = TargetItem->GetType() == ESkeletonTreeRowType::Socket &&
|
|
static_cast<FDisplayedSocketInfo*>(TargetItem.Get())->GetParentType() == ESocketParentType::Mesh;
|
|
|
|
GetEditableSkeletonInternal()->HandleAttachAssets(DroppedObjects, AttachToName, bAttachToMesh, GetPreviewScene());
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnItemScrolledIntoView( FDisplayedTreeRowInfoPtr InItem, const TSharedPtr<ITableRow>& InWidget)
|
|
{
|
|
if(DeferredRenameRequest.IsValid())
|
|
{
|
|
DeferredRenameRequest->RequestRename();
|
|
DeferredRenameRequest.Reset();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnTreeDoubleClick( FDisplayedTreeRowInfoPtr InItem )
|
|
{
|
|
InItem->OnItemDoubleClicked();
|
|
}
|
|
|
|
void SSkeletonTree::SetTreeItemExpansionRecursive(TSharedPtr< FDisplayedTreeRowInfo > TreeItem, bool bInExpansionState) const
|
|
{
|
|
SkeletonTreeView->SetItemExpansion(TreeItem, bInExpansionState);
|
|
|
|
// Recursively go through the children.
|
|
for (auto It = TreeItem->Children.CreateIterator(); It; ++It)
|
|
{
|
|
SetTreeItemExpansionRecursive(*It, bInExpansionState);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::PostUndo()
|
|
{
|
|
// Rebuild the tree view whenever we undo a change to the skeleton
|
|
CreateFromSkeleton();
|
|
|
|
CreateTreeColumns();
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->DeselectAll();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnFilterTextChanged( const FText& SearchText )
|
|
{
|
|
FilterText = SearchText;
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
void SSkeletonTree::SetSkeletalMesh(USkeletalMesh* NewSkeletalMesh)
|
|
{
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->SetPreviewMesh(NewSkeletalMesh);
|
|
}
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
void SSkeletonTree::SetSelectedSocket( const FSelectedSocketInfo& SocketInfo )
|
|
{
|
|
if (!bSelectingSocket)
|
|
{
|
|
TGuardValue<bool> RecursionGuard(bSelectingSocket, true);
|
|
|
|
// This function is called when something else selects a socket (i.e. *NOT* the user clicking on a row in the treeview)
|
|
// For example, this would be called if user clicked a socket hit point in the preview window
|
|
|
|
// Firstly, find which row (if any) contains the socket requested
|
|
for (auto SkeletonRowIt = DisplayMirror.CreateConstIterator(); SkeletonRowIt; ++SkeletonRowIt)
|
|
{
|
|
FDisplayedTreeRowInfoPtr SkeletonRow = *(SkeletonRowIt);
|
|
|
|
if (SkeletonRow->GetType() == ESkeletonTreeRowType::Socket && SkeletonRow->GetData() == SocketInfo.Socket)
|
|
{
|
|
SkeletonTreeView->SetSelection(SkeletonRow);
|
|
SkeletonTreeView->RequestScrollIntoView(SkeletonRow);
|
|
}
|
|
}
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->SetSelectedSocket(SocketInfo);
|
|
}
|
|
|
|
OnObjectSelectedMulticast.Broadcast(SocketInfo.Socket);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::SetSelectedBone( const FName& BoneName )
|
|
{
|
|
if (!bSelectingBone)
|
|
{
|
|
TGuardValue<bool> RecursionGuard(bSelectingBone, true);
|
|
// This function is called when something else selects a bone (i.e. *NOT* the user clicking on a row in the treeview)
|
|
// For example, this would be called if user clicked a bone hit point in the preview window
|
|
|
|
// Find which row (if any) contains the bone requested
|
|
for (auto SkeletonRowIt = DisplayMirror.CreateConstIterator(); SkeletonRowIt; ++SkeletonRowIt)
|
|
{
|
|
FDisplayedTreeRowInfoPtr SkeletonRow = *(SkeletonRowIt);
|
|
|
|
if (SkeletonRow->GetType() == ESkeletonTreeRowType::Bone &&
|
|
*static_cast<FName*>(SkeletonRow->GetData()) == BoneName)
|
|
{
|
|
SkeletonTreeView->SetSelection(SkeletonRow);
|
|
SkeletonTreeView->RequestScrollIntoView(SkeletonRow);
|
|
}
|
|
}
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->SetSelectedBone(BoneName);
|
|
}
|
|
|
|
OnObjectSelectedMulticast.Broadcast(nullptr);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::DeselectAll()
|
|
{
|
|
if (!bDeselectingAll)
|
|
{
|
|
TGuardValue<bool> RecursionGuard(bDeselectingAll, true);
|
|
SkeletonTreeView->ClearSelection();
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->DeselectAll();
|
|
}
|
|
|
|
OnObjectSelectedMulticast.Broadcast(nullptr);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::NotifyUser( FNotificationInfo& NotificationInfo )
|
|
{
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification( NotificationInfo );
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
|
|
TSharedRef< SWidget > SSkeletonTree::CreateFilterMenu()
|
|
{
|
|
const FSkeletonTreeCommands& Actions = FSkeletonTreeCommands::Get();
|
|
|
|
const bool CloseAfterSelection = true;
|
|
FMenuBuilder MenuBuilder( CloseAfterSelection, UICommandList );
|
|
|
|
MenuBuilder.BeginSection("Bones", LOCTEXT("OptionsMenuHeading", "Options"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Actions.ShowRetargeting);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Bones", LOCTEXT( "BonesMenuHeading", "Bones" ) );
|
|
{
|
|
MenuBuilder.AddMenuEntry( Actions.ShowAllBones );
|
|
MenuBuilder.AddMenuEntry( Actions.ShowMeshBones );
|
|
MenuBuilder.AddMenuEntry(Actions.ShowLODBones);
|
|
MenuBuilder.AddMenuEntry( Actions.ShowWeightedBones );
|
|
MenuBuilder.AddMenuEntry( Actions.HideBones );
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Sockets", LOCTEXT("SocketsMenuHeading", "Sockets"));
|
|
{
|
|
MenuBuilder.AddMenuEntry(Actions.ShowActiveSockets);
|
|
MenuBuilder.AddMenuEntry(Actions.ShowMeshSockets);
|
|
MenuBuilder.AddMenuEntry(Actions.ShowSkeletonSockets);
|
|
MenuBuilder.AddMenuEntry(Actions.ShowAllSockets);
|
|
MenuBuilder.AddMenuEntry(Actions.HideSockets);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SSkeletonTree::SetBoneFilter( EBoneFilter InBoneFilter )
|
|
{
|
|
check( InBoneFilter < EBoneFilter::Count );
|
|
BoneFilter = InBoneFilter;
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
bool SSkeletonTree::IsBoneFilter( EBoneFilter InBoneFilter ) const
|
|
{
|
|
return BoneFilter == InBoneFilter;
|
|
}
|
|
|
|
void SSkeletonTree::SetSocketFilter( ESocketFilter InSocketFilter )
|
|
{
|
|
check( InSocketFilter < ESocketFilter::Count );
|
|
SocketFilter = InSocketFilter;
|
|
|
|
SetPreviewComponentSocketFilter();
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
void SSkeletonTree::SetPreviewComponentSocketFilter() const
|
|
{
|
|
// Set the socket filter in the debug skeletal mesh component so the viewport can share the filter settings
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
|
|
bool bAllOrActive = (SocketFilter == ESocketFilter::All || SocketFilter == ESocketFilter::Active);
|
|
|
|
if (PreviewComponent)
|
|
{
|
|
PreviewComponent->bMeshSocketsVisible = bAllOrActive || SocketFilter == ESocketFilter::Mesh;
|
|
PreviewComponent->bSkeletonSocketsVisible = bAllOrActive || SocketFilter == ESocketFilter::Skeleton;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SSkeletonTree::IsSocketFilter( ESocketFilter InSocketFilter ) const
|
|
{
|
|
return SocketFilter == InSocketFilter;
|
|
}
|
|
|
|
// @todo combine these two functions to one if not used by anywhere else
|
|
bool SSkeletonTree::IsBoneWeighted( int32 MeshBoneIndex, UDebugSkelMeshComponent* PreviewComponent ) const
|
|
{
|
|
// MeshBoneIndex must be an index into the mesh's skeleton, *not* the source skeleton!!!
|
|
|
|
if ( !PreviewComponent || !PreviewComponent->SkeletalMesh || !PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels.Num() )
|
|
{
|
|
// If there's no mesh, then this bone can't possibly be weighted!
|
|
return false;
|
|
}
|
|
|
|
//Get current LOD
|
|
const int32 LODIndex = FMath::Clamp(PreviewComponent->PredictedLODLevel, 0, PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels.Num()-1);
|
|
FStaticLODModel& LODModel = PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels[ LODIndex ];
|
|
|
|
//Check whether the bone is vertex weighted
|
|
int32 Index = LODModel.ActiveBoneIndices.Find( MeshBoneIndex );
|
|
|
|
return Index != INDEX_NONE;
|
|
}
|
|
|
|
bool SSkeletonTree::IsBoneRequired(int32 MeshBoneIndex, UDebugSkelMeshComponent* PreviewComponent) const
|
|
{
|
|
// MeshBoneIndex must be an index into the mesh's skeleton, *not* the source skeleton!!!
|
|
|
|
if (!PreviewComponent || !PreviewComponent->SkeletalMesh || !PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels.Num())
|
|
{
|
|
// If there's no mesh, then this bone can't possibly be weighted!
|
|
return false;
|
|
}
|
|
|
|
//Get current LOD
|
|
const int32 LODIndex = FMath::Clamp(PreviewComponent->PredictedLODLevel, 0, PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels.Num() - 1);
|
|
FStaticLODModel& LODModel = PreviewComponent->SkeletalMesh->GetImportedResource()->LODModels[LODIndex];
|
|
|
|
//Check whether the bone is vertex weighted
|
|
int32 Index = LODModel.RequiredBones.Find(MeshBoneIndex);
|
|
|
|
return Index != INDEX_NONE;
|
|
}
|
|
|
|
FText SSkeletonTree::GetFilterMenuTitle() const
|
|
{
|
|
FText BoneFilterMenuText;
|
|
|
|
switch ( BoneFilter )
|
|
{
|
|
case EBoneFilter::All:
|
|
BoneFilterMenuText = LOCTEXT( "BoneFilterMenuAll", "All Bones" );
|
|
break;
|
|
|
|
case EBoneFilter::Mesh:
|
|
BoneFilterMenuText = LOCTEXT( "BoneFilterMenuMesh", "Mesh Bones" );
|
|
break;
|
|
|
|
case EBoneFilter::LOD:
|
|
BoneFilterMenuText = LOCTEXT("BoneFilterMenuLOD", "LOD Bones");
|
|
break;
|
|
|
|
case EBoneFilter::Weighted:
|
|
BoneFilterMenuText = LOCTEXT( "BoneFilterMenuWeighted", "Weighted Bones" );
|
|
break;
|
|
|
|
case EBoneFilter::None:
|
|
BoneFilterMenuText = LOCTEXT( "BoneFilterMenuHidden", "Bones Hidden" );
|
|
break;
|
|
|
|
default:
|
|
// Unknown mode
|
|
check( 0 );
|
|
break;
|
|
}
|
|
|
|
FText SocketFilterMenuText;
|
|
|
|
switch (SocketFilter)
|
|
{
|
|
case ESocketFilter::Active:
|
|
SocketFilterMenuText = LOCTEXT("SocketFilterMenuActive", "Active Sockets");
|
|
break;
|
|
|
|
case ESocketFilter::Mesh:
|
|
SocketFilterMenuText = LOCTEXT("SocketFilterMenuMesh", "Mesh Sockets");
|
|
break;
|
|
|
|
case ESocketFilter::Skeleton:
|
|
SocketFilterMenuText = LOCTEXT("SocketFilterMenuSkeleton", "Skeleton Sockets");
|
|
break;
|
|
|
|
case ESocketFilter::All:
|
|
SocketFilterMenuText = LOCTEXT("SocketFilterMenuAll", "All Sockets");
|
|
break;
|
|
|
|
case ESocketFilter::None:
|
|
SocketFilterMenuText = LOCTEXT("SocketFilterMenuHidden", "Sockets Hidden");
|
|
break;
|
|
|
|
default:
|
|
// Unknown mode
|
|
check(0);
|
|
break;
|
|
}
|
|
|
|
return FText::Format(LOCTEXT("FilterMenuLabelFormat", "{0}, {1}"), BoneFilterMenuText, SocketFilterMenuText);
|
|
}
|
|
|
|
bool SSkeletonTree::IsAddingSocketsAllowed() const
|
|
{
|
|
if ( SocketFilter == ESocketFilter::Skeleton ||
|
|
SocketFilter == ESocketFilter::Active ||
|
|
SocketFilter == ESocketFilter::All )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FReply SSkeletonTree::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
|
|
{
|
|
if ( UICommandList->ProcessCommandBindings( InKeyEvent ) )
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
void SSkeletonTree::OnDeleteSelectedRows()
|
|
{
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
if(TreeSelection.HasSelectedOfType(ESkeletonTreeRowType::AttachedAsset) || TreeSelection.HasSelectedOfType(ESkeletonTreeRowType::Socket) || TreeSelection.HasSelectedOfType(ESkeletonTreeRowType::VirtualBone))
|
|
{
|
|
FScopedTransaction Transaction( LOCTEXT( "SkeletonTreeDeleteSelected", "Delete selected sockets/meshes/bones from skeleton tree" ) );
|
|
|
|
DeleteAttachedAssets( TreeSelection.SelectedAssets );
|
|
DeleteSockets( TreeSelection.SelectedSockets );
|
|
DeleteVirtualBones( TreeSelection.SelectedVirtualBones );
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::DeleteAttachedAssets(const TArray<TSharedPtr<FDisplayedAttachedAssetInfo>>& InDisplayedAttachedAssetInfos)
|
|
{
|
|
DeselectAll();
|
|
|
|
TArray<FPreviewAttachedObjectPair> AttachedObjects;
|
|
for(const TSharedPtr<FDisplayedAttachedAssetInfo>& AttachedAssetInfo : InDisplayedAttachedAssetInfos)
|
|
{
|
|
FPreviewAttachedObjectPair Pair;
|
|
Pair.SetAttachedObject(AttachedAssetInfo->GetAsset());
|
|
Pair.AttachedTo = AttachedAssetInfo->GetParentName();
|
|
|
|
AttachedObjects.Add(Pair);
|
|
}
|
|
|
|
GetEditableSkeletonInternal()->HandleDeleteAttachedAssets(AttachedObjects, GetPreviewScene());
|
|
}
|
|
|
|
void SSkeletonTree::DeleteSockets(const TArray<TSharedPtr<FDisplayedSocketInfo>>& InDisplayedSocketInfos)
|
|
{
|
|
DeselectAll();
|
|
|
|
TArray<FSelectedSocketInfo> SocketInfo;
|
|
|
|
for (const TSharedPtr<FDisplayedSocketInfo>& DisplayedSocketInfo : InDisplayedSocketInfos)
|
|
{
|
|
USkeletalMeshSocket* SocketToDelete = static_cast<USkeletalMeshSocket*>( DisplayedSocketInfo->GetData() );
|
|
SocketInfo.Add(FSelectedSocketInfo(SocketToDelete, DisplayedSocketInfo->GetParentType() == ESocketParentType::Skeleton));
|
|
}
|
|
|
|
GetEditableSkeletonInternal()->HandleDeleteSockets(SocketInfo, GetPreviewScene());
|
|
}
|
|
|
|
void SSkeletonTree::DeleteVirtualBones(const TArray<TSharedPtr<FDisplayedVirtualBoneInfo>>& InDisplayedVirtualBonestInfos)
|
|
{
|
|
DeselectAll();
|
|
|
|
TArray<FName> VirtualBoneInfo;
|
|
|
|
for (const TSharedPtr<FDisplayedVirtualBoneInfo>& DisplayedVirtualBoneInfo : InDisplayedVirtualBonestInfos)
|
|
{
|
|
FName* VBToDelete = static_cast<FName*>(DisplayedVirtualBoneInfo->GetData());
|
|
VirtualBoneInfo.Add(*VBToDelete);
|
|
}
|
|
|
|
GetEditableSkeletonInternal()->HandleDeleteVirtualBones(VirtualBoneInfo, GetPreviewScene());
|
|
}
|
|
|
|
|
|
void SSkeletonTree::AddAttachedAssets( const FPreviewAssetAttachContainer& AttachedObjects )
|
|
{
|
|
for(auto Iter = AttachedObjects.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
const FPreviewAttachedObjectPair& Pair = (*Iter);
|
|
|
|
if ( !FilterText.IsEmpty() && !Pair.GetAttachedObject()->GetName().Contains( FilterText.ToString() ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FDisplayedAttachedAssetInfo> DisplayInfo = FDisplayedAttachedAssetInfo::Make(Pair.AttachedTo, Pair.GetAttachedObject(), SharedThis( this ) );
|
|
DisplayMirror.Add(DisplayInfo);
|
|
|
|
// for now it is a failure to not find where the asset is attached. Its possible that this might have to be changed to unloading the asset
|
|
// if there is a valid reason why the attach parent would not exist
|
|
if ( !AttachToParent( DisplayInfo, Pair.AttachedTo, (int32)( ESkeletonTreeRowType::Bone | ESkeletonTreeRowType::Socket ) ) )
|
|
{
|
|
// Just add it to the list if the parent bone isn't currently displayed
|
|
SkeletonRowList.Add( DisplayInfo );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnChangeShowingAdvancedOptions()
|
|
{
|
|
bShowingAdvancedOptions = !bShowingAdvancedOptions;
|
|
CreateTreeColumns();
|
|
}
|
|
|
|
bool SSkeletonTree::IsShowingAdvancedOptions() const
|
|
{
|
|
return bShowingAdvancedOptions;
|
|
}
|
|
|
|
UBlendProfile* SSkeletonTree::GetSelectedBlendProfile()
|
|
{
|
|
return BlendProfilePicker->GetSelectedBlendProfile();
|
|
}
|
|
|
|
FName SSkeletonTree::GetSelectedBlendProfileName() const
|
|
{
|
|
return BlendProfilePicker->GetSelectedBlendProfileName();
|
|
}
|
|
|
|
void SSkeletonTree::OnBlendProfileSelected(UBlendProfile* NewProfile)
|
|
{
|
|
CreateTreeColumns();
|
|
}
|
|
|
|
void SSkeletonTree::RecursiveSetBlendProfileScales(float InScaleToSet)
|
|
{
|
|
UBlendProfile* SelectedBlendProfile = BlendProfilePicker->GetSelectedBlendProfile();
|
|
if(SelectedBlendProfile)
|
|
{
|
|
TArray<FName> BoneNames;
|
|
FBoneTreeSelection TreeSelection(SkeletonTreeView->GetSelectedItems());
|
|
|
|
for(auto ItemIt = TreeSelection.SelectedBones.CreateConstIterator(); ItemIt; ++ItemIt)
|
|
{
|
|
BoneNames.Add(*static_cast<FName*>((*ItemIt)->GetData()));
|
|
}
|
|
|
|
GetEditableSkeletonInternal()->RecursiveSetBlendProfileScales(SelectedBlendProfile->GetFName(), BoneNames, InScaleToSet);
|
|
|
|
CreateTreeColumns();
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::HandleTreeRefresh()
|
|
{
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
void SSkeletonTree::PostRenameSocket(UObject* InAttachedObject, const FName& InOldName, const FName& InNewName)
|
|
{
|
|
TSharedPtr<IPersonaPreviewScene> LinkedPreviewScene = GetPreviewScene();
|
|
if (LinkedPreviewScene.IsValid())
|
|
{
|
|
LinkedPreviewScene->RemoveAttachedObjectFromPreviewComponent(InAttachedObject, InOldName);
|
|
LinkedPreviewScene->AttachObjectToPreviewComponent(InAttachedObject, InNewName);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::PostDuplicateSocket(UObject* InAttachedObject, const FName& InSocketName)
|
|
{
|
|
TSharedPtr<IPersonaPreviewScene> LinkedPreviewScene = GetPreviewScene();
|
|
if (LinkedPreviewScene.IsValid())
|
|
{
|
|
LinkedPreviewScene->AttachObjectToPreviewComponent(InAttachedObject, InSocketName);
|
|
}
|
|
|
|
CreateFromSkeleton();
|
|
}
|
|
|
|
void RecursiveSetLODChange(UDebugSkelMeshComponent* PreviewComponent, TSharedPtr<FDisplayedTreeRowInfo> TreeRow)
|
|
{
|
|
if (TreeRow.Get()->GetType() == ESkeletonTreeRowType::Bone)
|
|
{
|
|
static_cast<FDisplayedMeshBoneInfo*>(TreeRow.Get())->CacheLODChange(PreviewComponent);
|
|
}
|
|
|
|
for (auto& Child : TreeRow->Children)
|
|
{
|
|
RecursiveSetLODChange(PreviewComponent, Child);
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::OnLODSwitched()
|
|
{
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
UDebugSkelMeshComponent* PreviewComponent = GetPreviewScene()->GetPreviewMeshComponent();
|
|
|
|
if (PreviewComponent)
|
|
{
|
|
LastCachedLODForPreviewMeshComponent = PreviewComponent->PredictedLODLevel;
|
|
|
|
if (BoneFilter == EBoneFilter::Weighted || BoneFilter == EBoneFilter::LOD)
|
|
{
|
|
CreateFromSkeleton();
|
|
}
|
|
else
|
|
{
|
|
for (auto ItemIt : SkeletonRowList)
|
|
{
|
|
RecursiveSetLODChange(PreviewComponent, ItemIt);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSkeletonTree::DuplicateAndSelectSocket(const FSelectedSocketInfo& SocketInfoToDuplicate, const FName& NewParentBoneName /*= FName()*/)
|
|
{
|
|
USkeletalMesh* SkeletalMesh = GetPreviewScene().IsValid() ? GetPreviewScene()->GetPreviewMeshComponent()->SkeletalMesh : nullptr;
|
|
USkeletalMeshSocket* NewSocket = GetEditableSkeleton()->DuplicateSocket(SocketInfoToDuplicate, NewParentBoneName, SkeletalMesh);
|
|
|
|
if (GetPreviewScene().IsValid())
|
|
{
|
|
GetPreviewScene()->SetSelectedSocket(FSelectedSocketInfo(NewSocket, SocketInfoToDuplicate.bSocketIsOnSkeleton));
|
|
}
|
|
|
|
CreateFromSkeleton();
|
|
|
|
FSelectedSocketInfo NewSocketInfo(NewSocket, SocketInfoToDuplicate.bSocketIsOnSkeleton);
|
|
SetSelectedSocket(NewSocketInfo);
|
|
}
|
|
|
|
FSlateColor SSkeletonTree::GetFilterComboButtonForegroundColor() const
|
|
{
|
|
static const FName InvertedForegroundName("InvertedForeground");
|
|
static const FName DefaultForegroundName("DefaultForeground");
|
|
|
|
if (FilterComboButton.IsValid())
|
|
{
|
|
return FilterComboButton->IsHovered() ? FEditorStyle::GetSlateColor(InvertedForegroundName) : FEditorStyle::GetSlateColor(DefaultForegroundName);
|
|
}
|
|
return FSlateColor::UseForeground();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|