diff --git a/CHANGELOG.md b/CHANGELOG.md index d7295c5..4da18a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # NavisworksTransport 变更日志 +## [0.16.0] - 2026-06-26 + +### ✨ 新功能 + +- **贴合地面**:在"调整物体"窗口中新增"贴合地面"按钮,点选物体表面后自动旋转使该面贴地,并绕垂直轴搜索最小截面对齐路径方向。 + - 新增 `AlignToGroundMinCrossSectionYawSearcher`:在贴地旋转(固定俯仰/翻滚)基础上绕宿主 Up 搜索 yaw,使物体在路径方向的截面投影面积最小(粗搜 5° + 细搜 1°) + - 复用 `ObjectPassageProjectionOptimizer.ProjectExtent` 的 AABB 投影公式 + - 搜索完成后用 `ComposeHostCorrection` 将绝对贴地姿态转为相对于 baseline 的 host 欧拉修正,扣除 CAD 自带 yaw + pathYaw,避免确认/动画时重复旋转 + - `HostQuaternionToCanonical` 用相似变换 `R_canon = M⁻¹·R_host·M` 做坐标系转换 + - `ComputeGroundLiftForCorrection`:与自动调整同方法实测底边算 lift,测量后用新 correction+lift 完整重建使物体停留在贴地等价姿态 + +### 🐛 Bug 修复 + +- **自动调整/贴合地面后通行空间尺寸准确化**: + - 路径斜向时世界 AABB 的 X/Y/Z 不对应沿路径/垂直路径/高度,纯数学旋转 AABB 也会膨胀 + - 改为用 Navisworks API 实际旋转物体到 X 轴方向测 AABB,再转回来,获取准确的沿路径/垂直路径/高度尺寸 + - 提取 `MeasureXAlignedAabbAndSetCache` 辅助方法,自动调整和贴合地面共用 +- **自动调整后手动改角度尺寸不同步**:`ObjectRotationCorrection` setter 清空 `_autoAabbSx/Sy/Sz` 缓存,手动改角度走 `CalculateRotatedDimensions` 重新计算;自动调整/贴合地面直接设字段不经过 setter,缓存不受影响 +- **ObjectPassageProjectionOptimizer 初始候选缺失**:`OptimizeWithEvaluator` 初始化 `bestScore=Invalid` 从不评估 correction=Zero,当 baseline 已对齐路径时随机采样必然替换它。改为评估 Zero 作为初始候选 +- **ZUp 测试语义修正**:`LocalEulerRotationCorrection` 的 Y=up 轴、Z=non-up 轴,ZUp 下绕 Z(up) 转应传 YDegrees,4 个旧测试在 ZUp 下把 ZDegrees 当字面 Z 轴已修正 +- **AliasTreeTests 编译错误**:`SetAlias` 调用补全缺失的 alias 参数;4 个旧格式兼容测试改为期望抛 FormatException(`FromKey` 已按"不向后兼容"原则移除旧格式支持) + +### 🔧 改进 + +- **"调整物体"窗口按钮区布局**:改为 Grid 布局,6 个按钮用固定间距列隔开,所有按钮显式 `Margin="0"` 覆盖 Style 的 Margin,真正居中 +- **`CanonicalQuaternionToHostEulerCorrection` 加 hostType 参数重载**:去除全局 `CoordinateSystemManager` 依赖,便于单测 +- 新增 `AlignToGroundMinCrossSectionYawSearcherTests`(12 个单测)覆盖平地/倾斜/CAD歪斜/垂直退化/立方体/增量链闭环等场景 + ## [0.15.8] - 2026-06-09 ### ✨ 新功能 diff --git a/VERSION.md b/VERSION.md index 161757a..cf2ecfa 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,3 +1,3 @@ # 版本号 -0.15.8 +0.16.0 diff --git a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs index cb14d78..ec47b88 100644 --- a/src/UI/WPF/ViewModels/AnimationControlViewModel.cs +++ b/src/UI/WPF/ViewModels/AnimationControlViewModel.cs @@ -813,6 +813,12 @@ namespace NavisworksTransport.UI.WPF.ViewModels { if (SetProperty(ref _objectRotationCorrection, value)) { + // 手动改角度时清空自动调整/贴合地面的缓存尺寸, + // 让 UpdatePassageSpaceVisualization 走 CalculateRotatedDimensions 重新计算。 + // 贴地/自动调整直接设字段不经过 setter,不受影响。 + _autoAabbSx = null; + _autoAabbSy = null; + _autoAabbSz = null; // 角度修正值改变时,更新物体朝向和通行空间 UpdateObjectRotation(); } @@ -2219,8 +2225,15 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathAnimationManager?.ApplyObjectStartPlacementRequest(placementRequest); ObjectRotationCorrection = placementRequest.RotationCorrection; } - if (dialog.AutoAdjustAabbSizes.HasValue) - (_autoAabbSx, _autoAabbSy, _autoAabbSz) = dialog.AutoAdjustAabbSizes.Value; + if (isAutoAdjusted) + { + // 自动调整确认后用X轴对齐实测AABB设置尺寸, + // dialog.AutoAdjustAabbSizes 是世界AABB的X/Y/Z,路径斜向时不准。 + var aabbAdapter = CoordinateSystemManager.Instance.CreateHostAdapter(); + var aabbDoc = Autodesk.Navisworks.Api.Application.ActiveDocument; + var aabbItems = new ModelItemCollection { SelectedAnimatedObject }; + MeasureXAlignedAabbAndSetCache(SelectedAnimatedObject, aabbItems, aabbAdapter, aabbDoc); + } string liftSummary = enableGroundLift ? $", 上下偏移={_objectGroundLiftHeightInMeters:F3}m" @@ -2268,6 +2281,62 @@ namespace NavisworksTransport.UI.WPF.ViewModels ApplyAlignToGround(faceNormal); } + /// + /// 实际旋转物体到X轴方向测AABB,获取沿路径/垂直路径/高度的准确尺寸,再转回来。 + /// 路径斜向时世界AABB的X/Y/Z不对应语义尺寸,必须实际旋转物体用Navisworks API测量。 + /// 设置 _autoAabbSx=沿路径, _autoAabbSy=高度, _autoAabbSz=垂直路径。 + /// + private void MeasureXAlignedAabbAndSetCache(ModelItem item, ModelItemCollection modelItems, + HostCoordinateAdapter adapter, Autodesk.Navisworks.Api.Document doc) + { + if (CurrentPathRoute?.Points == null || CurrentPathRoute.Points.Count < 2) return; + + var pathStart = CurrentPathRoute.Points[0]; + var pathStartVec = new Vector3((float)pathStart.X, (float)pathStart.Y, (float)pathStart.Z); + Vector3 pathFwd = Vector3.Zero; + for (int i = 1; i < CurrentPathRoute.Points.Count; i++) + { + var p = CurrentPathRoute.Points[i]; + var c = new Vector3((float)p.X, (float)p.Y, (float)p.Z) - pathStartVec; + if (c.LengthSquared() > 1e-8f) { pathFwd = c; break; } + } + if (pathFwd.LengthSquared() <= 1e-8f) return; + + Vector3 hostUp = adapter.HostUpVector3; + Vector3 pathFwdH = pathFwd - hostUp * Vector3.Dot(pathFwd, hostUp); + if (pathFwdH.LengthSquared() <= 1e-8f) return; + pathFwdH = Vector3.Normalize(pathFwdH); + + Vector3 horizontalPerpX = Vector3.Normalize(Vector3.Cross(hostUp, Vector3.UnitX)); + float angleToX = (float)Math.Atan2( + Vector3.Dot(pathFwdH, horizontalPerpX), + Vector3.Dot(pathFwdH, Vector3.UnitX)); + + // 实际旋转物体到X轴方向,测AABB,再转回来 + Quaternion yawToX = Quaternion.CreateFromAxisAngle(hostUp, -angleToX); + var rotateToX = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor(); + rotateToX.Rotation = new Rotation3D(yawToX.X, yawToX.Y, yawToX.Z, yawToX.W); + doc.Models.OverridePermanentTransform(modelItems, rotateToX.Combine(), false); + + BoundingBox3D xAlignedBounds = item.BoundingBox(); + int upIndex = adapter.HostUpAxisIndex; + _autoAabbSx = xAlignedBounds.Max.X - xAlignedBounds.Min.X; + _autoAabbSy = upIndex == 1 ? xAlignedBounds.Max.Y - xAlignedBounds.Min.Y : xAlignedBounds.Max.Z - xAlignedBounds.Min.Z; + _autoAabbSz = upIndex == 1 ? xAlignedBounds.Max.Z - xAlignedBounds.Min.Z : xAlignedBounds.Max.Y - xAlignedBounds.Min.Y; + + // 转回来 + Quaternion yawBack = Quaternion.CreateFromAxisAngle(hostUp, angleToX); + var rotateBack = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor(); + rotateBack.Rotation = new Rotation3D(yawBack.X, yawBack.Y, yawBack.Z, yawBack.W); + doc.Models.OverridePermanentTransform(modelItems, rotateBack.Combine(), false); + + double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(); + LogManager.Debug( + $"[X轴对齐AABB] 沿路径={_autoAabbSx * unitsToMeters:F2}m, " + + $"垂直路径={_autoAabbSz * unitsToMeters:F2}m, " + + $"高度={_autoAabbSy * unitsToMeters:F2}m, angleToX={angleToX * 180.0 / Math.PI:F1}°"); + } + private void ApplyAlignToGround(Vector3 faceNormal) { var item = UseVirtualObject @@ -2418,57 +2487,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels _pathAnimationManager.SetObjectStartVerticalLiftDirect(_objectGroundLiftHeightInMeters); // 物体已在最终贴地姿态(ComputeGroundLiftForCorrection 重建完成)。 - // 路径可能斜向,直接测ABB投影偏大。实际旋转物体到X轴方向测AABB, - // 再转回来——必须用Navisworks API实际旋转才能获取准确AABB。 - if (hasSearchedExtents && CurrentPathRoute.Points.Count >= 2) + // 路径可能斜向,实际旋转物体到X轴方向测AABB获取准确语义尺寸。 + if (hasSearchedExtents) { - var pathStart = CurrentPathRoute.Points[0]; - var pathStartVec = new Vector3((float)pathStart.X, (float)pathStart.Y, (float)pathStart.Z); - Vector3 pathFwd = Vector3.Zero; - for (int i = 1; i < CurrentPathRoute.Points.Count; i++) - { - var p = CurrentPathRoute.Points[i]; - var c = new Vector3((float)p.X, (float)p.Y, (float)p.Z) - pathStartVec; - if (c.LengthSquared() > 1e-8f) { pathFwd = c; break; } - } - if (pathFwd.LengthSquared() > 1e-8f) - { - Vector3 hostUp = adapter.HostUpVector3; - Vector3 pathFwdH = pathFwd - hostUp * Vector3.Dot(pathFwd, hostUp); - if (pathFwdH.LengthSquared() > 1e-8f) - { - pathFwdH = Vector3.Normalize(pathFwdH); - Vector3 horizontalPerpX = Vector3.Normalize(Vector3.Cross(hostUp, Vector3.UnitX)); - float angleToX = (float)Math.Atan2( - Vector3.Dot(pathFwdH, horizontalPerpX), - Vector3.Dot(pathFwdH, Vector3.UnitX)); - - // 实际旋转物体到X轴方向,测AABB,再转回来 - // angleToX 是路径相对X轴的夹角,需要转 -angleToX 让路径对齐X - Quaternion yawToX = Quaternion.CreateFromAxisAngle(hostUp, -angleToX); - var rotateToX = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor(); - rotateToX.Rotation = new Rotation3D(yawToX.X, yawToX.Y, yawToX.Z, yawToX.W); - doc.Models.OverridePermanentTransform(modelItems, rotateToX.Combine(), false); - - BoundingBox3D xAlignedBounds = item.BoundingBox(); - int upIndex = adapter.HostUpAxisIndex; - _autoAabbSx = xAlignedBounds.Max.X - xAlignedBounds.Min.X; - _autoAabbSy = upIndex == 1 ? xAlignedBounds.Max.Y - xAlignedBounds.Min.Y : xAlignedBounds.Max.Z - xAlignedBounds.Min.Z; - _autoAabbSz = upIndex == 1 ? xAlignedBounds.Max.Z - xAlignedBounds.Min.Z : xAlignedBounds.Max.Y - xAlignedBounds.Min.Y; - - // 转回来 - Quaternion yawBack = Quaternion.CreateFromAxisAngle(hostUp, angleToX); - var rotateBack = Transform3D.CreateTranslation(new Vector3D(0, 0, 0)).Factor(); - rotateBack.Rotation = new Rotation3D(yawBack.X, yawBack.Y, yawBack.Z, yawBack.W); - doc.Models.OverridePermanentTransform(modelItems, rotateBack.Combine(), false); - - double unitsToMeters = UnitsConverter.GetUnitsToMetersConversionFactor(); - LogManager.Debug( - $"[贴合地面] X轴对齐AABB(实测): 沿路径={_autoAabbSx * unitsToMeters:F2}m, " + - $"垂直路径={_autoAabbSz * unitsToMeters:F2}m, " + - $"高度={_autoAabbSy * unitsToMeters:F2}m, angleToX={angleToX * 180.0 / Math.PI:F1}°"); - } - } + MeasureXAlignedAabbAndSetCache(item, modelItems, adapter, doc); } } diff --git a/src/UI/WPF/Views/EditRotationWindow.xaml b/src/UI/WPF/Views/EditRotationWindow.xaml index e5ed2a4..a8ecdaa 100644 --- a/src/UI/WPF/Views/EditRotationWindow.xaml +++ b/src/UI/WPF/Views/EditRotationWindow.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converters="clr-namespace:NavisworksTransport.UI.WPF.Converters" - Title="调整物体" Height="430" Width="470" + Title="调整物体" Height="430" Width="480" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" ShowInTaskbar="False" @@ -190,44 +190,46 @@ - - -