From ade69fd6fec1cd1c473e1cefc80888e70d847199 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 27 Mar 2026 20:32:01 +0100 Subject: [PATCH 1/6] Adds Instant Blender Controls option for transient transform activation --- .../Private/Blend4RealInputProcessor.cpp | 60 +++++++++++++++++-- .../Public/Blend4RealInputProcessor.h | 16 +++++ Source/Blend4Real/Public/Blend4RealSettings.h | 6 ++ 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index c97ce1c..24d12a5 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -42,9 +42,8 @@ FBlend4RealInputProcessor::~FBlend4RealInputProcessor() void FBlend4RealInputProcessor::RegisterInputProcessor() { - if (bIsEnabled && FSlateApplication::IsInitialized()) + if (FSlateApplication::IsInitialized()) { - PlatformInputs::InitializeKeyboardLayoutCache(); FSlateApplication::Get().RegisterInputPreProcessor(SharedThis(this)); } } @@ -59,6 +58,9 @@ void FBlend4RealInputProcessor::Init(TSharedPtr) GLevelEditorModeTools().SetShowWidget(true); GLevelEditorModeTools().SaveConfig(); + // Always register so we can intercept transform keys (G/R/S) for transient activation even when disabled + RegisterInputProcessor(); + // Load saved enabled state from global editor settings (stored in user's AppData, not project) bool bWasEnabled = false; GConfig->GetBool(TEXT("Blend4Real"), TEXT("bEnabled"), bWasEnabled, GEditorSettingsIni); @@ -76,11 +78,19 @@ void FBlend4RealInputProcessor::UnregisterInputProcessor() { if (FSlateApplication::IsInitialized()) { - PlatformInputs::ShutdownKeyboardLayoutCache(); FSlateApplication::Get().UnregisterInputPreProcessor(SharedThis(this)); } } +void FBlend4RealInputProcessor::EndTransientModeIfActive() +{ + if (bTransientMode) + { + bTransientMode = false; + ToggleEnabled(); + } +} + void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) { bIsEnabled = !bIsEnabled; @@ -122,13 +132,13 @@ void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) if (bIsEnabled) { - RegisterInputProcessor(); + PlatformInputs::InitializeKeyboardLayoutCache(); PivotVisualizationController->Enable(); UE_LOG(LogTemp, Display, TEXT("Blender Controls: Enabled")); } else { - UnregisterInputProcessor(); + PlatformInputs::ShutdownKeyboardLayoutCache(); PivotVisualizationController->Disable(); UE_LOG(LogTemp, Display, TEXT("Blender Controls: Disabled")); } @@ -232,9 +242,43 @@ void FBlend4RealInputProcessor::Tick(const float DeltaTime, FSlateApplication& S bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) { + // Instant Blender Controls: when disabled, intercept transform keys to activate transient mode if (!bIsEnabled) { - return false; + const UBlend4RealSettings* Settings = UBlend4RealSettings::Get(); + if (!Settings || !Settings->bInstantBlenderControls) + { + return false; + } + + const FVector2D MousePosition = SlateApp.GetCursorPos(); + if (!Blend4RealUtils::IsMouseOverViewport(MousePosition)) + { + return false; + } + + ETransformMode TransientTransformMode; + if (UBlend4RealSettings::MatchesChord(Settings->TranslationKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Translation; + } + else if (UBlend4RealSettings::MatchesChord(Settings->RotationKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Rotation; + } + else if (UBlend4RealSettings::MatchesChord(Settings->ScaleKey, InKeyEvent)) + { + TransientTransformMode = ETransformMode::Scale; + } + else + { + return false; + } + + bTransientMode = true; + ToggleEnabled(); + TransformController->BeginTransform(TransientTransformMode); + return true; } // Only process input if mouse is over a viewport (not requiring keyboard focus) @@ -312,6 +356,7 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, TransformController->ApplyNumericTransform(); } TransformController->EndTransform(true); + EndTransientModeIfActive(); return true; } @@ -319,6 +364,7 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, if (Key == EKeys::Escape && ModMask == 0) { TransformController->EndTransform(false); + EndTransientModeIfActive(); return true; } @@ -502,11 +548,13 @@ bool FBlend4RealInputProcessor::HandleMouseButtonDownEvent(FSlateApplication& Sl if (UBlend4RealSettings::MatchesChord(Settings->ApplyTransformKey, MouseEvent)) { TransformController->EndTransform(true); + EndTransientModeIfActive(); return true; } if (UBlend4RealSettings::MatchesChord(Settings->CancelTransformKey, MouseEvent)) { TransformController->EndTransform(false); + EndTransientModeIfActive(); return true; } } diff --git a/Source/Blend4Real/Public/Blend4RealInputProcessor.h b/Source/Blend4Real/Public/Blend4RealInputProcessor.h index ee0e031..9f8f71c 100644 --- a/Source/Blend4Real/Public/Blend4RealInputProcessor.h +++ b/Source/Blend4Real/Public/Blend4RealInputProcessor.h @@ -16,6 +16,16 @@ class UViewportOrbitInteraction; /** * Input processor for Blender-style controls in Unreal Editor. * Acts as a thin dispatcher routing input to specialized controllers. + * + * Always registered as a Slate input pre-processor. Supports two activation modes: + * + * - Persistent mode: toggled via the toolbar button. Blend4Real stays enabled until + * explicitly toggled off. All Blender controls (transforms, navigation, selection) are active. + * + * - Transient mode (opt-in via "Instant Blender Controls" setting): pressing a transform key + * (G/R/S) while Blend4Real is disabled temporarily activates it for the duration of the + * transform. Once confirmed (LMB/Enter/Space) or cancelled (RMB/Escape), Blend4Real + * disables itself and returns to standard Unreal controls. */ class FBlend4RealInputProcessor : public TSharedFromThis, public IInputProcessor { @@ -41,7 +51,13 @@ class FBlend4RealInputProcessor : public TSharedFromThis InLevelEditor); + /** End transient mode if active: clears the flag and disables Blend4Real. */ + void EndTransientModeIfActive(); + bool bIsEnabled = false; + + /** When true, Blend4Real was activated by a transform key press and will auto-disable when the transform ends. */ + bool bTransientMode = false; bool bCursorHidden = false; FVector2D LastMousePosition = FVector2D::ZeroVector; FIntPoint PreNavigationCursorPos = FIntPoint::ZeroValue; diff --git a/Source/Blend4Real/Public/Blend4RealSettings.h b/Source/Blend4Real/Public/Blend4RealSettings.h index d725595..e7d83f1 100644 --- a/Source/Blend4Real/Public/Blend4RealSettings.h +++ b/Source/Blend4Real/Public/Blend4RealSettings.h @@ -33,6 +33,12 @@ class BLEND4REAL_API UBlend4RealSettings : public UDeveloperSettings UPROPERTY(Config, EditAnywhere, Category = "Navigation", meta = (DisplayName = "Orbit Mode", ToolTip = "Controls how the camera orbits when using middle mouse button")) EBlend4RealOrbitMode OrbitMode = EBlend4RealOrbitMode::OrbitAroundMouseProjection; + // Instant Blender Controls: press G/R/S to start a transform without enabling Blend4Real first. + // Blend4Real activates automatically for the duration of the transform, then returns to standard Unreal controls. + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Blender Controls", + ToolTip = "When enabled, pressing a transform key (G/R/S) over a viewport will temporarily activate Blender controls for that transform. Once confirmed or cancelled, controls return to standard Unreal behavior. This works independently of the toolbar toggle.")) + bool bInstantBlenderControls = false; + // Helper methods to get orbit flags bool ShouldOrbitAroundSelection() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundSelection; } bool ShouldOrbitAroundMouseHit() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundMouseProjection; } From 722bcb9624a31cf13f50d2b9517b412d51495efd Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 27 Mar 2026 21:03:02 +0100 Subject: [PATCH 2/6] clean up --- Source/Blend4Real/Private/Blend4RealInputProcessor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index 24d12a5..466ae06 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -242,7 +242,7 @@ void FBlend4RealInputProcessor::Tick(const float DeltaTime, FSlateApplication& S bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) { - // Instant Blender Controls: when disabled, intercept transform keys to activate transient mode + // Instant Blender Controls: when disabled, intercept transform keys to activate transient mode. if (!bIsEnabled) { const UBlend4RealSettings* Settings = UBlend4RealSettings::Get(); @@ -439,7 +439,6 @@ bool FBlend4RealInputProcessor::HandleMouseMoveEvent(FSlateApplication& SlateApp const FVector2D Delta = CurrentPosition - LastMousePosition; LastMousePosition = CurrentPosition; - // For ongoing operations (navigation/transform), continue processing even if mouse moves outside viewport // This ensures smooth camera movement and transforms when mouse drags outside viewport const bool bInOperation = NavigationController->IsNavigating() || TransformController->IsTransforming(); From d2eb74a950f2e45d07b191bd4ed731e9ee7682b4 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 27 Mar 2026 22:48:02 +0100 Subject: [PATCH 3/6] Fix crash when pressing transform keys during PIE --- Source/Blend4Real/Private/Blend4Real.cpp | 13 +++++++++++++ .../Blend4Real/Private/Blend4RealInputProcessor.cpp | 7 +++++-- Source/Blend4Real/Public/Blend4RealInputProcessor.h | 4 ++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/Blend4Real/Private/Blend4Real.cpp b/Source/Blend4Real/Private/Blend4Real.cpp index f0abd18..99edeb0 100644 --- a/Source/Blend4Real/Private/Blend4Real.cpp +++ b/Source/Blend4Real/Private/Blend4Real.cpp @@ -122,10 +122,23 @@ void FBlend4RealModule::OnBeginPIE(bool bIsSimulating) { bWasEnabledBeforePIE = false; } + + // Unregister the input processor entirely during PIE to prevent transient mode from + // activating and accessing invalid viewport state + if (BlenderInputHandler.IsValid()) + { + BlenderInputHandler->UnregisterInputProcessor(); + } } void FBlend4RealModule::OnEndPIE(bool bIsSimulating) { + // Re-register the input processor so it can intercept keys again (including for transient mode) + if (BlenderInputHandler.IsValid()) + { + BlenderInputHandler->RegisterInputProcessor(); + } + // Re-enable if it was enabled before PIE started if (bWasEnabledBeforePIE && BlenderInputHandler.IsValid() && !BlenderInputHandler->IsEnabled()) { diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index 466ae06..c1f2dbe 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -120,9 +120,12 @@ void FBlend4RealInputProcessor::ToggleEnabled(const bool bInvalidateRender) { ViewportClient->Invalidate(); } - else if (GEditor && GEditor->GetActiveViewport()) + else if (GEditor) { - GEditor->GetActiveViewport()->Invalidate(); + if (FViewport* ActiveViewport = GEditor->GetActiveViewport()) + { + ActiveViewport->Invalidate(); + } } } diff --git a/Source/Blend4Real/Public/Blend4RealInputProcessor.h b/Source/Blend4Real/Public/Blend4RealInputProcessor.h index 9f8f71c..4ee687b 100644 --- a/Source/Blend4Real/Public/Blend4RealInputProcessor.h +++ b/Source/Blend4Real/Public/Blend4RealInputProcessor.h @@ -45,10 +45,10 @@ class FBlend4RealInputProcessor : public TSharedFromThis InLevelEditor); /** End transient mode if active: clears the flag and disables Blend4Real. */ From 6a151e4e8ad3288eb9dc8a091111f3bc6b717f6d Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 28 Mar 2026 13:06:23 +0100 Subject: [PATCH 4/6] Add options for "Instant Controls Level Viewport Only" Also Instant scale is set up to "Shift + S" not to clash with the WASD navigation --- .../Private/Blend4RealInputProcessor.cpp | 9 ++++---- Source/Blend4Real/Public/Blend4RealSettings.h | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index c1f2dbe..42b143f 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -255,21 +255,22 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, } const FVector2D MousePosition = SlateApp.GetCursorPos(); - if (!Blend4RealUtils::IsMouseOverViewport(MousePosition)) + const FName ViewportFilter = Settings->bInstantControlsLevelOnly ? FName("SLevelViewport") : NAME_None; + if (!Blend4RealUtils::IsMouseOverViewport(MousePosition, ViewportFilter)) { return false; } ETransformMode TransientTransformMode; - if (UBlend4RealSettings::MatchesChord(Settings->TranslationKey, InKeyEvent)) + if (UBlend4RealSettings::MatchesChord(Settings->InstantTranslationKey, InKeyEvent)) { TransientTransformMode = ETransformMode::Translation; } - else if (UBlend4RealSettings::MatchesChord(Settings->RotationKey, InKeyEvent)) + else if (UBlend4RealSettings::MatchesChord(Settings->InstantRotationKey, InKeyEvent)) { TransientTransformMode = ETransformMode::Rotation; } - else if (UBlend4RealSettings::MatchesChord(Settings->ScaleKey, InKeyEvent)) + else if (UBlend4RealSettings::MatchesChord(Settings->InstantScaleKey, InKeyEvent)) { TransientTransformMode = ETransformMode::Scale; } diff --git a/Source/Blend4Real/Public/Blend4RealSettings.h b/Source/Blend4Real/Public/Blend4RealSettings.h index e7d83f1..232a372 100644 --- a/Source/Blend4Real/Public/Blend4RealSettings.h +++ b/Source/Blend4Real/Public/Blend4RealSettings.h @@ -35,10 +35,30 @@ class BLEND4REAL_API UBlend4RealSettings : public UDeveloperSettings // Instant Blender Controls: press G/R/S to start a transform without enabling Blend4Real first. // Blend4Real activates automatically for the duration of the transform, then returns to standard Unreal controls. - UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Blender Controls", + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Controls", ToolTip = "When enabled, pressing a transform key (G/R/S) over a viewport will temporarily activate Blender controls for that transform. Once confirmed or cancelled, controls return to standard Unreal behavior. This works independently of the toolbar toggle.")) bool bInstantBlenderControls = false; + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Controls Level Viewport Only", + EditCondition = "bInstantBlenderControls", + ToolTip = "When enabled, Instant Blender Controls only activates in the Level Editor viewport, not in Blueprint or other viewports.")) + bool bInstantControlsLevelOnly = true; + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Grab Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a grab/translate transform in Instant Blender Controls mode.")) + FInputChord InstantTranslationKey = FInputChord(EKeys::G); + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Rotation Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a rotation transform in Instant Blender Controls mode.")) + FInputChord InstantRotationKey = FInputChord(EKeys::R); + + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Scale Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to start a scale transform in Instant Blender Controls mode.")) + FInputChord InstantScaleKey = FInputChord(EKeys::S, EModifierKey::Shift); + // Helper methods to get orbit flags bool ShouldOrbitAroundSelection() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundSelection; } bool ShouldOrbitAroundMouseHit() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundMouseProjection; } From b8f9944284701db3d0660a6317e0c11c08d4ef59 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 16 May 2026 15:41:38 +0200 Subject: [PATCH 5/6] instant duplicate key --- Source/Blend4Real/Private/Blend4RealInputProcessor.cpp | 8 ++++++++ Source/Blend4Real/Public/Blend4RealSettings.h | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp index 42b143f..e8ae4c0 100644 --- a/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp +++ b/Source/Blend4Real/Private/Blend4RealInputProcessor.cpp @@ -261,6 +261,14 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp, return false; } + if (UBlend4RealSettings::MatchesChord(Settings->InstantDuplicateKey, InKeyEvent)) + { + bTransientMode = true; + ToggleEnabled(); + SelectionActionsController->DuplicateSelectedAndGrab(); + return true; + } + ETransformMode TransientTransformMode; if (UBlend4RealSettings::MatchesChord(Settings->InstantTranslationKey, InKeyEvent)) { diff --git a/Source/Blend4Real/Public/Blend4RealSettings.h b/Source/Blend4Real/Public/Blend4RealSettings.h index 232a372..ee70c52 100644 --- a/Source/Blend4Real/Public/Blend4RealSettings.h +++ b/Source/Blend4Real/Public/Blend4RealSettings.h @@ -59,6 +59,11 @@ class BLEND4REAL_API UBlend4RealSettings : public UDeveloperSettings ToolTip = "Key to start a scale transform in Instant Blender Controls mode.")) FInputChord InstantScaleKey = FInputChord(EKeys::S, EModifierKey::Shift); + UPROPERTY(Config, EditAnywhere, Category = "General", meta = (DisplayName = "Instant Duplicate Key", + EditCondition = "bInstantBlenderControls", + ToolTip = "Key to duplicate selected actors and immediately enter grab mode in Instant Blender Controls mode.")) + FInputChord InstantDuplicateKey = FInputChord(EKeys::D, EModifierKey::Shift); + // Helper methods to get orbit flags bool ShouldOrbitAroundSelection() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundSelection; } bool ShouldOrbitAroundMouseHit() const { return OrbitMode == EBlend4RealOrbitMode::OrbitAroundMouseProjection; } From ee86a7e70a39a35e111f2f36e8d6aa78d3f6a4df Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 16 May 2026 15:45:25 +0200 Subject: [PATCH 6/6] git ignore IDE stuff --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a6d0372..b390af2 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,5 @@ Plugins/*/Binaries/* DerivedDataCache/* # Mac shitty metadata -.DS_Store \ No newline at end of file +.DS_Store +Source/Blend4Real/.idea/