新增碰撞时运动物体位置和朝向记录功能,在碰撞报告中可以查看两者碰撞情况

This commit is contained in:
tian 2026-02-10 18:39:18 +08:00
parent cf5aed422b
commit ee33e43048
7 changed files with 398 additions and 16 deletions

View File

@ -4,7 +4,7 @@
### [2026/2/8]
1. [.] (功能)增加预计算结果分析和排除建议
1. [x] (功能)增加预计算结果分析和排除建议
2. [ ] (优化)考虑在碰撞报告中,给每一个碰撞元素自动建立截图
3. [ ] (优化)考虑是否给截图按碰撞记录组织成子文件夹
4. [ ] 优化研究如何利用ClashDetective的多线程支持打开多线程

View File

@ -1100,6 +1100,7 @@ namespace NavisworksTransport.Core.Animation
framePosition.Z + (virtualBoundingBox.Max.Z - virtualBoundingBox.Min.Z) / 2
),
Item2Position = GetObjectPosition(collider),
Item1YawRadians = yawRadians, // 记录运动物体朝向
HasPositionInfo = true
};
@ -1114,11 +1115,15 @@ namespace NavisworksTransport.Core.Animation
// 统计碰撞信息
var framesWithCollision = _animationFrames.Count(f => f.HasCollision);
var totalCollisions = _animationFrames.Sum(f => f.Collisions.Count);
// 检查碰撞结果是否包含位置信息
var collisionsWithPosition = _allCollisionResults.Count(c => c.HasPositionInfo && c.Item1Position != null);
LogManager.Info($"=== 预计算完成 ===");
LogManager.Info($"总帧数: {_animationFrames.Count}");
LogManager.Info($"包含碰撞的帧: {framesWithCollision}");
LogManager.Info($"总碰撞次数: {totalCollisions}");
LogManager.Info($"记录的碰撞结果总数: {_allCollisionResults.Count} 个");
LogManager.Info($"包含位置信息的碰撞: {collisionsWithPosition}/{_allCollisionResults.Count}");
// 🔥 Human-in-the-Loop: 记录排除统计
if (_excludedObjects.Count > 0)

View File

@ -140,6 +140,55 @@ namespace NavisworksTransport
private Dictionary<string, List<CollisionResult>> _clashDetectiveResultsCache = new Dictionary<string, List<CollisionResult>>();
private readonly object _clashResultsCacheLock = new object();
// 🔥 新增记录ClashDetective确认碰撞时的位置信息
// key: "Item1Id|Item2Id", value: 确认碰撞时的位置信息
private Dictionary<string, CollisionPositionInfo> _confirmedCollisionPositions = new Dictionary<string, CollisionPositionInfo>();
/// <summary>
/// 碰撞位置信息用于记录ClashDetective确认时的位置
/// </summary>
private class CollisionPositionInfo
{
public Point3D Item1Position { get; set; }
public Point3D Item2Position { get; set; }
public double Item1YawRadians { get; set; }
public bool HasPositionInfo { get; set; }
}
/// <summary>
/// 生成碰撞对象的唯一key使用PathId不使用InstanceGuid因为多数元素为0
/// </summary>
private string GetCollisionObjectKey(ModelItem item1, ModelItem item2)
{
try
{
var doc = Application.ActiveDocument;
string key1 = "null";
string key2 = "null";
if (item1 != null && doc != null)
{
var pathId = doc.Models.CreatePathId(item1);
key1 = $"{pathId.ModelIndex}:{pathId.PathId}";
}
if (item2 != null && doc != null)
{
var pathId = doc.Models.CreatePathId(item2);
key2 = $"{pathId.ModelIndex}:{pathId.PathId}";
}
return $"{key1}|{key2}";
}
catch
{
// 回退到DisplayName可能不唯一但总比空key好
var key1 = item1?.DisplayName ?? "null";
var key2 = item2?.DisplayName ?? "null";
return $"{key1}|{key2}";
}
}
/// <summary>
/// 获取去重后的预计算碰撞结果(第一次去重,按碰撞对象对去重)
/// </summary>
@ -210,6 +259,7 @@ namespace NavisworksTransport
LogManager.Info($"ClashDetective结果已保存到数据库Id={resultId}, IsVirtualVehicle={isVirtualVehicle}");
// 保存被撞物体信息只保存Item2不保存车辆Item1
// 同时保存碰撞时运动物体的位置和朝向,用于还原碰撞场景
var collisionObjects = new List<ClashDetectiveCollisionObjectRecord>();
foreach (var collision in clashResults)
@ -220,14 +270,28 @@ namespace NavisworksTransport
// 使用 CreatePathId API 获取 ModelIndex 和 PathId
var pathId = Application.ActiveDocument.Models.CreatePathId(collision.Item2);
collisionObjects.Add(new ClashDetectiveCollisionObjectRecord
var collisionRecord = new ClashDetectiveCollisionObjectRecord
{
ResultId = resultId,
ModelIndex = pathId.ModelIndex,
PathId = pathId.PathId,
DisplayName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2),
ObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(collision.Item2)
});
};
// 保存碰撞时运动物体的位置和朝向(如果可用)
if (collision.HasPositionInfo && collision.Item1Position != null)
{
collisionRecord.Item1PosX = collision.Item1Position.X;
collisionRecord.Item1PosY = collision.Item1Position.Y;
collisionRecord.Item1PosZ = collision.Item1Position.Z;
collisionRecord.Item1YawRadians = collision.Item1YawRadians;
collisionRecord.HasPositionInfo = true;
LogManager.Debug($"[保存碰撞对象] 记录运动物体位置: ({collisionRecord.Item1PosX:F2}, {collisionRecord.Item1PosY:F2}, {collisionRecord.Item1PosZ:F2}), 朝向: {collisionRecord.Item1YawRadians:F2} rad");
}
collisionObjects.Add(collisionRecord);
}
}
@ -392,9 +456,10 @@ namespace NavisworksTransport
}
}
// 3. 从数据库读取被撞物体信息
// 3. 从数据库读取被撞物体信息(包含碰撞时运动物体的位置和朝向)
var collisionObjectsSql = @"
SELECT ModelIndex, PathId, DisplayName, ObjectName
SELECT ModelIndex, PathId, DisplayName, ObjectName,
Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo
FROM ClashDetectiveCollisionObjects
WHERE ResultId = @resultId
";
@ -412,7 +477,12 @@ namespace NavisworksTransport
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString()
ObjectName = reader["ObjectName"]?.ToString(),
Item1PosX = reader["Item1PosX"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosX"]) : (double?)null,
Item1PosY = reader["Item1PosY"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosY"]) : (double?)null,
Item1PosZ = reader["Item1PosZ"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosZ"]) : (double?)null,
Item1YawRadians = reader["Item1YawRadians"] != DBNull.Value ? Convert.ToDouble(reader["Item1YawRadians"]) : (double?)null,
HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1
});
}
}
@ -465,6 +535,16 @@ namespace NavisworksTransport
Distance = 0.0,
CreatedTime = DateTime.Now
};
// 恢复碰撞时运动物体的位置和朝向(如果可用)
if (obj.HasPositionInfo && obj.Item1PosX.HasValue && obj.Item1PosY.HasValue && obj.Item1PosZ.HasValue)
{
collisionResult.Item1Position = new Point3D(obj.Item1PosX.Value, obj.Item1PosY.Value, obj.Item1PosZ.Value);
collisionResult.Item1YawRadians = obj.Item1YawRadians ?? 0.0;
collisionResult.HasPositionInfo = true;
LogManager.Debug($"[加载碰撞结果] 恢复运动物体位置: ({obj.Item1PosX:F2}, {obj.Item1PosY:F2}, {obj.Item1PosZ:F2}), 朝向: {obj.Item1YawRadians:F2} rad");
}
results.Add(collisionResult);
}
@ -676,6 +756,9 @@ namespace NavisworksTransport
LogManager.Info($"[分组测试] 智能去重: {validCollisions.Count} 个检测点 -> {groupedCollisions.Count} 个唯一碰撞对");
// 🔥 初始化确认碰撞位置字典
_confirmedCollisionPositions.Clear();
// 缓存去重后的碰撞结果(每组取第一个)
lock (_resultsLock)
{
@ -796,6 +879,18 @@ namespace NavisworksTransport
confirmedCount++;
skippedCount += (sortedCandidates.Count - 1 - i); // 记录跳过的数量
// 🔥 关键:记录确认碰撞的候选帧位置信息,供后续使用
// 使用Item2作为key因为最终去重也是按Item2合并的
var confirmedPositionKey = GetCollisionObjectKey(candidate.Item1, candidate.Item2);
_confirmedCollisionPositions[confirmedPositionKey] = new CollisionPositionInfo
{
Item1Position = candidate.Item1Position,
Item2Position = candidate.Item2Position,
Item1YawRadians = candidate.Item1YawRadians,
HasPositionInfo = candidate.HasPositionInfo
};
LogManager.Debug($"[ClashDetective确认] 记录碰撞位置: {confirmedPositionKey}, 位置: ({candidate.Item1Position.X:F2}, {candidate.Item1Position.Y:F2}, {candidate.Item1Position.Z:F2})");
int subResultIndex = 1;
// 🔥 优化:预获取运动物体名称,避免在循环中重复获取
var animatedObjectName = ModelItemAnalysisHelper.GetSafeDisplayName(animatedObject);
@ -873,6 +968,10 @@ namespace NavisworksTransport
originalItem2 = clashResult.Item2; // 回退到原始对象
}
// 🔥 从确认碰撞位置字典中获取位置信息ClashDetective实际验证通过的帧
var positionKey = GetCollisionObjectKey(animatedObject, originalItem2);
_confirmedCollisionPositions.TryGetValue(positionKey, out var confirmedPosition);
var collisionResult = new CollisionResult
{
ClashGuid = clashResult.Guid,
@ -882,7 +981,12 @@ namespace NavisworksTransport
Item2 = originalItem2,
Center = clashResult.Center,
Distance = clashResult.Distance,
CreatedTime = DateTime.Now
CreatedTime = DateTime.Now,
// 🔥 使用ClashDetective确认时的位置信息
Item1Position = confirmedPosition?.Item1Position,
Item2Position = confirmedPosition?.Item2Position,
Item1YawRadians = confirmedPosition?.Item1YawRadians ?? 0,
HasPositionInfo = confirmedPosition != null
};
clashResults.Add(collisionResult);
}
@ -895,7 +999,9 @@ namespace NavisworksTransport
.Select(g => g.First())
.ToList();
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对");
// 🔥 日志:统计位置信息保留情况
var withPositionCount = finalClashResults.Count(c => c.HasPositionInfo && c.Item1Position != null);
LogManager.Info($"[最终去重] ClashDetective结果去重: {clashResults.Count} 个碰撞 -> {finalClashResults.Count} 个唯一碰撞对,其中 {withPositionCount} 个包含位置信息");
// 🔥 优化:在去重后为结果设置详细的 DisplayName
// 这样只对保留的结果进行名称计算,避免在子碰撞结果上浪费性能
@ -961,6 +1067,10 @@ namespace NavisworksTransport
LogManager.Info($"=== 使用预计算碰撞数据创建ClashDetective测试容差: {detectionGap}米)===");
LogManager.Info($"[预计算数据] 共有 {precomputedCollisions.Count} 个碰撞记录");
// 检查预计算碰撞结果是否包含位置信息
var collisionsWithPosition = precomputedCollisions.Count(c => c.HasPositionInfo && c.Item1Position != null);
LogManager.Info($"[预计算数据] 包含位置信息的碰撞: {collisionsWithPosition}/{precomputedCollisions.Count}");
// 直接使用所有预计算结果,只过滤有效对象(不去重)
var collisionResults = precomputedCollisions
@ -1884,9 +1994,10 @@ namespace NavisworksTransport
public double Distance { get; set; }
public DateTime CreatedTime { get; set; }
// 位置信息用于恢复测试
// 位置和朝向信息用于还原碰撞场景
public Point3D Item1Position { get; set; }
public Point3D Item2Position { get; set; }
public double Item1YawRadians { get; set; } // 运动物体朝向(弧度)
public bool HasPositionInfo { get; set; }
// IEquatable<CollisionResult> 实现:基于碰撞对象进行去重

View File

@ -195,6 +195,7 @@ namespace NavisworksTransport
");
// 7. ClashDetective碰撞对象表
// 扩展字段:记录碰撞时运动物体的位置和朝向,用于还原碰撞场景
ExecuteNonQuery(@"
CREATE TABLE IF NOT EXISTS ClashDetectiveCollisionObjects (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -203,6 +204,12 @@ namespace NavisworksTransport
PathId TEXT,
DisplayName TEXT,
ObjectName TEXT,
--
Item1PosX REAL,
Item1PosY REAL,
Item1PosZ REAL,
Item1YawRadians REAL,
HasPositionInfo INTEGER DEFAULT 0,
FOREIGN KEY(ResultId) REFERENCES ClashDetectiveResults(Id) ON DELETE CASCADE
)
");
@ -227,7 +234,7 @@ namespace NavisworksTransport
// 9. 设置数据库版本SQLite内置user_version
// 版本号格式:主版本*10000 + 次版本*100 + 修订号
// 例如2.1.3 = 20103
ExecuteNonQuery("PRAGMA user_version = 20100"); // v2.1.0
ExecuteNonQuery("PRAGMA user_version = 20101"); // v2.1.1 - 增加碰撞时运动物体位置和朝向字段
// 创建索引
ExecuteNonQuery("CREATE INDEX IF NOT EXISTS idx_reports_route ON CollisionReports(RouteId)");
@ -1074,8 +1081,10 @@ namespace NavisworksTransport
{
var sql = @"
INSERT INTO ClashDetectiveCollisionObjects
(ResultId, ModelIndex, PathId, DisplayName, ObjectName)
VALUES (@resultId, @modelIndex, @pathId, @displayName, @objectName)
(ResultId, ModelIndex, PathId, DisplayName, ObjectName,
Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo)
VALUES (@resultId, @modelIndex, @pathId, @displayName, @objectName,
@item1PosX, @item1PosY, @item1PosZ, @item1YawRadians, @hasPositionInfo)
";
foreach (var obj in objects)
@ -1087,6 +1096,11 @@ namespace NavisworksTransport
cmd.Parameters.AddWithValue("@pathId", obj.PathId ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@displayName", obj.DisplayName ?? "");
cmd.Parameters.AddWithValue("@objectName", obj.ObjectName ?? "");
cmd.Parameters.AddWithValue("@item1PosX", obj.Item1PosX.HasValue ? (object)obj.Item1PosX.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1PosY", obj.Item1PosY.HasValue ? (object)obj.Item1PosY.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1PosZ", obj.Item1PosZ.HasValue ? (object)obj.Item1PosZ.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@item1YawRadians", obj.Item1YawRadians.HasValue ? (object)obj.Item1YawRadians.Value : DBNull.Value);
cmd.Parameters.AddWithValue("@hasPositionInfo", obj.HasPositionInfo ? 1 : 0);
cmd.ExecuteNonQuery();
}
}
@ -1112,7 +1126,8 @@ namespace NavisworksTransport
try
{
var sql = @"
SELECT Id, ResultId, ModelIndex, PathId, DisplayName, ObjectName
SELECT Id, ResultId, ModelIndex, PathId, DisplayName, ObjectName,
Item1PosX, Item1PosY, Item1PosZ, Item1YawRadians, HasPositionInfo
FROM ClashDetectiveCollisionObjects
WHERE ResultId = @resultId
";
@ -1131,7 +1146,12 @@ namespace NavisworksTransport
ModelIndex = reader["ModelIndex"] != DBNull.Value ? Convert.ToInt32(reader["ModelIndex"]) : (int?)null,
PathId = reader["PathId"] != DBNull.Value ? reader["PathId"].ToString() : null,
DisplayName = reader["DisplayName"]?.ToString(),
ObjectName = reader["ObjectName"]?.ToString()
ObjectName = reader["ObjectName"]?.ToString(),
Item1PosX = reader["Item1PosX"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosX"]) : (double?)null,
Item1PosY = reader["Item1PosY"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosY"]) : (double?)null,
Item1PosZ = reader["Item1PosZ"] != DBNull.Value ? Convert.ToDouble(reader["Item1PosZ"]) : (double?)null,
Item1YawRadians = reader["Item1YawRadians"] != DBNull.Value ? Convert.ToDouble(reader["Item1YawRadians"]) : (double?)null,
HasPositionInfo = reader["HasPositionInfo"] != DBNull.Value && Convert.ToInt32(reader["HasPositionInfo"]) == 1
});
}
}
@ -2598,6 +2618,7 @@ namespace NavisworksTransport
/// <summary>
/// ClashDetective碰撞对象数据模型
/// 包含碰撞时运动物体的位置和朝向信息,用于还原碰撞场景
/// </summary>
public class ClashDetectiveCollisionObjectRecord
{
@ -2607,6 +2628,13 @@ namespace NavisworksTransport
public string PathId { get; set; }
public string DisplayName { get; set; }
public string ObjectName { get; set; }
// 碰撞时运动物体的位置和朝向(用于还原碰撞场景)
public double? Item1PosX { get; set; }
public double? Item1PosY { get; set; }
public double? Item1PosZ { get; set; }
public double? Item1YawRadians { get; set; }
public bool HasPositionInfo { get; set; }
}
/// <summary>

View File

@ -89,6 +89,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private double _detectionTolerance;
private CollisionReportResult _currentReport;
private ObservableCollection<ExcludedObjectRecord> _excludedObjects;
// 保存运动物体原始状态,用于碰撞查看后恢复
private ModelItemTransformHelper.ObjectStateSnapshot _savedAnimatedObjectState;
private ModelItem _currentAnimatedObject;
#endregion
@ -637,10 +641,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
collisionItems.Add(item);
}
// 更新UI集合
// 更新UI集合(按序号排序保持顺序)
SafeExecute(() =>
{
foreach (var item in collisionItems.OrderBy(i => i.Title))
foreach (var item in collisionItems.OrderBy(i => i.Index))
AnimationCollisions.Add(item);
}, "更新碰撞报告项目列表", true);
@ -1229,6 +1233,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// <summary>
/// 高亮并聚焦到碰撞项目中的被撞对象Item2
/// 同时将运动物体Item1移动到碰撞时的位置和朝向还原碰撞场景
/// </summary>
public void HighlightAndFocusCollisionItem(CollisionReportItem collisionItem)
{
@ -1259,12 +1264,92 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info($"聚焦到被撞对象: {collisionItem.Title}");
}
// 还原运动物体到碰撞时的位置和朝向
LogManager.Debug($"[碰撞详情点击] Item1={collisionData.Item1?.DisplayName}, HasPositionInfo={collisionData.HasPositionInfo}, Item1Position={collisionData.Item1Position}");
if (collisionData.Item1 != null && collisionData.HasPositionInfo && collisionData.Item1Position != null)
{
// 首次查看碰撞时保存运动物体状态
if (_savedAnimatedObjectState == null || _currentAnimatedObject != collisionData.Item1)
{
_currentAnimatedObject = collisionData.Item1;
_savedAnimatedObjectState = ModelItemTransformHelper.SaveObjectState(collisionData.Item1);
LogManager.Info($"[碰撞查看] 已保存运动物体原始状态,供后续恢复使用");
}
RestoreAnimatedObjectToCollisionPosition(collisionData);
}
else
{
LogManager.Warning($"[碰撞详情点击] 无法还原运动物体位置: Item1={(collisionData.Item1 != null ? "" : "")}, HasPositionInfo={collisionData.HasPositionInfo}, Item1Position={(collisionData.Item1Position != null ? "" : "")}");
}
}
catch (Exception ex)
{
LogManager.Error($"聚焦到被撞对象失败: {ex.Message}", ex);
}
}
/// <summary>
/// 将运动物体还原到碰撞时的位置和朝向
/// 使用ModelItemTransformHelper.MoveItemToPositionAndYaw从CAD原始状态精确定位
/// </summary>
private void RestoreAnimatedObjectToCollisionPosition(CollisionResult collisionData)
{
try
{
var animatedObject = collisionData.Item1;
var targetPosition = collisionData.Item1Position;
var targetYaw = collisionData.Item1YawRadians;
if (animatedObject == null || targetPosition == null)
{
LogManager.Warning("[还原碰撞位置] 运动物体或位置信息为空");
return;
}
// 计算目标底面位置Item1Position存储的是包围盒中心需要转换为底面
var currentBounds = animatedObject.BoundingBox();
double halfHeight = (currentBounds.Max.Z - currentBounds.Min.Z) / 2.0;
var targetGroundPosition = new Point3D(
targetPosition.X,
targetPosition.Y,
targetPosition.Z - halfHeight
);
// 使用工具方法从CAD原始状态移动到目标位置
ModelItemTransformHelper.MoveItemToPositionAndYaw(animatedObject, targetGroundPosition, targetYaw);
LogManager.Info($"[还原碰撞位置] 运动物体已移动到碰撞位置: ({targetGroundPosition.X:F2}, {targetGroundPosition.Y:F2}, {targetGroundPosition.Z:F2}), 朝向: {targetYaw * 180 / Math.PI:F2}°");
}
catch (Exception ex)
{
LogManager.Error($"[还原碰撞位置] 失败: {ex.Message}", ex);
}
}
/// <summary>
/// 恢复运动物体到原始状态(查看碰撞前保存的状态)
/// </summary>
public void RestoreAnimatedObjectToOriginalState()
{
try
{
if (_currentAnimatedObject != null && _savedAnimatedObjectState != null)
{
ModelItemTransformHelper.RestoreObjectState(_currentAnimatedObject, _savedAnimatedObjectState);
LogManager.Info("[碰撞查看] 运动物体已恢复到原始状态");
// 清除保存的状态
_savedAnimatedObjectState = null;
_currentAnimatedObject = null;
}
}
catch (Exception ex)
{
LogManager.Error($"[恢复运动物体状态] 失败: {ex.Message}", ex);
}
}
#endregion
}

View File

@ -134,6 +134,12 @@ namespace NavisworksTransport.UI.WPF.Views
{
try
{
// 恢复运动物体到原始状态(查看碰撞前保存的状态)
if (_viewModel != null)
{
_viewModel.RestoreAnimatedObjectToOriginalState();
}
// 清理资源
if (_viewModel != null)
{

View File

@ -101,5 +101,152 @@ namespace NavisworksTransport.Utils
var restoreTransform = Transform3D.CreateTranslation(restoreOffset);
doc.Models.OverridePermanentTransform(modelItems, restoreTransform, false);
}
/// <summary>
/// 将物体从当前位置移动到指定位置和朝向(增量模式)
/// 适用于碰撞位置还原等场景
/// </summary>
/// <param name="item">要移动的物体</param>
/// <param name="targetPosition">目标位置(地面位置,即包围盒底面)</param>
/// <param name="targetYaw">目标朝向弧度绕Z轴</param>
public static void MoveItemToPositionAndYaw(ModelItem item, Point3D targetPosition, double targetYaw)
{
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
// 获取当前状态
var currentBounds = item.BoundingBox();
var currentGroundPos = new Point3D(
currentBounds.Center.X,
currentBounds.Center.Y,
currentBounds.Min.Z
);
var currentYaw = GetYawFromTransform(item.Transform);
// 计算位置和旋转增量
var deltaPos = new Vector3D(
targetPosition.X - currentGroundPos.X,
targetPosition.Y - currentGroundPos.Y,
targetPosition.Z - currentGroundPos.Z
);
double deltaYaw = targetYaw - currentYaw;
// 应用增量变换和ClashDetective中的做法一致
Transform3D transform;
if (Math.Abs(deltaYaw) > 0.001)
{
// 有旋转:需要补偿绕原点旋转带来的位置偏移
double cos = Math.Cos(deltaYaw);
double sin = Math.Sin(deltaYaw);
double rotatedX = currentGroundPos.X * cos - currentGroundPos.Y * sin;
double rotatedY = currentGroundPos.X * sin + currentGroundPos.Y * cos;
var compensatedTranslation = new Vector3D(
targetPosition.X - rotatedX,
targetPosition.Y - rotatedY,
deltaPos.Z
);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation;
transform = components.Combine();
}
else
{
// 纯平移
transform = Transform3D.CreateTranslation(deltaPos);
}
doc.Models.OverridePermanentTransform(modelItems, transform, false);
}
/// <summary>
/// 物体状态快照,用于保存和恢复
/// </summary>
public class ObjectStateSnapshot
{
public Point3D Position { get; set; }
public double YawRadians { get; set; }
public Transform3D Transform { get; set; }
}
/// <summary>
/// 保存物体当前状态
/// </summary>
public static ObjectStateSnapshot SaveObjectState(ModelItem item)
{
if (item == null) return null;
var bounds = item.BoundingBox();
return new ObjectStateSnapshot
{
Position = new Point3D(bounds.Center.X, bounds.Center.Y, bounds.Min.Z),
YawRadians = GetYawFromTransform(item.Transform),
Transform = item.Transform
};
}
/// <summary>
/// 从状态快照恢复物体位置
/// 先回到CAD原始位置再移动到保存的位置
/// </summary>
public static void RestoreObjectState(ModelItem item, ObjectStateSnapshot state)
{
if (item == null || state == null) return;
var doc = Application.ActiveDocument;
var modelItems = new ModelItemCollection { item };
// 1. 先回到CAD原始位置
doc.Models.ResetPermanentTransform(modelItems);
// 2. 从CAD原始位置移动到保存的位置
// 获取CAD原始状态
var originalBounds = item.BoundingBox();
var originalGroundPos = new Point3D(
originalBounds.Center.X,
originalBounds.Center.Y,
originalBounds.Min.Z
);
var originalYaw = GetYawFromTransform(item.Transform);
// 计算到保存位置的增量
var deltaPos = new Vector3D(
state.Position.X - originalGroundPos.X,
state.Position.Y - originalGroundPos.Y,
state.Position.Z - originalGroundPos.Z
);
double deltaYaw = state.YawRadians - originalYaw;
// 应用增量变换
Transform3D transform;
if (Math.Abs(deltaYaw) > 0.001)
{
double cos = Math.Cos(deltaYaw);
double sin = Math.Sin(deltaYaw);
double rotatedX = originalGroundPos.X * cos - originalGroundPos.Y * sin;
double rotatedY = originalGroundPos.X * sin + originalGroundPos.Y * cos;
var compensatedTranslation = new Vector3D(
state.Position.X - rotatedX,
state.Position.Y - rotatedY,
deltaPos.Z
);
var identity = Transform3D.CreateTranslation(new Vector3D(0, 0, 0));
var components = identity.Factor();
components.Rotation = new Rotation3D(new UnitVector3D(0, 0, 1), deltaYaw);
components.Translation = compensatedTranslation;
transform = components.Combine();
}
else
{
transform = Transform3D.CreateTranslation(deltaPos);
}
doc.Models.OverridePermanentTransform(modelItems, transform, false);
}
}
}