solidworks自动标注折弯4 无向图 c#

bash 复制代码
全局注释文字高度设置为 0.0035,结果: True
找到折弯特征边线折弯1
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
未找到可用二级面:保留折弯节点,仅参与图结构/跨节点处理
找到折弯特征边线折弯2
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
找到 2 个二级面
找到折弯特征边线折弯4
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
找到 2 个二级面
折弯图构建完成:节点=3,边=2
折弯图已保存: E:\cqh\code\my_c#\c#_sw\share\src\drw\bend_graph.json
跳过内圆弧侧一级面(面积 = 969.66 mm^2)
跳过内圆弧侧一级面(面积 = 895.83 mm^2)
节点内配对尝试:一级面面积 = 895.83 mm^2, 二级面面积 = 69.44 mm^2, 距离 = 21.10 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [二级面] 候选标注边,长度: 2.00 mm
  [二级面] 候选标注边,长度: 2.00 mm
节点内配对成功:已创建尺寸
跳过内圆弧侧一级面(面积 = 895.83 mm^2)
跳过内圆弧侧一级面(面积 = 659.72 mm^2)
节点内配对尝试:一级面面积 = 969.66 mm^2, 二级面面积 = 69.44 mm^2, 距离 = 40.07 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [二级面] 候选标注边,长度: 2.00 mm
  [二级面] 候选标注边,长度: 2.00 mm
节点内配对成功:已创建尺寸
跳过内圆弧侧一级面(面积 = 969.66 mm^2)
跳过内圆弧侧一级面(面积 = 621.53 mm^2)
节点间配对尝试:NodeA=0, NodeB=1, A面积=969.66 mm^2, B面积=659.72 mm^2, 距离=42.52 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 19.00 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 19.00 mm
节点间配对成功:NodeA=0, NodeB=1, A面积=969.66 mm^2, B面积=659.72 mm^2
节点间配对尝试:NodeA=0, NodeB=2, A面积=895.83 mm^2, B面积=621.53 mm^2, 距离=41.20 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 17.90 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 17.90 mm
节点间配对成功:NodeA=0, NodeB=2, A面积=895.83 mm^2, B面积=621.53 mm^2
标注完成,共 4 个尺寸,涉及 2/3 个折弯节点
全局注释文字高度设置为 0.0035,结果: True
找到折弯特征边线折弯1
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
未找到可用二级面:保留折弯节点,仅参与图结构/跨节点处理
找到折弯特征边线折弯2
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
找到 2 个二级面
找到折弯特征边线折弯4
最大圆柱面面积: 114.537104455109 mm^2
识别到折弯内外圆柱面:R内=0.10 mm, R外=2.10 mm
找到 4 条与轴线平行的边
找到 4 个下一级面
其中外圆柱来源一级面: 2 个
二级面候选为圆柱面,已按规则跳过
二级面候选为圆柱面,已按规则跳过
找到 2 个二级面
折弯图构建完成:节点=3,边=2
折弯图已保存: E:\cqh\code\my_c#\c#_sw\share\src\drw\bend_graph.json
跳过内圆弧侧一级面(面积 = 969.66 mm^2)
跳过内圆弧侧一级面(面积 = 895.83 mm^2)
节点内配对尝试:一级面面积 = 895.83 mm^2, 二级面面积 = 69.44 mm^2, 距离 = 21.10 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [二级面] 候选标注边,长度: 2.00 mm
  [二级面] 候选标注边,长度: 2.00 mm
节点内配对成功:已创建尺寸
跳过内圆弧侧一级面(面积 = 895.83 mm^2)
跳过内圆弧侧一级面(面积 = 659.72 mm^2)
节点内配对尝试:一级面面积 = 969.66 mm^2, 二级面面积 = 69.44 mm^2, 距离 = 40.07 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [二级面] 候选标注边,长度: 2.00 mm
  [二级面] 候选标注边,长度: 2.00 mm
节点内配对成功:已创建尺寸
跳过内圆弧侧一级面(面积 = 969.66 mm^2)
跳过内圆弧侧一级面(面积 = 621.53 mm^2)
节点间配对尝试:NodeA=0, NodeB=1, A面积=969.66 mm^2, B面积=659.72 mm^2, 距离=42.52 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [一级面] 候选标注边,长度: 27.93 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 19.00 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 19.00 mm
节点间配对成功:NodeA=0, NodeB=1, A面积=969.66 mm^2, B面积=659.72 mm^2
节点间配对尝试:NodeA=0, NodeB=2, A面积=895.83 mm^2, B面积=621.53 mm^2, 距离=41.20 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [一级面] 候选标注边,长度: 25.80 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 17.90 mm
  [节点间外圆柱非连接一级面] 候选标注边,长度: 17.90 mm
节点间配对成功:NodeA=0, NodeB=2, A面积=895.83 mm^2, B面积=621.53 mm^2
标注完成,共 4 个尺寸,涉及 2/3 个折弯节点
csharp 复制代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;

namespace tools
{
    public class benddim
    {
        class BendNode
        {
            public Feature? BendFeature;
            public Face? MainCylinderFace;
            public Face? InnerCylinderFace;
            public Face? OuterCylinderFace;
            public double[]? Axis;
            public double[]? Center;
            public List<Face> FirstLevelFaces = new List<Face>();
            public List<Face> OuterFirstLevelFaces = new List<Face>();
            public List<(Face face, string level, Face sourceFirstFace)> SecondaryFaces = new List<(Face face, string level, Face sourceFirstFace)>();
        }

        class BendEdge
        {
            public int NodeA;
            public int NodeB;
            public Face? ConnectedFirstFaceA;
            public Face? ConnectedFirstFaceB;
        }

        class BendGraphDump
        {
            public DateTime CreatedAt;
            public int NodeCount;
            public int EdgeCount;
            public List<object> Nodes = new List<object>();
            public List<object> Edges = new List<object>();
        }

        public static void AddBendDimensions(ISldWorks swApp)
        {
            try
            {
                var swModel = (ModelDoc2)swApp.ActiveDoc;
                swApp.CommandInProgress = true;
                if (swModel == null) { Console.WriteLine("没有活动文档"); return; }

                var swSelMgr = (SelectionMgr)swModel.SelectionManager;
                if (swSelMgr.GetSelectedObjectType3(1, -1) != (int)swSelectType_e.swSelDRAWINGVIEWS)
                { Console.WriteLine("请先选择一个视图"); return; }

                var view = (View)swSelMgr.GetSelectedObject(1);
                var partDoc = (PartDoc)view.ReferencedDocument;
                if (partDoc == null) { Console.WriteLine("无法获取零件文档"); return; }
                ApplyGlobalAnnotationTextHeight((ModelDoc2)partDoc);

                var xformData = (double[])view.ModelToViewTransform.ArrayData;
                var bounds = (double[])view.GetOutline();

                int count = 0;
                int totalDimensions = 0;
                double offset = 0.005;
                
                // 全局已标注面组合集合,用于跨折弯去重
                var dimensionedPairs = new HashSet<string>();
                var bendNodes = new List<BendNode>();

                // 遍历 Body 的特征找折弯
                foreach (Body2 body in (object[])partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false))
                {
                    foreach (Feature feat in (object[])body.GetFeatures())
                    {
                        var subFeat = (Feature)feat.GetFirstSubFeature();
                        while (subFeat != null)
                        {
                            if (subFeat.GetTypeName() == "OneBend" )
                            {
                                Console.WriteLine("找到折弯特征" + subFeat.Name);
                                var node = BuildBendNode(subFeat);
                                if (node != null)
                                    bendNodes.Add(node);
                            }

                            subFeat = (Feature)subFeat.GetNextSubFeature();
                        }
                    }
                }

                var graphEdges = BuildBendEdges(bendNodes);
                Console.WriteLine($"折弯图构建完成:节点={bendNodes.Count},边={graphEdges.Count}");
                SaveBendGraphAsJson(bendNodes, graphEdges);

                foreach (var node in bendNodes)
                {
                    int dimCount = ProcessBendNode(swModel, view, xformData, bounds, node, ref offset, dimensionedPairs);
                    totalDimensions += dimCount;
                    if (dimCount > 0)
                        count++;
                }

                totalDimensions += ProcessGraphEdges(swModel, view, xformData, bounds, bendNodes, graphEdges, ref offset, dimensionedPairs);

                Console.WriteLine($"标注完成,共 {totalDimensions} 个尺寸,涉及 {count}/{bendNodes.Count} 个折弯节点");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"错误: {ex.Message}");
            }
        }

        static List<BendEdge> BuildBendEdges(List<BendNode> nodes)
        {
            var edges = new List<BendEdge>();
            for (int i = 0; i < nodes.Count; i++)
            {
                for (int j = i + 1; j < nodes.Count; j++)
                {
                    Face? connectedA = null;
                    Face? connectedB = null;
                    foreach (var fa in nodes[i].FirstLevelFaces)
                    {
                        foreach (var fb in nodes[j].FirstLevelFaces)
                        {
                            if (!IsSameFace(fa, fb)) continue;
                            connectedA = fa;
                            connectedB = fb;
                            break;
                        }
                        if (connectedA != null) break;
                    }
                    if (connectedA == null || connectedB == null) continue;

                    edges.Add(new BendEdge
                    {
                        NodeA = i,
                        NodeB = j,
                        ConnectedFirstFaceA = connectedA,
                        ConnectedFirstFaceB = connectedB
                    });
                }
            }
            return edges;
        }

        static BendNode? BuildBendNode(Feature bendFeat)
        {
            var node = new BendNode { BendFeature = bendFeat };

            // 1. 找最大圆柱面(节点主圆柱)
            Face cylFace = null;
            double[] axis = null;
            double[] center = null;
            double maxArea = 0;

            foreach (Face f in (object[])bendFeat.GetFaces())
            {
                var s = (Surface)f.GetSurface();
                if (s.IsCylinder())
                {
                    double area = f.GetArea();
                    if (area > maxArea)
                    {
                        maxArea = area;
                        var p = (double[])s.CylinderParams;
                        axis = new[] { p[3], p[4], p[5] };
                        center = new[] { p[0], p[1], p[2] };
                        cylFace = f;
                    }
                }
            }
            if (cylFace == null) return null;
            Console.WriteLine("最大圆柱面面积: " + maxArea * 1000000 + " mm^2");
            node.MainCylinderFace = cylFace;
            node.Axis = axis;
            node.Center = center;

            // 1.1 当前折弯通常有一对内/外圆柱面:半径小的是内圆柱,半径大的是外圆柱
            Face bendInnerCylinderFace = null;
            Face bendOuterCylinderFace = null;
            double minRadius = double.MaxValue;
            double maxRadius = double.MinValue;
            foreach (Face f in (object[])bendFeat.GetFaces())
            {
                if (!TryGetCylinderData(f, out _, out var cylAxis, out var radius)) continue;
                if (!IsParallel(cylAxis, axis)) continue;

                if (radius < minRadius)
                {
                    minRadius = radius;
                    bendInnerCylinderFace = f;
                }

                if (radius > maxRadius)
                {
                    maxRadius = radius;
                    bendOuterCylinderFace = f;
                }
            }
            if (bendInnerCylinderFace != null && bendOuterCylinderFace != null && bendInnerCylinderFace != bendOuterCylinderFace)
                Console.WriteLine($"识别到折弯内外圆柱面:R内={minRadius * 1000:F2} mm, R外={maxRadius * 1000:F2} mm");
            node.InnerCylinderFace = bendInnerCylinderFace;
            node.OuterCylinderFace = bendOuterCylinderFace;

            // 2/3. 从外圆柱和内圆柱都收集一级面(每个节点应包含两侧共4个一级面)
            var firstLevelFaces = new List<Face>();
            int parallelEdgeCount = 0;
            var outerFirstLevelFaces = new List<Face>();
            if (bendOuterCylinderFace != null)
                parallelEdgeCount += CollectFirstLevelFacesFromCylinder(bendOuterCylinderFace, axis, firstLevelFaces, outerFirstLevelFaces);
            if (bendInnerCylinderFace != null && bendInnerCylinderFace != bendOuterCylinderFace)
                parallelEdgeCount += CollectFirstLevelFacesFromCylinder(bendInnerCylinderFace, axis, firstLevelFaces);

            Console.WriteLine("找到 " + parallelEdgeCount + " 条与轴线平行的边");
            if (firstLevelFaces.Count == 0) return null;
            Console.WriteLine("找到 " + firstLevelFaces.Count + " 个下一级面");
            Console.WriteLine("其中外圆柱来源一级面: " + outerFirstLevelFaces.Count + " 个");
            node.FirstLevelFaces = firstLevelFaces;
            node.OuterFirstLevelFaces = outerFirstLevelFaces;

            // 4. 在下一级面里找与圆柱轴平行的最远的线,然后取相交面
            var secondLevelPairs = new List<(Face sourceFirstFace, Face secondFace)>();
            foreach (var face in firstLevelFaces)
            {
                Edge farthestEdge = null;
                double maxDist = -1;

                foreach (Edge e in (object[])face.GetEdges())
                {
                    var c = (Curve)e.GetCurve();
                    if (c.IsLine())
                    {
                        var lineParams = (double[])c.LineParams;
                        var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                        if (IsParallel(edgeDir, axis))
                        {
                            // 计算边到圆柱中心的距离
                            var pt = new[] { lineParams[0], lineParams[1], lineParams[2] };
                            double dist = PointToAxisDistance(pt, center, axis);
                            if (dist > maxDist)
                            {
                                maxDist = dist;
                                farthestEdge = e;
                            }
                        }
                    }
                }

                if (farthestEdge != null)
                {
                    foreach (Face f in (object[])farthestEdge.GetTwoAdjacentFaces())
                    {
                        if (f == face) continue;
                        var sf = (Surface)f.GetSurface();
                        if (sf.IsCylinder())
                        {
                            Console.WriteLine("二级面候选为圆柱面,已按规则跳过");
                            continue;
                        }

                        if (!secondLevelPairs.Any(x => x.sourceFirstFace == face && x.secondFace == f))
                        {
                            secondLevelPairs.Add((face, f));
                        }
                    }
                }
            }
            if (secondLevelPairs.Count == 0)
            {
                Console.WriteLine("未找到可用二级面:保留折弯节点,仅参与图结构/跨节点处理");
            }
            else
            {
                Console.WriteLine("找到 " + secondLevelPairs.Count + " 个二级面");
            }

            // 5. 仅使用二级面,不再继续获取第三级面
            var secondaryFaces = new List<(Face face, string level, Face sourceFirstFace)>(); // 面及其级别+来源一级面
            foreach (var pair in secondLevelPairs)
            {
                var sourceFirstFace = pair.sourceFirstFace;
                var face = pair.secondFace;
                if (!secondaryFaces.Any(x => x.face == face && x.sourceFirstFace == sourceFirstFace))
                {
                    secondaryFaces.Add((face, "二级面", sourceFirstFace));
                }
            }
            node.SecondaryFaces = secondaryFaces;

            return node;
        }

        static int ProcessBendNode(ModelDoc2 swModel, View view, double[] xformData, double[] bounds, BendNode node, ref double offset, HashSet<string> dimensionedPairs)
        {
            var axis = node.Axis;
            if (axis == null) return 0;
            var firstLevelFaces = node.FirstLevelFaces;
            var secondaryFaces = node.SecondaryFaces;

            // 6. 为每个一级面找到对应的二级面并标注
            // 使用全局已标注面集合防止重复标注(跨折弯共享)
            int dimensionCount = 0;
            
            foreach (var firstFace in firstLevelFaces)
            {
                // 规则:内圆弧面的一级面不参与和二级面的节点内配对
                if (node.InnerCylinderFace != null && FacesIntersect(firstFace, node.InnerCylinderFace))
                {
                    Console.WriteLine($"跳过内圆弧侧一级面(面积 = {firstFace.GetArea() * 1000000:F2} mm^2)");
                    continue;
                }

                var s1 = (Surface)firstFace.GetSurface();
                if (!s1.IsPlane()) continue;

                // 确定放置方式
                string placement = GetPlacement(firstFace, xformData);
                if (placement == "none") continue;

                // 为当前一级面生成所有可用二级面候选,并按距离从远到近尝试
                var candidates = new List<(Face secFace, string level, string pairKey, double dist)>();
                foreach (var item in secondaryFaces)
                {
                    if (item.sourceFirstFace == firstFace) continue;
                    var secFace = item.face;
                    if (secFace == firstFace) continue;
                    if (FacesIntersect(firstFace, secFace)) continue;

                    var pairKey = GetFacePairKey(firstFace, secFace, item.level);
                    if (dimensionedPairs.Contains(pairKey))
                    {
                        Console.WriteLine($"跳过已标注组合:一级面面积 = {firstFace.GetArea() * 1000000:F2} mm^2, {item.level}面积 = {secFace.GetArea() * 1000000:F2} mm^2");
                        continue;
                    }

                    double dist = FaceToFaceDistance(firstFace, secFace);
                    candidates.Add((secFace, item.level, pairKey, dist));
                }

                if (candidates.Count == 0) continue;
                candidates.Sort((a, b) => b.dist.CompareTo(a.dist));

                bool placed = false;
                foreach (var cand in candidates)
                {
                    Console.WriteLine($"节点内配对尝试:一级面面积 = {firstFace.GetArea() * 1000000:F2} mm^2, {cand.level}面积 = {cand.secFace.GetArea() * 1000000:F2} mm^2, 距离 = {cand.dist * 1000:F2} mm");

                    // 标注 - 获取3D边并在视图中查找对应可见边
                    var edgeCandidates1 = GetEdgesForDimension(firstFace, axis, "一级面");
                    var edgeCandidates2 = GetEdgesForDimension(cand.secFace, axis, cand.level);
                    if (edgeCandidates2.Count == 0)
                        Console.WriteLine($"[{cand.level}] 未找到不平行于轴线的直边,按规则跳过该候选");

                    Edge visEdge1 = null, visEdge2 = null;

                    foreach (var e1 in edgeCandidates1)
                    {
                        visEdge1 = FindVisibleEdge(view, e1);
                        if (visEdge1 != null) break;
                    }

                    if (visEdge1 == null)
                    {
                        Console.WriteLine("一级面的标注边在视图中均不可见,继续尝试下一个候选配对");
                        continue;
                    }

                    foreach (var e2 in edgeCandidates2)
                    {
                        visEdge2 = FindVisibleEdge(view, e2);
                        if (visEdge2 != null) break;
                    }

                    if (visEdge2 == null)
                    {
                        Console.WriteLine("二级面的标注边在视图中均不可见,继续尝试下一个候选配对");
                        continue;
                    }

                    // 创建 SelectData 并设置视图上下文
                    var selMgr = (SelectionMgr)swModel.SelectionManager;
                    var selData = selMgr.CreateSelectData();
                    selData.View = view;

                    // 将可见边转换为 Entity 并选择
                    ((Entity)visEdge1).Select4(true, selData);
                    ((Entity)visEdge2).Select4(true, selData);

                    double x, y;
                    if (placement == "h")
                    {
                        x = (bounds[0] + bounds[2]) / 2;
                        y = bounds[3] - offset;
                    }
                    else
                    {
                        y = (bounds[1] + bounds[3]) / 2;
                        x = bounds[0] + offset;
                    }

                    var addedDimension = swModel.AddDimension2(x, y, 0) as DisplayDimension;
                    swModel.ClearSelection2(true);
                    if (addedDimension == null)
                    {
                        Console.WriteLine("AddDimension2 返回空,继续尝试下一个候选配对");
                        continue;
                    }

                    // 仅在成功添加尺寸后才去重,避免失败后误占配对
                    dimensionedPairs.Add(cand.pairKey);
                    offset += 0.005;
                    dimensionCount++;
                    placed = true;
                    Console.WriteLine("节点内配对成功:已创建尺寸");
                    break;
                }

                if (!placed)
                    Console.WriteLine($"节点内配对失败:一级面面积 = {firstFace.GetArea() * 1000000:F2} mm^2,所有候选均不可标注");
            }

            return dimensionCount;
        }

        static int ProcessGraphEdges(ModelDoc2 swModel, View view, double[] xformData, double[] bounds, List<BendNode> nodes, List<BendEdge> edges, ref double offset, HashSet<string> dimensionedPairs)
        {
            int dimensionCount = 0;
            foreach (var edge in edges)
            {
                var connectedFaceA = edge.ConnectedFirstFaceA;
                var connectedFaceB = edge.ConnectedFirstFaceB;
                var axisA = nodes[edge.NodeA].Axis;
                if (connectedFaceA == null || connectedFaceB == null || axisA == null) continue;

                // 节点间规则:完全跳过连接面,只处理两节点外圆柱来源一级面中的非连接面配对
                var nodeAOuterFaces = nodes[edge.NodeA].OuterFirstLevelFaces;
                var nodeBOuterFaces = nodes[edge.NodeB].OuterFirstLevelFaces;
                if (nodeAOuterFaces.Count == 0 || nodeBOuterFaces.Count == 0) continue;

                var pairCandidates = new List<(Face faceA, Face faceB, string pairKey, double dist)>();
                foreach (var faceA in nodeAOuterFaces)
                {
                    if (IsSameFace(faceA, connectedFaceA)) continue;
                    foreach (var faceB in nodeBOuterFaces)
                    {
                        if (IsSameFace(faceB, connectedFaceB)) continue;
                        if (IsSameFace(faceA, faceB)) continue;
                        if (FacesIntersect(faceA, faceB)) continue;

                        var pairKey = GetFacePairKey(faceA, faceB, "节点间外圆柱非连接一级面");
                        if (dimensionedPairs.Contains(pairKey)) continue;

                        double dist = FaceToFaceDistance(faceA, faceB);
                        pairCandidates.Add((faceA, faceB, pairKey, dist));
                    }
                }

                if (pairCandidates.Count == 0)
                {
                    Console.WriteLine($"节点间未找到可标注的外圆柱非连接面配对:NodeA={edge.NodeA}, NodeB={edge.NodeB}");
                    continue;
                }

                pairCandidates.Sort((a, b) => b.dist.CompareTo(a.dist));
                bool placed = false;
                foreach (var candidate in pairCandidates)
                {
                    var areaA = candidate.faceA.GetArea() * 1000000;
                    var areaB = candidate.faceB.GetArea() * 1000000;
                    Console.WriteLine(
                        $"节点间配对尝试:NodeA={edge.NodeA}, NodeB={edge.NodeB}, A面积={areaA:F2} mm^2, B面积={areaB:F2} mm^2, 距离={candidate.dist * 1000:F2} mm");

                    if (!TryAddDimension(swModel, view, xformData, bounds, candidate.faceA, candidate.faceB, axisA, "节点间外圆柱非连接一级面", ref offset))
                        continue;

                    dimensionedPairs.Add(candidate.pairKey);
                    dimensionCount++;
                    placed = true;
                    Console.WriteLine(
                        $"节点间配对成功:NodeA={edge.NodeA}, NodeB={edge.NodeB}, A面积={areaA:F2} mm^2, B面积={areaB:F2} mm^2");
                    break;
                }

                if (!placed)
                    Console.WriteLine($"节点间配对失败:NodeA={edge.NodeA}, NodeB={edge.NodeB},候选均不可标注");
            }
            return dimensionCount;
        }

        static bool TryAddDimension(ModelDoc2 swModel, View view, double[] xformData, double[] bounds, Face face1, Face face2, double[] axis, string level, ref double offset)
        {
            string placement = GetPlacement(face1, xformData);
            if (placement == "none") return false;

            var edgeCandidates1 = GetEdgesForDimension(face1, axis, "一级面");
            var edgeCandidates2 = GetEdgesForDimension(face2, axis, level);
            if (edgeCandidates2.Count == 0) return false;
            Edge visEdge1 = null, visEdge2 = null;

            foreach (var e1 in edgeCandidates1)
            {
                visEdge1 = FindVisibleEdge(view, e1);
                if (visEdge1 != null) break;
            }
            if (visEdge1 == null) return false;

            foreach (var e2 in edgeCandidates2)
            {
                visEdge2 = FindVisibleEdge(view, e2);
                if (visEdge2 != null) break;
            }
            if (visEdge2 == null) return false;

            var selMgr = (SelectionMgr)swModel.SelectionManager;
            var selData = selMgr.CreateSelectData();
            selData.View = view;
            ((Entity)visEdge1).Select4(true, selData);
            ((Entity)visEdge2).Select4(true, selData);

            double x, y;
            if (placement == "h")
            {
                x = (bounds[0] + bounds[2]) / 2;
                y = bounds[3] - offset;
            }
            else
            {
                y = (bounds[1] + bounds[3]) / 2;
                x = bounds[0] + offset;
            }
            swModel.AddDimension2(x, y, 0);
            offset += 0.005;
            swModel.ClearSelection2(true);
            return true;
        }

        static bool FacesIntersect(Face faceA, Face faceB)
        {
            foreach (Edge e in (object[])faceA.GetEdges())
            {
                foreach (Face adj in (object[])e.GetTwoAdjacentFaces())
                {
                    if (adj == faceB) return true;
                }
            }
            return false;
        }

        static bool IsSameFace(Face faceA, Face faceB)
        {
            if (faceA == null || faceB == null) return false;
            if (Object.ReferenceEquals(faceA, faceB)) return true;
            return faceA.GetHashCode() == faceB.GetHashCode();
        }

        static double FaceToFaceDistance(Face faceA, Face faceB)
        {
            var pa = GetFaceRepresentativePoint(faceA);
            var pb = GetFaceRepresentativePoint(faceB);
            if (pa == null || pb == null) return 0;
            return Distance3D(pa, pb);
        }

        static double[]? GetFaceRepresentativePoint(Face face)
        {
            foreach (Edge e in (object[])face.GetEdges())
            {
                var v = (Vertex)e.GetStartVertex();
                if (v == null) continue;
                var pt = (double[])v.GetPoint();
                if (pt != null && pt.Length >= 3) return new[] { pt[0], pt[1], pt[2] };
            }
            return null;
        }

        static double Distance3D(double[] a, double[] b)
        {
            var dx = a[0] - b[0];
            var dy = a[1] - b[1];
            var dz = a[2] - b[2];
            return Math.Sqrt(dx * dx + dy * dy + dz * dz);
        }

        static int CollectFirstLevelFacesFromCylinder(Face cylFace, double[] axis, List<Face> firstLevelFaces, List<Face>? sourceFacesCollector = null)
        {
            int parallelCount = 0;
            foreach (Edge e in (object[])cylFace.GetEdges())
            {
                var c = (Curve)e.GetCurve();
                if (!c.IsLine()) continue;

                var lineParams = (double[])c.LineParams;
                var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                if (!IsParallel(edgeDir, axis)) continue;

                parallelCount++;
                foreach (Face f in (object[])e.GetTwoAdjacentFaces())
                {
                    if (f == cylFace) continue;
                    if (!firstLevelFaces.Contains(f))
                        firstLevelFaces.Add(f);

                    if (sourceFacesCollector != null && !sourceFacesCollector.Contains(f))
                        sourceFacesCollector.Add(f);
                }
            }
            return parallelCount;
        }

        static bool IsParallel(double[] a, double[] b)
        {
            double dot = Math.Abs(a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
            double ma = Math.Sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
            double mb = Math.Sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
            return Math.Abs(dot - ma * mb) < 0.001;
        }

        static double PointToAxisDistance(double[] point, double[] axisCenter, double[] axisDir)
        {
            // 计算点到轴线的距离
            // 向量从轴线中心指向点
            double[] v = { point[0] - axisCenter[0], point[1] - axisCenter[1], point[2] - axisCenter[2] };
            
            // 投影到轴线方向
            double dot = v[0] * axisDir[0] + v[1] * axisDir[1] + v[2] * axisDir[2];
            double axisLen = Math.Sqrt(axisDir[0] * axisDir[0] + axisDir[1] * axisDir[1] + axisDir[2] * axisDir[2]);
            double proj = dot / axisLen;
            
            // 投影点
            double[] projPoint = {
                axisCenter[0] + proj * axisDir[0] / axisLen,
                axisCenter[1] + proj * axisDir[1] / axisLen,
                axisCenter[2] + proj * axisDir[2] / axisLen
            };
            
            // 距离
            double dx = point[0] - projPoint[0];
            double dy = point[1] - projPoint[1];
            double dz = point[2] - projPoint[2];
            return Math.Sqrt(dx * dx + dy * dy + dz * dz);
        }

        static bool TryGetCylinderData(Face face, out double[] center, out double[] axis, out double radius)
        {
            center = Array.Empty<double>();
            axis = Array.Empty<double>();
            radius = 0;

            var s = (Surface)face.GetSurface();
            if (!s.IsCylinder()) return false;

            var p = (double[])s.CylinderParams;
            if (p == null || p.Length < 7) return false;

            center = new[] { p[0], p[1], p[2] };
            axis = new[] { p[3], p[4], p[5] };
            radius = p[6];
            return true;
        }

        static void SaveBendGraphAsJson(List<BendNode> nodes, List<BendEdge> edges)
        {
            try
            {
                var dump = new BendGraphDump
                {
                    CreatedAt = DateTime.Now,
                    NodeCount = nodes.Count,
                    EdgeCount = edges.Count
                };

                for (int i = 0; i < nodes.Count; i++)
                {
                    var n = nodes[i];
                    var nodeObj = new
                    {
                        index = i,
                        featureName = n.BendFeature?.Name ?? "",
                        mainCylinderAreaMm2 = n.MainCylinderFace != null ? n.MainCylinderFace.GetArea() * 1000000 : 0,
                        innerRadiusMm = TryGetCylinderRadiusMm(n.InnerCylinderFace),
                        outerRadiusMm = TryGetCylinderRadiusMm(n.OuterCylinderFace),
                        firstLevelFaceAreasMm2 = n.FirstLevelFaces.Select(f => Math.Round(f.GetArea() * 1000000, 3)).ToList()
                    };
                    dump.Nodes.Add(nodeObj);
                }

                foreach (var e in edges)
                {
                    var edgeObj = new
                    {
                        nodeA = e.NodeA,
                        nodeB = e.NodeB,
                        connectedFaceAAreaMm2 = e.ConnectedFirstFaceA != null ? Math.Round(e.ConnectedFirstFaceA.GetArea() * 1000000, 3) : 0,
                        connectedFaceBAreaMm2 = e.ConnectedFirstFaceB != null ? Math.Round(e.ConnectedFirstFaceB.GetArea() * 1000000, 3) : 0
                    };
                    dump.Edges.Add(edgeObj);
                }

                var outPath = Path.Combine("E:\\cqh\\code\\my_c#\\c#_sw\\share\\src\\drw", "bend_graph.json");
                var json = JsonConvert.SerializeObject(dump, Formatting.Indented);
                File.WriteAllText(outPath, json);
                Console.WriteLine($"折弯图已保存: {outPath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"保存折弯图JSON失败: {ex.Message}");
            }
        }

        static double TryGetCylinderRadiusMm(Face? face)
        {
            if (face == null) return 0;
            if (!TryGetCylinderData(face, out _, out _, out var radius)) return 0;
            return Math.Round(radius * 1000, 3);
        }

        static string GetPlacement(Face face, double[] xf)
        {
            var s = (Surface)face.GetSurface();
            if (!s.IsPlane()) return "none";
            var p = (double[])s.PlaneParams;
            double nx = Math.Abs(p[0]), ny = Math.Abs(p[1]), nz = Math.Abs(p[2]);

            if (nx > ny && nx > nz)
                return Math.Round(xf[0]) != 0 ? "h" : (Math.Round(xf[1]) != 0 ? "v" : "none");
            if (ny > nx && ny > nz)
                return Math.Round(xf[3]) != 0 ? "h" : (Math.Round(xf[4]) != 0 ? "v" : "none");
            if (nz > nx && nz > ny)
                return Math.Round(xf[6]) != 0 ? "h" : (Math.Round(xf[7]) != 0 ? "v" : "none");
            return "none";
        }

        /// <summary>
        /// 获取面上所有不与轴线平行的直边,按长度降序排列(用于尺寸标注)
        /// 折弯标注需要选择不平行于折弯轴线的边,返回多条候选边供视图中逐个匹配
        /// </summary>
        static List<Edge> GetEdgesForDimension(Face face, double[] axis, string faceLevel = "", bool allowParallelEdges = false)
        {
            var candidates = new List<(Edge edge, double len)>();

            foreach (Edge e in (object[])face.GetEdges())
            {
                var c = (Curve)e.GetCurve();
                if (c.IsLine())
                {
                    var lineParams = (double[])c.LineParams;
                    var edgeDir = new[] { lineParams[3], lineParams[4], lineParams[5] };
                    
                    // 默认跳过与轴线平行的边;兜底模式允许保留平行边
                    if (!allowParallelEdges && IsParallel(edgeDir, axis))
                        continue;

                    double startParam = 0, endParam = 0;
                    bool isClosed = false, isPeriodic = false;
                    c.GetEndParams(out startParam, out endParam, out isClosed, out isPeriodic);
                    double len = Math.Abs(endParam - startParam);
                    
                    candidates.Add((e, len));
                }
            }

            // 按长度降序排列,优先使用长边
            candidates.Sort((a, b) => b.len.CompareTo(a.len));
            
            var label = string.IsNullOrEmpty(faceLevel) ? "" : $"[{faceLevel}] ";
            foreach (var item in candidates)
                Console.WriteLine($"  {label}候选标注边,长度: {item.len * 1000:F2} mm");
            
            if (candidates.Count == 0)
                Console.WriteLine($"  {label}未找到不平行于轴线的直边");

            return candidates.Select(x => x.edge).ToList();
        }

        /// <summary>
        /// 在视图中查找3D边对应的可见边
        /// 按组件获取可见边(返回可转换为Edge的对象),通过3D端点坐标近似匹配解决COM封送问题
        /// 注意:IDrawingEdge接口在SolidWorks C#互操作中不存在,不能类型转换;
        ///       GetVisibleEntities(null,...) 返回的对象也是__ComObject无法转换;
        ///       正确做法是用 GetVisibleEntities(comp,...) 按组件获取,返回的对象可转换为Edge
        /// </summary>
        static Edge FindVisibleEdge(View view, Edge modelEdge)
        {
            // 获取目标3D边的端点坐标
            var startVert = (Vertex)modelEdge.GetStartVertex();
            var endVert = (Vertex)modelEdge.GetEndVertex();
            if (startVert == null || endVert == null) return null;

            var edgeStart = (double[])startVert.GetPoint();
            var edgeEnd = (double[])endVert.GetPoint();
            if (edgeStart == null || edgeEnd == null) return null;

            // 获取视图中的可见组件
            var visibleComps = (object[])view.GetVisibleComponents();
            if (visibleComps == null) return null;

            foreach (Component2 comp in visibleComps)

            {
                if (comp == null) continue;

                // 按组件获取可见边(与 GetVisibleEntities(null,...) 不同,按组件获取返回可转换为Edge的对象)
                var visibleEdges = (object[])view.GetVisibleEntities(
                    comp, (int)swViewEntityType_e.swViewEntityType_Edge);
                if (visibleEdges == null) continue;

                foreach (object obj in visibleEdges)
                {
                    if (obj == null) continue;

                    // GetVisibleEntities(comp,...) 返回的对象可转换为 Edge
                    if (!(obj is Edge visEdge)) continue;

                    try
                    {
                        // 先用引用判断(速度快)
                        if (Object.ReferenceEquals(visEdge, modelEdge)) return visEdge;

                        // 再用3D端点坐标近似判断(解决COM封送导致的引用不一致问题)
                        var meStartVert = (Vertex)visEdge.GetStartVertex();
                        var meEndVert = (Vertex)visEdge.GetEndVertex();
                        if (meStartVert == null || meEndVert == null) continue;

                        var meStart = (double[])meStartVert.GetPoint();
                        var meEnd = (double[])meEndVert.GetPoint();
                        if (meStart == null || meEnd == null) continue;

                        bool coordsMatch =
                            (IsPointClose(edgeStart, meStart) && IsPointClose(edgeEnd, meEnd))
                            ||
                            (IsPointClose(edgeStart, meEnd) && IsPointClose(edgeEnd, meStart));

                        if (coordsMatch) return visEdge;
                    }
                    catch
                    {
                        continue;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 判断两个3D点是否在容差范围内近似相等
        /// </summary>
        static bool IsPointClose(double[] p1, double[] p2, double tol = 0.001)
        {
            if (p1 == null || p2 == null || p1.Length < 3 || p2.Length < 3) return false;
            var dx = Math.Abs(p1[0] - p2[0]);
            var dy = Math.Abs(p1[1] - p2[1]);
            var dz = Math.Abs(p1[2] - p2[2]);
            var dist = Math.Sqrt(dx * dx + dy * dy + dz * dz);
            var close = dx < tol && dy < tol && dz < tol;
           // Console.WriteLine($"[IsPointClose] p1=({p1[0]:F6},{p1[1]:F6},{p1[2]:F6}) p2=({p2[0]:F6},{p2[1]:F6},{p2[2]:F6}) dx={dx:F6} dy={dy:F6} dz={dz:F6} dist={dist:F6} tol={tol} => {close}");
            return close;
        }

        static double[] TransformPoint(MathUtility math, MathTransform xf, double[] pt)
        {
            var mp = (MathPoint)math.CreatePoint(pt);
            mp = (MathPoint)mp.MultiplyTransform(xf);
            return (double[])mp.ArrayData;
        }

        /// <summary>
        /// 使用零件文档全局明细注释文字格式,避免折弯注释被局部样式托管覆盖。
        /// </summary>
        static void ApplyGlobalAnnotationTextHeight(ModelDoc2 partModel)
        {
            try
            {
                var myTextFormat = partModel.Extension.GetUserPreferenceTextFormat(
                    (int)swUserPreferenceTextFormat_e.swDetailingAnnotationTextFormat, 0) as TextFormat;
                if (myTextFormat == null) return;

                myTextFormat.CharHeight = 0.0035;
                bool boolstatus = partModel.Extension.SetUserPreferenceTextFormat(
                    (int)swUserPreferenceTextFormat_e.swDetailingAnnotationTextFormat, 0, myTextFormat);
                Console.WriteLine($"全局注释文字高度设置为 0.0035,结果: {boolstatus}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"设置全局注释文字高度失败:{ex.Message}");
            }
        }

        /// <summary>
        /// 生成一对面的唯一标识键,用于防止重复标注
        /// 基于Face对象的引用生成唯一键,确保只有完全相同的物理面才会被去重
        /// </summary>
        static string GetFacePairKey(Face face1, Face face2, string level)
        {
            // 使用Face对象的HashCode生成唯一标识
            // 按HashCode排序生成键,确保 (A,B) 和 (B,A) 生成相同的键
            int hash1 = face1.GetHashCode();
            int hash2 = face2.GetHashCode();
            
            var hashes = new[] { hash1, hash2 };
            Array.Sort(hashes);
            
            // 加入配对级别,确保一级对二级 与 一级对三级 不会互相去重
            return $"{hashes[0]}|{hashes[1]}|{level}";
        }
    }
}
相关推荐
c++之路1 小时前
C++ 多线程
开发语言·c++
CHANG_THE_WORLD1 小时前
<Fluent Python > Unicode 文本与字节
开发语言·python
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
凯瑟琳.奥古斯特2 小时前
Bootstrap快速上手指南
开发语言·前端·css·bootstrap·html
我就是妖怪2 小时前
Kimi K2.6 智能效果实测与能力全景展示
开发语言
中二痞2 小时前
下载Python 版本,环境变量变更以及PyCharm更换python版本
开发语言·python·pycharm
故事和你912 小时前
洛谷-算法2-3-分治与倍增5
开发语言·数据结构·c++·算法·动态规划·图论
SilentSamsara2 小时前
标准库精讲:collections/itertools/functools/pathlib 实战
开发语言·vscode·python·青少年编程·pycharm
我是唐青枫2 小时前
C#.NET ThreadLocal 深入解析:线程独享数据、性能收益与实战边界
c#·.net