Files
UnrealEngineUWP/Engine/Source/Editor/SkeletonEditor/Private/SSkeletonTree.cpp
Matthew Griffin 4136b4f3f8 Copying //UE4/Release-Staging-4.14 to //UE4/Dev-Main (Source: //UE4/Release-4.14 @ 3167010)
#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

Change 3165774 on 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

Change 3165444 on 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

Change 3163698 on 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

Change 3160641 on 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]
2016-10-26 14:33:35 -04:00

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