From a3d1915dec2ff3aa2922c3c3bcf57198bea3d669 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Mon, 15 Sep 2025 20:47:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=84=E7=90=86=E9=80=9A?= =?UTF-8?q?=E9=81=93=E5=87=A0=E4=BD=95=E4=BD=93=E9=94=99=E8=AF=AF=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/design/2026/NavisworksAPI使用方法.md | 89 ++++++++++- src/Utils/GeometryHelper.cs | 193 ++++++++++++----------- 2 files changed, 185 insertions(+), 97 deletions(-) diff --git a/doc/design/2026/NavisworksAPI使用方法.md b/doc/design/2026/NavisworksAPI使用方法.md index 679efc3..e0fef1a 100644 --- a/doc/design/2026/NavisworksAPI使用方法.md +++ b/doc/design/2026/NavisworksAPI使用方法.md @@ -1877,15 +1877,18 @@ private static void SimpleProgressExample() 事件模式总结 场景1:第一次打开文件(2ND_FLOOR) - - 事件#1 (6.6秒):Count=0,状态保持无模型(文档清空准备加载) - - 事件#2 (6.7秒):Count=1,从无模型→有模型(文件加载完成) + +- 事件#1 (6.6秒):Count=0,状态保持无模型(文档清空准备加载) +- 事件#2 (6.7秒):Count=1,从无模型→有模型(文件加载完成) 场景2:重新打开新文件(4TH_FLOOR) - - 事件#3 (25.6秒):Count=0,从有模型→无模型(旧文件被清空) - - 事件#4 (25.6秒):Count=1,从无模型→有模型(新文件加载完成) + +- 事件#3 (25.6秒):Count=0,从有模型→无模型(旧文件被清空) +- 事件#4 (25.6秒):Count=1,从无模型→有模型(新文件加载完成) 场景3:关闭程序 - - 事件#5 (39.8秒):Count=0,从有模型→无模型(程序关闭前清理) + +- 事件#5 (39.8秒):Count=0,从有模型→无模型(程序关闭前清理) 关键发现 @@ -1896,4 +1899,78 @@ private static void SimpleProgressExample() - 无模型→无模型:初始清理 - 无模型→有模型:文档加载完成 ✅ 这是我们需要初始化的时机 - 有模型→无模型:文档关闭/切换 ✅ 这是我们需要清理的时机 - 3. 时间间隔很短:事件成对出现,间隔仅0.1秒左右 \ No newline at end of file + 3. 时间间隔很短:事件成对出现,间隔仅0.1秒左右 + +### Navisworks Fragment和Path的API分析 + + 从COM API文档可以看到几个关键信息: + + 1. Fragment概念(第8页) + + A Fragment is the representation of (possibly part of) a particular instance + of a Geometry node within the scene graph. Large Geometry may be broken into + multiple Fragments for more efficient displaying. + + 关键点: + +- Fragment是几何体的一个片段或部分 +- 大型几何体会被分割为多个Fragment以提高显示效率 +- 每个Fragment提供访问: + - 完整的Transform3f(从本地到世界坐标空间) + - Fragment代表的Path + - 用于显示的几何体 + - 用于显示的外观 + + 2. Path概念(第8页) + + A Path is the sequence of nodes from the Partition at the root of the + scene graph to a particular node within it that uniquely specifies + a particular instance. + + 关键点: + +- Path是从根分区到特定节点的节点序列 +- Path用来唯一标识特定实例 +- 但这里的"实例"指的是场景图中的节点实例,不是几何体的不同部分 + + 3. 你的地板问题分析 + + 真正的问题: + + COM API明确说明了"大型几何体会被分割为多个Fragment"。你的地板有226个Fragment,这些Fragment都有相同的路径键值 [1,1,1,1,1,1,1],这是完全正常的: + +- 相同Path:因为这226个Fragment都属于同一个ModelItem(地板) +- 不同Fragment:因为地板几何体很复杂,被Navisworks自动分割为226个显示片段 + + 去重逻辑的错误: + + 当前的去重逻辑错误地认为: + // 错误假设:相同路径 = 重复实例 + if (pathKey相同) { + 去重(); // 错误!丢失了地板的225个片段 + } + + 正确的理解: + +- 多实例去重:应该针对不同ModelItem的相同几何体 +- 多片段保留:应该保留同一ModelItem的所有Fragment + + 4. 解决方案建议 + + 需要修改去重逻辑,区分: + + 1. 真正的多实例:不同位置的相同几何体(如多个相同的门) + 2. 几何体分片:同一几何体的多个显示片段(如复杂地板) + + 可能的改进方向: + // 改进的去重逻辑 + var uniqueKey = new { + PathKey = fragment.path.ArrayData, + GeometryHash = fragment.GetGeometryHash(), // 需要调研是否有此API + Transform = fragment.GetLocalToWorldMatrix() // 考虑变换信息 + }; + + 这样可以正确处理: + +- ✅ 相同门窗的多实例 → 正确去重 +- ✅ 地板的多片段 → 正确保留 diff --git a/src/Utils/GeometryHelper.cs b/src/Utils/GeometryHelper.cs index 326536a..131b671 100644 --- a/src/Utils/GeometryHelper.cs +++ b/src/Utils/GeometryHelper.cs @@ -23,29 +23,29 @@ namespace NavisworksTransport public static List ExtractTopViewOutline(ModelItem modelItem, double tolerance = 0.5) { var points = new List(); - + try { LogManager.WriteLog($"开始提取模型 {modelItem.DisplayName} 的几何数据"); LogManager.WriteLog($"模型项类型: {modelItem.GetType().Name}"); LogManager.WriteLog($"模型项GUID: {modelItem.InstanceGuid}"); - + // 详细检查模型是否有几何数据 bool hasGeometry = modelItem.HasGeometry; LogManager.WriteLog($"[关键检查] ModelItem.HasGeometry = {hasGeometry}"); - + if (!hasGeometry) { LogManager.WriteLog("[关键发现] 模型项没有几何数据 - HasGeometry 返回 false"); LogManager.WriteLog($"模型项详细信息:"); LogManager.WriteLog($" - 显示名称: {modelItem.DisplayName}"); LogManager.WriteLog($" - 实例GUID: {modelItem.InstanceGuid}"); - + // 检查子项数量来判断是否为叶节点 int childrenCount = modelItem.Children.Count(); LogManager.WriteLog($" - 子项数量: {childrenCount}"); LogManager.WriteLog($" - 是否为叶节点: {childrenCount == 0}"); - + // 尝试检查包围盒 try { @@ -63,7 +63,7 @@ namespace NavisworksTransport { LogManager.WriteLog($" - 获取包围盒失败: {bboxEx.Message}"); } - + return points; } @@ -73,7 +73,7 @@ namespace NavisworksTransport var targetItem = modelItem; int instanceCount = targetItem.Instances.Count(); LogManager.WriteLog($"原始模型实例数: {instanceCount}"); - + while (targetItem.Instances.Count() > 1) { targetItem = targetItem.Parent; @@ -140,30 +140,30 @@ namespace NavisworksTransport { modelItem }; - + var comState = ComStateManager.GetState(); comSelection = ComApiBridge.ToInwOpSelection(modelCollection); - + LogManager.WriteLog($"COM 选择创建成功,路径数: {comSelection.Paths().Count}"); - // 使用优化的片段去重方法 - var uniqueFragments = GetUniqueFragments(comSelection); - LogManager.WriteLog($"获取到 {uniqueFragments.Count} 个唯一片段"); + // 获取所有片段(已移除错误的去重逻辑) + var allFragments = GetAllFragments(comSelection); + LogManager.WriteLog($"获取到 {allFragments.Count} 个片段"); try { - foreach (var fragmentInfo in uniqueFragments) + foreach (var fragmentInfo in allFragments) { try { var callback = new OptimizedGeometryCallback(fragmentInfo.TransformMatrix); fragmentInfo.Fragment.GenerateSimplePrimitives( - ComApi.nwEVertexProperty.eNORMAL, + ComApi.nwEVertexProperty.eNORMAL, callback); - + var fragmentTriangles = callback.GetTriangles(); triangles.AddRange(fragmentTriangles); - + LogManager.WriteLog($"片段生成了 {fragmentTriangles.Count} 个三角形"); } catch (Exception ex) @@ -175,7 +175,7 @@ namespace NavisworksTransport finally { // 释放所有片段COM对象 - foreach (var fragmentInfo in uniqueFragments) + foreach (var fragmentInfo in allFragments) { try { @@ -208,74 +208,82 @@ namespace NavisworksTransport } /// - /// 获取唯一的片段(解决多实例问题) + /// 获取所有片段(移除错误的去重逻辑) /// /// COM 选择 - /// 唯一片段信息集合 - public static List GetUniqueFragments(ComApi.InwOpSelection selection) + /// 所有片段信息集合 + public static List GetAllFragments(ComApi.InwOpSelection selection) { - var fragmentMap = new Dictionary(); + var fragmentList = new List(); + int totalPathCount = 0; + int totalFragmentCount = 0; try { foreach (ComApi.InwOaPath3 path in selection.Paths()) { + totalPathCount++; + int fragmentsInPath = 0; + try { + LogManager.Debug($"[调试] 处理路径 #{totalPathCount},片段数量: {path.Fragments().Count}"); + foreach (ComApi.InwOaFragment3 fragment in path.Fragments()) { + fragmentsInPath++; + totalFragmentCount++; + try { - // 获取片段的唯一标识 + // 获取片段的路径信息(仅用于日志记录) var pathArray = ((Array)fragment.path.ArrayData).ToArray(); var pathKey = string.Join(",", pathArray); - if (!fragmentMap.ContainsKey(pathKey)) + // 获取变换矩阵 + var transform = (ComApi.InwLTransform3f3)(object)fragment.GetLocalToWorldMatrix(); + + // 🔧 关键修复:预处理Transform矩阵并立即释放COM对象 + double[] transformMatrix = null; + if (transform != null) { - // 获取变换矩阵 - var transform = (ComApi.InwLTransform3f3)(object)fragment.GetLocalToWorldMatrix(); - - // 🔧 关键修复:预处理Transform矩阵并立即释放COM对象 - double[] transformMatrix = null; - if (transform != null) + try { - try - { - // 提取矩阵数据到普通数组 - var matrixArray = (Array)(object)transform.Matrix; - transformMatrix = matrixArray.ToArray(); - LogManager.WriteLog($"成功提取Transform矩阵,16个元素: [{string.Join(",", transformMatrix.Take(4))}...]"); - } - catch (Exception ex) - { - LogManager.WriteLog($"提取Transform矩阵失败: {ex.Message}"); - transformMatrix = null; - } - finally - { - // 立即释放COM对象,避免内存泄露 - Marshal.ReleaseComObject(transform); - } + // 提取矩阵数据到普通数组 + var matrixArray = (Array)(object)transform.Matrix; + transformMatrix = matrixArray.ToArray(); } - - fragmentMap[pathKey] = new FragmentInfo + catch (Exception ex) { - Fragment = fragment, - TransformMatrix = transformMatrix, - PathKey = pathKey - }; + LogManager.Error($"提取Transform矩阵失败: {ex.Message}"); + transformMatrix = null; + } + finally + { + // 立即释放COM对象,避免内存泄露 + Marshal.ReleaseComObject(transform); + } } - // 注意:不在这里释放fragment,因为它需要被调用方使用 + + // 使用递增索引作为唯一标识,确保每个片段都被处理 + var uniqueKey = $"fragment_{totalFragmentCount}"; + + fragmentList.Add(new FragmentInfo + { + Fragment = fragment, + TransformMatrix = transformMatrix, + PathKey = uniqueKey // 使用唯一索引而不是pathKey + }); } catch (Exception ex) { - LogManager.WriteLog($"处理单个片段失败: {ex.Message}"); + LogManager.Error($"处理单个片段失败: {ex.Message}"); } } } catch (Exception ex) { - LogManager.WriteLog($"遍历路径片段失败: {ex.Message}"); + LogManager.Error($"遍历路径片段失败: {ex.Message}"); } finally { @@ -289,10 +297,16 @@ namespace NavisworksTransport } catch (Exception ex) { - LogManager.WriteLog($"获取唯一片段失败: {ex.Message}"); + LogManager.Error($"获取所有片段失败: {ex.Message}"); } - return fragmentMap.Values.ToList(); + LogManager.Info($"[调试] 片段处理总结:"); + LogManager.Info($"[调试] - 总路径数: {totalPathCount}"); + LogManager.Info($"[调试] - 总片段数: {totalFragmentCount}"); + LogManager.Info($"[调试] - 处理片段数: {fragmentList.Count}"); + LogManager.Info($"[调试] - 处理比例: {(totalFragmentCount > 0 ? (double)fragmentList.Count / totalFragmentCount * 100 : 0):F1}%"); + + return fragmentList; } /// @@ -311,15 +325,14 @@ namespace NavisworksTransport // 找到最高的 Z 坐标 var maxZ = triangles.SelectMany(t => new[] { t.Point1.Z, t.Point2.Z, t.Point3.Z }).Max(); - LogManager.WriteLog($"最高 Z 坐标: {maxZ}"); // 筛选顶部表面的三角形 - var topTriangles = triangles.Where(t => - Math.Abs(t.Point1.Z - maxZ) < tolerance && - Math.Abs(t.Point2.Z - maxZ) < tolerance && + var topTriangles = triangles.Where(t => + Math.Abs(t.Point1.Z - maxZ) < tolerance && + Math.Abs(t.Point2.Z - maxZ) < tolerance && Math.Abs(t.Point3.Z - maxZ) < tolerance).ToList(); - LogManager.WriteLog($"顶部三角形数量: {topTriangles.Count}"); + LogManager.Debug($"顶部三角形数量: {topTriangles.Count}"); if (topTriangles.Count == 0) return outlinePoints; @@ -340,21 +353,21 @@ namespace NavisworksTransport // 找到边界边(只出现一次的边) var boundaryEdges = FindBoundaryEdges(edges); - LogManager.WriteLog($"边界边数量: {boundaryEdges.Count}"); + LogManager.Debug($"边界边数量: {boundaryEdges.Count}"); if (boundaryEdges.Count > 0) { // 构建轮廓 var outlinePoints2D = BuildOutlineFromEdges(boundaryEdges); - + // 转换为3D点(使用最高Z坐标) outlinePoints = outlinePoints2D.Select(p => new Point3D(p.X, p.Y, maxZ)).ToList(); - LogManager.WriteLog($"最终轮廓点数量: {outlinePoints.Count}"); + LogManager.Debug($"最终轮廓点数量: {outlinePoints.Count}"); } } catch (Exception ex) { - LogManager.WriteLog($"生成轮廓失败: {ex.Message}"); + LogManager.Error($"生成轮廓失败: {ex.Message}"); } return outlinePoints; @@ -373,8 +386,6 @@ namespace NavisworksTransport try { - LogManager.WriteLog($"开始查找边界边,总边数: {edges.Count}"); - // 统计每条边出现的次数 foreach (var edge in edges) { @@ -446,13 +457,13 @@ namespace NavisworksTransport try { var vertexConnectionCount = new Dictionary(); - + // 统计每个顶点的连接数 foreach (var edge in boundaryEdges) { var startKey = $"{edge.Start.X:F3},{edge.Start.Y:F3}"; var endKey = $"{edge.End.X:F3},{edge.End.Y:F3}"; - + vertexConnectionCount[startKey] = (vertexConnectionCount.ContainsKey(startKey) ? vertexConnectionCount[startKey] : 0) + 1; vertexConnectionCount[endKey] = (vertexConnectionCount.ContainsKey(endKey) ? vertexConnectionCount[endKey] : 0) + 1; } @@ -496,14 +507,14 @@ namespace NavisworksTransport { contourCount++; LogManager.WriteLog($"=== 开始构建第 {contourCount} 个轮廓 ==="); - + var currentContour = BuildSingleContour(remainingEdges); - + if (currentContour.Count >= 3) // 至少3个点才能形成有效轮廓 { LogManager.WriteLog($"第 {contourCount} 个轮廓包含 {currentContour.Count} 个点"); allOutlinePoints.AddRange(currentContour); - + // 添加轮廓分隔符(使用特殊坐标标记) if (contourCount > 1) { @@ -518,7 +529,7 @@ namespace NavisworksTransport } LogManager.WriteLog($"轮廓构建完成,共 {contourCount} 个轮廓,总点数: {allOutlinePoints.Count}"); - + // 如果有多个轮廓,返回最大的外部轮廓 if (contourCount > 1) { @@ -550,7 +561,7 @@ namespace NavisworksTransport // 从第一条边开始 var currentEdge = remainingEdges[0]; remainingEdges.RemoveAt(0); - + contour.Add(currentEdge.Start); var currentPoint = currentEdge.End; var startPoint = currentEdge.Start; @@ -560,7 +571,7 @@ namespace NavisworksTransport // 连接后续的边 int maxIterations = remainingEdges.Count + 10; // 防止无限循环 int iteration = 0; - + while (remainingEdges.Count > 0 && iteration < maxIterations) { iteration++; @@ -570,9 +581,9 @@ namespace NavisworksTransport for (int i = 0; i < remainingEdges.Count; i++) { var edge = remainingEdges[i]; - + // 检查边的起点是否与当前点连接 - if (Math.Abs(edge.Start.X - currentPoint.X) < tolerance && + if (Math.Abs(edge.Start.X - currentPoint.X) < tolerance && Math.Abs(edge.Start.Y - currentPoint.Y) < tolerance) { nextEdgeIndex = i; @@ -580,7 +591,7 @@ namespace NavisworksTransport break; } // 检查边的终点是否与当前点连接(反向) - else if (Math.Abs(edge.End.X - currentPoint.X) < tolerance && + else if (Math.Abs(edge.End.X - currentPoint.X) < tolerance && Math.Abs(edge.End.Y - currentPoint.Y) < tolerance) { nextEdgeIndex = i; @@ -593,9 +604,9 @@ namespace NavisworksTransport { contour.Add(currentPoint); remainingEdges.RemoveAt(nextEdgeIndex); - + // 检查是否回到起点(闭合轮廓) - if (Math.Abs(currentPoint.X - startPoint.X) < tolerance && + if (Math.Abs(currentPoint.X - startPoint.X) < tolerance && Math.Abs(currentPoint.Y - startPoint.Y) < tolerance) { LogManager.WriteLog($"轮廓已闭合,点数: {contour.Count}"); @@ -674,8 +685,8 @@ namespace NavisworksTransport if (contour.Count >= 3) { var area = CalculateContourArea(contour); - LogManager.WriteLog($"轮廓 {i+1} 面积: {area:F2}, 点数: {contour.Count}"); - + LogManager.WriteLog($"轮廓 {i + 1} 面积: {area:F2}, 点数: {contour.Count}"); + if (area > maxArea) { maxArea = area; @@ -725,14 +736,14 @@ namespace NavisworksTransport bool isDuplicate = false; foreach (var existing in uniquePoints) { - if (Math.Abs(point.X - existing.X) < tolerance && + if (Math.Abs(point.X - existing.X) < tolerance && Math.Abs(point.Y - existing.Y) < tolerance) { isDuplicate = true; break; } } - + if (!isDuplicate) { uniquePoints.Add(point); @@ -797,7 +808,7 @@ namespace NavisworksTransport foreach (var point in remainingPoints) { var distance = Math.Sqrt( - Math.Pow(point.X - currentPoint.X, 2) + + Math.Pow(point.X - currentPoint.X, 2) + Math.Pow(point.Y - currentPoint.Y, 2) ); @@ -813,7 +824,7 @@ namespace NavisworksTransport sortedPoints.Add(nearestPoint.Value); remainingPoints.Remove(nearestPoint.Value); currentPoint = nearestPoint.Value; - + LogManager.WriteLog($"下一个点: ({nearestPoint.Value.X:F2}, {nearestPoint.Value.Y:F2}), 距离: {minDistance:F2}"); } else @@ -829,10 +840,10 @@ namespace NavisworksTransport var firstPoint = sortedPoints[0]; var lastPoint = sortedPoints[sortedPoints.Count - 1]; var closingDistance = Math.Sqrt( - Math.Pow(lastPoint.X - firstPoint.X, 2) + + Math.Pow(lastPoint.X - firstPoint.X, 2) + Math.Pow(lastPoint.Y - firstPoint.Y, 2) ); - + LogManager.WriteLog($"轮廓闭合距离: {closingDistance:F2}"); } } @@ -1027,7 +1038,7 @@ namespace NavisworksTransport public OptimizedGeometryCallback(double[] transformMatrix) { _transformMatrix = transformMatrix; - + if (_transformMatrix != null) { LogManager.WriteLog($"几何回调处理器使用预处理矩阵,元素数量: {_transformMatrix.Length}"); @@ -1219,4 +1230,4 @@ namespace NavisworksTransport return $"Edge2D[{Start} -> {End}]"; } } -} \ No newline at end of file +} \ No newline at end of file