c# solidworks 检查折弯拉孔

csharp 复制代码
using System;
using System.Collections.Generic;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using View = SolidWorks.Interop.sldworks.View;

namespace tools
{
    /// <summary>
    /// 选中工程图视图:在模型 3D 坐标中检查圆孔/圆弧圆心到折弯中心线距离是否 &gt; 4×板厚 + 圆半径。
    /// 折弯中心线取折弯前(折叠配置)OneBend 圆柱轴线,不用平板型式草图坐标。
    /// <see cref="View.GetBendLines"/> 仅作对照日志。
    /// </summary>
    public static class selected_view_arc_bend_clearance
    {
        const double MinClearanceThicknessFactor = 4.0;
        const double MinCircleEdgeParamSpanRad = 0.08;
        const double BendLineCoincidentTolM = 0.00005;
        const double CircleDedupeCenterTolM = 0.00035;
        const double CircleDedupeRadiusRelTol = 0.004;
        const double NearFullCircleParamSpanRad = 5.5;
        const int MaxSubFeatureWalkSteps = 100_000;

        sealed class ModelSegment3D
        {
            public double[] P0 = Array.Empty<double>();
            public double[] P1 = Array.Empty<double>();
        }

        sealed class BendRefInfo
        {
            public string FullName = string.Empty;
            public double[] AxisCenter = Array.Empty<double>();
            public double[] AxisDir = Array.Empty<double>();
            public double[] CenterLineP0 = Array.Empty<double>();
            public double[] CenterLineP1 = Array.Empty<double>();
            public double RadiusM;
            public bool HasAxis;
            public bool HasCenterLineSegment;
        }

        sealed class CircleArcItem
        {
            public double RadiusM;
            public double ParamSpanRad;
            public string Kind = string.Empty;
            public double[] CenterModel = Array.Empty<double>();
            public double[] AxisModel = Array.Empty<double>();
        }

        public static void run(SldWorks swApp, ModelDoc2 swModel)
        {
            try
            {
                if (swModel == null)
                {
                    Console.WriteLine("[arc_bend_clearance] 错误:没有活动文档。");
                    return;
                }

                if (swModel.GetType() != (int)swDocumentTypes_e.swDocDRAWING)
                {
                    swApp.SendMsgToUser("当前文档不是工程图。");
                    return;
                }

                var selMgr = (SelectionMgr)swModel.SelectionManager;
                if (selMgr == null || selMgr.GetSelectedObjectType3(1, -1) != (int)swSelectType_e.swSelDRAWINGVIEWS)
                {
                    swApp.SendMsgToUser("请先选中一个工程图视图(建议平板型式/展开视图)。");
                    return;
                }

                var view = selMgr.GetSelectedObject6(1, -1) as View;
                if (view == null)
                {
                    swApp.SendMsgToUser("无法获取选中视图。");
                    return;
                }

                runForView(swApp, swModel, view);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[arc_bend_clearance] 失败:{ex.Message}");
                swApp?.SendMsgToUser($"圆弧折弯距检查失败:{ex.Message}");
            }
        }

        public static void runForView(SldWorks swApp, ModelDoc2 swModel, View view)
        {
            if (view == null)
            {
                return;
            }

            var partModel = view.ReferencedDocument as ModelDoc2;
            if (partModel == null || partModel.GetType() != (int)swDocumentTypes_e.swDocPART)
            {
                swApp.SendMsgToUser("视图未关联零件,无法读取板厚。");
                return;
            }

            double thicknessMm = get_thickness.run(partModel);
            if (thicknessMm <= 1e-6)
            {
                swApp.SendMsgToUser("无法读取板厚,请先确认零件钣金厚度或自定义属性。");
                return;
            }

            Console.WriteLine(
                $"[arc_bend_clearance] 视图「{view.Name}」板厚={thicknessMm:F2} mm,"
                + $"圆心距折弯要求 > 4×板厚+圆半径(4×板厚={thicknessMm * MinClearanceThicknessFactor:F2} mm)");

            using (ActivateViewReferencedPartConfiguration(partModel, view))
            {
                var partDoc = (PartDoc)partModel;
                string foldedCfg = TryResolveFoldedConfigurationName(partModel);
                List<BendRefInfo> bendRefs;
                using (new PartConfigurationScope(partModel, foldedCfg))
                {
                    bendRefs = CollectNamedBendRefsFromPart(partModel, partDoc);
                }

                var viewBendLines = CollectBendLineSegmentsModel3D(swApp, view);

                if (bendRefs.Count == 0 && viewBendLines.Count == 0)
                {
                    swApp.SendMsgToUser(
                        "未读取到折弯:请确认视图引用钣金零件,且零件特征树含 OneBend,或工程图已显示折弯线。");
                    return;
                }

                LogNamedBendRefs(bendRefs);

                if (viewBendLines.Count > 0)
                {
                    Console.WriteLine(
                        $"[arc_bend_clearance] 工程图 GetBendLines 对照 {viewBendLines.Count} 条(模型 3D,不参与主判定)");
                    for (int i = 0; i < viewBendLines.Count; i++)
                    {
                        var seg = viewBendLines[i];
                        Console.WriteLine(
                            $"[arc_bend_clearance]   对照折弯线#{i + 1} "
                            + FormatPointMm(seg.P0) + " → " + FormatPointMm(seg.P1));
                        Console.WriteLine(
                            $"[arc_bend_clearance]     参数式 {FormatSegmentParametricMm(seg.P0, seg.P1)}");
                    }
                }

                var distanceRefs = BuildDistanceRefs(bendRefs, viewBendLines);

                var arcs = CollectCircleArcItems(swApp, view, distanceRefs);
                if (arcs.Count == 0)
                {
                    swApp.SendMsgToUser("视图中未找到圆弧或圆线段。");
                    return;
                }

                Console.WriteLine($"[arc_bend_clearance] 圆弧/圆线段 {arcs.Count} 处");

                var violations = new List<string>();
                foreach (var arc in arcs)
                {
                    double centerDistM = MinDistancePointToBendRefs(
                        arc.CenterModel, distanceRefs, out string nearestBendName);
                    double holeRadiusMm = arc.RadiusM * 1000.0;
                    double requiredCenterDistMm =
                        thicknessMm * MinClearanceThicknessFactor + holeRadiusMm;
                    double requiredCenterDistM = requiredCenterDistMm / 1000.0;
                    double centerDistMm = centerDistM * 1000.0;
                    string bendHint = string.IsNullOrEmpty(nearestBendName)
                        ? ""
                        : $"(最近折弯「{nearestBendName}」)";

                    Console.WriteLine($"[arc_bend_clearance] {arc.Kind} R={holeRadiusMm:F2} mm");
                    Console.WriteLine(
                        $"[arc_bend_clearance]   圆心 3D C={FormatPointMm(arc.CenterModel)}");
                    Console.WriteLine(
                        $"[arc_bend_clearance]   圆轴线 {FormatAxisParametricMm(arc.CenterModel, arc.AxisModel)}");
                    Console.WriteLine(
                        $"[arc_bend_clearance]   圆心距折弯 {centerDistMm:F2} mm{bendHint},"
                        + $"要求 > {requiredCenterDistMm:F2} mm"
                        + $"(4×{thicknessMm:F2}+{holeRadiusMm:F2})");

                    if (centerDistM <= requiredCenterDistM + 1e-9)
                    {
                        violations.Add(
                            $"  · {arc.Kind} R={holeRadiusMm:F2} mm,距「{nearestBendName}」{centerDistMm:F2} mm"
                            + $" ≤ {requiredCenterDistMm:F2} mm(4×板厚+圆R),会拉孔");
                    }
                }

                if (violations.Count > 0)
                {
                    string preview = string.Join(
                        "\n",
                        violations.Count <= 8 ? violations : violations.GetRange(0, 8));
                    if (violations.Count > 8)
                    {
                        preview += $"\n... 另有 {violations.Count - 8} 处";
                    }

                    string msg =
                        $"板厚 {thicknessMm:F2} mm:以下圆心到折弯距离未大于 4×板厚+圆半径,会拉孔:\n"
                        + preview;
                    Console.WriteLine(msg);
                    swApp.SendMsgToUser(msg);
                }
                else
                {
                    swApp.SendMsgToUser(
                        $"检查通过:{arcs.Count} 处圆孔/圆弧圆心到最近折弯均 > 4×板厚+圆半径。");
                }
            }
        }

        /// <summary>与 benddim 一致:草图局部 → 逆 ModelToSketch → 逆 ModelToView → 模型坐标。</summary>
        static List<ModelSegment3D> CollectBendLineSegmentsModel3D(SldWorks swApp, View view)
        {
            var segments = new List<ModelSegment3D>();
            object? raw;
            try
            {
                raw = view.GetBendLines();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[arc_bend_clearance] GetBendLines 异常:{ex.Message}");
                return segments;
            }

            if (raw is not object[] arr || arr.Length == 0)
            {
                return segments;
            }

            Sketch? viewSketch = null;
            try
            {
                viewSketch = (Sketch)view.GetSketch();
            }
            catch
            {
                viewSketch = null;
            }

            if (viewSketch == null)
            {
                Console.WriteLine("[arc_bend_clearance] view.GetSketch() 为空,无法将折弯线变换到模型坐标");
                return segments;
            }

            foreach (object item in arr)
            {
                if (item is not SketchSegment seg || seg is not SketchLine line)
                {
                    continue;
                }

                try
                {
                    var sp0 = line.GetStartPoint2() as SketchPoint;
                    var sp1 = line.GetEndPoint2() as SketchPoint;
                    if (sp0 == null || sp1 == null)
                    {
                        continue;
                    }

                    var sk0 = new[] { sp0.X, sp0.Y, sp0.Z };
                    var sk1 = new[] { sp1.X, sp1.Y, sp1.Z };

                    if (!TrySketchLocalToModelPoint(swApp, view, viewSketch, sk0, out var m0)
                        || !TrySketchLocalToModelPoint(swApp, view, viewSketch, sk1, out var m1)
                        || m0 == null || m1 == null)
                    {
                        continue;
                    }

                    if (Distance3D(m0, m1) < 1e-6)
                    {
                        continue;
                    }

                    segments.Add(new ModelSegment3D { P0 = m0, P1 = m1 });
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[arc_bend_clearance] 折弯线段读取失败:{ex.Message}");
                }
            }

            return segments;
        }

        static List<CircleArcItem> CollectCircleArcItems(
            SldWorks swApp,
            View view,
            List<BendRefInfo> distanceRefs)
        {
            var items = new List<CircleArcItem>();
            var seen = new HashSet<string>(StringComparer.Ordinal);

            object[]? comps = null;
            try
            {
                comps = (object[])view.GetVisibleComponents();
            }
            catch
            {
                comps = null;
            }

            if (comps == null || comps.Length == 0)
            {
                return items;
            }

            foreach (Component2 comp in comps)
            {
                if (comp == null)
                {
                    continue;
                }

                object[]? edges = null;
                try
                {
                    edges = (object[])view.GetVisibleEntities(
                        comp, (int)swViewEntityType_e.swViewEntityType_Edge);
                }
                catch
                {
                    edges = null;
                }

                if (edges == null)
                {
                    continue;
                }

                foreach (object edgeObj in edges)
                {
                    if (edgeObj is not Edge edge)
                    {
                        continue;
                    }

                    if (!TryParseCircleEdge(edge, out var cp, out var span))
                    {
                        continue;
                    }

                    double radiusM = cp[6];
                    var centerModel = new[] { cp[0], cp[1], cp[2] };
                    var axisModel = new[] { cp[3], cp[4], cp[5] };
                    string key = BuildCircleDedupeKey(cp[0], cp[1], cp[2], radiusM, span);
                    if (!seen.Add(key))
                    {
                        continue;
                    }

                    if (IsLikelyBendReliefArc(span, centerModel, distanceRefs))
                    {
                        Console.WriteLine(
                            $"[arc_bend_clearance] 跳过折弯圆角弧 R={radiusM * 1000:F2} mm span={span:F2} rad");
                        continue;
                    }

                    string kind = span >= NearFullCircleParamSpanRad ? "圆孔" : "圆弧";
                    items.Add(new CircleArcItem
                    {
                        RadiusM = radiusM,
                        ParamSpanRad = span,
                        Kind = kind,
                        CenterModel = centerModel,
                        AxisModel = axisModel,
                    });
                }
            }

            return items;
        }

        static bool TryParseCircleEdge(Edge edge, out double[] cp, out double span)
        {
            cp = Array.Empty<double>();
            span = 0;
            try
            {
                var curve = (Curve)edge.GetCurve();
                if (curve == null || !curve.IsCircle())
                {
                    return false;
                }

                cp = (double[])curve.CircleParams;
                if (cp == null || cp.Length < 7 || cp[6] <= 1e-9)
                {
                    return false;
                }

                curve.GetEndParams(out var t0, out var t1, out _, out _);
                span = Math.Abs(t1 - t0);
                return span >= MinCircleEdgeParamSpanRad;
            }
            catch
            {
                return false;
            }
        }

        static string BuildCircleDedupeKey(double cx, double cy, double cz, double r, double span)
        {
            double cTol = CircleDedupeCenterTolM;
            double rTol = Math.Max(1e-6, r * CircleDedupeRadiusRelTol);
            long qx = (long)Math.Round(cx / cTol);
            long qy = (long)Math.Round(cy / cTol);
            long qz = (long)Math.Round(cz / cTol);
            long qr = (long)Math.Round(r / rTol);
            long qSpan = (long)Math.Round(span * 100.0);
            return $"{qx}:{qy}:{qz}:{qr}:{qSpan}";
        }

        static bool IsLikelyBendReliefArc(double spanRad, double[] centerModel, List<BendRefInfo> distanceRefs)
        {
            if (spanRad >= NearFullCircleParamSpanRad)
            {
                return false;
            }

            double d = MinDistancePointToBendRefs(centerModel, distanceRefs, out _);
            return d <= BendLineCoincidentTolM;
        }

        static List<BendRefInfo> BuildDistanceRefs(List<BendRefInfo> bendRefs, List<ModelSegment3D> viewBendLines)
        {
            if (bendRefs.Count > 0)
            {
                return bendRefs;
            }

            var fallback = new List<BendRefInfo>();
            for (int i = 0; i < viewBendLines.Count; i++)
            {
                var seg = viewBendLines[i];
                fallback.Add(new BendRefInfo
                {
                    FullName = $"工程图折弯线#{i + 1}",
                    HasCenterLineSegment = true,
                    CenterLineP0 = seg.P0,
                    CenterLineP1 = seg.P1,
                });
            }

            return fallback;
        }

        static double MinDistancePointToBendRefs(
            double[] point,
            List<BendRefInfo> bendRefs,
            out string nearestBendName)
        {
            nearestBendName = string.Empty;
            double best = double.MaxValue;
            foreach (var br in bendRefs)
            {
                double d = MinDistancePointToSingleBendRef(point, br);
                if (d < best)
                {
                    best = d;
                    nearestBendName = br.FullName;
                }
            }

            return best;
        }

        static double MinDistancePointToSingleBendRef(double[] point, BendRefInfo br)
        {
            if (br.HasAxis)
            {
                return PointToLineDistance3D(point, br.AxisCenter, br.AxisDir);
            }

            if (br.HasCenterLineSegment)
            {
                return PointToSegmentDistance3D(point, br.CenterLineP0, br.CenterLineP1);
            }

            return double.MaxValue;
        }

        static double MinDistancePointToSegments3D(
            double[] point,
            List<ModelSegment3D> segments,
            out int bestSegmentIndex)
        {
            bestSegmentIndex = -1;
            double best = double.MaxValue;
            for (int i = 0; i < segments.Count; i++)
            {
                var seg = segments[i];
                double d = PointToSegmentDistance3D(point, seg.P0, seg.P1);
                if (d < best)
                {
                    best = d;
                    bestSegmentIndex = i;
                }
            }

            return best;
        }

        static double PointToLineDistance3D(double[] p, double[] linePoint, double[] lineDir)
        {
            double dx = lineDir[0];
            double dy = lineDir[1];
            double dz = lineDir[2];
            double lenSq = dx * dx + dy * dy + dz * dz;
            if (lenSq < 1e-18)
            {
                return Distance3D(p, linePoint);
            }

            double vx = p[0] - linePoint[0];
            double vy = p[1] - linePoint[1];
            double vz = p[2] - linePoint[2];
            double t = (vx * dx + vy * dy + vz * dz) / lenSq;
            var q = new[]
            {
                linePoint[0] + t * dx,
                linePoint[1] + t * dy,
                linePoint[2] + t * dz,
            };
            return Distance3D(p, q);
        }

        static bool TrySketchLocalToModelPoint(
            ISldWorks swApp,
            View view,
            Sketch sketch,
            double[] sketchLocal3,
            out double[]? model3)
        {
            model3 = null;
            try
            {
                var math = swApp.IGetMathUtility();
                if (math == null)
                {
                    return false;
                }

                var mp = (MathPoint)math.CreatePoint(sketchLocal3);
                if (mp == null)
                {
                    return false;
                }

                var skInv = (MathTransform)sketch.ModelToSketchTransform.Inverse();
                mp = (MathPoint)mp.MultiplyTransform(skInv);
                var viewInv = (MathTransform)view.ModelToViewTransform.Inverse();
                mp = (MathPoint)mp.MultiplyTransform(viewInv);
                var arr = (double[])mp.ArrayData;
                if (arr == null || arr.Length < 3)
                {
                    return false;
                }

                model3 = new[] { arr[0], arr[1], arr[2] };
                return true;
            }
            catch
            {
                return false;
            }
        }

        static double PointToSegmentDistance3D(double[] p, double[] a, double[] b)
        {
            double dx = b[0] - a[0];
            double dy = b[1] - a[1];
            double dz = b[2] - a[2];
            double lenSq = dx * dx + dy * dy + dz * dz;
            if (lenSq < 1e-18)
            {
                return Distance3D(p, a);
            }

            double t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy + (p[2] - a[2]) * dz) / lenSq;
            if (t < 0.0)
            {
                t = 0.0;
            }
            else if (t > 1.0)
            {
                t = 1.0;
            }

            var q = new[]
            {
                a[0] + t * dx,
                a[1] + t * dy,
                a[2] + t * dz,
            };
            return Distance3D(p, q);
        }

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

        static void LogNamedBendRefs(List<BendRefInfo> bendRefs)
        {
            if (bendRefs.Count == 0)
            {
                Console.WriteLine("[arc_bend_clearance] 零件特征树未读到 OneBend 折弯(将回退工程图折弯线)");
                return;
            }

            Console.WriteLine($"[arc_bend_clearance] 零件 OneBend 折弯 {bendRefs.Count} 处(折弯前 3D 中心线)");
            foreach (var br in bendRefs)
            {
                if (br.HasAxis)
                {
                    Console.WriteLine(
                        $"[arc_bend_clearance]   折弯「{br.FullName}」圆柱 R={br.RadiusM * 1000:F2} mm "
                        + $"轴心 {FormatPointMm(br.AxisCenter)}");
                    Console.WriteLine(
                        $"[arc_bend_clearance]     轴线 {FormatAxisParametricMm(br.AxisCenter, br.AxisDir)}");
                    if (br.HasCenterLineSegment)
                    {
                        Console.WriteLine(
                            $"[arc_bend_clearance]     中心线段 "
                            + FormatPointMm(br.CenterLineP0) + " → " + FormatPointMm(br.CenterLineP1));
                        Console.WriteLine(
                            $"[arc_bend_clearance]       参数式 "
                            + FormatSegmentParametricMm(br.CenterLineP0, br.CenterLineP1));
                    }
                }
                else
                {
                    Console.WriteLine($"[arc_bend_clearance]   折弯「{br.FullName}」未读到折弯前圆柱轴线");
                }
            }
        }

        static string TryResolveFoldedConfigurationName(ModelDoc2 partModel)
        {
            string activeName = string.Empty;
            try
            {
                activeName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? string.Empty;
            }
            catch
            {
                activeName = string.Empty;
            }

            activeName = activeName.Trim();
            if (string.IsNullOrEmpty(activeName))
            {
                return string.Empty;
            }

            try
            {
                var cfg = partModel.ConfigurationManager?.ActiveConfiguration as Configuration;
                Configuration? parent = cfg?.GetParent() as Configuration;
                string parentName = parent?.Name?.Trim() ?? string.Empty;
                if (!string.IsNullOrEmpty(parentName)
                    && !string.Equals(parentName, activeName, StringComparison.OrdinalIgnoreCase))
                {
                    Console.WriteLine(
                        $"[arc_bend_clearance] 折弯中心线读取配置「{parentName}」(视图引用「{activeName}」)");
                    return parentName;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[arc_bend_clearance] 解析折叠配置失败:{ex.Message},使用当前配置");
            }

            Console.WriteLine($"[arc_bend_clearance] 折弯中心线读取配置「{activeName}」");
            return activeName;
        }

        static List<BendRefInfo> CollectNamedBendRefsFromPart(ModelDoc2 partModel, PartDoc partDoc)
        {
            var list = new List<BendRefInfo>();
            var seenFeatIds = new HashSet<int>();

            try
            {
                foreach (Body2 body in (object[])partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false))
                {
                    if (body == null)
                    {
                        continue;
                    }

                    object[]? features = (object[]?)body.GetFeatures();
                    if (features == null)
                    {
                        continue;
                    }

                    foreach (object featureObject in features)
                    {
                        if (featureObject is not Feature parentFeat)
                        {
                            continue;
                        }

                        string parentName = parentFeat.Name ?? string.Empty;
                        var subFeat = (Feature?)parentFeat.GetFirstSubFeature();
                        while (subFeat != null)
                        {
                            if (IsLinearSheetMetalBendSubFeature(subFeat))
                            {
                                string fullName = string.IsNullOrEmpty(parentName)
                                    ? subFeat.Name ?? "?"
                                    : $"{parentName}+{subFeat.Name}";
                                TryAddBendRef(subFeat, fullName, list, seenFeatIds);
                            }

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

                int steps = 0;
                Feature? top = (Feature?)partModel.FirstFeature();
                while (top != null && steps < MaxSubFeatureWalkSteps)
                {
                    steps++;
                    CollectOneBendsInPartSubtree(top, string.Empty, list, seenFeatIds, ref steps);
                    top = (Feature?)top.GetNextFeature();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[arc_bend_clearance] 读取零件折弯失败:{ex.Message}");
            }

            return list;
        }

        static void CollectOneBendsInPartSubtree(
            Feature node,
            string parentPath,
            List<BendRefInfo> sink,
            HashSet<int> seenFeatIds,
            ref int steps)
        {
            if (steps >= MaxSubFeatureWalkSteps)
            {
                return;
            }

            steps++;
            string nodeName = node.Name ?? string.Empty;
            string path = string.IsNullOrEmpty(parentPath) ? nodeName : $"{parentPath}+{nodeName}";

            if (IsLinearSheetMetalBendSubFeature(node))
            {
                TryAddBendRef(node, path, sink, seenFeatIds);
            }

            Feature? sub = (Feature?)node.GetFirstSubFeature();
            while (sub != null && steps < MaxSubFeatureWalkSteps)
            {
                if (IsLinearSheetMetalBendSubFeature(sub))
                {
                    string bendPath = string.IsNullOrEmpty(path)
                        ? sub.Name ?? "?"
                        : $"{path}+{sub.Name}";
                    TryAddBendRef(sub, bendPath, sink, seenFeatIds);
                }

                string type2 = string.Empty;
                try
                {
                    type2 = sub.GetTypeName2() ?? string.Empty;
                }
                catch
                {
                    type2 = string.Empty;
                }

                if (!string.Equals(type2, "FlatPattern", StringComparison.Ordinal))
                {
                    CollectOneBendsInPartSubtree(sub, path, sink, seenFeatIds, ref steps);
                }

                sub = (Feature?)sub.GetNextSubFeature();
            }
        }

        static void TryAddBendRef(
            Feature bendFeat,
            string fullName,
            List<BendRefInfo> sink,
            HashSet<int> seenFeatIds)
        {
            int featId;
            try
            {
                featId = bendFeat.GetID();
            }
            catch
            {
                featId = (fullName ?? string.Empty).GetHashCode();
            }

            if (!seenFeatIds.Add(featId))
            {
                return;
            }

            var info = new BendRefInfo { FullName = fullName ?? "?" };
            if (!TryReadMainBendCylinderAxis(bendFeat, out var center, out var axis, out var radius, out Face? mainCylFace))
            {
                return;
            }

            info.HasAxis = true;
            info.AxisCenter = center;
            info.AxisDir = axis;
            info.RadiusM = radius;
            if (mainCylFace != null
                && TryReadBendCenterLineSegment(mainCylFace, center, axis, out var p0, out var p1))
            {
                info.HasCenterLineSegment = true;
                info.CenterLineP0 = p0;
                info.CenterLineP1 = p1;
            }

            sink.Add(info);
        }

        static bool TryReadMainBendCylinderAxis(
            Feature bendFeat,
            out double[] center,
            out double[] axis,
            out double radius,
            out Face? mainCylinderFace)
        {
            center = Array.Empty<double>();
            axis = Array.Empty<double>();
            radius = 0;
            mainCylinderFace = null;
            double maxArea = 0;
            foreach (Face face in EnumerateFeatureFaces(bendFeat))
            {
                if (!TryGetCylinderData(face, out var ctr, out var ax, out var r))
                {
                    continue;
                }

                double area;
                try
                {
                    area = face.GetArea();
                }
                catch
                {
                    area = 0;
                }

                if (area > maxArea)
                {
                    maxArea = area;
                    center = ctr;
                    axis = ax;
                    radius = r;
                    mainCylinderFace = face;
                }
            }

            return maxArea > 0;
        }

        static bool TryReadBendCenterLineSegment(
            Face cylinderFace,
            double[] axisCenter,
            double[] axisDir,
            out double[] p0,
            out double[] p1)
        {
            p0 = axisCenter;
            p1 = axisCenter;
            double tMin = double.MaxValue;
            double tMax = double.MinValue;
            bool hasSample = false;

            object[]? edges = null;
            try
            {
                edges = (object[]?)cylinderFace.GetEdges();
            }
            catch
            {
                edges = null;
            }

            if (edges == null)
            {
                return false;
            }

            foreach (object edgeObj in edges)
            {
                if (edgeObj is not Edge edge)
                {
                    continue;
                }

                if (!TrySampleEdgePoints(edge, out var samples))
                {
                    continue;
                }

                foreach (double[] pt in samples)
                {
                    double t = ProjectPointOnAxis(axisCenter, axisDir, pt);
                    tMin = Math.Min(tMin, t);
                    tMax = Math.Max(tMax, t);
                    hasSample = true;
                }
            }

            if (!hasSample || tMax + 1e-9 < tMin)
            {
                return false;
            }

            p0 = PointOnAxis(axisCenter, axisDir, tMin);
            p1 = PointOnAxis(axisCenter, axisDir, tMax);
            return Distance3D(p0, p1) > 1e-6;
        }

        static bool TrySampleEdgePoints(Edge edge, out List<double[]> samples)
        {
            samples = new List<double[]>();
            try
            {
                var v0 = (edge.GetStartVertex() as Vertex)?.GetPoint() as double[];
                var v1 = (edge.GetEndVertex() as Vertex)?.GetPoint() as double[];
                if (v0 != null && v0.Length >= 3)
                {
                    samples.Add(new[] { v0[0], v0[1], v0[2] });
                }

                if (v1 != null && v1.Length >= 3)
                {
                    samples.Add(new[] { v1[0], v1[1], v1[2] });
                }

                var curve = (Curve?)edge.GetCurve();
                if (curve != null)
                {
                    curve.GetEndParams(out double t0, out double t1, out _, out _);
                    for (int i = 1; i < 4; i++)
                    {
                        double t = t0 + (t1 - t0) * i / 4.0;
                        var raw = (double[]?)curve.Evaluate(t);
                        if (raw != null && raw.Length >= 3)
                        {
                            samples.Add(new[] { raw[0], raw[1], raw[2] });
                        }
                    }
                }
            }
            catch
            {
                samples.Clear();
            }

            return samples.Count > 0;
        }

        static double ProjectPointOnAxis(double[] axisCenter, double[] axisDir, double[] point)
        {
            double dx = axisDir[0];
            double dy = axisDir[1];
            double dz = axisDir[2];
            double lenSq = dx * dx + dy * dy + dz * dz;
            if (lenSq < 1e-18)
            {
                return 0;
            }

            double vx = point[0] - axisCenter[0];
            double vy = point[1] - axisCenter[1];
            double vz = point[2] - axisCenter[2];
            return (vx * dx + vy * dy + vz * dz) / lenSq;
        }

        static double[] PointOnAxis(double[] axisCenter, double[] axisDir, double t)
            => new[]
            {
                axisCenter[0] + t * axisDir[0],
                axisCenter[1] + t * axisDir[1],
                axisCenter[2] + t * axisDir[2],
            };

        static bool IsLinearSheetMetalBendSubFeature(Feature subFeat)
        {
            try
            {
                if (string.Equals(subFeat.GetTypeName(), "OneBend", StringComparison.Ordinal)
                    || string.Equals(subFeat.GetTypeName2() ?? "", "OneBend", StringComparison.Ordinal))
                {
                    return true;
                }

                return subFeat.GetDefinition() is OneBendFeatureData;
            }
            catch
            {
                return false;
            }
        }

        static IEnumerable<Face> EnumerateFeatureFaces(Feature feat)
        {
            if (feat == null)
            {
                yield break;
            }

            object? raw;
            try
            {
                raw = feat.GetFaces();
            }
            catch
            {
                yield break;
            }

            if (raw == null)
            {
                yield break;
            }

            if (raw is object[] arr)
            {
                foreach (object o in arr)
                {
                    if (o is Face f)
                    {
                        yield return f;
                    }
                }

                yield break;
            }

            if (raw is Face one)
            {
                yield return one;
            }
        }

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

            try
            {
                var s = (Surface)face.GetSurface();
                if (s == null || !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;
            }
            catch
            {
                return false;
            }
        }

        static string FormatPointMm(double[] p)
            => $"({p[0] * 1000:F3},{p[1] * 1000:F3},{p[2] * 1000:F3}) mm";

        static string FormatAxisParametricMm(double[] center, double[] axis)
            => $"P(t)=({center[0] * 1000:F3},{center[1] * 1000:F3},{center[2] * 1000:F3}) mm"
               + $"+t·({axis[0]:G6},{axis[1]:G6},{axis[2]:G6})";

        static string FormatSegmentParametricMm(double[] p0, double[] p1)
        {
            var d = new[]
            {
                (p1[0] - p0[0]) * 1000.0,
                (p1[1] - p0[1]) * 1000.0,
                (p1[2] - p0[2]) * 1000.0,
            };
            return $"P(u)={FormatPointMm(p0)}+u·({d[0]:F3},{d[1]:F3},{d[2]:F3}) mm,u∈[0,1]";
        }

        static IDisposable ActivateViewReferencedPartConfiguration(ModelDoc2 partModel, View view)
            => new ViewReferencedConfigurationScope(partModel, view);

        sealed class PartConfigurationScope : IDisposable
        {
            readonly ModelDoc2 _model;
            readonly string _restoreName;
            readonly bool _switched;
            bool _disposed;

            public PartConfigurationScope(ModelDoc2 partModel, string targetConfigName)
            {
                _model = partModel;
                _restoreName = string.Empty;
                _switched = false;
                string target = (targetConfigName ?? string.Empty).Trim();
                if (string.IsNullOrEmpty(target))
                {
                    return;
                }

                try
                {
                    _restoreName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? string.Empty;
                }
                catch
                {
                    _restoreName = string.Empty;
                }

                if (string.Equals(_restoreName, target, StringComparison.OrdinalIgnoreCase))
                {
                    return;
                }

                try
                {
                    partModel.ShowConfiguration2(target);
                    _switched = true;
                    try
                    {
                        partModel.EditRebuild3();
                    }
                    catch
                    {
                        // ignored
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[arc_bend_clearance] 切换折叠配置失败:{ex.Message}");
                }
            }

            public void Dispose()
            {
                if (_disposed)
                {
                    return;
                }

                _disposed = true;
                if (!_switched || string.IsNullOrEmpty(_restoreName))
                {
                    return;
                }

                try
                {
                    _model.ShowConfiguration2(_restoreName);
                }
                catch
                {
                    // ignored
                }
            }
        }

        sealed class ViewReferencedConfigurationScope : IDisposable
        {
            readonly ModelDoc2 _model;
            readonly string _restoreName;
            readonly bool _switched;
            bool _disposed;

            public ViewReferencedConfigurationScope(ModelDoc2 partModel, View view)
            {
                _model = partModel;
                _restoreName = "";
                _switched = false;
                try
                {
                    _restoreName = partModel.ConfigurationManager?.ActiveConfiguration?.Name ?? "";
                }
                catch
                {
                    // ignored
                }

                string refCfg = "";
                try
                {
                    refCfg = (view.ReferencedConfiguration ?? "").Trim();
                }
                catch
                {
                    refCfg = "";
                }

                if (string.IsNullOrEmpty(refCfg))
                {
                    Console.WriteLine("[arc_bend_clearance] 视图未指定引用配置,使用零件当前活动配置");
                    return;
                }

                if (string.Equals(_restoreName, refCfg, StringComparison.OrdinalIgnoreCase))
                {
                    return;
                }

                try
                {
                    Console.WriteLine(
                        $"[arc_bend_clearance] 切换零件到视图引用配置「{refCfg}」(原「{_restoreName}」)");
                    partModel.ShowConfiguration2(refCfg);
                    _switched = true;
                    try
                    {
                        partModel.EditRebuild3();
                    }
                    catch
                    {
                        // ignored
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[arc_bend_clearance] 切换配置失败:{ex.Message}");
                }
            }

            public void Dispose()
            {
                if (_disposed)
                {
                    return;
                }

                _disposed = true;
                if (!_switched || string.IsNullOrEmpty(_restoreName))
                {
                    return;
                }

                try
                {
                    _model.ShowConfiguration2(_restoreName);
                }
                catch
                {
                    // ignored
                }
            }
        }
    }
}
相关推荐
njsgcs4 天前
导出glb可视化让vlm看图找面 自己配合
智能制造·solidworks
花开月满西楼4 天前
零基础速学SolidWorks2026【曲面】曲面缝合
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼4 天前
零基础速学SolidWorks2026【曲面】删除面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼5 天前
零基础速学SolidWorks2026【曲面】加厚曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼8 天前
零基础速学SolidWorks2026【曲面】边界曲面和等距曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼8 天前
零基础速学SolidWorks2026【曲面】平面区域和填充曲面
3d建模·solidworks·机械设计·机械绘图绘制
蓝悦无人机8 天前
SOLIDWORKS高级应用之方程
solidworks·参数化建模·if 判断语句
花开月满西楼9 天前
零基础速学SolidWorks2026【曲面】分割线
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼11 天前
零基础速学SolidWorks2026【曲面】建模核心基础
solidworks·机械设计·机械绘图绘制
花开月满西楼12 天前
零基础速学SolidWorks2026【草图】绘制、编辑命令和约束
solidworks·机械设计