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");

        }

    }

}
相关推荐
njsgcs17 小时前
c# solidworks工程图折弯注释大小 swSheetMetalBendNotesTextFormat
solidworks
njsgcs21 小时前
c# solidworks 检查折弯拉孔
solidworks
njsgcs3 天前
c# solidworks 自动标注折弯7 图可视化,清晰定义,画点改画线
solidworks
njsgcs5 天前
solidworks装配体显示子零件文档的颜色外观办法
solidworks
solidwork_s5 天前
在SOLIDWORKS中如何将小数点显示为逗号
solidworks
ddsoft1236 天前
CAMWorks 用户自定义车刀教程
软件·solidworks·钣金
njsgcs15 天前
solidworks二次开发文档chm位置 sldworksapi.chm
solidworks
njsgcs21 天前
制作solidworks插件 装配体导出展开耗时分析
开发语言·c#·solidworks
njsgcs21 天前
c# solidworks 标注攻牙
开发语言·c#·solidworks