feat: 贴合地面功能 + 通行空间尺寸准确化 + UI修复 (0.16.0)
贴合地面:点选物体表面自动贴地+搜索最小截面对齐路径 - AlignToGroundMinCrossSectionYawSearcher 搜索yaw - ComposeHostCorrection 扣除CAD yaw+pathYaw避免重复旋转 - ComputeGroundLiftForCorrection 实测底边算lift 通行空间尺寸:实际旋转物体到X轴方向测AABB再转回 - MeasureXAlignedAabbAndSetCache 自动调整和贴合地面共用 - ObjectRotationCorrection setter 清空缓存修复手动改角度不同步 UI:调整物体窗口按钮区改Grid布局真正居中 版本号 0.15.8 -> 0.16.0
This commit is contained in:
parent
3cb5d8d423
commit
105061bade
28
CHANGELOG.md
28
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
|
||||
|
||||
### ✨ 新功能
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
# 版本号
|
||||
|
||||
0.15.8
|
||||
0.16.0
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实际旋转物体到X轴方向测AABB,获取沿路径/垂直路径/高度的准确尺寸,再转回来。
|
||||
/// 路径斜向时世界AABB的X/Y/Z不对应语义尺寸,必须实际旋转物体用Navisworks API测量。
|
||||
/// 设置 _autoAabbSx=沿路径, _autoAabbSy=高度, _autoAabbSz=垂直路径。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 @@
|
||||
</Border>
|
||||
|
||||
<!-- 按钮栏 -->
|
||||
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="20,12">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Content="初始值"
|
||||
<Border Grid.Row="2" Background="#FFF8FBFF" BorderBrush="#FFD4E7FF" BorderThickness="0,1,0,0" Padding="10,12">
|
||||
<Grid HorizontalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0" Content="初始值"
|
||||
Click="OnResetClick"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"/>
|
||||
<Button Content="自动调整"
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
<Button Grid.Column="2" Content="自动调整"
|
||||
Click="OnAutoAdjustClick"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"/>
|
||||
<Button Content="贴合地面"
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
<Button Grid.Column="4" Content="贴合地面"
|
||||
Click="OnAlignToGroundClick"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"/>
|
||||
<Button Content="平移"
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
<Button Grid.Column="6" Content="平移"
|
||||
Click="OnTranslateClick"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"/>
|
||||
<Button Content="确认"
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
<Button Grid.Column="8" Content="确认"
|
||||
Click="OnConfirmClick"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"
|
||||
Margin="0,0,8,0"/>
|
||||
<Button Content="取消"
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
<Button Grid.Column="10" Content="取消"
|
||||
Click="OnCancelClick"
|
||||
Style="{StaticResource SecondaryButtonStyle}"
|
||||
Width="64"
|
||||
Height="32"/>
|
||||
</StackPanel>
|
||||
Width="64" Height="32" Margin="0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user