c# 检查solidworks展开后没折弯缝问题 3d投影可视化

展平

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

namespace tools
{
    /// <summary>
    /// 选中工程图视图:检查每条折弯线两个端点是否被零件可见直线几何横穿。
    /// 折弯线从零件平板型式 FlatPattern→ProfileFeature 草图读取,按 <see cref="swBendLineDirection_e"/> 过滤边框线。
    /// 任一端点未被穿过则弹窗提示。
    /// </summary>
    public static partial class selected_view_bend_line_endpoint_check
    {
        /// <summary>点到可见线段的最大垂距(米)。</summary>
        const double PointOnSegmentLineDistTolM = 0.0001;
        /// <summary>允许点略微超出可见线段端点的延伸量(米)。</summary>
        const double PointOnSegmentExtendTolM = 0.0002;
        /// <summary>图纸 2D 坐标下的容差(米)。</summary>
        const double Sheet2DLineDistTolM = 0.0002;
        /// <summary>与折弯线方向余弦低于此值才视为「横穿」可见边(排除与折弯线平行的边)。</summary>
        const double CrossingLineMaxAlignAbs = 0.25;

        sealed class ModelSegment3D
        {
            public int Index;
            public double LengthM;
            /// <summary>展开模型坐标(日志/弹窗)。</summary>
            public double[] P0 = Array.Empty<double>();
            public double[] P1 = Array.Empty<double>();
            public double SheetX0;
            public double SheetY0;
            public double SheetX1;
            public double SheetY1;
            public bool HasSheet2D;
        }

        sealed class SheetSegment2D
        {
            public double X0;
            public double Y0;
            public double X1;
            public double Y1;
        }

        sealed class EndpointViolation
        {
            public int BendLineIndex;
            public double BendLineLengthMm;
            public bool IsStart;
            public double[] ModelPoint = Array.Empty<double>();
            public double SheetX = double.NaN;
            public double SheetY = double.NaN;
        }

        public static void run(SldWorks swApp, ModelDoc2 swModel)
        {
            try
            {
                if (swModel == null)
                {
                    Console.WriteLine("[bend_line_endpoint] 错误:没有活动文档。");
                    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($"[bend_line_endpoint] 失败:{ex.Message}");
                swApp?.SendMsgToUser($"折弯线端点检查失败:{ex.Message}");
            }
        }

        /// <summary>对指定视图执行折弯线端点检查(自动化出图可调用)。通过返回 true,未通过返回 false。</summary>
        /// <param name="appendAbortHint">为 true 时弹窗附加「出图已终止」说明(new_drw 跳过保存用,不关闭工程图)。</param>
        public static bool runForView(
            SldWorks swApp,
            ModelDoc2 swModel,
            View view,
            bool appendAbortHint = false)
        {
            if (swApp == null || swModel == null || view == null)
            {
                Console.WriteLine("[bend_line_endpoint] 参数无效,检查未通过。");
                return false;
            }

            if (!TryResolvePartModelForView(view, out var partModel, out var scopeComponent, out var resolveFail))
            {
                Console.WriteLine($"[bend_line_endpoint] 无法解析视图零件:{resolveFail}");
                NotifyFail(swApp, $"无法解析视图关联零件:{resolveFail}", appendAbortHint);
                return false;
            }

            List<ModelSegment3D> bendLines;
            List<SheetSegment2D> visibleLinesSheet2D;
            List<ModelSegment3D> visibleLines3D;
            using (ActivateViewReferencedPartConfiguration(partModel, view, scopeComponent))
            {
                bendLines = CollectBendLineSegmentsFromFlatPattern(swApp, partModel, view);
                if (bendLines.Count == 0)
                {
                    NotifyFail(
                        swApp,
                        "零件平板型式特征中未读取到折弯线:请确认零件含 FlatPattern 且视图引用展开配置。",
                        appendAbortHint);
                    return false;
                }

                visibleLinesSheet2D = CollectVisibleStraightSegmentsSheet2D(swApp, view);
                visibleLines3D = CollectVisibleStraightSegmentsModel3D(view);
            }

            Console.WriteLine(
                $"[bend_line_endpoint] 视图「{view.Name}」折弯线 {bendLines.Count} 条,"
                + $"可见直线 {visibleLines3D.Count} 条(图纸 2D {visibleLinesSheet2D.Count} 条)");
            Console.WriteLine(
                "[bend_line_endpoint] 可见线图纸 = GetPoint() → ModelToViewTransform(与 benddim 一致)");
            Console.WriteLine(
                "[bend_line_endpoint] 折弯线 = FlatPattern ProfileFeature 草图 + GetBendLineDirection(Up/Down) → 模型3D → 图纸");
            Console.WriteLine(
                "[bend_line_endpoint] 横穿检查:优先模型3D,回退图纸2D;可视化用图纸2D");
            LogModelSegmentsBBox("[bend_line_endpoint] 可见线 bbox(模型3D)", visibleLines3D);
            LogModelSegmentsBBox("[bend_line_endpoint] 折弯线 bbox(模型3D)", bendLines);
            LogBendLineInventory(swApp, view, bendLines);

            var violations = new List<EndpointViolation>();
            for (int i = 0; i < bendLines.Count; i++)
            {
                var seg = bendLines[i];
                CheckBendEndpoint(seg, true, visibleLinesSheet2D, visibleLines3D, violations);
                CheckBendEndpoint(seg, false, visibleLinesSheet2D, visibleLines3D, violations);
            }

            SaveDebugVisualization(bendLines, visibleLinesSheet2D, violations);

            if (visibleLines3D.Count == 0 && visibleLinesSheet2D.Count == 0)
            {
                NotifyFail(swApp, "视图中未找到零件可见直线几何,无法检查折弯线端点。", appendAbortHint);
                return false;
            }

            if (violations.Count == 0)
            {
                Console.WriteLine(
                    $"[bend_line_endpoint] 检查通过:{bendLines.Count} 条折弯线共 {bendLines.Count * 2} 个端点均在可见几何线上。");
                return true;
            }

            LogViolations(violations);
            NotifyFail(swApp, BuildViolationMessage(violations), appendAbortHint);
            return false;
        }

        static void NotifyFail(SldWorks swApp, string message, bool appendAbortHint)
        {
            if (appendAbortHint)
            {
                message += "\n\n出图已终止,后续保存已跳过(工程图保持打开)。";
            }

            Console.WriteLine($"[bend_line_endpoint] 检查未通过:{message.Replace("\n", " | ")}");
            swApp.SendMsgToUser(message);
        }

        static void CheckBendEndpoint(
            ModelSegment3D seg,
            bool isStart,
            List<SheetSegment2D> visibleLinesSheet2D,
            List<ModelSegment3D> visibleLines3D,
            List<EndpointViolation> violations)
        {
            var modelPt = isStart ? seg.P0 : seg.P1;
            if (IsPointOnCrossingVisibleLineModel3D(modelPt, seg, visibleLines3D)
                || IsPointOnCrossingVisibleLineSheet2D(isStart, seg, visibleLinesSheet2D))
            {
                return;
            }

            double sheetX = double.NaN;
            double sheetY = double.NaN;
            if (seg.HasSheet2D)
            {
                sheetX = isStart ? seg.SheetX0 : seg.SheetX1;
                sheetY = isStart ? seg.SheetY0 : seg.SheetY1;
            }

            violations.Add(new EndpointViolation
            {
                BendLineIndex = seg.Index,
                BendLineLengthMm = seg.LengthM * 1000.0,
                IsStart = isStart,
                ModelPoint = isStart ? seg.P0 : seg.P1,
                SheetX = sheetX,
                SheetY = sheetY,
            });
        }

        /// <summary>模型 3D 空间横穿判定(与可见边 GetPoint 同空间,benddim 折弯模型链)。</summary>
        static bool IsPointOnCrossingVisibleLineModel3D(
            double[] point,
            ModelSegment3D bendSeg,
            List<ModelSegment3D> visibleLines3D)
        {
            var bendDir3 = Normalize3D(Sub3(bendSeg.P1, bendSeg.P0));
            if (bendDir3 == null)
            {
                return false;
            }

            foreach (var line in visibleLines3D)
            {
                var edgeDir3 = Normalize3D(Sub3(line.P1, line.P0));
                if (edgeDir3 == null)
                {
                    continue;
                }

                double align = Math.Abs(
                    bendDir3[0] * edgeDir3[0] + bendDir3[1] * edgeDir3[1] + bendDir3[2] * edgeDir3[2]);
                if (align > CrossingLineMaxAlignAbs)
                {
                    continue;
                }

                if (IsPointOnSegment3D(
                        point, line.P0, line.P1,
                        PointOnSegmentLineDistTolM, PointOnSegmentExtendTolM))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>图纸 2D 空间横穿判定(benddim TryModelPointToSheetXY)。</summary>
        static bool IsPointOnCrossingVisibleLineSheet2D(
            bool isStart,
            ModelSegment3D bendSeg,
            List<SheetSegment2D> visibleLinesSheet2D)
        {
            if (!bendSeg.HasSheet2D)
            {
                return false;
            }

            var bendDir2 = Normalize2D(
                bendSeg.SheetX1 - bendSeg.SheetX0,
                bendSeg.SheetY1 - bendSeg.SheetY0);
            if (bendDir2 == null)
            {
                return false;
            }

            double px = isStart ? bendSeg.SheetX0 : bendSeg.SheetX1;
            double py = isStart ? bendSeg.SheetY0 : bendSeg.SheetY1;

            foreach (var line in visibleLinesSheet2D)
            {
                var edgeDir2 = Normalize2D(line.X1 - line.X0, line.Y1 - line.Y0);
                if (edgeDir2 == null)
                {
                    continue;
                }

                if (Math.Abs(bendDir2[0] * edgeDir2[0] + bendDir2[1] * edgeDir2[1]) > CrossingLineMaxAlignAbs)
                {
                    continue;
                }

                if (IsPointOnSegment2D(
                        px, py,
                        line.X0, line.Y0, line.X1, line.Y1,
                        Sheet2DLineDistTolM, PointOnSegmentExtendTolM))
                {
                    return true;
                }
            }

            return false;
        }

        static void LogModelSegmentsBBox(string tag, List<ModelSegment3D> segments)
        {
            if (segments.Count == 0)
            {
                Console.WriteLine($"{tag}: (空)");
                return;
            }

            double minX = double.PositiveInfinity;
            double minY = double.PositiveInfinity;
            double minZ = double.PositiveInfinity;
            double maxX = double.NegativeInfinity;
            double maxY = double.NegativeInfinity;
            double maxZ = double.NegativeInfinity;

            foreach (var seg in segments)
            {
                UpdateModelPointBounds(seg.P0, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
                UpdateModelPointBounds(seg.P1, ref minX, ref minY, ref minZ, ref maxX, ref maxY, ref maxZ);
            }

            Console.WriteLine(
                $"{tag}: X[{minX * 1000:F2},{maxX * 1000:F2}] mm "
                + $"Y[{minY * 1000:F2},{maxY * 1000:F2}] mm "
                + $"Z[{minZ * 1000:F2},{maxZ * 1000:F2}] mm,{segments.Count} 条");
        }

        static void UpdateModelPointBounds(
            double[] p,
            ref double minX,
            ref double minY,
            ref double minZ,
            ref double maxX,
            ref double maxY,
            ref double maxZ)
        {
            if (p == null || p.Length < 3)
            {
                return;
            }

            minX = Math.Min(minX, p[0]);
            minY = Math.Min(minY, p[1]);
            minZ = Math.Min(minZ, p[2]);
            maxX = Math.Max(maxX, p[0]);
            maxY = Math.Max(maxY, p[1]);
            maxZ = Math.Max(maxZ, p[2]);
        }

        static void LogBendLineInventory(ISldWorks swApp, View view, List<ModelSegment3D> bendLines)
        {
            foreach (var seg in bendLines)
            {
                string sheetPart = "";
                if (seg.HasSheet2D)
                {
                    double sheetLenMm = Distance2D(seg.SheetX0, seg.SheetY0, seg.SheetX1, seg.SheetY1) * 1000.0;
                    sheetPart =
                        $",图纸 {FormatPointSheetMm(seg.SheetX0, seg.SheetY0)}→{FormatPointSheetMm(seg.SheetX1, seg.SheetY1)} L={sheetLenMm:F2} mm";
                }
                else if (TryModelPointToSheetXY(swApp, view, seg.P0, out var x0, out var y0)
                    && TryModelPointToSheetXY(swApp, view, seg.P1, out var x1, out var y1))
                {
                    double sheetLenMm = Distance2D(x0, y0, x1, y1) * 1000.0;
                    sheetPart =
                        $",图纸 {FormatPointSheetMm(x0, y0)}→{FormatPointSheetMm(x1, y1)} L={sheetLenMm:F2} mm";
                }

                Console.WriteLine(
                    $"[bend_line_endpoint]   折弯线#{seg.Index} 模型 "
                    + $"{FormatPointMm(seg.P0)}→{FormatPointMm(seg.P1)} L={seg.LengthM * 1000:F2} mm{sheetPart}");
            }
        }

        static void LogViolations(List<EndpointViolation> violations)
        {
            foreach (var v in violations)
            {
                string endLabel = v.IsStart ? "起点" : "终点";
                string sheetPart = !double.IsNaN(v.SheetX)
                    ? $",图纸({v.SheetX * 1000:F2},{v.SheetY * 1000:F2}) mm"
                    : "";
                Console.WriteLine(
                    $"[bend_line_endpoint] 折弯线#{v.BendLineIndex} L={v.BendLineLengthMm:F2} mm {endLabel} "
                    + $"模型 {FormatPointMm(v.ModelPoint)}{sheetPart} 未被横穿可见几何线穿过");
            }
        }

        static string BuildViolationMessage(List<EndpointViolation> violations)
        {
            var sb = new StringBuilder();
            sb.AppendLine("以下折弯线端点未被横穿可见几何线穿过(折弯线可能短了一截或视图缺边):");
            foreach (var v in violations)
            {
                string endLabel = v.IsStart ? "起点" : "终点";
                sb.AppendLine(
                    $"  · 折弯线#{v.BendLineIndex} L={v.BendLineLengthMm:F2} mm {endLabel} {FormatPointMm(v.ModelPoint)}");
            }

            return sb.ToString().TrimEnd();
        }

        /// <summary>从零件平板型式特征(FlatPattern → UiBend 草图线段)采集折弯线,变换到模型 3D。</summary>
        static List<ModelSegment3D> CollectBendLineSegmentsFromFlatPattern(
            ISldWorks swApp,
            ModelDoc2 partModel,
            View view)
        {
            var segments = new List<ModelSegment3D>();
            var flatPatterns = FindQualifiedFlatPatternFeatures(partModel);
            if (flatPatterns.Count == 0)
            {
                Console.WriteLine("[bend_line_endpoint] 零件未找到平板型式 FlatPattern 特征");
                return segments;
            }

            foreach (var flatFeat in flatPatterns)
            {
                string featName = "";
                try
                {
                    featName = flatFeat.Name ?? "?";
                }
                catch
                {
                    featName = "?";
                }

                CollectBendLinesFromFlatPatternFeature(swApp, flatFeat, view, segments);
            }

            ReindexBendSegments(segments);
            return segments;
        }

        static void ReindexBendSegments(List<ModelSegment3D> segments)
        {
            for (int i = 0; i < segments.Count; i++)
            {
                segments[i].Index = i + 1;
            }
        }

        static List<Feature> FindQualifiedFlatPatternFeatures(ModelDoc2 partModel)
        {
            var list = new List<Feature>();
            if (partModel.GetType() != (int)swDocumentTypes_e.swDocPART)
            {
                return list;
            }

            var partDoc = (PartDoc)partModel;
            object[]? bodies;
            try
            {
                bodies = (object[]?)partDoc.GetBodies2((int)swBodyType_e.swSolidBody, false);
            }
            catch
            {
                bodies = null;
            }

            if (bodies != null)
            {
                foreach (object bodyObj in bodies)
                {
                    if (bodyObj is not Body2 body)
                    {
                        continue;
                    }

                    object[]? features;
                    try
                    {
                        features = (object[]?)body.GetFeatures();
                    }
                    catch
                    {
                        continue;
                    }

                    if (!BodyFeatureTreeHasSheetMetal(features))
                    {
                        continue;
                    }

                    if (features == null || features.Length == 0)
                    {
                        continue;
                    }

                    if (features[features.Length - 1] is not Feature lastFeat)
                    {
                        continue;
                    }

                    if (!string.Equals(lastFeat.GetTypeName2(), "FlatPattern", StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    list.Add(lastFeat);
                }
            }

            if (list.Count > 0)
            {
                return list;
            }

            Feature? top = (Feature?)partModel.FirstFeature();
            while (top != null)
            {
                if (string.Equals(top.GetTypeName2(), "FlatPattern", StringComparison.OrdinalIgnoreCase))
                {
                    list.Add(top);
                }

                top = (Feature?)top.GetNextFeature();
            }

            return list;
        }

        static bool BodyFeatureTreeHasSheetMetal(object[]? features)
        {
            if (features == null)
            {
                return false;
            }

            foreach (object featureObj in features)
            {
                if (featureObj is Feature feature
                    && string.Equals(feature.GetTypeName2(), "SheetMetal", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        static void CollectBendLinesFromFlatPatternFeature(
            ISldWorks swApp,
            Feature flatPattern,
            View view,
            List<ModelSegment3D> sink)
        {
            string featName = "";
            try
            {
                featName = flatPattern.Name ?? "?";
            }
            catch
            {
                featName = "?";
            }

            Sketch? sketch = FindFlatPatternProfileSketch(flatPattern);
            if (sketch == null)
            {
                Console.WriteLine(
                    $"[bend_line_endpoint] 平板特征「{featName}」未找到 ProfileFeature 折弯草图");
                return;
            }

            int before = sink.Count;
            int totalLines = 0;
            int bendLines = 0;
            AddSketchBendLineSegmentsToModel(swApp, sketch, view, sink, ref totalLines, ref bendLines);
            Console.WriteLine(
                $"[bend_line_endpoint] 平板特征「{featName}」草图直线 {totalLines} 条,"
                + $"GetBendLineDirection 折弯线 {bendLines} 条,采纳 {sink.Count - before} 条");
        }

        /// <summary>FlatPattern 下存放展开几何的 ProfileFeature 草图(含折弯线与边框线,靠方向枚举区分)。</summary>
        static Sketch? FindFlatPatternProfileSketch(Feature flatPattern)
        {
            Feature? sub = (Feature?)flatPattern.GetFirstSubFeature();
            while (sub != null)
            {
                if (string.Equals(sub.GetTypeName2(), "ProfileFeature", StringComparison.OrdinalIgnoreCase)
                    && sub.GetSpecificFeature2() is Sketch sketch)
                {
                    return sketch;
                }

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

            return null;
        }

        static void AddSketchBendLineSegmentsToModel(
            ISldWorks swApp,
            Sketch sketch,
            View view,
            List<ModelSegment3D> sink,
            ref int totalLineCount,
            ref int bendLineCount)
        {
            object? raw;
            try
            {
                raw = sketch.GetSketchSegments();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[bend_line_endpoint] GetSketchSegments 异常:{ex.Message}");
                return;
            }

            if (raw is not object[] segs)
            {
                return;
            }

            foreach (object obj in segs)
            {
                if (obj is not SketchLine line)
                {
                    continue;
                }

                totalLineCount++;
                if (!TryIsSketchBendLineSegment(line, out _))
                {
                    continue;
                }

                bendLineCount++;

                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 (!TryPartSketchLocalToModelPoint(swApp, sketch, sk0, out var m0)
                        || !TryPartSketchLocalToModelPoint(swApp, sketch, sk1, out var m1)
                        || m0 == null || m1 == null)
                    {
                        continue;
                    }

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

                    double sx0 = 0;
                    double sy0 = 0;
                    double sx1 = 0;
                    double sy1 = 0;
                    bool hasSheet = TryModelPointToSheetXY(swApp, view, m0, out sx0, out sy0)
                        && TryModelPointToSheetXY(swApp, view, m1, out sx1, out sy1);

                    sink.Add(new ModelSegment3D
                    {
                        P0 = m0,
                        P1 = m1,
                        LengthM = Distance3D(m0, m1),
                        HasSheet2D = hasSheet,
                        SheetX0 = sx0,
                        SheetY0 = sy0,
                        SheetX1 = sx1,
                        SheetY1 = sy1,
                    });
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[bend_line_endpoint] 平板草图折弯线段读取失败:{ex.Message}");
                }
            }
        }

        /// <summary>
        /// 草图线段是否为折弯线(<see cref="SketchLine.GetBendLineDirection"/> → <see cref="swBendLineDirection_e"/>;
        /// 边框轮廓线为 swNotBendLine)。
        /// </summary>
        static bool TryIsSketchBendLineSegment(SketchLine line, out int bendDirection)
        {
            bendDirection = (int)swBendLineDirection_e.swNotBendLine;
            try
            {
                bendDirection = line.GetBendLineDirection();
            }
            catch
            {
                return false;
            }

            return bendDirection == (int)swBendLineDirection_e.swUpDirection
                   || bendDirection == (int)swBendLineDirection_e.swDownDirection;
        }

        /// <summary>零件草图局部坐标 → 模型 3D(仅 ModelToSketchTransform 逆变换,不经视图草图)。</summary>
        static bool TryPartSketchLocalToModelPoint(
            ISldWorks swApp,
            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 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 List<ModelSegment3D> CollectVisibleStraightSegmentsModel3D(View view)
        {
            var segments = new List<ModelSegment3D>();
            object[]? comps;
            try
            {
                comps = (object[])view.GetVisibleComponents();
            }
            catch
            {
                return segments;
            }

            if (comps == null)
            {
                return segments;
            }

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

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

                if (visibleEdges == null)
                {
                    continue;
                }

                foreach (object obj in visibleEdges)
                {
                    if (obj is not Edge edge)
                    {
                        continue;
                    }

                    TryAddVisibleStraightSegment(edge, segments);
                }
            }

            return segments;
        }

        static List<SheetSegment2D> CollectVisibleStraightSegmentsSheet2D(ISldWorks swApp, View view)
        {
            var segments = new List<SheetSegment2D>();
            foreach (var seg in CollectVisibleStraightSegmentsModel3D(view))
            {
                if (!TryModelPointToSheetXY(swApp, view, seg.P0, out var x0, out var y0)
                    || !TryModelPointToSheetXY(swApp, view, seg.P1, out var x1, out var y1))
                {
                    continue;
                }

                if (Distance2D(x0, y0, x1, y1) < 1e-6)
                {
                    continue;
                }

                segments.Add(new SheetSegment2D { X0 = x0, Y0 = y0, X1 = x1, Y1 = y1 });
            }

            return segments;
        }

        static void TryAddVisibleStraightSegment(Edge edge, List<ModelSegment3D> sink)
        {
            try
            {
                var curve = (Curve)edge.GetCurve();
                if (curve == null || !curve.IsLine())
                {
                    return;
                }

                var sv = (Vertex)edge.GetStartVertex();
                var ev = (Vertex)edge.GetEndVertex();
                if (sv == null || ev == null)
                {
                    return;
                }

                var v0 = (double[])sv.GetPoint();
                var v1 = (double[])ev.GetPoint();
                if (v0 == null || v1 == null || v0.Length < 3 || v1.Length < 3)
                {
                    return;
                }

                if (Distance3D(v0, v1) < 1e-6)
                {
                    return;
                }

                sink.Add(new ModelSegment3D
                {
                    P0 = new[] { v0[0], v0[1], v0[2] },
                    P1 = new[] { v1[0], v1[1], v1[2] },
                });
            }
            catch
            {
                // skip
            }
        }

        static bool IsPointOnSegment3D(
            double[] point,
            double[] a,
            double[] b,
            double lineDistTol,
            double extendTol)
        {
            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(point, a) <= lineDistTol;
            }

            double len = Math.Sqrt(lenSq);
            double t = ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy + (point[2] - a[2]) * dz) / lenSq;
            double tMin = -extendTol / len;
            double tMax = 1.0 + extendTol / len;
            if (t < tMin || t > tMax)
            {
                return false;
            }

            double dist = PointToLineDistance3D(point, a, b);
            return dist <= lineDistTol;
        }

        static bool IsPointOnSegment2D(
            double px,
            double py,
            double ax,
            double ay,
            double bx,
            double by,
            double lineDistTol,
            double extendTol)
        {
            double dx = bx - ax;
            double dy = by - ay;
            double lenSq = dx * dx + dy * dy;
            if (lenSq < 1e-18)
            {
                return Distance2D(px, py, ax, ay) <= lineDistTol;
            }

            double len = Math.Sqrt(lenSq);
            double t = ((px - ax) * dx + (py - ay) * dy) / lenSq;
            double tMin = -extendTol / len;
            double tMax = 1.0 + extendTol / len;
            if (t < tMin || t > tMax)
            {
                return false;
            }

            double vx = px - ax;
            double vy = py - ay;
            double cross = Math.Abs(vx * dy - vy * dx) / len;
            return cross <= lineDistTol;
        }

        static bool TryResolvePartModelForView(
            View view,
            out ModelDoc2 partModel,
            out Component2? scopeComponent,
            out string failReason)
        {
            partModel = null!;
            scopeComponent = null;
            failReason = "";

            ModelDoc2? refModel = null;
            try
            {
                refModel = view.ReferencedDocument as ModelDoc2;
            }
            catch
            {
                refModel = null;
            }

            if (refModel == null)
            {
                failReason = "无法获取视图关联文档";
                return false;
            }

            int docType;
            try
            {
                docType = refModel.GetType();
            }
            catch
            {
                failReason = "无法识别视图关联文档类型";
                return false;
            }

            if (docType == (int)swDocumentTypes_e.swDocPART)
            {
                partModel = refModel;
                return true;
            }

            if (docType == (int)swDocumentTypes_e.swDocASSEMBLY)
            {
                return TryResolveSheetMetalPartFromView(view, out partModel, out scopeComponent, out failReason);
            }

            failReason = $"视图关联文档类型({docType})不支持";
            return false;
        }

        static bool TryResolveSheetMetalPartFromView(
            View view,
            out ModelDoc2 partModel,
            out Component2? scopeComponent,
            out string failReason)
        {
            partModel = null!;
            scopeComponent = null;
            failReason = "";

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

            if (comps == null || comps.Length == 0)
            {
                failReason = "装配视图中无可见组件";
                return false;
            }

            ModelDoc2? bestDoc = null;
            Component2? bestComp = null;
            foreach (Component2 comp in comps)
            {
                if (comp == null)
                {
                    continue;
                }

                ModelDoc2? compDoc;
                try
                {
                    compDoc = comp.GetModelDoc2() as ModelDoc2;
                }
                catch
                {
                    continue;
                }

                if (compDoc == null || compDoc.GetType() != (int)swDocumentTypes_e.swDocPART)
                {
                    continue;
                }

                bestDoc = compDoc;
                bestComp = comp;
                break;
            }

            if (bestDoc == null)
            {
                failReason = "装配视图中未找到零件组件";
                return false;
            }

            partModel = bestDoc;
            scopeComponent = bestComp;
            return true;
        }

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

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

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

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

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

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

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

            public void Dispose()
            {
                if (_disposed || !_switched || string.IsNullOrEmpty(_restoreName))
                {
                    return;
                }

                _disposed = true;
                try
                {
                    _model.ShowConfiguration2(_restoreName);
                    _model.EditRebuild3();
                }
                catch
                {
                    // ignored
                }
            }
        }

        static bool TryModelPointToSheetXY(ISldWorks swApp, View view, double[] model3, out double sheetX, out double sheetY)
        {
            sheetX = sheetY = 0;
            try
            {
                var math = swApp.IGetMathUtility();
                if (math == null)
                {
                    return false;
                }

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

                var modelToSheet = (MathTransform)view.ModelToViewTransform;
                if (modelToSheet == null)
                {
                    return false;
                }

                mp = (MathPoint)mp.MultiplyTransform(modelToSheet);
                var arr = (double[])mp.ArrayData;
                if (arr == null || arr.Length < 2)
                {
                    return false;
                }

                sheetX = arr[0];
                sheetY = arr[1];
                return true;
            }
            catch
            {
                return false;
            }
        }

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

            double t = ((p[0] - lineA[0]) * dx + (p[1] - lineA[1]) * dy + (p[2] - lineA[2]) * dz) / lenSq;
            var cx = lineA[0] + t * dx;
            var cy = lineA[1] + t * dy;
            var cz = lineA[2] + t * dz;
            return Distance3D(p, new[] { cx, cy, cz });
        }

        static bool PointsEqual3D(double[] a, double[] b, double tol)
            => Math.Abs(a[0] - b[0]) <= tol
               && Math.Abs(a[1] - b[1]) <= tol
               && Math.Abs(a[2] - b[2]) <= tol;

        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 double Distance2D(double x0, double y0, double x1, double y1)
        {
            double dx = x1 - x0;
            double dy = y1 - y0;
            return Math.Sqrt(dx * dx + dy * dy);
        }

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

        static string FormatPointSheetMm(double x, double y)
            => $"({x * 1000:F2},{y * 1000:F2})";

        static double[]? Sub3(double[] a, double[] b)
            => new[] { a[0] - b[0], a[1] - b[1], a[2] - b[2] };

        static double[]? Normalize3D(double[] v)
        {
            double len = Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
            if (len < 1e-12)
            {
                return null;
            }

            return new[] { v[0] / len, v[1] / len, v[2] / len };
        }

        static double[]? Normalize2D(double dx, double dy)
        {
            double len = Math.Sqrt(dx * dx + dy * dy);
            if (len < 1e-12)
            {
                return null;
            }

            return new[] { dx / len, dy / len };
        }
    }
}
csharp 复制代码
using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Drawing.Imaging;

using System.IO;

using System.Text;



namespace tools

{

    public static partial class selected_view_bend_line_endpoint_check

    {

        const int VisualPaddingPx = 32;

        const int VisualMinCanvasW = 1200;

        const int VisualMinCanvasH = 900;



        sealed class LineSeg2D

        {

            public double X0;

            public double Y0;

            public double X1;

            public double Y1;

        }



        static void SaveDebugVisualization(

            List<ModelSegment3D> bendLines,

            List<SheetSegment2D> visibleLinesSheet2D,

            List<EndpointViolation> violations)

        {

            try

            {

                var visibleSheet2D = ConvertSheetSegmentsToLineSeg2D(visibleLinesSheet2D);

                var bendSheet2D = ConvertBendSegmentsToSheet2D(bendLines);



                if (bendSheet2D.Count == 0 && visibleSheet2D.Count == 0)

                {

                    Console.WriteLine("[bend_line_endpoint] 可视化跳过:未收集到可绘制的线段");

                    return;

                }



                LogSegmentBounds("[bend_line_endpoint] 可见线 bbox(图纸)", visibleSheet2D);

                LogSegmentBounds("[bend_line_endpoint] 折弯线 bbox(图纸)", bendSheet2D);



                BuildFitLayout(

                    visibleSheet2D,

                    bendSheet2D,

                    out double minX,

                    out double minY,

                    out double maxX,

                    out double maxY,

                    out int canvasW,

                    out int canvasH);



                var dir = GetDrwSourceDirectory();

                Directory.CreateDirectory(dir);

                string pngPath = Path.Combine(dir, "bend_line_endpoint_debug.png");

                string svgPath = Path.Combine(dir, "bend_line_endpoint_debug.svg");



                string svg = BuildLinesOnlySvg(

                    visibleSheet2D, bendSheet2D, minX, minY, maxX, maxY, canvasW, canvasH);

                File.WriteAllText(svgPath, svg, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));



                using (var bmp = RenderLinesOnlyBitmap(

                           visibleSheet2D, bendSheet2D, minX, minY, maxX, maxY, canvasW, canvasH))

                {

                    bmp.Save(pngPath, ImageFormat.Png);

                }



                Console.WriteLine(

                    $"[bend_line_endpoint] 可视化:可见线 {visibleSheet2D.Count} 条,折弯线 {bendSheet2D.Count} 条(图纸 2D)");

                Console.WriteLine($"[bend_line_endpoint] 可视化 PNG: {pngPath}");

                Console.WriteLine($"[bend_line_endpoint] 可视化 SVG: {svgPath}");

            }

            catch (Exception ex)

            {

                Console.WriteLine($"[bend_line_endpoint] 保存可视化失败: {ex.Message}");

            }

        }



        static List<LineSeg2D> ConvertSheetSegmentsToLineSeg2D(List<SheetSegment2D> sheetSegments)

        {

            var list = new List<LineSeg2D>();

            foreach (var seg in sheetSegments)

            {

                list.Add(new LineSeg2D { X0 = seg.X0, Y0 = seg.Y0, X1 = seg.X1, Y1 = seg.Y1 });

            }



            return list;

        }



        static List<LineSeg2D> ConvertBendSegmentsToSheet2D(List<ModelSegment3D> bendLines)

        {

            var list = new List<LineSeg2D>();

            foreach (var seg in bendLines)

            {

                if (!seg.HasSheet2D)

                {

                    continue;

                }



                if (Distance2D(seg.SheetX0, seg.SheetY0, seg.SheetX1, seg.SheetY1) < 1e-9)

                {

                    continue;

                }



                list.Add(new LineSeg2D

                {

                    X0 = seg.SheetX0,

                    Y0 = seg.SheetY0,

                    X1 = seg.SheetX1,

                    Y1 = seg.SheetY1,

                });

            }



            return list;

        }



        static void LogSegmentBounds(string tag, List<LineSeg2D> segments)

        {

            if (segments.Count == 0)

            {

                Console.WriteLine($"{tag}: (空)");

                return;

            }



            double minX = double.PositiveInfinity;

            double minY = double.PositiveInfinity;

            double maxX = double.NegativeInfinity;

            double maxY = double.NegativeInfinity;

            foreach (var seg in segments)

            {

                minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));

                minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));

                maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));

                maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));

            }



            Console.WriteLine(

                $"{tag}: X[{minX * 1000:F2},{maxX * 1000:F2}] mm "

                + $"Y[{minY * 1000:F2},{maxY * 1000:F2}] mm,{segments.Count} 条");

        }



        static void BuildFitLayout(

            List<LineSeg2D> visibleLines,

            List<LineSeg2D> bendLines,

            out double minX,

            out double minY,

            out double maxX,

            out double maxY,

            out int canvasW,

            out int canvasH)

        {

            minX = double.PositiveInfinity;

            minY = double.PositiveInfinity;

            maxX = double.NegativeInfinity;

            maxY = double.NegativeInfinity;



            foreach (var seg in visibleLines)

            {

                minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));

                minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));

                maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));

                maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));

            }



            foreach (var seg in bendLines)

            {

                minX = Math.Min(minX, Math.Min(seg.X0, seg.X1));

                minY = Math.Min(minY, Math.Min(seg.Y0, seg.Y1));

                maxX = Math.Max(maxX, Math.Max(seg.X0, seg.X1));

                maxY = Math.Max(maxY, Math.Max(seg.Y0, seg.Y1));

            }



            if (double.IsInfinity(minX))

            {

                minX = minY = 0;

                maxX = maxY = 1;

            }



            double spanX = Math.Max(maxX - minX, 1e-9);

            double spanY = Math.Max(maxY - minY, 1e-9);

            double padX = spanX * 0.06;

            double padY = spanY * 0.06;

            minX -= padX;

            minY -= padY;

            maxX += padX;

            maxY += padY;

            spanX = maxX - minX;

            spanY = maxY - minY;



            double aspect = spanX / spanY;

            if (aspect > (double)VisualMinCanvasW / VisualMinCanvasH)

            {

                canvasW = VisualMinCanvasW;

                canvasH = Math.Max(VisualMinCanvasH, (int)Math.Round(VisualMinCanvasW / aspect));

            }

            else

            {

                canvasH = VisualMinCanvasH;

                canvasW = Math.Max(VisualMinCanvasW, (int)Math.Round(VisualMinCanvasH * aspect));

            }

        }



        static PointF DataToCanvas(

            double x,

            double y,

            double minX,

            double minY,

            double maxX,

            double maxY,

            int canvasW,

            int canvasH)

        {

            double spanX = Math.Max(maxX - minX, 1e-9);

            double spanY = Math.Max(maxY - minY, 1e-9);

            double drawW = canvasW - VisualPaddingPx * 2.0;

            double drawH = canvasH - VisualPaddingPx * 2.0;

            float px = VisualPaddingPx + (float)((x - minX) / spanX * drawW);

            float py = VisualPaddingPx + (float)((maxY - y) / spanY * drawH);

            return new PointF(px, py);

        }



        static Bitmap RenderLinesOnlyBitmap(

            List<LineSeg2D> visibleLines,

            List<LineSeg2D> bendLines,

            double minX,

            double minY,

            double maxX,

            double maxY,

            int canvasW,

            int canvasH)

        {

            var bmp = new Bitmap(canvasW, canvasH, PixelFormat.Format32bppArgb);

            using (var g = Graphics.FromImage(bmp))

            {

                g.SmoothingMode = SmoothingMode.AntiAlias;

                g.Clear(Color.White);



                using (var visiblePen = new Pen(Color.FromArgb(51, 65, 85), 1f))

                using (var bendPen = new Pen(Color.FromArgb(220, 38, 38), 2.5f))

                {

                    foreach (var line in visibleLines)

                    {

                        var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);

                        var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);

                        g.DrawLine(visiblePen, a, b);

                    }



                    foreach (var line in bendLines)

                    {

                        var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);

                        var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);

                        g.DrawLine(bendPen, a, b);

                    }

                }

            }



            return bmp;

        }



        static string BuildLinesOnlySvg(

            List<LineSeg2D> visibleLines,

            List<LineSeg2D> bendLines,

            double minX,

            double minY,

            double maxX,

            double maxY,

            int canvasW,

            int canvasH)

        {

            var sb = new StringBuilder();

            sb.AppendLine(

                $"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{canvasW}\" height=\"{canvasH}\" viewBox=\"0 0 {canvasW} {canvasH}\">");

            sb.AppendLine("<rect width=\"100%\" height=\"100%\" fill=\"#ffffff\"/>");



            foreach (var line in visibleLines)

            {

                var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);

                var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);

                sb.AppendLine(FormattableString.Invariant(

                    $"<line x1=\"{a.X:F2}\" y1=\"{a.Y:F2}\" x2=\"{b.X:F2}\" y2=\"{b.Y:F2}\" stroke=\"#334155\" stroke-width=\"1\"/>"));

            }



            foreach (var line in bendLines)

            {

                var a = DataToCanvas(line.X0, line.Y0, minX, minY, maxX, maxY, canvasW, canvasH);

                var b = DataToCanvas(line.X1, line.Y1, minX, minY, maxX, maxY, canvasW, canvasH);

                sb.AppendLine(FormattableString.Invariant(

                    $"<line x1=\"{a.X:F2}\" y1=\"{a.Y:F2}\" x2=\"{b.X:F2}\" y2=\"{b.Y:F2}\" stroke=\"#dc2626\" stroke-width=\"2.5\"/>"));

            }



            sb.AppendLine("</svg>");

            return sb.ToString();

        }



        static string GetDrwSourceDirectory()

        {

            try

            {

                var loc = typeof(selected_view_bend_line_endpoint_check).Assembly.Location;

                if (!string.IsNullOrEmpty(loc))

                {

                    var asmDir = Path.GetDirectoryName(loc);

                    if (!string.IsNullOrEmpty(asmDir))

                    {

                        var fromSwPlugin = Path.GetFullPath(Path.Combine(

                            asmDir, "..", "..", "..", "..", "share", "src", "drw"));

                        if (Directory.Exists(fromSwPlugin))

                        {

                            return fromSwPlugin;

                        }



                        var fromShare = Path.GetFullPath(Path.Combine(

                            asmDir, "..", "..", "..", "src", "drw"));

                        if (Directory.Exists(fromShare))

                        {

                            return fromShare;

                        }

                    }

                }

            }

            catch

            {

                // ignored

            }



            return Path.Combine(AppContext.BaseDirectory, "bend_line_endpoint_debug");

        }

    }

}
相关推荐
solidwork_s10 天前
SOLIDWORKS格式处理干货:STEP、IGS、X_T 优化技巧
solidworks
njsgcs19 天前
导出glb可视化让vlm看图找面 自己配合
智能制造·solidworks
花开月满西楼20 天前
零基础速学SolidWorks2026【曲面】曲面缝合
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼20 天前
零基础速学SolidWorks2026【曲面】删除面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼20 天前
零基础速学SolidWorks2026【曲面】加厚曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼23 天前
零基础速学SolidWorks2026【曲面】边界曲面和等距曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼23 天前
零基础速学SolidWorks2026【曲面】平面区域和填充曲面
3d建模·solidworks·机械设计·机械绘图绘制
蓝悦无人机24 天前
SOLIDWORKS高级应用之方程
solidworks·参数化建模·if 判断语句
花开月满西楼24 天前
零基础速学SolidWorks2026【曲面】分割线
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼1 个月前
零基础速学SolidWorks2026【曲面】建模核心基础
solidworks·机械设计·机械绘图绘制