Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Source/Blend4Real/Private/Blend4Real.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
{
Expand Down
69 changes: 60 additions & 9 deletions Source/Blend4Real/Private/Blend4RealInputProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ FBlend4RealInputProcessor::~FBlend4RealInputProcessor()

void FBlend4RealInputProcessor::RegisterInputProcessor()
{
if (bIsEnabled && FSlateApplication::IsInitialized())
if (FSlateApplication::IsInitialized())
{
PlatformInputs::InitializeKeyboardLayoutCache();
FSlateApplication::Get().RegisterInputPreProcessor(SharedThis(this));
}
}
Expand All @@ -59,6 +58,9 @@ void FBlend4RealInputProcessor::Init(TSharedPtr<ILevelEditor>)
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);
Expand All @@ -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;
Expand Down Expand Up @@ -110,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();
}
}
}

Expand All @@ -122,13 +135,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"));
}
Expand Down Expand Up @@ -232,9 +245,44 @@ 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();
const FName ViewportFilter = Settings->bInstantControlsLevelOnly ? FName("SLevelViewport") : NAME_None;
if (!Blend4RealUtils::IsMouseOverViewport(MousePosition, ViewportFilter))
{
return false;
}

ETransformMode TransientTransformMode;
if (UBlend4RealSettings::MatchesChord(Settings->InstantTranslationKey, InKeyEvent))
{
TransientTransformMode = ETransformMode::Translation;
}
else if (UBlend4RealSettings::MatchesChord(Settings->InstantRotationKey, InKeyEvent))
{
TransientTransformMode = ETransformMode::Rotation;
}
else if (UBlend4RealSettings::MatchesChord(Settings->InstantScaleKey, 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)
Expand Down Expand Up @@ -312,13 +360,15 @@ bool FBlend4RealInputProcessor::HandleKeyDownEvent(FSlateApplication& SlateApp,
TransformController->ApplyNumericTransform();
}
TransformController->EndTransform(true);
EndTransientModeIfActive();
return true;
}

// Escape cancels transform
if (Key == EKeys::Escape && ModMask == 0)
{
TransformController->EndTransform(false);
EndTransientModeIfActive();
return true;
}

Expand Down Expand Up @@ -393,7 +443,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();
Expand Down Expand Up @@ -502,11 +551,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;
}
}
Expand Down
20 changes: 18 additions & 2 deletions Source/Blend4Real/Public/Blend4RealInputProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<FBlend4RealInputProcessor>, public IInputProcessor
{
Expand All @@ -35,13 +45,19 @@ class FBlend4RealInputProcessor : public TSharedFromThis<FBlend4RealInputProcess

void ToggleEnabled(const bool bInvalidateRender = true);
bool IsEnabled() const { return bIsEnabled; }

private:
void RegisterInputProcessor();
void UnregisterInputProcessor();

private:
void Init(TSharedPtr<ILevelEditor> 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;
Expand Down
26 changes: 26 additions & 0 deletions Source/Blend4Real/Public/Blend4RealSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ 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 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; }
Expand Down