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
                }
            }
        }
    }
}
相关推荐
njsgcs2 天前
c# solidworks 自动标注折弯7 图可视化,清晰定义,画点改画线
solidworks
njsgcs4 天前
solidworks装配体显示子零件文档的颜色外观办法
solidworks
solidwork_s4 天前
在SOLIDWORKS中如何将小数点显示为逗号
solidworks
ddsoft1235 天前
CAMWorks 用户自定义车刀教程
软件·solidworks·钣金
njsgcs14 天前
solidworks二次开发文档chm位置 sldworksapi.chm
solidworks
njsgcs20 天前
制作solidworks插件 装配体导出展开耗时分析
开发语言·c#·solidworks
njsgcs20 天前
c# solidworks 标注攻牙
开发语言·c#·solidworks
njsgcs21 天前
c# solidworks GetPartBox无法获得正确实体边界框原因
开发语言·c#·solidworks
solidwork_s24 天前
SOLIDWORKS Simulation 连接类型有那些
solidworks