From e21e9347050c1d6197989e8a0167bb1312623efe Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Sat, 4 Apr 2026 10:58:29 +0800 Subject: [PATCH] Add rail preferred normal repair script --- scripts/Fix-RailPreferredNormals.ps1 | 209 ++++++++++++++++++ src/Core/Animation/PathAnimationManager.cs | 4 +- src/Core/PathPlanningModels.cs | 2 +- src/UI/WPF/ViewModels/PathEditingViewModel.cs | 8 +- .../CanonicalRailOffsetResolver.cs | 2 +- src/Utils/RailPathPoseHelper.cs | 12 +- 6 files changed, 223 insertions(+), 14 deletions(-) create mode 100644 scripts/Fix-RailPreferredNormals.ps1 diff --git a/scripts/Fix-RailPreferredNormals.ps1 b/scripts/Fix-RailPreferredNormals.ps1 new file mode 100644 index 0000000..d85a680 --- /dev/null +++ b/scripts/Fix-RailPreferredNormals.ps1 @@ -0,0 +1,209 @@ +<# +用法示例 + +1. 批量修复当前目录下所有 XML 路径文件 +powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型" + +2. 递归修复目录及其子目录中的所有 XML 路径文件 +powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型" -Recurse + +3. 只修复单个 XML 路径文件 +powershell -ExecutionPolicy Bypass -File "C:\Users\Tellme\apps\NavisworksTransport-rail-mount-modes\scripts\Fix-RailPreferredNormals.ps1" -Path "C:\Users\Tellme\Documents\NavisworksTransport\模型\副本_人工_0331_133243.xml" + +脚本规则 + +- 只处理 pathType="Rail" 且 railMountMode="OverRail" 的路径 +- 只修复 railPreferredNormalY < 0 的路径 +- 修复方式为将 railPreferredNormalX / Y / Z 三个分量一起取反 +- 原文件会被直接覆盖,同时自动保留一个同名 .bak 备份文件 +#> + +param( + [Parameter(Mandatory = $true)] + [string]$Path, + + [switch]$Recurse +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Format-NormalValue { + param( + [Parameter(Mandatory = $true)] + [double]$Value + ) + + return $Value.ToString("0.###", [System.Globalization.CultureInfo]::InvariantCulture) +} + +function Test-ParseableDoubleText { + param( + [Parameter(Mandatory = $true)] + [string]$Text, + + [ref]$Value + ) + + return [double]::TryParse( + $Text, + [System.Globalization.NumberStyles]::Float -bor [System.Globalization.NumberStyles]::AllowLeadingSign, + [System.Globalization.CultureInfo]::InvariantCulture, + $Value) +} + +function Update-RouteNode { + param( + [Parameter(Mandatory = $true)] + [System.Xml.XmlElement]$RouteNode, + + [Parameter(Mandatory = $true)] + [string]$SourcePath + ) + + if ($RouteNode.GetAttribute("pathType") -ne "Rail") { + return $false + } + + if ($RouteNode.GetAttribute("railMountMode") -ne "OverRail") { + return $false + } + + $xText = $RouteNode.GetAttribute("railPreferredNormalX") + $yText = $RouteNode.GetAttribute("railPreferredNormalY") + $zText = $RouteNode.GetAttribute("railPreferredNormalZ") + + if ([string]::IsNullOrWhiteSpace($xText) -or + [string]::IsNullOrWhiteSpace($yText) -or + [string]::IsNullOrWhiteSpace($zText)) { + return $false + } + + $x = 0.0 + $y = 0.0 + $z = 0.0 + if (-not (Test-ParseableDoubleText -Text $xText -Value ([ref]$x)) -or + -not (Test-ParseableDoubleText -Text $yText -Value ([ref]$y)) -or + -not (Test-ParseableDoubleText -Text $zText -Value ([ref]$z))) { + throw "文件 '$SourcePath' 中存在无法解析的 railPreferredNormal 值。" + } + + if ($y -ge 0) { + return $false + } + + $RouteNode.SetAttribute("railPreferredNormalX", (Format-NormalValue -Value (-$x))) + $RouteNode.SetAttribute("railPreferredNormalY", (Format-NormalValue -Value (-$y))) + $RouteNode.SetAttribute("railPreferredNormalZ", (Format-NormalValue -Value (-$z))) + return $true +} + +function Save-XmlDocument { + param( + [Parameter(Mandatory = $true)] + [xml]$Document, + + [Parameter(Mandatory = $true)] + [string]$TargetPath + ) + + $settings = New-Object System.Xml.XmlWriterSettings + $settings.Indent = $true + $settings.IndentChars = " " + $settings.Encoding = New-Object System.Text.UTF8Encoding($false) + $settings.NewLineChars = "`r`n" + $settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace + + $writer = [System.Xml.XmlWriter]::Create($TargetPath, $settings) + try { + $Document.Save($writer) + } + finally { + $writer.Dispose() + } +} + +function Update-XmlFile { + param( + [Parameter(Mandatory = $true)] + [string]$FilePath + ) + + [xml]$xml = Get-Content -LiteralPath $FilePath -Raw + $routeNodes = $xml.SelectNodes("//*[local-name()='Route']") + if ($null -eq $routeNodes -or $routeNodes.Count -eq 0) { + return [pscustomobject]@{ + FilePath = $FilePath + Modified = $false + RouteCount = 0 + } + } + + $modifiedRouteCount = 0 + foreach ($routeNode in $routeNodes) { + if (Update-RouteNode -RouteNode $routeNode -SourcePath $FilePath) { + $modifiedRouteCount++ + } + } + + if ($modifiedRouteCount -gt 0) { + $backupPath = "$FilePath.bak" + if (-not (Test-Path -LiteralPath $backupPath)) { + Copy-Item -LiteralPath $FilePath -Destination $backupPath + } + + Save-XmlDocument -Document $xml -TargetPath $FilePath + } + + return [pscustomobject]@{ + FilePath = $FilePath + Modified = ($modifiedRouteCount -gt 0) + RouteCount = $modifiedRouteCount + } +} + +$resolvedPath = Resolve-Path -LiteralPath $Path +$item = Get-Item -LiteralPath $resolvedPath + +$files = @() +if ($item.PSIsContainer) { + $childParams = @{ + LiteralPath = $item.FullName + File = $true + } + if ($Recurse) { + $childParams["Recurse"] = $true + } + + $files = Get-ChildItem @childParams | Where-Object { $_.Extension -ieq ".xml" } +} +else { + $files = @($item) +} + +if ($files.Count -eq 0) { + Write-Host "未找到可处理的 XML 文件。" + exit 0 +} + +$results = @() +foreach ($file in $files) { + $results += Update-XmlFile -FilePath $file.FullName +} + +$modifiedFiles = @($results | Where-Object { $_.Modified }) +$modifiedRoutes = 0 +if ($modifiedFiles.Count -gt 0) { + $measure = $modifiedFiles | Measure-Object -Property RouteCount -Sum + if ($null -ne $measure -and $null -ne $measure.Sum) { + $modifiedRoutes = [int]$measure.Sum + } +} + +Write-Host ("扫描文件: {0}" -f $results.Count) +Write-Host ("修改文件: {0}" -f $modifiedFiles.Count) +Write-Host ("修复路径: {0}" -f $modifiedRoutes) + +foreach ($result in $modifiedFiles) { + Write-Host ("已修复: {0} (路径数={1})" -f $result.FilePath, $result.RouteCount) +} diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs index 76eb2d2..54ad5b6 100644 --- a/src/Core/Animation/PathAnimationManager.cs +++ b/src/Core/Animation/PathAnimationManager.cs @@ -770,7 +770,7 @@ namespace NavisworksTransport.Core.Animation else { startPosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, startPosition, previousPoint, nextPoint, objectHeight); - LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}"); + LogManager.Debug($"[移动到起点] Rail路径调整: 参考点=({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2}), 物体中心=({startPosition.X:F2},{startPosition.Y:F2},{startPosition.Z:F2}), 物体高度={objectHeight:F2}, 安装={_route.RailMountMode}, 参考面={(PathRoute.IsTopReferenceFaceForMountMode(_route.RailMountMode) ? "顶面参考点" : "底面参考点")}"); } if (TryCreateRailPathRotation( @@ -1278,7 +1278,7 @@ namespace NavisworksTransport.Core.Animation else { framePosition = RailPathPoseHelper.ResolveObjectSpaceCenterPosition(_route, framePosition, p1, p2, objectHeight); - LogManager.Debug($"[Rail路径] 调整物体位置: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}), 安装={_route.RailMountMode}, 对接={(PathRoute.IsTopPayloadAnchorForMountMode(_route.RailMountMode) ? "顶面对接" : "底面对接")}"); + LogManager.Debug($"[Rail路径] 调整物体位置: 参考点=({p1.X:F2},{p1.Y:F2},{p1.Z:F2})->({p2.X:F2},{p2.Y:F2},{p2.Z:F2}), 物体中心=({framePosition.X:F2},{framePosition.Y:F2},{framePosition.Z:F2}), 安装={_route.RailMountMode}, 参考面={(PathRoute.IsTopReferenceFaceForMountMode(_route.RailMountMode) ? "顶面参考点" : "底面参考点")}"); } } } diff --git a/src/Core/PathPlanningModels.cs b/src/Core/PathPlanningModels.cs index eabea26..2553df7 100644 --- a/src/Core/PathPlanningModels.cs +++ b/src/Core/PathPlanningModels.cs @@ -566,7 +566,7 @@ namespace NavisworksTransport [Serializable] public class PathRoute { - public static bool IsTopPayloadAnchorForMountMode(RailMountMode railMountMode) + public static bool IsTopReferenceFaceForMountMode(RailMountMode railMountMode) { return railMountMode == RailMountMode.UnderRail; } diff --git a/src/UI/WPF/ViewModels/PathEditingViewModel.cs b/src/UI/WPF/ViewModels/PathEditingViewModel.cs index c533ce7..e8abb29 100644 --- a/src/UI/WPF/ViewModels/PathEditingViewModel.cs +++ b/src/UI/WPF/ViewModels/PathEditingViewModel.cs @@ -2943,16 +2943,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels private double GetAssemblyAnchorDirection() { - return PathRoute.IsTopPayloadAnchorForMountMode(AssemblyMountMode) + return PathRoute.IsTopReferenceFaceForMountMode(AssemblyMountMode) ? 1.0 : -1.0; } private string GetAssemblyAnchorText() { - return PathRoute.IsTopPayloadAnchorForMountMode(AssemblyMountMode) - ? "顶面对接" - : "底面对接"; + return PathRoute.IsTopReferenceFaceForMountMode(AssemblyMountMode) + ? "顶面参考点" + : "底面参考点"; } private sealed class AssemblyReferenceLine diff --git a/src/Utils/CoordinateSystem/CanonicalRailOffsetResolver.cs b/src/Utils/CoordinateSystem/CanonicalRailOffsetResolver.cs index eab2073..23edf3b 100644 --- a/src/Utils/CoordinateSystem/CanonicalRailOffsetResolver.cs +++ b/src/Utils/CoordinateSystem/CanonicalRailOffsetResolver.cs @@ -15,7 +15,7 @@ namespace NavisworksTransport.Utils.CoordinateSystem // 路径点本身已经是安装参考点(终点锚点)语义。 // 动画跟踪中心只需要相对安装参考点沿轨道法向偏移半个高度, // 不能叠加任何“参考线 -> 锚点”旧语义偏移,否则会把终点/逐帧参考点重复平移。 - double halfHeightOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode) + double halfHeightOffset = PathRoute.IsTopReferenceFaceForMountMode(route.RailMountMode) ? -objectHeight / 2.0 : objectHeight / 2.0; return halfHeightOffset + route.RailNormalOffset; diff --git a/src/Utils/RailPathPoseHelper.cs b/src/Utils/RailPathPoseHelper.cs index 27b6fb7..39b7776 100644 --- a/src/Utils/RailPathPoseHelper.cs +++ b/src/Utils/RailPathPoseHelper.cs @@ -50,7 +50,7 @@ namespace NavisworksTransport.Utils return 0.0; } - if (PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)) + if (PathRoute.IsTopReferenceFaceForMountMode(route.RailMountMode)) { return -objectHeight; } @@ -60,8 +60,8 @@ namespace NavisworksTransport.Utils /// /// 计算通行空间中心相对于 Rail 参考点的 Z 偏移(模型单位)。 - /// 顶面对接时,通行空间中心位于对接点下方半高; - /// 底面对接时,通行空间中心位于对接点上方半高。 + /// 路径参考点位于构件顶面时,通行空间中心位于参考点下方半高; + /// 路径参考点位于构件底面时,通行空间中心位于参考点上方半高。 /// public static double GetObjectSpaceCenterZOffset(PathRoute route, double objectSpaceHeight) { @@ -70,7 +70,7 @@ namespace NavisworksTransport.Utils return 0.0; } - if (PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)) + if (PathRoute.IsTopReferenceFaceForMountMode(route.RailMountMode)) { return -objectSpaceHeight / 2.0; } @@ -605,7 +605,7 @@ namespace NavisworksTransport.Utils private static double GetBottomOffsetMagnitude(PathRoute route, double objectHeight) { - double baseOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode) + double baseOffset = PathRoute.IsTopReferenceFaceForMountMode(route.RailMountMode) ? -objectHeight : 0.0; return baseOffset + route.RailNormalOffset; @@ -613,7 +613,7 @@ namespace NavisworksTransport.Utils private static double GetObjectSpaceCenterOffsetMagnitude(PathRoute route, double objectSpaceHeight) { - double baseOffset = PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode) + double baseOffset = PathRoute.IsTopReferenceFaceForMountMode(route.RailMountMode) ? -objectSpaceHeight / 2.0 : objectSpaceHeight / 2.0; return baseOffset + route.RailNormalOffset;