<# .SYNOPSIS Runs build\ue_spawn_layout.py through the Unreal editor commandlet (no window, no GPU). Without -SaveMap it is a DRY RUN: the station is built from anchorage_layout.json in an unsaved temporary level, every actor is read back and compared with the layout, and the editor quits. Nothing is written to the project. With -SaveMap /Game/Maps/Anchorage/Anchorage_Station the level is created and saved (an existing one only with -Overwrite). Do that only when the station is wanted in the game project. -Layout names the layout file (default anchorage_layout.json, the station without its exterior). The completed station is anchorage_exterior_layout.json: it also lists the station's own meshes (outer shell, decks, ice), which are imported from exterior_export\fbx: into memory only in the dry run, into -MeshDir (default /Game/Anchorage/Meshes) for real. Log and report of a named layout get its name: kit_export\ue_spawn_*_. .EXAMPLE powershell -NoProfile -ExecutionPolicy Bypass -File build\ue_spawn_layout.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File build\ue_spawn_layout.ps1 -Layout anchorage_exterior_layout.json powershell -NoProfile -ExecutionPolicy Bypass -File build\ue_spawn_layout.ps1 -SaveMap /Game/Maps/Anchorage/Anchorage_Station #> [CmdletBinding()] param( [string]$SaveMap = '', [switch]$Overwrite, [string]$Layout = '', [string]$MeshDir = '', # the robots are skeletal meshes, which want a render device (2026-10-21): -Render runs on the GPU, offscreen [switch]$Render ) $ErrorActionPreference = 'Stop' $Here = $PSScriptRoot $Root = Split-Path -Parent $Here $Uproject = 'D:\HADAL - The Descent\TheDescentUE\TheDescent.uproject' $EditorCmd = 'E:\Tools\UE_5.8\Engine\Binaries\Win64\UnrealEditor-Cmd.exe' $Script = (Join-Path $Here 'ue_spawn_layout.py').Replace('\', '/') if (-not $Layout) { $Layout = Join-Path $Root 'anchorage_layout.json' } if (-not (Split-Path -IsAbsolute $Layout)) { $Layout = Join-Path $Root $Layout } $Tag = [IO.Path]::GetFileNameWithoutExtension($Layout) $Suffix = $(if ($Tag -eq 'anchorage_layout') { '' } else { '_' + $Tag }) $LogFile = Join-Path $Root ('kit_export\ue_spawn_last' + $Suffix + '.log') $env:UE_SDKS_ROOT = 'D:\HADAL - The Descent\.tools\AutoSDK' $env:ANCHORAGE_LAYOUT = $Layout.Replace('\', '/') $env:ANCHORAGE_SAVE_MAP = $SaveMap $env:ANCHORAGE_OVERWRITE = $(if ($Overwrite) { '1' } else { '' }) $env:ANCHORAGE_REPORT = (Join-Path $Root ('kit_export\ue_spawn_report' + $Suffix + '.json')).Replace('\', '/') $env:ANCHORAGE_MESH_DIR = $MeshDir $rhi = if ($Render) { @('-AllowCommandletRendering', '-RenderOffscreen') } else { @('-nullrhi') } $argList = @( ('"' + $Uproject + '"'), '-run=pythonscript', ('-script="' + $Script + '"'), '-unattended', '-nosplash') + $rhi + @('-nosound', '-NoLiveCoding', ('-abslog="' + $LogFile + '"') ) $sw = [System.Diagnostics.Stopwatch]::StartNew() $proc = Start-Process -FilePath $EditorCmd -ArgumentList $argList -PassThru -WindowStyle Hidden $proc.WaitForExit() $sw.Stop() $lines = Get-Content $LogFile -ErrorAction SilentlyContinue $lines | Where-Object { $_ -match '\[anchorage\]' } | ForEach-Object { $_ -replace '^\[[0-9.:\-]+\]\[\s*\d+\]', '' } | ForEach-Object { Write-Host $_ } $done = $lines | Where-Object { $_ -match '\[anchorage\] \+\s*[0-9.]+s\s+DONE' } $secs = [math]::Round($sw.Elapsed.TotalSeconds, 1) if ($done) { Write-Host ("SPAWN OK exit={0} {1}s" -f $proc.ExitCode, $secs); exit 0 } Write-Host ("SPAWN FAIL exit={0} {1}s log={2}" -f $proc.ExitCode, $secs, $LogFile) exit 1