diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.cpp new file mode 100644 index 0000000..60c7e2b --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.cpp @@ -0,0 +1,230 @@ +#include "PS_Editor_FloorCutManager.h" +#include "PS_Editor_MandatoryAnchor.h" +#include "PS_Editor_SceneLoader.h" +#include "Components/PrimitiveComponent.h" +#include "EngineUtils.h" +#include "Engine/World.h" + +bool UPS_Editor_FloorCutManager::ShouldCreateSubsystem(UObject* Outer) const +{ + // Only create on real game worlds — skip preview / inactive worlds. + if (const UWorld* World = Cast(Outer)) + { + const EWorldType::Type WT = World->WorldType; + return WT == EWorldType::Game || WT == EWorldType::PIE || WT == EWorldType::Editor; + } + return false; +} + +void UPS_Editor_FloorCutManager::Initialize(FSubsystemCollectionBase& Collection) +{ + Super::Initialize(Collection); + bEnabled = false; + HeightAboveAnchor = 200.f; + bAnchorZCached = false; + CachedAnchorZ = 0.f; +} + +void UPS_Editor_FloorCutManager::Deinitialize() +{ + // Restore everything we touched, so a world tear-down leaves a clean state. + RestoreAll(); + Snapshots.Empty(); + Super::Deinitialize(); +} + +void UPS_Editor_FloorCutManager::SetEnabled(bool bInEnabled) +{ + // Editor-only feature: silently ignore in gameplay mode. The widgets gate calls already, + // but this is a belt-and-braces safety so nothing leaks into a SceneLoader-driven runtime. + if (!UPS_Editor_SceneLoader::IsInEditorMode()) + { + if (bEnabled) + { + RestoreAll(); + bEnabled = false; + } + return; + } + + if (bEnabled == bInEnabled) return; + + bEnabled = bInEnabled; + if (bEnabled) + { + ApplyCutToWorld(); + } + else + { + RestoreAll(); + } +} + +void UPS_Editor_FloorCutManager::SetHeightAboveAnchor(float HeightCm) +{ + // Clamp to a sane range matching the UI slider (0–1000 cm). + HeightCm = FMath::Clamp(HeightCm, 0.f, 10000.f); + if (FMath::IsNearlyEqual(HeightAboveAnchor, HeightCm)) return; + + HeightAboveAnchor = HeightCm; + if (bEnabled) + { + ApplyCutToWorld(); + } +} + +float UPS_Editor_FloorCutManager::GetCutWorldZ() const +{ + if (!bAnchorZCached) + { + // Const-cast acceptable: lazy cache only, no observable mutation of "logical" state. + const_cast(this)->ResolveAnchorZ(); + } + return CachedAnchorZ + HeightAboveAnchor; +} + +void UPS_Editor_FloorCutManager::ReapplyCut() +{ + if (!bEnabled) return; + ApplyCutToWorld(); +} + +void UPS_Editor_FloorCutManager::OnActorSpawned(AActor* Actor) +{ + if (!bEnabled || !Actor) return; + + ResolveAnchorZ(); + const float CutZ = CachedAnchorZ + HeightAboveAnchor; + + TArray Prims; + Actor->GetComponents(Prims); + for (UPrimitiveComponent* P : Prims) + { + ApplyCutToComponent(P, CutZ); + } +} + +void UPS_Editor_FloorCutManager::InvalidateAnchorCache() +{ + bAnchorZCached = false; +} + +float UPS_Editor_FloorCutManager::ResolveAnchorZ() +{ + bAnchorZCached = true; + CachedAnchorZ = 0.f; + + UWorld* World = GetWorld(); + if (!World) return CachedAnchorZ; + + for (TActorIterator It(World); It; ++It) + { + if (APS_Editor_MandatoryAnchor* Anchor = *It) + { + CachedAnchorZ = Anchor->GetActorLocation().Z; + UE_LOG(LogTemp, Verbose, TEXT("PS_Editor: FloorCut anchor Z = %f"), CachedAnchorZ); + return CachedAnchorZ; + } + } + + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: FloorCut — no APS_Editor_MandatoryAnchor found, using world Z=0 as reference")); + return CachedAnchorZ; +} + +void UPS_Editor_FloorCutManager::ApplyCutToWorld() +{ + UWorld* World = GetWorld(); + if (!World) return; + + ResolveAnchorZ(); + const float CutZ = CachedAnchorZ + HeightAboveAnchor; + + int32 Hidden = 0; + int32 Visible = 0; + + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + AActor* Actor = *ActorIt; + if (!Actor) continue; + + // Never hide the anchor itself (it's hidden in game anyway, but defensive). + if (Actor->IsA()) continue; + + TArray Prims; + Actor->GetComponents(Prims); + for (UPrimitiveComponent* P : Prims) + { + if (!P) continue; + + const FBoxSphereBounds B = P->Bounds; + const float MinZ = B.Origin.Z - B.BoxExtent.Z; + if (MinZ > CutZ) + { + ApplyCutToComponent(P, CutZ); + ++Hidden; + } + else + { + // Component sits at or straddles the plane → must be in its restored state. + RestoreComponent(P); + ++Visible; + } + } + } + + UE_LOG(LogTemp, Verbose, TEXT("PS_Editor: FloorCut applied — CutZ=%.1f, hidden=%d, kept=%d, anchor=%.1f, height=%.1f"), + CutZ, Hidden, Visible, CachedAnchorZ, HeightAboveAnchor); +} + +void UPS_Editor_FloorCutManager::ApplyCutToComponent(UPrimitiveComponent* Comp, float CutZ) +{ + if (!Comp) return; + + const FBoxSphereBounds B = Comp->Bounds; + const float MinZ = B.Origin.Z - B.BoxExtent.Z; + if (MinZ <= CutZ) + { + // Below the plane — make sure it is in its restored state (caller may not have). + RestoreComponent(Comp); + return; + } + + // Snapshot before first mutation, so we can restore the exact original state. + TWeakObjectPtr Key(Comp); + if (!Snapshots.Contains(Key)) + { + FCutSnapshot Snap; + Snap.bWasVisible = Comp->IsVisible(); + Snap.CollisionEnabled = Comp->GetCollisionEnabled(); + Snapshots.Add(Key, Snap); + } + + Comp->SetVisibility(false, /*bPropagateToChildren=*/false); + Comp->SetCollisionEnabled(ECollisionEnabled::NoCollision); +} + +void UPS_Editor_FloorCutManager::RestoreComponent(UPrimitiveComponent* Comp) +{ + if (!Comp) return; + TWeakObjectPtr Key(Comp); + const FCutSnapshot* Snap = Snapshots.Find(Key); + if (!Snap) return; + + Comp->SetVisibility(Snap->bWasVisible, /*bPropagateToChildren=*/false); + Comp->SetCollisionEnabled(Snap->CollisionEnabled); + Snapshots.Remove(Key); +} + +void UPS_Editor_FloorCutManager::RestoreAll() +{ + for (auto It = Snapshots.CreateIterator(); It; ++It) + { + UPrimitiveComponent* Comp = It.Key().Get(); + if (Comp) + { + Comp->SetVisibility(It.Value().bWasVisible, /*bPropagateToChildren=*/false); + Comp->SetCollisionEnabled(It.Value().CollisionEnabled); + } + } + Snapshots.Empty(); +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.h new file mode 100644 index 0000000..8dc70cb --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_FloorCutManager.h @@ -0,0 +1,101 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/WorldSubsystem.h" +#include "Engine/EngineTypes.h" +#include "UObject/WeakObjectPtr.h" +#include "PS_Editor_FloorCutManager.generated.h" + +class UPrimitiveComponent; +class APS_Editor_MandatoryAnchor; + +/** + * Editor-only horizontal "floor cut" plane. + * + * Hides every UPrimitiveComponent whose world-space bounds-min Z is strictly above + * AnchorZ + HeightAboveAnchor, by toggling per-component visibility and collision. + * The original state is snapshotted before any modification and restored on disable + * (or on plugin exit), so the feature is non-destructive. + * + * Editor-only by design — the gameplay runtime loaded via SceneLoader::LoadScene never + * touches this subsystem. UI in the editor widgets is the only consumer. + * + * Limitations: + * - Components straddling the cut plane (minZ <= cut <= maxZ) stay fully visible. + * Per-pixel cuts would require modifying master materials, which we explicitly + * don't want. + * - Anchor Z is sampled once and cached. If the user moves the APS_Editor_MandatoryAnchor + * at runtime, call InvalidateAnchorCache() to re-sample. + * + * Usage: + * - Get via UWorld::GetSubsystem(World) + * - SetEnabled(true), SetHeightAboveAnchor(200.f) + * - OnActorSpawned(NewActor) when SpawnManager spawns a new actor while active + */ +UCLASS() +class PS_EDITOR_API UPS_Editor_FloorCutManager : public UWorldSubsystem +{ + GENERATED_BODY() + +public: + // UWorldSubsystem + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + virtual bool ShouldCreateSubsystem(UObject* Outer) const override; + + /** Enable / disable the cut. On disable, all snapshotted components are restored. */ + void SetEnabled(bool bInEnabled); + bool IsEnabled() const { return bEnabled; } + + /** Set the cut height in centimeters above the MandatoryAnchor. Re-applies immediately + * if the cut is enabled. */ + void SetHeightAboveAnchor(float HeightCm); + float GetHeightAboveAnchor() const { return HeightAboveAnchor; } + + /** Computed world Z of the cut plane (AnchorZ + HeightAboveAnchor). Returns + * TNumericLimits::Max() if no anchor is found. */ + float GetCutWorldZ() const; + + /** Force a re-application of the current cut on all current components. + * Cheap (no allocation if components haven't moved). */ + void ReapplyCut(); + + /** Called by the SpawnManager whenever it spawns a new actor while the cut is + * active, so the new actor's primitives are evaluated against the cut. */ + void OnActorSpawned(AActor* Actor); + + /** Drop the cached anchor Z (re-sampled on next ReapplyCut). */ + void InvalidateAnchorCache(); + +private: + /** Per-component snapshot of the state we tweak, so we can restore exactly. */ + struct FCutSnapshot + { + bool bWasVisible = true; + TEnumAsByte CollisionEnabled = ECollisionEnabled::QueryAndPhysics; + }; + + /** Apply the cut to a single primitive (snapshot if needed, then hide). */ + void ApplyCutToComponent(UPrimitiveComponent* Comp, float CutZ); + /** Restore a single primitive from its snapshot, if any. */ + void RestoreComponent(UPrimitiveComponent* Comp); + + /** Walk the world and apply the cut to every primitive. */ + void ApplyCutToWorld(); + /** Restore every snapshotted primitive. */ + void RestoreAll(); + + /** Find the world Z of the first MandatoryAnchor in the world, or fall back to 0. + * Result is cached in CachedAnchorZ. */ + float ResolveAnchorZ(); + + bool bEnabled = false; + float HeightAboveAnchor = 200.f; // cm — default 2m as agreed with user + + bool bAnchorZCached = false; + float CachedAnchorZ = 0.f; + + /** Snapshots of components we have modified, keyed by weak pointer so destroyed + * primitives are simply ignored on restore. */ + TMap, FCutSnapshot> Snapshots; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp index 3b856ed..67812b0 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp @@ -8,6 +8,7 @@ #include "PS_Editor_GroundSnap.h" #include "PS_Editor_NavUtils.h" #include "PS_Editor_MandatoryAnchor.h" +#include "PS_Editor_FloorCutManager.h" #include "AIController.h" #include "BrainComponent.h" #include "EngineUtils.h" @@ -310,6 +311,19 @@ AActor* UPS_Editor_SpawnManager::SpawnActorDirect(UClass* ActorClass, FVector Lo SpawnedActors.Add(SpawnedActor); RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor); + // If the editor's floor-cut is active, evaluate the new actor against it so it doesn't + // appear floating above a "hidden" floor. + if (UWorld* W = PC->GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + if (FC->IsEnabled()) + { + FC->OnActorSpawned(SpawnedActor); + } + } + } + return SpawnedActor; } @@ -335,6 +349,17 @@ void UPS_Editor_SpawnManager::TrackSpawnedActor(AActor* Actor) if (APlayerController* PC = OwnerPC.Get()) { RegisterSpawnedActorId(PC->GetWorld(), Actor); + // Re-evaluate against the floor cut (duplicate / paste paths land here). + if (UWorld* W = PC->GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + if (FC->IsEnabled()) + { + FC->OnActorSpawned(Actor); + } + } + } } } } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index 99766a3..c36292b 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -32,6 +32,7 @@ #include "DesktopPlatformModule.h" #include "PS_Editor_GameInstanceSubsystem.h" #include "PS_Editor_TimelineSubsystem.h" +#include "PS_Editor_FloorCutManager.h" #include "Engine/World.h" #include "PS_Editor_UIStyleAsset.h" #include "PS_Editor_UIStyle.h" @@ -892,6 +893,70 @@ TSharedRef UPS_Editor_MainWidget::BuildToolbar() }) ] ] + + SHorizontalBox::Slot().AutoWidth() [ MakeSeparator() ] + // ---- Floor cut: editor-only horizontal plane to hide upper floors ---- + + SHorizontalBox::Slot().AutoWidth() + [ + MakeToggleButton(EPS_Editor_Icon::Grid, EPS_Editor_Icon::Grid, + TAttribute::CreateLambda([this]() -> FString + { + UWorld* W = GetWorld(); + UPS_Editor_FloorCutManager* FC = W ? W->GetSubsystem() : nullptr; + return (FC && FC->IsEnabled()) ? TEXT("Floor cut On") : TEXT("Floor cut"); + }), + [this]() -> bool + { + UWorld* W = GetWorld(); + UPS_Editor_FloorCutManager* FC = W ? W->GetSubsystem() : nullptr; + return FC && FC->IsEnabled(); + }, + [this]() + { + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetEnabled(!FC->IsEnabled()); + } + } + }) + ] + + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(4.0f, 0.0f) + [ + SNew(SBox).MinDesiredWidth(120.0f) + [ + SAssignNew(FloorCutSlider, SSlider) + .MinValue(0.0f) + .MaxValue(1000.0f) + .StepSize(10.0f) + .MouseUsesStep(true) + .Value(200.0f) + .ToolTipText(FText::FromString(TEXT("Cut height above MandatoryAnchor (cm) — range 0..1000, step 10"))) + .OnValueChanged_Lambda([this](float NewValue) + { + const float Snapped = FMath::RoundToFloat(NewValue / 10.f) * 10.f; + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetHeightAboveAnchor(Snapped); + } + } + if (FloorCutValueText.IsValid()) + { + FloorCutValueText->SetText(FText::FromString(FString::Printf(TEXT("%.0f cm"), Snapped))); + } + }) + ] + ] + + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(2.0f, 0.0f) + [ + SAssignNew(FloorCutValueText, STextBlock) + .Text(FText::FromString(TEXT("200 cm"))) + .Font(FPS_Editor_UIStyle::SmallFont(S)) + .ColorAndOpacity(FPS_Editor_UIStyle::TextMuted(S)) + .MinDesiredWidth(48.0f) + ] ]; } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h index 0338ee6..447c3d4 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h @@ -107,6 +107,10 @@ private: TSharedPtr SnapText; TSharedPtr SnapSizeField; + // Floor-cut UI (editor-only horizontal plane that hides primitives above AnchorZ+H) + TSharedPtr FloorCutValueText; + TSharedPtr FloorCutSlider; + // Spawn catalog TSharedPtr SpawnListContainer; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp index 366cf76..a23269b 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.cpp @@ -32,6 +32,7 @@ #include "DesktopPlatformModule.h" #include "PS_Editor_GameInstanceSubsystem.h" #include "PS_Editor_TimelineSubsystem.h" +#include "PS_Editor_FloorCutManager.h" #include "Engine/World.h" // Forward declaration — the real definition lives further down with the timeline UI block, @@ -740,6 +741,69 @@ TSharedRef UPS_Editor_MainWidget_Legacy::BuildToolbar() }) ] ] + // ---- Floor cut: editor-only horizontal plane to hide upper floors ---- + + SHorizontalBox::Slot().AutoWidth().Padding(16.0f, 0.0f, 2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(FText::FromString(TEXT("Toggle floor cut. Hides primitives whose bounds-min Z is above (Anchor.Z + Height)"))) + .OnClicked_Lambda([this]() -> FReply + { + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetEnabled(!FC->IsEnabled()); + if (FloorCutToggleText.IsValid()) + { + FloorCutToggleText->SetText(FText::FromString(FC->IsEnabled() ? TEXT("Floor cut: On") : TEXT("Floor cut: Off"))); + } + } + } + return FReply::Handled(); + }) + [ + SAssignNew(FloorCutToggleText, STextBlock) + .Text(FText::FromString(TEXT("Floor cut: Off"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ] + + SHorizontalBox::Slot().AutoWidth().Padding(4.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SBox).MinDesiredWidth(120.0f) + [ + SAssignNew(FloorCutSlider, SSlider) + .MinValue(0.0f) + .MaxValue(1000.0f) + .StepSize(10.0f) + .MouseUsesStep(true) + .Value(200.0f) + .ToolTipText(FText::FromString(TEXT("Cut height above MandatoryAnchor (cm) — range 0..1000, step 10"))) + .OnValueChanged_Lambda([this](float NewValue) + { + // Snap to 10 cm — SSlider's MouseUsesStep already does this for clicks, + // but a drag still emits raw floats, so we re-snap here. + const float Snapped = FMath::RoundToFloat(NewValue / 10.f) * 10.f; + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetHeightAboveAnchor(Snapped); + } + } + if (FloorCutValueText.IsValid()) + { + FloorCutValueText->SetText(FText::FromString(FString::Printf(TEXT("%.0f cm"), Snapped))); + } + }) + ] + ] + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SAssignNew(FloorCutValueText, STextBlock) + .Text(FText::FromString(TEXT("200 cm"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .MinDesiredWidth(48.0f) + ] ]; } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h index 75cf610..c7e4f66 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_Legacy.h @@ -80,6 +80,11 @@ private: TSharedPtr SnapText; TSharedPtr SnapSizeField; + // Floor-cut UI (editor-only horizontal plane that hides primitives above AnchorZ+H) + TSharedPtr FloorCutToggleText; + TSharedPtr FloorCutSlider; + TSharedPtr FloorCutValueText; + // Spawn catalog TSharedPtr SpawnListContainer; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.cpp index e91f2ea..47e9dea 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.cpp @@ -4,7 +4,9 @@ #include "PS_Editor_SpawnManager.h" #include "PS_Editor_SceneSerializer.h" #include "PS_Editor_PlayerController.h" +#include "PS_Editor_FloorCutManager.h" #include "GameFramework/PlayerController.h" +#include "Engine/World.h" void UPS_Editor_MainWidget_UMG::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager) { @@ -161,6 +163,54 @@ void UPS_Editor_MainWidget_UMG::ClearSelection() } } +// -------------------- Floor cut -------------------- + +void UPS_Editor_MainWidget_UMG::SetFloorCutEnabled(bool bEnabled) +{ + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetEnabled(bEnabled); + } + } +} + +void UPS_Editor_MainWidget_UMG::SetFloorCutHeightCm(float HeightCm) +{ + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + FC->SetHeightAboveAnchor(HeightCm); + } + } +} + +bool UPS_Editor_MainWidget_UMG::IsFloorCutEnabled() const +{ + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + return FC->IsEnabled(); + } + } + return false; +} + +float UPS_Editor_MainWidget_UMG::GetFloorCutHeightCm() const +{ + if (UWorld* W = GetWorld()) + { + if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem()) + { + return FC->GetHeightAboveAnchor(); + } + } + return 200.f; +} + // -------------------- Getters -------------------- EPS_Editor_TransformMode UPS_Editor_MainWidget_UMG::GetCurrentTransformMode() const diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.h index e7ff9be..5bddf43 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget_UMG.h @@ -70,6 +70,22 @@ public: UFUNCTION(BlueprintCallable, Category = "PS Editor|Selection") void ClearSelection(); + // ---- Floor cut (editor-only horizontal plane that hides upper floors) ---- + + /** Enable/disable the floor cut. Slider value drives the cut height in cm above the + * MandatoryAnchor. */ + UFUNCTION(BlueprintCallable, Category = "PS Editor|FloorCut") + void SetFloorCutEnabled(bool bEnabled); + + UFUNCTION(BlueprintCallable, Category = "PS Editor|FloorCut") + void SetFloorCutHeightCm(float HeightCm); + + UFUNCTION(BlueprintPure, Category = "PS Editor|FloorCut") + bool IsFloorCutEnabled() const; + + UFUNCTION(BlueprintPure, Category = "PS Editor|FloorCut") + float GetFloorCutHeightCm() const; + // ==================== BP-PURE GETTERS ==================== UFUNCTION(BlueprintPure, Category = "PS Editor|Tools") EPS_Editor_TransformMode GetCurrentTransformMode() const;