param( [string]$RunRoot = "", [string]$LibPackRoot = "", [string]$OcctRoot = "", [string]$RuntimeRoot = "", [switch]$SkipRuntimeJson ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Resolve-NormalizedPath { param( [Parameter(Mandatory = $true)] [string]$Path ) return [System.IO.Path]::GetFullPath($Path) } function Resolve-ConfiguredPath { param( [string]$ConfiguredPath, [string[]]$EnvironmentVariableNames ) if (-not [string]::IsNullOrWhiteSpace($ConfiguredPath)) { return Resolve-NormalizedPath -Path $ConfiguredPath } foreach ($variableName in $EnvironmentVariableNames) { $value = [Environment]::GetEnvironmentVariable($variableName) if (-not [string]::IsNullOrWhiteSpace($value)) { return Resolve-NormalizedPath -Path $value } } return "" } function Ensure-Directory { param( [Parameter(Mandatory = $true)] [string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null } } function Copy-MatchingFiles { param( [Parameter(Mandatory = $true)] [string]$SourceDir, [Parameter(Mandatory = $true)] [string[]]$Patterns, [Parameter(Mandatory = $true)] [string]$DestinationDir ) if (-not (Test-Path -LiteralPath $SourceDir)) { return } foreach ($pattern in $Patterns) { Get-ChildItem -LiteralPath $SourceDir -Filter $pattern -File -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $DestinationDir $_.Name) -Force } } } function Copy-PluginDirectory { param( [Parameter(Mandatory = $true)] [string]$SourceDir, [Parameter(Mandatory = $true)] [string]$DestinationDir ) if (-not (Test-Path -LiteralPath $SourceDir)) { return } Ensure-Directory -Path $DestinationDir Get-ChildItem -LiteralPath $SourceDir -Force | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $DestinationDir -Recurse -Force } } function Resolve-RuntimeRoot { param([string]$ConfiguredRoot) if (-not [string]::IsNullOrWhiteSpace($ConfiguredRoot)) { return Resolve-NormalizedPath -Path $ConfiguredRoot } $localAppData = [Environment]::GetEnvironmentVariable("LOCALAPPDATA") if ([string]::IsNullOrWhiteSpace($localAppData)) { throw "LOCALAPPDATA is not available." } return Resolve-NormalizedPath -Path (Join-Path $localAppData "QETDeps") } function Read-ExistingRuntimeJson { param([string]$RuntimeConfigPath) if (-not (Test-Path -LiteralPath $RuntimeConfigPath)) { return $null } return Get-Content -LiteralPath $RuntimeConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json } function Resolve-OcctRoot { param( [string]$ConfiguredRoot, $ExistingRuntime ) $resolvedConfiguredRoot = Resolve-ConfiguredPath -ConfiguredPath $ConfiguredRoot -EnvironmentVariableNames @( "FREECAD_OCCT_ROOT", "QET_OCCT_ROOT" ) if (-not [string]::IsNullOrWhiteSpace($resolvedConfiguredRoot)) { return $resolvedConfiguredRoot } if ($null -ne $ExistingRuntime -and $ExistingRuntime.PSObject.Properties.Name -contains "occt_root") { $existingOcctRoot = [string]$ExistingRuntime.occt_root if (-not [string]::IsNullOrWhiteSpace($existingOcctRoot) -and (Test-Path -LiteralPath $existingOcctRoot)) { return Resolve-NormalizedPath -Path $existingOcctRoot } } return "" } $resolvedRunRoot = Resolve-ConfiguredPath -ConfiguredPath $RunRoot -EnvironmentVariableNames @( "FREECAD_RUN_ROOT", "QET_FREECAD_RUN_ROOT" ) if ([string]::IsNullOrWhiteSpace($resolvedRunRoot)) { throw "RunRoot is required. Pass -RunRoot or set FREECAD_RUN_ROOT / QET_FREECAD_RUN_ROOT." } $resolvedLibPackRoot = Resolve-ConfiguredPath -ConfiguredPath $LibPackRoot -EnvironmentVariableNames @( "FREECAD_LIBPACK_ROOT", "QET_FREECAD_LIBPACK_ROOT" ) if ([string]::IsNullOrWhiteSpace($resolvedLibPackRoot)) { throw "LibPackRoot is required. Pass -LibPackRoot or set FREECAD_LIBPACK_ROOT / QET_FREECAD_LIBPACK_ROOT." } $runBinDir = Join-Path $resolvedRunRoot "bin" if (-not (Test-Path -LiteralPath $runBinDir)) { throw "Run directory bin folder was not found: $runBinDir" } if (-not (Test-Path -LiteralPath $resolvedLibPackRoot)) { throw "LibPack root was not found: $resolvedLibPackRoot" } $copySpecs = @( @{ Source = (Join-Path $resolvedLibPackRoot "bin"); Patterns = @("*.dll") }, @{ Source = (Join-Path $resolvedLibPackRoot "lib"); Patterns = @("*.dll") }, @{ Source = (Join-Path $resolvedLibPackRoot "bin"); Patterns = @("python.exe", "pythonw.exe", "py.exe", "python*.zip") }, @{ Source = (Join-Path $resolvedLibPackRoot "bin\Lib\site-packages\shiboken6"); Patterns = @("*.dll", "*.pyd") }, @{ Source = (Join-Path $resolvedLibPackRoot "bin\Lib\site-packages\PySide6"); Patterns = @("*.dll", "*.pyd") } ) foreach ($copySpec in $copySpecs) { Copy-MatchingFiles -SourceDir $copySpec.Source -Patterns $copySpec.Patterns -DestinationDir $runBinDir } $pluginRoot = Join-Path $resolvedLibPackRoot "bin\Lib\site-packages\PySide6\plugins" $pluginDirs = @( "platforms", "imageformats", "iconengines", "platformthemes", "styles" ) foreach ($pluginDir in $pluginDirs) { Copy-PluginDirectory ` -SourceDir (Join-Path $pluginRoot $pluginDir) ` -DestinationDir (Join-Path $runBinDir $pluginDir) } Copy-PluginDirectory ` -SourceDir (Join-Path $resolvedLibPackRoot "bin\Lib") ` -DestinationDir (Join-Path $runBinDir "Lib") Copy-PluginDirectory ` -SourceDir (Join-Path $resolvedLibPackRoot "bin\DLLs") ` -DestinationDir (Join-Path $runBinDir "DLLs") if (-not $SkipRuntimeJson) { $resolvedRuntimeRoot = Resolve-RuntimeRoot -ConfiguredRoot $RuntimeRoot Ensure-Directory -Path $resolvedRuntimeRoot $runtimeConfigPath = Join-Path $resolvedRuntimeRoot "runtime.json" $diagnosticLogPath = Join-Path $resolvedRuntimeRoot "bootstrap.log" $existingRuntime = Read-ExistingRuntimeJson -RuntimeConfigPath $runtimeConfigPath $resolvedOcctRoot = Resolve-OcctRoot -ConfiguredRoot $OcctRoot -ExistingRuntime $existingRuntime $resolvedFreeCadPython = "" $pythonCandidate = Join-Path $runBinDir "python.exe" if (Test-Path -LiteralPath $pythonCandidate) { $resolvedFreeCadPython = $pythonCandidate } $resolvedFreeCadCmd = "" $cmdCandidate = Join-Path $runBinDir "FreeCADCmd.exe" if (Test-Path -LiteralPath $cmdCandidate) { $resolvedFreeCadCmd = $cmdCandidate } $qet3dPython = if (-not [string]::IsNullOrWhiteSpace($resolvedFreeCadPython)) { $resolvedFreeCadPython } else { $resolvedFreeCadCmd } $runtimeObject = [ordered]@{ schema_version = 1 runtime_root = $resolvedRuntimeRoot runtime_config = $runtimeConfigPath diagnostic_log = $diagnosticLogPath occt_root = $resolvedOcctRoot freecad_root = $resolvedRunRoot freecad_lib = $runBinDir freecad_python = $resolvedFreeCadPython freecad_cmd = $resolvedFreeCadCmd qet_3d_python = $qet3dPython path_prefix = @($runBinDir) } ($runtimeObject | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $runtimeConfigPath -Encoding UTF8 } Write-Host "FreeCAD runtime deployment completed." Write-Host (" Run root: {0}" -f $resolvedRunRoot) Write-Host (" LibPack root: {0}" -f $resolvedLibPackRoot) if (-not $SkipRuntimeJson) { Write-Host (" runtime.json: {0}" -f (Join-Path (Resolve-RuntimeRoot -ConfiguredRoot $RuntimeRoot) "runtime.json")) }