重构视角控制辅助类,优化聚焦方法;记录相关API使用方法

This commit is contained in:
tian 2026-02-10 15:04:46 +08:00
parent 78158c1cb1
commit c28f9762bc
2 changed files with 517 additions and 68 deletions

View File

@ -3678,3 +3678,430 @@ public static void SaveAndRestoreViewpointExample()
3. **理解每个属性的作用**:不要混淆"视图方向"和"向上向量"
4. **正确的设置顺序很重要**Position → Rotation → AlignDirection → WorldUpVector
5. **使用 CreateCopy() 而不是 new Viewpoint()**:保留当前视角的其他属性
## 13. 视角控制ViewpointAPI
基于 Focus 功能实现的真实API用法总结ViewpointHelper开发经验
### 13.1 核心API概念
**关键发现**`ModelItem.Transform` **不反映** override 后的位置变化,获取实际位置应使用 `BoundingBox().Center`
#### 视角控制的核心类
| 类/方法 | 用途 | 关键特性 |
|---------|------|---------|
| `Viewpoint` | 视角状态对象 | 可修改 Position、Rotation、WorldUpVector |
| `doc.CurrentViewpoint.Value` | 获取当前视角 | 需要用 `CreateCopy()` 创建副本修改 |
| `AlignDirection(Vector3D)` | 旋转相机指向方向 | 使用最短路径旋转 |
| `AlignUp(Vector3D)` | 旋转相机对齐向上向量 | 围绕视线方向旋转 |
| `ZoomBox(BoundingBox3D)` | 强制适应视图 | **会覆盖**相机距离设置 |
| `Rotation3D()` | 清除旋转状态 | 用于创建标准视角(俯视图等) |
### 13.2 聚焦到模型对象的两种方法
#### 方法一:距离计算法(精确控制物体占屏比例)
适用于**聚焦到单个对象**,精确控制物体在视图中的占比。
```csharp
// ✅ 正确:计算相机距离实现目标占比
public void FocusOnModelItem(ModelItem item, double viewAngleDegrees = 45.0,
double targetViewRatio = 0.25)
{
Document doc = Application.ActiveDocument;
// 1. 获取对象包围盒
var boundingBox = item.BoundingBox();
Point3D targetCenter = boundingBox.Center;
double targetSize = Math.Max(boundingBox.Size.X, boundingBox.Size.Y);
// 2. 计算相机距离使用FOV公式
double fovRadians = viewAngleDegrees * Math.PI / 180.0;
double cameraDistance = (targetSize / 2.0) / Math.Tan(fovRadians / 2.0) / targetViewRatio;
// 3. 计算相机位置(使用模型标准视角向量)
Vector3D viewDirection = doc.FrontRightTopViewVector; // 标准45度视角
Vector3D upVector = doc.FrontRightTopViewUpVector;
Point3D cameraPosition = new Point3D(
targetCenter.X - viewDirection.X * cameraDistance,
targetCenter.Y - viewDirection.Y * cameraDistance,
targetCenter.Z - viewDirection.Z * cameraDistance
);
// 4. 应用视角
ApplyViewpoint(cameraPosition, targetCenter, upVector, useAlignDirection: true);
}
// 通用视角应用方法
private static void ApplyViewpoint(Point3D cameraPosition, Point3D targetPoint,
Vector3D upVector, bool useAlignDirection)
{
Document doc = Application.ActiveDocument;
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
newViewpoint.Position = cameraPosition;
if (useAlignDirection)
{
// 使用AlignDirection/AlignUp精确控制方向
Vector3D lookDirection = new Vector3D(
targetPoint.X - cameraPosition.X,
targetPoint.Y - cameraPosition.Y,
targetPoint.Z - cameraPosition.Z);
lookDirection.Normalize();
newViewpoint.AlignDirection(lookDirection);
newViewpoint.AlignUp(upVector);
}
else
{
// 使用Rotation直接设置适用于标准视角
newViewpoint.Rotation = new Rotation3D();
newViewpoint.WorldUpVector = new UnitVector3D(upVector.X, upVector.Y, upVector.Z);
}
doc.CurrentViewpoint.CopyFrom(newViewpoint);
}
```
**关键公式**
```
cameraDistance = (targetSize / 2) / tan(FOV / 2) / targetViewRatio
示例:
- targetSize = 2米, FOV = 45°, ratio = 0.25 (25%占屏)
- cameraDistance = 1.0 / tan(22.5°) / 0.25 ≈ 9.66 米
```
#### 方法二ZoomBox法确保完整可见
适用于**显示路径、多个对象或大型物体**,确保完全可见。
```csharp
// ✅ 正确使用ZoomBox确保完整可见性
public void FocusOnBoundingBox(BoundingBox3D bounds, double marginRatio = 0.1)
{
Document doc = Application.ActiveDocument;
// 1. 计算扩展后的包围盒(添加边距)
Vector3D size = bounds.Size;
Vector3D margin = new Vector3D(
size.X * marginRatio,
size.Y * marginRatio,
size.Z * marginRatio
);
BoundingBox3D expandedBox = new BoundingBox3D(
new Point3D(bounds.Min.X - margin.X, bounds.Min.Y - margin.Y, bounds.Min.Z - margin.Z),
new Point3D(bounds.Max.X + margin.X, bounds.Max.Y + margin.Y, bounds.Max.Z + margin.Z)
);
// 2. 设置相机位置(估算)
Point3D targetCenter = expandedBox.Center;
Vector3D viewDirection = doc.FrontRightTopViewVector;
double estimatedDistance = Math.Max(size.X, size.Y) * 2.0;
Point3D cameraPosition = new Point3D(
targetCenter.X - viewDirection.X * estimatedDistance,
targetCenter.Y - viewDirection.Y * estimatedDistance,
targetCenter.Z - viewDirection.Z * estimatedDistance
);
// 3. 应用视角设置Rotation和UpVector
ApplyViewpoint(cameraPosition, targetCenter, doc.FrontRightTopViewUpVector,
useAlignDirection: false);
// 4. 关键使用ZoomBox强制适应视图
doc.ActiveView.ZoomBox(expandedBox); // ZoomBox会覆盖相机距离
}
```
### 13.3 模型标准视角向量
**重要发现**Navisworks 提供模型特定的标准视角向量。
```csharp
// ✅ 获取模型的标准视角方向45度俯视
Vector3D standardViewDirection = doc.FrontRightTopViewVector;
Vector3D standardUpVector = doc.FrontRightTopViewUpVector;
// 其他可用视角向量:
// - FrontViewVector / FrontViewUpVector // 正视图
// - TopViewVector / TopViewUpVector // 俯视图
// - RightViewVector / RightViewUpVector // 右视图
// - FrontRightTopViewVector / ... // 45度俯视最常用
```
**使用场景**
- **Focus功能**:使用 `FrontRightTopViewVector` 保持与UI一致的标准视角
- **正视图截图**:使用 `FrontViewVector` 获取正交投影效果
- **俯视图分析**:使用 `TopViewVector` 进行平面分析
### 13.4 相机距离计算方法对比
| 方法 | 适用场景 | 优点 | 缺点 |
|------|---------|------|------|
| FOV公式法 | 单个对象聚焦 | 精确控制占屏比例 | 需要计算目标尺寸 |
| ZoomBox法 | 路径/多对象显示 | 确保完全可见,自动计算距离 | 覆盖相机位置设置 |
| 固定距离 | 快速预览 | 简单快速 | 对象大小不一时效果差 |
**FOV计算代码详解**
```csharp
// 计算相机距离的完整公式
private static double CalculateCameraDistance(double targetSize, double fovDegrees,
double targetRatio)
{
// 1. 转换FOV为弧度
double fovRadians = fovDegrees * Math.PI / 180.0;
// 2. 计算半角正切值
double halfFovTan = Math.Tan(fovRadians / 2.0);
// 3. 计算目标在FOV中占据一半时的距离
// targetSize/2 是目标半宽halfFovTan 是半角正切
double distanceForFullTarget = (targetSize / 2.0) / halfFovTan;
// 4. 根据目标占屏比例调整距离
// ratio=0.5表示目标占视图50%,距离减半
double finalDistance = distanceForFullTarget / targetRatio;
return finalDistance;
}
// 使用示例:
// targetSize=3m, FOV=45°, ratio=0.25 → distance ≈ 14.5m
// 这意味着相机距离目标14.5米时3米宽的物体将占据视图的25%
```
### 13.5 常见错误和解决方案
#### 错误1混用距离计算和ZoomBox
```csharp
// ❌ 错误先设置相机距离再用ZoomBox覆盖
var viewpoint = doc.CurrentViewpoint.Value.CreateCopy();
viewpoint.Position = calculatedPosition; // 设置位置
doc.CurrentViewpoint.CopyFrom(viewpoint);
doc.ActiveView.ZoomBox(bounds); // ZoomBox会覆盖之前的距离设置
// ✅ 正确:二选一
// 方案A只用距离计算不调用ZoomBox
viewpoint.Position = calculatedPosition;
doc.CurrentViewpoint.CopyFrom(viewpoint);
// 方案B用ZoomBox距离会被自动计算
doc.ActiveView.ZoomBox(bounds);
```
#### 错误2忽略Rotation3D的影响
```csharp
// ❌ 错误:不清除旋转直接设置视角
var viewpoint = doc.CurrentViewpoint.Value.CreateCopy();
viewpoint.Position = newPosition;
viewpoint.AlignDirection(newDirection); // 可能受原有旋转影响
doc.CurrentViewpoint.CopyFrom(viewpoint);
// ✅ 正确创建新Rotation3D清除旋转状态
var viewpoint = doc.CurrentViewpoint.Value.CreateCopy();
viewpoint.Rotation = new Rotation3D(); // 清除旋转
viewpoint.AlignDirection(newDirection); // 从标准状态开始
viewpoint.AlignUp(newUpVector);
doc.CurrentViewpoint.CopyFrom(viewpoint);
```
#### 错误3使用错误的向上向量
```csharp
// ❌ 错误假设世界Z轴是向上向量
Vector3D upVector = new Vector3D(0, 0, 1);
// ✅ 正确:使用模型的标准向上向量
Vector3D upVector = doc.FrontRightTopViewUpVector;
// 或者根据需要计算正确的向上向量
// 例如俯视图中Y轴可能是向上
Vector3D topViewUp = new Vector3D(0, 1, 0);
```
### 13.6 实用工具代码
```csharp
/// <summary>
/// 视角控制工具类 - 基于实际项目经验
/// </summary>
public static class ViewpointHelper
{
/// <summary>
/// 聚焦到单个模型对象(精确控制占屏比例)
/// </summary>
public static void FocusOnModelItem(ModelItem item, double viewAngleDegrees = 45.0,
double targetViewRatio = 0.25)
{
if (item == null) return;
var bounds = item.BoundingBox();
Point3D targetCenter = bounds.Center;
double targetSize = Math.Max(bounds.Size.X, bounds.Size.Y);
// 计算相机距离
double cameraDistance = CalculateCameraDistance(
targetSize, viewAngleDegrees, targetViewRatio);
// 使用模型标准视角
Document doc = Application.ActiveDocument;
Vector3D viewDir = doc.FrontRightTopViewVector;
Vector3D upVector = doc.FrontRightTopViewUpVector;
Point3D cameraPos = new Point3D(
targetCenter.X - viewDir.X * cameraDistance,
targetCenter.Y - viewDir.Y * cameraDistance,
targetCenter.Z - viewDir.Z * cameraDistance
);
ApplyViewpoint(cameraPos, targetCenter, upVector, useAlignDirection: true);
}
/// <summary>
/// 聚焦到碰撞点(两个对象的中间位置)
/// </summary>
public static void FocusOnCollision(ModelItem item1, ModelItem item2,
double viewAngleDegrees = 45.0)
{
var bounds1 = item1.BoundingBox();
var bounds2 = item2.BoundingBox();
// 计算碰撞中心(两个包围盒最近的点)
Point3D collisionCenter = CalculateCollisionCenter(bounds1, bounds2);
double maxSize = Math.Max(
Math.Max(bounds1.Size.X, bounds1.Size.Y),
Math.Max(bounds2.Size.X, bounds2.Size.Y)
);
double cameraDistance = CalculateCameraDistance(
maxSize, viewAngleDegrees, 0.35);
Document doc = Application.ActiveDocument;
Vector3D viewDir = doc.FrontRightTopViewVector;
Vector3D upVector = doc.FrontRightTopViewUpVector;
Point3D cameraPos = new Point3D(
collisionCenter.X - viewDir.X * cameraDistance,
collisionCenter.Y - viewDir.Y * cameraDistance,
collisionCenter.Z - viewDir.Z * cameraDistance
);
ApplyViewpoint(cameraPos, collisionCenter, upVector, useAlignDirection: true);
}
/// <summary>
/// 聚焦到指定点和大小的区域ZoomBox法
/// </summary>
public static void FocusOnPosition(Point3D center, double size,
double marginRatio = 0.1)
{
Document doc = Application.ActiveDocument;
// 创建目标包围盒
double halfSize = size / 2.0;
double margin = size * marginRatio;
BoundingBox3D targetBox = new BoundingBox3D(
new Point3D(center.X - halfSize - margin, center.Y - halfSize - margin,
center.Z - halfSize - margin),
new Point3D(center.X + halfSize + margin, center.Y + halfSize + margin,
center.Z + halfSize + margin)
);
// 先设置视角方向
Vector3D viewDir = doc.FrontRightTopViewVector;
Vector3D upVector = doc.FrontRightTopViewUpVector;
double distance = size * 2.0;
Point3D cameraPos = new Point3D(
center.X - viewDir.X * distance,
center.Y - viewDir.Y * distance,
center.Z - viewDir.Z * distance
);
ApplyViewpoint(cameraPos, center, upVector, useAlignDirection: false);
// 使用ZoomBox确保可见
doc.ActiveView.ZoomBox(targetBox);
}
// 私有辅助方法...
private static double CalculateCameraDistance(double targetSize, double fovDegrees,
double ratio)
{
double fovRadians = fovDegrees * Math.PI / 180.0;
return (targetSize / 2.0) / Math.Tan(fovRadians / 2.0) / ratio;
}
private static void ApplyViewpoint(Point3D cameraPos, Point3D target,
Vector3D upVector, bool useAlignDirection)
{
Document doc = Application.ActiveDocument;
Viewpoint vp = doc.CurrentViewpoint.Value.CreateCopy();
vp.Position = cameraPos;
if (useAlignDirection)
{
Vector3D lookDir = new Vector3D(
target.X - cameraPos.X,
target.Y - cameraPos.Y,
target.Z - cameraPos.Z);
lookDir.Normalize();
vp.AlignDirection(lookDir);
vp.AlignUp(upVector);
}
else
{
vp.Rotation = new Rotation3D();
vp.WorldUpVector = new UnitVector3D(upVector.X, upVector.Y, upVector.Z);
}
doc.CurrentViewpoint.CopyFrom(vp);
}
}
```
### 13.7 视角控制最佳实践
1. **选择合适的聚焦方法**
- 单个对象精确聚焦 → 距离计算法
- 路径或区域显示 → ZoomBox法
- 碰撞点查看 → 碰撞中心计算 + 距离法
2. **使用模型标准视角向量**
- 不要硬编码向量值
- 使用 `doc.FrontRightTopViewVector` 等API
3. **理解ZoomBox的副作用**
- ZoomBox会覆盖相机距离设置
- ZoomBox后不要再调整Position来微调距离
4. **正确处理Rotation3D**
- 创建新视角时,先用 `new Rotation3D()` 清除旋转
- 再使用 `AlignDirection` 设置精确方向
5. **保存和恢复视角**
```csharp
// 保存
Viewpoint savedView = doc.CurrentViewpoint.Value.CreateCopy();
// ... 修改视角 ...
// 恢复
doc.CurrentViewpoint.CopyFrom(savedView);
```
---
**文档版本**2026.2
**最后更新**:基于 ViewpointHelper 重构经验(聚焦功能实现)
**适用版本**Navisworks Manage 2026

View File

@ -104,31 +104,14 @@ namespace NavisworksTransport.Utils
double cameraDistanceMeters = UnitsConverter.ConvertToMeters(cameraDistance);
LogManager.Info($"相机设置: 位置=({cameraPosition.X:F2}, {cameraPosition.Y:F2}, {cameraPosition.Z:F2}), 焦点=({focusCenter.X:F2}, {focusCenter.Y:F2}, {focusCenter.Z:F2}), 相机距离={cameraDistanceMeters:F2}米");
// 3. 创建当前视角的副本(关键:使用 CreateCopy 而不是 new Viewpoint()
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
// 设置标准俯视图的相机配置
// 相机位置:在目标正上方
newViewpoint.Position = cameraPosition;
// 直接设置 Rotation 为无旋转(单位四元数),实现标准的俯视图
// 这样可以彻底清除任何旋转,使视图与世界坐标轴对齐
newViewpoint.Rotation = new Rotation3D();
// 设置向上向量Y 轴向上(标准俯视图的向上方向)
newViewpoint.WorldUpVector = new UnitVector3D(0, 1, 0);
// 4. 扩展路径包围盒,避免边缘贴得太近
// 3. 计算扩展边距
double boxWidth = viewBoundingBox.Max.X - viewBoundingBox.Min.X;
double expansionFactor = VIEW_BOUNDING_BOX_EXPANSION_FACTOR + (EXPANSION_METERS / baseDimension);
double expansionMargin = boxWidth * (expansionFactor - 1) / 2;
BoundingBox3D expandedViewBoundingBox = BoundingBoxGeometryUtils.ExpandBoundingBox(viewBoundingBox, expansionMargin);
// 5. 使用 ZoomBox 调整视野范围(使用扩展后的视野包围盒,确保整个路径都在视野内)
newViewpoint.ZoomBox(expandedViewBoundingBox);
// 6. 应用视角到文档
doc.CurrentViewpoint.CopyFrom(newViewpoint);
// 4. 应用视角(使用通用方法,带 ZoomBox
Vector3D upVector = new Vector3D(0, 1, 0); // Y轴向上
ApplyViewpointWithZoomBox(cameraPosition, focusCenter, upVector, viewBoundingBox, expansionMargin);
LogManager.Info($"视角已调整完成: 俯视图(已摆正), 基准尺寸={baseDimension:F2}米, 相机距离={cameraDistanceMeters:F2}米, FOV=自动, 包围盒扩展系数={VIEW_BOUNDING_BOX_EXPANSION_FACTOR:F1}, 扩展边距={expansionMargin:F2}米");
}
@ -277,6 +260,88 @@ namespace NavisworksTransport.Utils
System.Threading.Thread.Sleep(VIEW_UPDATE_DELAY_MS);
}
#region
/// <summary>
/// 应用视角设置(核心通用方法)
/// </summary>
/// <param name="cameraPosition">相机位置</param>
/// <param name="targetPoint">目标点(相机看向的位置)</param>
/// <param name="upVector">相机上方向</param>
/// <param name="useAlignDirection">是否使用 AlignDirection/AlignUptrue还是直接设置 Rotationfalse</param>
private static void ApplyViewpoint(Point3D cameraPosition, Point3D targetPoint, Vector3D upVector, bool useAlignDirection = true)
{
Document doc = Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
throw new InvalidOperationException("没有活动的文档");
}
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
newViewpoint.Position = cameraPosition;
if (useAlignDirection)
{
// 方法1使用 AlignDirection + AlignUp推荐用于聚焦对象
Vector3D lookDirection = new Vector3D(
targetPoint.X - cameraPosition.X,
targetPoint.Y - cameraPosition.Y,
targetPoint.Z - cameraPosition.Z
);
lookDirection.Normalize();
newViewpoint.AlignDirection(lookDirection);
newViewpoint.AlignUp(upVector);
}
else
{
// 方法2直接设置 Rotation 和 WorldUpVector用于标准俯视图等
newViewpoint.Rotation = new Rotation3D();
newViewpoint.WorldUpVector = new UnitVector3D(upVector.X, upVector.Y, upVector.Z);
}
doc.CurrentViewpoint.CopyFrom(newViewpoint);
}
/// <summary>
/// 应用视角并缩放视野(使用 ZoomBox
/// </summary>
/// <param name="cameraPosition">相机位置</param>
/// <param name="targetPoint">目标点</param>
/// <param name="upVector">相机上方向</param>
/// <param name="viewBoundingBox">视野包围盒(用于 ZoomBox</param>
/// <param name="expansionMargin">包围盒扩展边距</param>
private static void ApplyViewpointWithZoomBox(Point3D cameraPosition, Point3D targetPoint, Vector3D upVector, BoundingBox3D viewBoundingBox, double expansionMargin = 0)
{
Document doc = Application.ActiveDocument;
if (doc == null || doc.IsClear)
{
throw new InvalidOperationException("没有活动的文档");
}
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
newViewpoint.Position = cameraPosition;
// 使用 AlignDirection + AlignUp
Vector3D lookDirection = new Vector3D(
targetPoint.X - cameraPosition.X,
targetPoint.Y - cameraPosition.Y,
targetPoint.Z - cameraPosition.Z
);
lookDirection.Normalize();
newViewpoint.AlignDirection(lookDirection);
newViewpoint.AlignUp(upVector);
// 使用 ZoomBox 调整视野
BoundingBox3D expandedBox = expansionMargin > 0
? BoundingBoxGeometryUtils.ExpandBoundingBox(viewBoundingBox, expansionMargin)
: viewBoundingBox;
newViewpoint.ZoomBox(expandedBox);
doc.CurrentViewpoint.CopyFrom(newViewpoint);
}
#endregion
#region
/// <summary>
@ -326,31 +391,9 @@ namespace NavisworksTransport.Utils
// 3. 计算相机位置(使用模型标准视角方向)
Point3D cameraPosition = CalculateCameraPosition(center, maxDimension, viewAngleDegrees, targetViewRatio);
// 4. 创建视角并设置相机
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
newViewpoint.Position = cameraPosition;
// 获取模型的标准视角向量
Vector3D viewDirection = doc.FrontRightTopViewVector;
viewDirection.Normalize();
// 使用 AlignDirection 设置相机朝向(看向目标方向)
// 方向是从相机指向目标
Vector3D lookDirection = new Vector3D(
center.X - cameraPosition.X,
center.Y - cameraPosition.Y,
center.Z - cameraPosition.Z
);
lookDirection.Normalize();
newViewpoint.AlignDirection(lookDirection);
// 使用 AlignUp 设置相机的上方向(基于模型的标准上方向)
// 4. 应用视角(使用通用方法)
Vector3D upVector = doc.FrontRightTopViewUpVector;
upVector.Normalize();
newViewpoint.AlignUp(upVector);
// 5. 应用视角(不使用 ZoomBox让相机距离决定视野比例
doc.CurrentViewpoint.CopyFrom(newViewpoint);
ApplyViewpoint(cameraPosition, center, upVector, useAlignDirection: true);
LogManager.Info($"视角已调整: 标准前右上视角, 目标占据视图{targetViewRatio:P0}");
}
@ -446,30 +489,9 @@ namespace NavisworksTransport.Utils
// 计算相机位置(使用模型标准视角方向)
Point3D cameraPosition = CalculateCameraPosition(center, targetSize, viewAngleDegrees, 0.25);
// 创建视角并设置相机
Viewpoint newViewpoint = doc.CurrentViewpoint.Value.CreateCopy();
newViewpoint.Position = cameraPosition;
// 获取模型的标准视角向量
Vector3D viewDirection = doc.FrontRightTopViewVector;
viewDirection.Normalize();
// 使用 AlignDirection 设置相机朝向
Vector3D lookDirection = new Vector3D(
center.X - cameraPosition.X,
center.Y - cameraPosition.Y,
center.Z - cameraPosition.Z
);
lookDirection.Normalize();
newViewpoint.AlignDirection(lookDirection);
// 使用 AlignUp 设置相机的上方向
// 应用视角(使用通用方法)
Vector3D upVector = doc.FrontRightTopViewUpVector;
upVector.Normalize();
newViewpoint.AlignUp(upVector);
// 应用视角(不使用 ZoomBox让相机距离决定视野比例
doc.CurrentViewpoint.CopyFrom(newViewpoint);
ApplyViewpoint(cameraPosition, center, upVector, useAlignDirection: true);
}
catch (Exception ex)
{