From abc1e82493910c8f725e12520fd6271aa13d9f59 Mon Sep 17 00:00:00 2001 From: Nicholas Harding <40708936+sir306@users.noreply.github.com> Date: Tue, 31 Mar 2026 11:33:31 +1300 Subject: [PATCH 1/3] Improve macOS screenshot handling and packaging docs Add robust macOS screenshot flow and packaging guidance. Implement macOS-specific preflight write probe, FScreenshotRequest delegate handler, async PNG compression/save, timeout handling, and state reset; add BeginDestroy override to unbind the screenshot delegate. Introduce platform-specific Engine.ini files (Mac/Windows) and move renderer MegaLights setting into per-platform configs; add local mac signing setting to DefaultEngine.ini. Update .gitignore to exclude hdf5 share and MobiusCaptures test data. Expand docs/BUILD-AND-RUN.md with macOS packaging entitlements and runtime Files & Folders permission guidance. Will perform a clean clone and test following documentation steps to ensure the process is correct and no steps are missed. Once confirmed will create tutorial video to aid mac users --- .gitignore | 2 + .../ProjectMobius/Config/DefaultEngine.ini | 3 +- .../ProjectMobius/Config/Mac/MacEngine.ini | 2 + .../Config/Windows/WindowsEngine.ini | 2 + .../Private/Util/FrameGrabberHelper.cpp | 218 ++++++++++++++++-- .../Public/Util/FrameGrabberHelper.h | 17 ++ docs/BUILD-AND-RUN.md | 76 +++++- 7 files changed, 305 insertions(+), 15 deletions(-) create mode 100644 UnrealFolder/ProjectMobius/Config/Mac/MacEngine.ini create mode 100644 UnrealFolder/ProjectMobius/Config/Windows/WindowsEngine.ini diff --git a/.gitignore b/.gitignore index 426751e55..eea5e4e1a 100644 --- a/.gitignore +++ b/.gitignore @@ -179,6 +179,7 @@ UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/i UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/lib/pkgconfig/ UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/lib/libhdf5.settings UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/include/H5pubconf.h +UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/install/share/ # HDF5 third-party (scoped to vendored path) UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/**/java/.classes @@ -190,6 +191,7 @@ UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/. UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/CLAUDE.md UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/CMakeUserPresets.json UnrealFolder/ProjectMobius/Plugins/Hdf5DataPlugin/Source/ThirdParty/hdf5-2.0.0/HDF5Examples/CMakeUserPresets.json +UnrealFolder/ProjectMobius/UnitTestSampleData/**/MobiusCaptures/ # ===================================================== diff --git a/UnrealFolder/ProjectMobius/Config/DefaultEngine.ini b/UnrealFolder/ProjectMobius/Config/DefaultEngine.ini index 8c9ad6980..f84a2c314 100644 --- a/UnrealFolder/ProjectMobius/Config/DefaultEngine.ini +++ b/UnrealFolder/ProjectMobius/Config/DefaultEngine.ini @@ -93,7 +93,6 @@ r.TemporalAA.Upsampling=False r.DefaultFeature.Bloom=False r.DefaultBackBufferPixelFormat=4 r.AllowStaticLighting=False -r.MegaLights.EnableForProject=True r.RayTracing.Shadows=True r.VertexFoggingForOpaque=False r.Velocity.EnableVertexDeformation=1 @@ -374,3 +373,5 @@ AutoRepair=False bAutoRepair=False EnabledByDefault=False +[/Script/MacTargetPlatform.XcodeProjectSettings] +bMacSignToRunLocally=True diff --git a/UnrealFolder/ProjectMobius/Config/Mac/MacEngine.ini b/UnrealFolder/ProjectMobius/Config/Mac/MacEngine.ini new file mode 100644 index 000000000..709526ba2 --- /dev/null +++ b/UnrealFolder/ProjectMobius/Config/Mac/MacEngine.ini @@ -0,0 +1,2 @@ +[/Script/Engine.RendererSettings] +r.MegaLights.EnableForProject=False diff --git a/UnrealFolder/ProjectMobius/Config/Windows/WindowsEngine.ini b/UnrealFolder/ProjectMobius/Config/Windows/WindowsEngine.ini new file mode 100644 index 000000000..08ffc6221 --- /dev/null +++ b/UnrealFolder/ProjectMobius/Config/Windows/WindowsEngine.ini @@ -0,0 +1,2 @@ +[/Script/Engine.RendererSettings] +r.MegaLights.EnableForProject=True diff --git a/UnrealFolder/ProjectMobius/Source/MobiusCore/Private/Util/FrameGrabberHelper.cpp b/UnrealFolder/ProjectMobius/Source/MobiusCore/Private/Util/FrameGrabberHelper.cpp index d78edae7e..02882abbd 100644 --- a/UnrealFolder/ProjectMobius/Source/MobiusCore/Private/Util/FrameGrabberHelper.cpp +++ b/UnrealFolder/ProjectMobius/Source/MobiusCore/Private/Util/FrameGrabberHelper.cpp @@ -7,6 +7,7 @@ #include "HAL/PlatformFileManager.h" #include "ImageUtils.h" #include "Misc/FileHelper.h" +#include "Misc/Paths.h" #include "RenderingThread.h" // For FlushRenderingCommands (Mac GPU sync) #include "Slate/SceneViewport.h" #include "Subsystems/MobiusCustomLoggerSubsystem.h" @@ -76,6 +77,45 @@ namespace }); } } + +#if PLATFORM_MAC + bool IsLikelyMacPrivacyProtectedPath(const FString& Path) + { + FString NormalizedPath = Path; + FPaths::NormalizeFilename(NormalizedPath); + + FString UserDir = FPlatformProcess::UserDir(); + FPaths::NormalizeDirectoryName(UserDir); + + const TArray ProtectedRoots = + { + FPaths::Combine(UserDir, TEXT("Desktop")), + FPaths::Combine(UserDir, TEXT("Documents")), + FPaths::Combine(UserDir, TEXT("Downloads")) + }; + + for (FString ProtectedRoot : ProtectedRoots) + { + FPaths::NormalizeDirectoryName(ProtectedRoot); + if (NormalizedPath.StartsWith(ProtectedRoot)) + { + return true; + } + } + + return false; + } + + FString BuildMacWriteFailureDetail(const FString& Path) + { + if (IsLikelyMacPrivacyProtectedPath(Path)) + { + return TEXT(" macOS Files & Folders privacy may be blocking access to this Desktop/Documents/Downloads location."); + } + + return TEXT(" The path is valid, so the failure is likely a macOS permission or Unreal screenshot write-path issue."); + } +#endif } void UFrameGrabberHelper::Configure(bool bInUseFullResolution, FIntPoint InDownscaleSize) @@ -103,6 +143,21 @@ void UFrameGrabberHelper::Configure(bool bInUseFullResolution, FIntPoint InDowns #endif } +void UFrameGrabberHelper::BeginDestroy() +{ +#if PLATFORM_MAC + ResetMacCaptureState(); + + if (bMacScreenshotDelegateBound) + { + UGameViewportClient::OnScreenshotCaptured().RemoveAll(this); + bMacScreenshotDelegateBound = false; + } +#endif + + Super::BeginDestroy(); +} + #if !PLATFORM_MAC bool UFrameGrabberHelper::TryInitialize() { @@ -258,35 +313,68 @@ void UFrameGrabberHelper::TriggerCapture(const FString& InFileName) MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Created directory: %s"), *DestPath)); } - // Full path for the screenshot (without .png - engine may add it) - FString FullPath = DestPath / PendingFileName; + PendingOutputPath = DestPath / (PendingFileName + TEXT(".png")); + FPaths::NormalizeFilename(PendingOutputPath); + + FString PreflightFailureReason; + if (!PreflightMacScreenshotWrite(DestPath, PreflightFailureReason)) + { + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Error: Write preflight failed for %s. Reason: %s"), + *DestPath, + *PreflightFailureReason)); + MobiusReportError( + FText::FromString(TEXT("Save Permission Error")), + FText::FromString(PreflightFailureReason), + FText::FromString(TEXT("FrameGrabberHelper::TriggerCapture")), + EMobiusErrorSeverity::Error, + true); + PendingOutputPath.Empty(); + return; + } + + if (!bMacScreenshotDelegateBound) + { + UGameViewportClient::OnScreenshotCaptured().AddUObject(this, &UFrameGrabberHelper::HandleMacScreenshotCaptured); + bMacScreenshotDelegateBound = true; + } - MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Requesting screenshot via FScreenshotRequest: %s"), *FullPath)); + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Requesting screenshot via FScreenshotRequest: %s"), *PendingOutputPath)); - // Request screenshot with filename, no HDR, show UI notification, no unique suffix - // Signature: RequestScreenshot(const FString& Filename, bool bShowUI, bool bAddFilenameSuffix, bool bInHDR) - FScreenshotRequest::RequestScreenshot(FullPath, true, false, false); + // Request screenshot with filename, no HDR, show UI notification, no unique suffix. + // We still request Unreal's screenshot flow, but the pixel buffer is saved by our own delegate so the + // final write reports explicit permission/path failures instead of silently disappearing. + FScreenshotRequest::RequestScreenshot(PendingOutputPath, true, false, false); bIsCapturing = true; - // Reset capturing flag after a delay since the engine saves asynchronously - FTimerHandle TimerHandle; if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld()) { GEngine->GameViewport->GetWorld()->GetTimerManager().SetTimer( - TimerHandle, + MacCaptureTimeoutHandle, [this]() { - bIsCapturing = false; - MobiusLog(TEXT("[FrameGrabber][Mac] Capture flag reset after timeout.")); + if (!bIsCapturing) + { + return; + } + + const FString TimedOutOutputPath = PendingOutputPath; + ResetMacCaptureState(); + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Error: Screenshot capture timed out before a save callback for %s"), *TimedOutOutputPath)); + MobiusReportError( + FText::FromString(TEXT("Capture Timed Out")), + FText::FromString(TEXT("The screenshot was requested, but macOS never delivered image data back to the game. This points to an Unreal capture-path problem rather than simple folder creation.")), + FText::FromString(TEXT("FrameGrabberHelper::TriggerCapture")), + EMobiusErrorSeverity::Error, + true); }, - 0.5f, + 3.0f, false ); } else { MobiusLog(TEXT("[FrameGrabber][Mac] Warning: Could not set timer for capture reset - no valid world context.")); - bIsCapturing = false; + ResetMacCaptureState(); } #else // Windows/other: Use FFrameGrabber @@ -340,6 +428,110 @@ void UFrameGrabberHelper::TriggerCapture(const FString& InFileName) #endif } +#if PLATFORM_MAC +bool UFrameGrabberHelper::PreflightMacScreenshotWrite(const FString& DestPath, FString& OutFailureReason) const +{ + const FString ProbePath = DestPath / FString::Printf(TEXT(".mobius_write_probe_%llu.tmp"), static_cast(FPlatformTime::Cycles64())); + const bool bProbeWritten = FFileHelper::SaveStringToFile( + TEXT("probe"), + *ProbePath, + FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); + + if (!bProbeWritten) + { + OutFailureReason = FString::Printf( + TEXT("Project Mobius could create or resolve the folder but could not create a test file in %s.%s"), + *DestPath, + *BuildMacWriteFailureDetail(DestPath)); + return false; + } + + IFileManager::Get().Delete(*ProbePath, false, true, true); + return true; +} + +void UFrameGrabberHelper::HandleMacScreenshotCaptured(int32 InSizeX, int32 InSizeY, const TArray& InImageData) +{ + if (!bIsCapturing) + { + return; + } + + if (PendingOutputPath.IsEmpty()) + { + MobiusLog(TEXT("[FrameGrabber][Mac] Warning: Received screenshot pixels with no pending output path.")); + ResetMacCaptureState(); + return; + } + + if (InSizeX <= 0 || InSizeY <= 0 || InImageData.Num() == 0) + { + const FString FailedOutputPath = PendingOutputPath; + ResetMacCaptureState(); + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Error: Invalid screenshot pixel data for %s"), *FailedOutputPath)); + MobiusReportError( + FText::FromString(TEXT("Capture Failed")), + FText::FromString(TEXT("The screenshot completed but returned no pixel data.")), + FText::FromString(TEXT("FrameGrabberHelper::HandleMacScreenshotCaptured")), + EMobiusErrorSeverity::Error, + true); + return; + } + + const FString OutputPath = PendingOutputPath; + TArray ImageData = InImageData; + for (FColor& Pixel : ImageData) + { + Pixel.A = 255; + } + + Async(EAsyncExecution::ThreadPool, [WeakThis = TWeakObjectPtr(this), OutputPath, InSizeX, InSizeY, ImageData = MoveTemp(ImageData)]() mutable + { + TArray64 PNGData; + FImageUtils::PNGCompressImageArray(InSizeX, InSizeY, ImageData, PNGData); + + const bool bSaved = PNGData.Num() > 0 && FFileHelper::SaveArrayToFile(PNGData, *OutputPath); + + AsyncTask(ENamedThreads::GameThread, [WeakThis, OutputPath, InSizeX, InSizeY, bSaved]() + { + if (!WeakThis.IsValid()) + { + return; + } + + WeakThis->ResetMacCaptureState(); + + if (!bSaved) + { + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Error: Failed to save screenshot after capture: %s"), *OutputPath)); + MobiusReportError( + FText::FromString(TEXT("Save Failed")), + FText::FromString(FString::Printf(TEXT("Captured screenshot pixels successfully but could not write %s.%s"), *OutputPath, *BuildMacWriteFailureDetail(OutputPath))), + FText::FromString(TEXT("FrameGrabberHelper::HandleMacScreenshotCaptured")), + EMobiusErrorSeverity::Error, + true); + return; + } + + MobiusLog(FString::Printf(TEXT("[FrameGrabber][Mac] Screenshot saved successfully (%dx%d): %s"), + InSizeX, InSizeY, *OutputPath)); + }); + }); +} + +void UFrameGrabberHelper::ResetMacCaptureState() +{ + if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWorld() && MacCaptureTimeoutHandle.IsValid()) + { + GEngine->GameViewport->GetWorld()->GetTimerManager().ClearTimer(MacCaptureTimeoutHandle); + } + + MacCaptureTimeoutHandle.Invalidate(); + PendingOutputPath.Empty(); + bIsCapturing = false; +} +#endif + void UFrameGrabberHelper::Tick(float DeltaTime) { #if PLATFORM_MAC diff --git a/UnrealFolder/ProjectMobius/Source/MobiusCore/Public/Util/FrameGrabberHelper.h b/UnrealFolder/ProjectMobius/Source/MobiusCore/Public/Util/FrameGrabberHelper.h index d80efe686..4c7ca0fbd 100644 --- a/UnrealFolder/ProjectMobius/Source/MobiusCore/Public/Util/FrameGrabberHelper.h +++ b/UnrealFolder/ProjectMobius/Source/MobiusCore/Public/Util/FrameGrabberHelper.h @@ -34,6 +34,8 @@ class MOBIUSCORE_API UFrameGrabberHelper : public UObject /** Tick to be called each frame to poll frames internally */ void Tick(float DeltaTime); + virtual void BeginDestroy() override; + bool IsCapturing() const { return bIsCapturing; } #if PLATFORM_MAC bool IsInitialized() const { return true; } // Mac uses FScreenshotRequest, always "initialized" @@ -49,6 +51,21 @@ class MOBIUSCORE_API UFrameGrabberHelper : public UObject TUniquePtr FrameGrabber; #endif +#if PLATFORM_MAC + /** Try creating and removing a small probe file so macOS write denials are surfaced early. */ + bool PreflightMacScreenshotWrite(const FString& DestPath, FString& OutFailureReason) const; + + /** Handle the raw screenshot pixels so macOS writes go through our own code path. */ + void HandleMacScreenshotCaptured(int32 InSizeX, int32 InSizeY, const TArray& InImageData); + + /** Reset macOS screenshot state and clear any timeout. */ + void ResetMacCaptureState(); + + bool bMacScreenshotDelegateBound = false; + FString PendingOutputPath; + FTimerHandle MacCaptureTimeoutHandle; +#endif + FIntPoint TargetSize; FIntPoint ViewportSize; FIntPoint ConfiguredDownscaleSize = FIntPoint(800, 600); diff --git a/docs/BUILD-AND-RUN.md b/docs/BUILD-AND-RUN.md index ac38541b2..478de3709 100644 --- a/docs/BUILD-AND-RUN.md +++ b/docs/BUILD-AND-RUN.md @@ -170,7 +170,9 @@ UnrealEditor-Cmd.exe ProjectMobius.uproject -run=GenerateDatasmithMaterials -una Or use the provided scripts: `Scripts/GenerateDatasmithMaterials.bat` (Windows) / `Scripts/GenerateDatasmithMaterials.sh` (macOS/Linux). -## Package Example +## Package + +### Example command ```bash RunUAT.bat BuildCookRun ^ @@ -180,6 +182,78 @@ RunUAT.bat BuildCookRun ^ -archivedirectory=./Binaries/Release ``` +### macOS packaged build configuration + +Project Mobius currently packages macOS development builds with the App +Sandbox whitelist disabled. This is intentional for the current file workflow. +The packaged app opens user-selected simulation data files and writes +screenshots and related output into a `MobiusCaptures` folder beside the +selected data file. That flow is not currently implemented against the stricter +macOS sandbox access model, so the sandbox whitelist must remain disabled for +these builds. + +Change these source entitlement files before packaging a macOS build: + +- `UnrealFolder/ProjectMobius/Build/Mac/Resources/Sandbox.NoNet.entitlements` +- `UnrealFolder/ProjectMobius/Build/Mac/Resources/Sandbox.Server.entitlements` + +In both files, keep the App Sandbox setting disabled: + +```xml +com.apple.security.app-sandbox + +``` + +The network-enabled entitlement file also keeps client and server access +enabled: + +```xml +com.apple.security.network.client + +com.apple.security.network.server + +``` + +For local macOS packaging, keep the following setting in +`UnrealFolder/ProjectMobius/Config/DefaultEngine.ini`: + +```ini +[/Script/MacTargetPlatform.XcodeProjectSettings] +bMacSignToRunLocally=True +``` + +After packaging, verify the generated entitlements in: + +`UnrealFolder/ProjectMobius/Saved/StagedBuilds/ProjectMobius (Mac).build/Mac/ProjectMobius.build/DerivedSources/Entitlements.plist` + +Do not edit the generated entitlements file directly. It is recreated during +packaging. The source of truth is the entitlement templates under +`UnrealFolder/ProjectMobius/Build/Mac/Resources/`. + +### macOS packaged build runtime permissions + +Disabling the App Sandbox whitelist does not bypass macOS privacy prompts. If +the packaged app opens or saves data in protected folders such as `Documents`, +`Desktop`, or `Downloads`, macOS may still require the user to approve access +before Project Mobius can save screenshots beside the selected data file. + +To review or change that permission on macOS: + +1. Open `Apple menu > System Settings`. +2. Go to `Privacy & Security`. +3. Open `Files & Folders`. +4. Find `Project Mobius`. +5. Enable access for the folder in use, such as `Documents`, `Desktop`, or + `Downloads`. + +If `Project Mobius` is not listed yet, launch the packaged build and try +opening or saving a file in the target folder once. macOS should then prompt +for access. + +Repackaged local builds may need this approval again after a rebuild, because +macOS tracks Files & Folders access against the packaged app's signing +identity. + ## Related References - [UE Automation Guide](../UnrealFolder/ProjectMobius/BuildDocs/UE-Automation-Guide.md) From d513d1e337434fbadc9b64c8d7b5f5c01d94de0f Mon Sep 17 00:00:00 2001 From: Nicholas Harding <40708936+sir306@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:50:48 +1300 Subject: [PATCH 2/3] Clarify Unreal build steps and macOS Xcode notes Update docs/BUILD-AND-RUN.md to clarify Unreal Engine build workflow: recommend opening the .uproject and allowing Unreal to rebuild missing modules automatically, and only generate IDE/Visual Studio project files and manually build the editor if the automatic rebuild fails or IDE integration is required. Replace "Xcode Command Line Tools" with requiring full Xcode selected via xcode-select and add Unreal 5.5 Xcode compatibility details (min 15.2.0, max 16.9.0) plus note that local setup used Xcode 16.4. --- docs/BUILD-AND-RUN.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/BUILD-AND-RUN.md b/docs/BUILD-AND-RUN.md index 478de3709..4179faa08 100644 --- a/docs/BUILD-AND-RUN.md +++ b/docs/BUILD-AND-RUN.md @@ -8,7 +8,7 @@ This walkthrough covers the full developer setup pipeline for Project Mobius: - Clone the repository - Configure and build with CMake -- Compile the Unreal target +- Open the Unreal project and let Unreal rebuild missing modules when needed - Package a distributable build* - Add Twinmotion compatibility - Understand Datasmith modes in Unreal Engine (`Runtime Datasmith` vs `Twinmotion Datasmith`) @@ -43,7 +43,10 @@ Common: macOS (Apple Silicon) - macOS 11 or newer on Apple Silicon -- Xcode Command Line Tools +- Full Xcode selected via `xcode-select` +- Unreal Engine 5.5 Xcode compatibility in the local engine source: + `15.2.0` minimum, `16.9.0` maximum +- Current local setup used `Xcode 16.4` - Ninja macOS support exists in parts of the codebase, but Windows is still the @@ -78,8 +81,8 @@ cmake --build _superbuild --config Release --parallel Build the Unreal target after the superbuild: 1. Open `UnrealFolder/ProjectMobius/ProjectMobius.uproject` in Unreal Engine 5.5. -2. Generate Visual Studio project files from the `.uproject` if needed. -3. Build `ProjectMobiusEditor` in `Development Editor`. +2. If Unreal prompts to rebuild missing modules, allow it to compile the editor target. +3. Generate Visual Studio project files and build `ProjectMobiusEditor` manually only if the automatic rebuild fails or you want IDE integration. @@ -94,8 +97,8 @@ cmake --build _superbuild --parallel Build the Unreal target after the superbuild: 1. Open `UnrealFolder/ProjectMobius/ProjectMobius.uproject` in Unreal Engine 5.5. -2. Generate IDE project files from the `.uproject` if you need them for your platform. -3. Build `ProjectMobiusEditor` in `Development Editor`. +2. If Unreal prompts to rebuild missing modules, allow it to compile the editor target. +3. Generate IDE project files and build `ProjectMobiusEditor` manually only if the automatic rebuild fails or you want IDE integration. From a1189ba7b3b041803341cdba4dd58509582540c0 Mon Sep 17 00:00:00 2001 From: Nicholas Harding <40708936+sir306@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:33:56 +1300 Subject: [PATCH 3/3] Docs: clarify macOS build & Xcode support Clarify macOS build and packaging guidance in BUILD-AND-RUN.md. Reword Xcode compatibility to state UE5.5 supports Xcode 15.2.0 through 16.9.0 and note the docs were last tested with Xcode 16.4. Add a known macOS limitation: heatmap Gaussian blur depends on Epic's OpenCV plugin and Mac OpenCV libs are not shipped, requiring a custom plugin or code replacement. Improve Unreal build instructions to prefer letting the editor rebuild missing modules and explain IDE integration options (Visual Studio/Xcode) only when needed. Expand macOS packaging notes to describe local entitlement templates, warn that they may not be present in a fresh checkout, instruct developers to run a local packaging pass to generate and review them, and remind contributors not to commit local packaging resources or generated StagedBuilds. Also adjust wording to make clear disabling the App Sandbox does not bypass macOS privacy prompts. --- docs/BUILD-AND-RUN.md | 51 ++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/docs/BUILD-AND-RUN.md b/docs/BUILD-AND-RUN.md index 4179faa08..053d7d04f 100644 --- a/docs/BUILD-AND-RUN.md +++ b/docs/BUILD-AND-RUN.md @@ -44,14 +44,20 @@ Common: - macOS 11 or newer on Apple Silicon - Full Xcode selected via `xcode-select` -- Unreal Engine 5.5 Xcode compatibility in the local engine source: - `15.2.0` minimum, `16.9.0` maximum -- Current local setup used `Xcode 16.4` +- Unreal Engine 5.5 in the local engine source supports Xcode `15.2.0` + through `16.9.0` +- These notes were last tested with `Xcode 16.4` - Ninja macOS support exists in parts of the codebase, but Windows is still the primary development and validation target. +Known macOS limitation: heatmap Gaussian blur is currently unavailable. The +current Visualization module relies on Epic's built-in OpenCV plugin, and this +project does not yet ship the Mac OpenCV libraries needed for that path. +Supporting Gaussian blur on macOS will require a custom plugin or an equivalent +code-side replacement. + ## Clone the Repository @@ -81,8 +87,12 @@ cmake --build _superbuild --config Release --parallel Build the Unreal target after the superbuild: 1. Open `UnrealFolder/ProjectMobius/ProjectMobius.uproject` in Unreal Engine 5.5. -2. If Unreal prompts to rebuild missing modules, allow it to compile the editor target. -3. Generate Visual Studio project files and build `ProjectMobiusEditor` manually only if the automatic rebuild fails or you want IDE integration. +2. If Unreal prompts to rebuild missing modules, let it compile the editor + target. +3. If you want IDE integration, open the `.uproject` directly in an IDE that + supports it, or generate Visual Studio project files if your setup requires + them. Build `ProjectMobiusEditor` manually only if the automatic rebuild + fails. @@ -97,8 +107,11 @@ cmake --build _superbuild --parallel Build the Unreal target after the superbuild: 1. Open `UnrealFolder/ProjectMobius/ProjectMobius.uproject` in Unreal Engine 5.5. -2. If Unreal prompts to rebuild missing modules, allow it to compile the editor target. -3. Generate IDE project files and build `ProjectMobiusEditor` manually only if the automatic rebuild fails or you want IDE integration. +2. If Unreal prompts to rebuild missing modules, let it compile the editor + target. +3. If you want IDE integration, generate the Xcode project or workspace from + the `.uproject` if your setup requires it. Build `ProjectMobiusEditor` + manually only if the automatic rebuild fails. @@ -188,19 +201,31 @@ RunUAT.bat BuildCookRun ^ ### macOS packaged build configuration Project Mobius currently packages macOS development builds with the App -Sandbox whitelist disabled. This is intentional for the current file workflow. +Sandbox disabled. This is intentional for the current file workflow. The packaged app opens user-selected simulation data files and writes screenshots and related output into a `MobiusCaptures` folder beside the selected data file. That flow is not currently implemented against the stricter -macOS sandbox access model, so the sandbox whitelist must remain disabled for -these builds. +macOS sandbox access model, so the App Sandbox must remain disabled for these +builds. -Change these source entitlement files before packaging a macOS build: +Local macOS packaging uses the following entitlement templates when they are +present: - `UnrealFolder/ProjectMobius/Build/Mac/Resources/Sandbox.NoNet.entitlements` - `UnrealFolder/ProjectMobius/Build/Mac/Resources/Sandbox.Server.entitlements` -In both files, keep the App Sandbox setting disabled: +These are local packaging resources, not authored project source. A fresh +checkout may not contain them yet. + +If these files are missing on your machine, run one local macOS packaging pass +first so Unreal creates the local Mac packaging resources. Then review the +local entitlement files before packaging again. + +Because this is a public repository, keep files under +`UnrealFolder/ProjectMobius/Build/Mac/Resources/` local and do not commit them. +Do not commit generated files under `Saved/StagedBuilds/...` either. + +In both files, keep the App Sandbox disabled: ```xml com.apple.security.app-sandbox @@ -235,7 +260,7 @@ packaging. The source of truth is the entitlement templates under ### macOS packaged build runtime permissions -Disabling the App Sandbox whitelist does not bypass macOS privacy prompts. If +Disabling the App Sandbox does not bypass macOS privacy prompts. If the packaged app opens or saves data in protected folders such as `Documents`, `Desktop`, or `Downloads`, macOS may still require the user to approve access before Project Mobius can save screenshots beside the selected data file.