diff --git a/IFLOW.md b/IFLOW.md
index b0a3aca..6616c24 100644
--- a/IFLOW.md
+++ b/IFLOW.md
@@ -319,7 +319,7 @@ msiexec /i NavisworksTransport.Setup.msi /qn /l*v install.log
- **日志记录**: 使用内置的LogManager
- **异常处理**: 通过GlobalExceptionHandler统一管理,不掩盖问题
- **线程安全**: COM API调用必须在主UI线程(STA)执行
-- **单位系统**:
+- **单位系统**:
- **内部计算**: 统一使用模型单位(避免频繁转换,提高性能)
- **对外输出**: 转换为米(m)单位(用户界面、日志、数据库存储等)
- **单位转换**: 使用 `UnitsConverter.ConvertToMeters()` 和 `UnitsConverter.ConvertFromMeters()` 进行转换
diff --git a/src/Core/Properties/CategoryAttributeManager.cs b/src/Core/Properties/CategoryAttributeManager.cs
index d810de3..ede8491 100644
--- a/src/Core/Properties/CategoryAttributeManager.cs
+++ b/src/Core/Properties/CategoryAttributeManager.cs
@@ -546,6 +546,13 @@ namespace NavisworksTransport
if (items == null || items.Count == 0)
return;
+ // 确保在主线程上执行(Navisworks COM API 要求)
+ if (!NavisworksApiHelper.IsOnUIThread())
+ {
+ NavisworksApiHelper.ExecuteOnUIThread(() => PreCalculateRailBaselinePaths(items));
+ return;
+ }
+
LogManager.Info($"[空轨] 开始预计算 {items.Count} 个空轨模型的基准路径");
int successCount = 0;
@@ -556,7 +563,7 @@ namespace NavisworksTransport
try
{
// 调用RailGeometryHelper提取基准路径
- var baselinePath = PathPlanning.RailGeometryHelper.ExtractRailBaselinePath(item);
+ var baselinePath = RailGeometryHelper.ExtractRailBaselinePath(item);
if (baselinePath != null && baselinePath.PathPoints != null && baselinePath.PathPoints.Count > 0)
{
diff --git a/src/PathPlanning/RailGeometryHelper.cs b/src/PathPlanning/RailGeometryHelper.cs
index 3c9eb22..24dc992 100644
--- a/src/PathPlanning/RailGeometryHelper.cs
+++ b/src/PathPlanning/RailGeometryHelper.cs
@@ -407,6 +407,12 @@ namespace NavisworksTransport.PathPlanning
/// 成功提取的基准路径数量
public static int ExtractAndRenderAllRailBaselines()
{
+ // 确保在主线程上执行(Navisworks COM API 要求)
+ if (!NavisworksApiHelper.IsOnUIThread())
+ {
+ return NavisworksApiHelper.ExecuteOnUIThread(ExtractAndRenderAllRailBaselines);
+ }
+
try
{
LogManager.Info("[空轨] 开始提取所有空轨模型的基准路径");
@@ -458,15 +464,6 @@ namespace NavisworksTransport.PathPlanning
return 0;
}
}
-
- ///
- /// 兼容方法:从空轨几何体提取下表面中心线(一步完成)
- ///
- /// 空轨模型项
- /// 起点(3D坐标)
- /// 终点(3D坐标)
- /// 采样间隔(模型单位),默认0.5
- /// 路径点列表(包含精确的Z坐标)
///
/// 使用 OBB 包围体提取空轨下表面中心线(新方法,支持倾斜)
@@ -726,21 +723,23 @@ namespace NavisworksTransport.PathPlanning
centroid.Y + axes[0].Y * (minX + maxX) / 2 + axes[1].Y * (minY + maxY) / 2 + axes[2].Y * (minZ + maxZ) / 2,
centroid.Z + axes[0].Z * (minX + maxX) / 2 + axes[1].Z * (minY + maxY) / 2 + axes[2].Z * (minZ + maxZ) / 2
);
-
+
+
var obb = new OrientedBoundingBox
{
+
Center = obbCenter,
Axes = axes,
Extents = new Point3D(
(maxX - minX) / 2,
(maxY - minY) / 2,
(maxZ - minZ) / 2
- )
+ ),
+ // 保存投影范围信息,用于计算下表面
+ ProjectedMinZ = minZ,
+ ProjectedMaxZ = maxZ
+
};
-
- // 保存投影范围信息,用于计算下表面
- obb.ProjectedMinZ = minZ;
- obb.ProjectedMaxZ = maxZ;
LogManager.Info($"[空轨] OBB各轴长度(模型单位): {obb.GetAxisLength(0):F2}, {obb.GetAxisLength(1):F2}, {obb.GetAxisLength(2):F2}");
@@ -796,166 +795,6 @@ namespace NavisworksTransport.PathPlanning
return result;
}
- ///
- /// 兼容方法:从空轨几何体提取下表面中心线(一步完成)
- ///
- /// 空轨模型项
- /// 起点(3D坐标)
- /// 终点(3D坐标)
- /// 采样间隔(模型单位),默认0.5
- /// 路径点列表(包含精确的Z坐标)
- public static List ExtractRailBottomCenterLine(
- ModelItem railModel,
- Point3D startPoint,
- Point3D endPoint,
- double samplingInterval = 0.5)
- {
- try
- {
- LogManager.Info($"[空轨] 开始提取下表面中心线: {railModel.DisplayName}");
- LogManager.Info($"[空轨] 起点: ({startPoint.X:F2}, {startPoint.Y:F2}, {startPoint.Z:F2})");
- LogManager.Info($"[空轨] 终点: ({endPoint.X:F2}, {endPoint.Y:F2}, {endPoint.Z:F2})");
- LogManager.Info($"[空轨] 采样间隔: {samplingInterval:F2}");
-
- // 1. 提取空轨下表面信息(带缓存)
- var surfaceInfo = ExtractRailBottomSurface(railModel);
- if (surfaceInfo == null || surfaceInfo.BottomTriangles.Count == 0)
- {
- throw new InvalidOperationException($"无法从空轨模型提取下表面三角形: {railModel.DisplayName}");
- }
-
- LogManager.Info($"[空轨] 提取到 {surfaceInfo.BottomTriangles.Count} 个下表面三角形");
-
- // 2. 提取骨架线
- var skeleton = ExtractSkeletonFromTriangles(surfaceInfo.BottomTriangles);
- LogManager.Info($"[空轨] 提取到 {skeleton.Count} 个骨架点");
-
- // 3. 沿骨架线生成采样点
- var samplePoints = GenerateSamplePointsAlongSkeleton(skeleton, samplingInterval);
- LogManager.Info($"[空轨] 生成 {samplePoints.Count} 个采样点");
-
- // 4. 对每个采样点,从下方射线投射获取精确高度
- var pathPoints = new List();
- int successCount = 0;
- int failCount = 0;
-
- foreach (var samplePoint in samplePoints)
- {
- try
- {
- double bottomZ = GetBottomHeightByRaycast(
- samplePoint,
- surfaceInfo.BottomTriangles,
- surfaceInfo.MinZ);
-
- if (bottomZ > surfaceInfo.MinZ - 1000) // 检查是否有效
- {
- pathPoints.Add(new Point3D(
- samplePoint.X,
- samplePoint.Y,
- bottomZ));
- successCount++;
- }
- else
- {
- LogManager.Warning($"[空轨] 采样点 ({samplePoint.X:F2}, {samplePoint.Y:F2}) 未找到有效交点");
- failCount++;
- }
- }
- catch (Exception ex)
- {
- LogManager.Warning($"[空轨] 采样点 ({samplePoint.X:F2}, {samplePoint.Y:F2}) 射线投射失败: {ex.Message}");
- failCount++;
- }
- }
-
- LogManager.Info($"[空轨] 提取完成: 成功 {successCount} 个点, 失败 {failCount} 个点");
-
- if (pathPoints.Count < 2)
- {
- throw new InvalidOperationException($"提取的中心线点数不足: {pathPoints.Count} (最少需要2个)");
- }
-
- return pathPoints;
- }
- catch (Exception ex)
- {
- LogManager.Error($"[空轨] 提取下表面中心线失败: {ex.Message}");
- throw;
- }
- }
-
- ///
- /// 提取空轨下表面信息(带缓存)
- ///
- private static RailBottomSurfaceInfo ExtractRailBottomSurface(ModelItem railModel)
- {
- try
- {
- // 生成缓存键(使用DisplayName作为唯一标识)
- var cacheKey = $"rail_{railModel.DisplayName}_{railModel.GetHashCode()}";
-
- // 检查缓存
- if (_railCache.TryGetValue(cacheKey, out var cachedInfo))
- {
- // 检查缓存是否过期
- if (DateTime.Now - cachedInfo.CacheTime < _cacheValidDuration)
- {
- LogManager.Debug($"[空轨] 使用缓存的下表面信息");
- return cachedInfo;
- }
- else
- {
- LogManager.Debug($"[空轨] 缓存已过期,重新提取");
- _railCache.Remove(cacheKey);
- }
- }
-
- // 提取所有三角形
- var allTriangles = GeometryHelper.ExtractTriangles(new[] { railModel });
- if (allTriangles.Count == 0)
- {
- LogManager.Warning($"[空轨] 模型 {railModel.DisplayName} 没有三角形数据");
- return null;
- }
-
- LogManager.Info($"[空轨] 提取到 {allTriangles.Count} 个三角形");
-
- // 筛选下表面三角形
- var bottomTriangles = FilterBottomTriangles(allTriangles);
- LogManager.Info($"[空轨] 筛选出 {bottomTriangles.Count} 个下表面三角形");
-
- if (bottomTriangles.Count == 0)
- {
- LogManager.Warning($"[空轨] 没有找到下表面三角形");
- return null;
- }
-
- // 计算包围盒
- var bounds = CalculateBoundingBox(bottomTriangles);
-
- // 创建下表面信息
- var surfaceInfo = new RailBottomSurfaceInfo
- {
- BottomTriangles = bottomTriangles,
- Bounds = bounds,
- MinZ = bounds.Min.Z,
- MaxZ = bounds.Max.Z,
- CacheTime = DateTime.Now
- };
-
- // 加入缓存
- _railCache[cacheKey] = surfaceInfo;
-
- return surfaceInfo;
- }
- catch (Exception ex)
- {
- LogManager.Error($"[空轨] 提取下表面信息失败: {ex.Message}");
- return null;
- }
- }
-
///
/// 筛选下表面三角形(法线向下,Z < 0)
///
@@ -1018,222 +857,6 @@ namespace NavisworksTransport.PathPlanning
return normal;
}
- ///
- /// 从下表面三角形提取骨架线(支持拐弯)
- ///
- private static List ExtractSkeletonFromTriangles(List bottomTriangles)
- {
- try
- {
- // 1. 计算每个三角形的中心点
- var triangleCenters = new List();
- foreach (var triangle in bottomTriangles)
- {
- var center = new Point3D(
- (triangle.Point1.X + triangle.Point2.X + triangle.Point3.X) / 3,
- (triangle.Point1.Y + triangle.Point2.Y + triangle.Point3.Y) / 3,
- (triangle.Point1.Z + triangle.Point2.Z + triangle.Point3.Z) / 3
- );
- triangleCenters.Add(center);
- }
-
- LogManager.Info($"[空轨] 计算了 {triangleCenters.Count} 个三角形中心点");
-
- // 2. 按邻近度排序,形成路径
- var sortedPoints = SortPointsByProximity(triangleCenters);
- LogManager.Info($"[空轨] 排序后得到 {sortedPoints.Count} 个骨架点");
-
- // 2.5 使用全局PCA压缩对称点(适用于直线路径)
- var compressedPath = CollapseSymmetricPointsByPCA(sortedPoints);
- LogManager.Info($"[空轨] PCA压缩后得到 {compressedPath.Count} 个骨架点");
-
- // 3. 简化路径(移除过近的点)
- var simplifiedPath = SimplifyPath(compressedPath, 0.1); // 0.1米阈值
- LogManager.Info($"[空轨] 简化后得到 {simplifiedPath.Count} 个骨架点");
-
- // 4. 平滑路径(可选)
- var smoothedPath = SmoothPath(simplifiedPath);
- LogManager.Info($"[空轨] 平滑后得到 {smoothedPath.Count} 个骨架点");
-
- return smoothedPath;
- }
- catch (Exception ex)
- {
- LogManager.Error($"[空轨] 提取骨架线失败: {ex.Message}");
- throw;
- }
- }
-
- ///
- /// 按邻近度排序点(形成路径)
- ///
- private static List SortPointsByProximity(List points)
- {
- if (points.Count == 0) return new List();
-
- var sorted = new List();
- var remaining = new List(points);
-
- // 从边界点开始(选择X最小的点)
- var current = remaining.OrderBy(p => p.X).First();
- sorted.Add(current);
- remaining.Remove(current);
-
- // 依次查找最近的点
- while (remaining.Count > 0)
- {
- Point3D nearest = null;
- double minDistance = double.MaxValue;
-
- foreach (var point in remaining)
- {
- double distance = CalculateDistance2D(current, point);
- if (distance < minDistance)
- {
- minDistance = distance;
- nearest = point;
- }
- }
-
- if (nearest != null && minDistance < 10.0) // 10米阈值,避免跳跃
- {
- sorted.Add(nearest);
- remaining.Remove(nearest);
- current = nearest;
- }
- else
- {
- // 没有找到合适的邻近点,结束
- break;
- }
- }
-
- return sorted;
- }
-
- ///
- /// 使用全局PCA压缩对称点(适用于直线路径)
- ///
- private static List CollapseSymmetricPointsByPCA(List points)
- {
- try
- {
- if (points.Count < 3)
- {
- return new List(points);
- }
-
- LogManager.Info("[空轨] 开始PCA压缩对称点");
-
- // 1. 计算质心
- var centroid = new Point3D(
- points.Average(p => p.X),
- points.Average(p => p.Y),
- points.Average(p => p.Z)
- );
-
- // 2. 计算协方差矩阵
- double covXX = 0, covYY = 0, covZZ = 0;
- double covXY = 0, covXZ = 0, covYZ = 0;
-
- foreach (var point in points)
- {
- double dx = point.X - centroid.X;
- double dy = point.Y - centroid.Y;
- double dz = point.Z - centroid.Z;
-
- covXX += dx * dx;
- covYY += dy * dy;
- covZZ += dz * dz;
- covXY += dx * dy;
- covXZ += dx * dz;
- covYZ += dy * dz;
- }
-
- covXX /= points.Count;
- covYY /= points.Count;
- covZZ /= points.Count;
- covXY /= points.Count;
- covXZ /= points.Count;
- covYZ /= points.Count;
-
- // 3. 计算特征值和特征向量(3x3对称矩阵)
- // 使用数值方法计算特征值
- var eigenvalues = new double[3];
- var eigenvectors = new Point3D[3];
-
- ComputeEigen3x3(covXX, covYY, covZZ, covXY, covXZ, covYZ, eigenvalues, eigenvectors);
-
- // 4. 找到最小特征值对应的特征向量(压缩方向)
- int minIndex = 0;
- if (eigenvalues[1] < eigenvalues[minIndex]) minIndex = 1;
- if (eigenvalues[2] < eigenvalues[minIndex]) minIndex = 2;
-
- var compressAxis = eigenvectors[minIndex];
-
- LogManager.Info($"[空轨] PCA特征值: {eigenvalues[0]:F4}, {eigenvalues[1]:F4}, {eigenvalues[2]:F4}");
- LogManager.Info($"[空轨] 压缩方向: ({compressAxis.X:F3}, {compressAxis.Y:F3}, {compressAxis.Z:F3})");
-
- // 5. 沿路径方向分段
- // 找到最大特征值对应的特征向量(路径方向)
- int maxIndex = 0;
- if (eigenvalues[1] > eigenvalues[maxIndex]) maxIndex = 1;
- if (eigenvalues[2] > eigenvalues[maxIndex]) maxIndex = 2;
-
- var pathAxis = eigenvectors[maxIndex];
-
- // 将点投影到路径方向
- var projected = points.Select(p => new
- {
- Point = p,
- Position = Dot(new Point3D((p - centroid).X, (p - centroid).Y, (p - centroid).Z), pathAxis)
- }).OrderBy(x => x.Position).ToList();
-
- // 6. 分段压缩
- var segmentLength = 0.5; // 0.5米分段
- var segments = new List>();
- var currentSegment = new List();
- double currentStart = projected[0].Position;
-
- foreach (var item in projected)
- {
- if (item.Position - currentStart < segmentLength)
- {
- currentSegment.Add(item.Point);
- }
- else
- {
- segments.Add(currentSegment);
- currentSegment = new List { item.Point };
- currentStart = item.Position;
- }
- }
- if (currentSegment.Count > 0)
- segments.Add(currentSegment);
-
- // 7. 每段压缩到中心
- var compressed = new List();
- foreach (var segment in segments)
- {
- var segmentCentroid = new Point3D(
- segment.Average(p => p.X),
- segment.Average(p => p.Y),
- segment.Average(p => p.Z)
- );
- compressed.Add(segmentCentroid);
- }
-
- LogManager.Info($"[空轨] PCA压缩完成: {points.Count}个点 -> {compressed.Count}个点");
-
- return compressed;
- }
- catch (Exception ex)
- {
- LogManager.Warning($"[空轨] PCA压缩失败: {ex.Message},返回原始点");
- return new List(points);
- }
- }
-
///
/// 计算3x3对称矩阵的特征值和特征向量
///
@@ -1327,113 +950,6 @@ namespace NavisworksTransport.PathPlanning
eigenvectors[2] = new Point3D(V[0, 2], V[1, 2], V[2, 2]);
}
- ///
- /// 点积
- ///
- private static double Dot(Point3D a, Point3D b)
- {
- return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
- }
-
- ///
- /// 点积(Vector3D版本)
- ///
- private static double Dot(Vector3D a, Point3D b)
- {
- return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
- }
-
- ///
- /// 简化路径(移除过近的点)
- ///
- private static List SimplifyPath(List path, double threshold)
- {
- if (path.Count <= 2) return new List(path);
-
- var simplified = new List { path[0] };
-
- for (int i = 1; i < path.Count - 1; i++)
- {
- double distanceToPrev = CalculateDistance2D(path[i], simplified.Last());
- double distanceToNext = CalculateDistance2D(path[i], path[i + 1]);
-
- if (distanceToPrev >= threshold || distanceToNext >= threshold)
- {
- simplified.Add(path[i]);
- }
- }
-
- simplified.Add(path.Last());
- return simplified;
- }
-
- ///
- /// 平滑路径(简单移动平均)
- ///
- private static List SmoothPath(List path)
- {
- if (path.Count <= 2) return new List(path);
-
- var smoothed = new List();
-
- // 第一个点保持不变
- smoothed.Add(path[0]);
-
- // 中间点使用移动平均
- for (int i = 1; i < path.Count - 1; i++)
- {
- var prev = path[i - 1];
- var curr = path[i];
- var next = path[i + 1];
-
- var smoothedPoint = new Point3D(
- (prev.X + curr.X + next.X) / 3,
- (prev.Y + curr.Y + next.Y) / 3,
- (prev.Z + curr.Z + next.Z) / 3
- );
-
- smoothed.Add(smoothedPoint);
- }
-
- // 最后一个点保持不变
- smoothed.Add(path.Last());
-
- return smoothed;
- }
-
- ///
- /// 沿骨架线生成采样点
- ///
- private static List GenerateSamplePointsAlongSkeleton(
- List skeleton,
- double samplingInterval)
- {
- if (skeleton.Count < 2)
- {
- throw new ArgumentException("骨架线至少需要2个点");
- }
-
- var samplePoints = new List();
-
- // 计算骨架线总长度
- double totalLength = CalculatePathLength(skeleton);
-
- // 计算采样点数
- int sampleCount = Math.Max(2, (int)Math.Ceiling(totalLength / samplingInterval));
-
- LogManager.Info($"[空轨] 骨架线总长度: {totalLength:F2}, 采样点数: {sampleCount}");
-
- // 沿骨架线均匀采样
- for (int i = 0; i < sampleCount; i++)
- {
- double progress = (double)i / (sampleCount - 1);
- var point = InterpolateOnPath(skeleton, progress);
- samplePoints.Add(point);
- }
-
- return samplePoints;
- }
-
///
/// 在路径上插值(支持拐弯)
///
@@ -1554,35 +1070,5 @@ namespace NavisworksTransport.PathPlanning
return double.MinValue;
}
}
-
- ///
- /// 计算包围盒(复用Utils中的方法)
- ///
- private static BoundingBox3D CalculateBoundingBox(List triangles)
- {
- if (triangles.Count == 0)
- {
- return new BoundingBox3D(new Point3D(0, 0, 0), new Point3D(0, 0, 0));
- }
-
- double minX = double.MaxValue, minY = double.MaxValue, minZ = double.MaxValue;
- double maxX = double.MinValue, maxY = double.MinValue, maxZ = double.MinValue;
-
- foreach (var triangle in triangles)
- {
- minX = Math.Min(minX, Math.Min(triangle.Point1.X, Math.Min(triangle.Point2.X, triangle.Point3.X)));
- minY = Math.Min(minY, Math.Min(triangle.Point1.Y, Math.Min(triangle.Point2.Y, triangle.Point3.Y)));
- minZ = Math.Min(minZ, Math.Min(triangle.Point1.Z, Math.Min(triangle.Point2.Z, triangle.Point3.Z)));
-
- maxX = Math.Max(maxX, Math.Max(triangle.Point1.X, Math.Max(triangle.Point2.X, triangle.Point3.X)));
- maxY = Math.Max(maxY, Math.Max(triangle.Point1.Y, Math.Max(triangle.Point2.Y, triangle.Point3.Y)));
- maxZ = Math.Max(maxZ, Math.Max(triangle.Point1.Z, Math.Max(triangle.Point2.Z, triangle.Point3.Z)));
- }
-
- return new BoundingBox3D(
- new Point3D(minX, minY, minZ),
- new Point3D(maxX, maxY, maxZ));
- }
-
- }
+ }
}
\ No newline at end of file
diff --git a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
index 972667e..acc3161 100644
--- a/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
+++ b/src/UI/WPF/ViewModels/ModelSettingsViewModel.cs
@@ -526,8 +526,8 @@ namespace NavisworksTransport.UI.WPF.ViewModels
try
{
- // 2. 纯业务逻辑执行(后台线程,不使用UIStateManager)
- var result = await Task.Run(() =>
+ // 2. 业务逻辑执行(必须在主线程上执行,因为包含 Navisworks COM API 调用)
+ var result = NavisworksApiHelper.ExecuteOnUIThread(() =>
{
try
{