c# solidworks 删除报错配合(包括子装配体)

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

namespace tools
{
    /// <summary>
    /// 遍历装配体及其子装配体特征树,删除配合特征(<see cref="Mate2"/>)且 <see cref="Feature.GetErrorCode2"/> 非零的配合。
    /// </summary>
    public static class delete_broken_mates
    {
        private sealed class ScanStats
        {
            public int AssemblyCount;
            public int FeatureCount;
            public int MateCount;
            public int SuppressedMateCount;
            public int BrokenMateCount;
            public int NonMateErrorCount;
        }

        public static int Run(SldWorks swApp, ModelDoc2 swModel)
        {
            if (swApp == null || swModel == null)
            {
                Console.WriteLine("[delete_broken_mates] swApp/swModel 为空,退出。");
                return 0;
            }

            if (swModel.GetType() != (int)swDocumentTypes_e.swDocASSEMBLY)
            {
                Console.WriteLine("[delete_broken_mates] 当前文档不是装配体,退出。");
                return 0;
            }

            string docPath = "";
            try { docPath = swModel.GetPathName() ?? ""; } catch { /* ignore */ }
            string docTitle = "";
            try { docTitle = swModel.GetTitle() ?? ""; } catch { /* ignore */ }
            Console.WriteLine($"[delete_broken_mates] 开始扫描装配体(含子装配体): {docTitle} ({docPath})");

            string originalActiveTitle = "";
            try { originalActiveTitle = (swApp.ActiveDoc as ModelDoc2)?.GetTitle()?.Trim() ?? docTitle; }
            catch { originalActiveTitle = docTitle; }

            var stats = new ScanStats();
            int deleted = 0;

            deleted += ScanAndDeleteInDocument(swModel, "根装配体", stats);

            var processedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            string rootKey = GetDocumentKey(swModel);
            if (!string.IsNullOrEmpty(rootKey))
            {
                processedKeys.Add(rootKey);
            }

            foreach ((ModelDoc2 subDoc, string label) in CollectSubAssemblyDocuments((AssemblyDoc)swModel, processedKeys))
            {
                ModelDoc2? workDoc = TryActivateAssemblyDocument(swApp, subDoc, label);
                if (workDoc == null)
                {
                    Console.WriteLine($"[delete_broken_mates] 无法激活子装配体文档,跳过: {label}");
                    continue;
                }

                deleted += ScanAndDeleteInDocument(workDoc, label, stats);
            }

            RestoreActiveDocument(swApp, originalActiveTitle);

            Console.WriteLine(
                $"[delete_broken_mates] 全部完成: 扫描装配体 {stats.AssemblyCount} 个, " +
                $"特征 {stats.FeatureCount} 个, 配合 {stats.MateCount} 个(抑制 {stats.SuppressedMateCount} 个), " +
                $"报错配合 {stats.BrokenMateCount} 个, 非配合报错特征 {stats.NonMateErrorCount} 个, " +
                $"实际删除 {deleted} 个。");
            return deleted;
        }

        private static int ScanAndDeleteInDocument(ModelDoc2 doc, string contextLabel, ScanStats stats)
        {
            stats.AssemblyCount++;
            Console.WriteLine($"[delete_broken_mates] ── 扫描 {contextLabel} ──");
            Console.WriteLine("[delete_broken_mates] 执行 EditRebuild3...");
            doc.EditRebuild3();

            var toDelete = new List<Feature>();
            Feature root = (Feature)doc.FirstFeature();
            CollectBrokenMatesRecursive(root, toDelete, stats, depth: 0);

            Console.WriteLine($"[delete_broken_mates] {contextLabel} 扫描: 报错配合 {toDelete.Count} 个");
            return DeleteMateFeatures(doc, toDelete, contextLabel);
        }

        private static List<(ModelDoc2 doc, string label)> CollectSubAssemblyDocuments(
            AssemblyDoc rootAsm,
            HashSet<string> processedKeys)
        {
            var result = new List<(ModelDoc2, string)>();
            object[]? raw = rootAsm.GetComponents(false) as object[];
            if (raw == null)
            {
                return result;
            }

            foreach (object obj in raw)
            {
                if (obj is not Component2 comp)
                {
                    continue;
                }

                if (SafeIsSuppressed(comp))
                {
                    continue;
                }

                TryResolveLightweightComponent(comp);
                ModelDoc2? subDoc = TryGetAssemblyDoc(comp);
                if (subDoc == null)
                {
                    continue;
                }

                string key = GetDocumentKey(subDoc);
                if (string.IsNullOrEmpty(key) || !processedKeys.Add(key))
                {
                    continue;
                }

                result.Add((subDoc, BuildComponentLabel(comp, subDoc)));
            }

            return result;
        }

        private static ModelDoc2? TryActivateAssemblyDocument(SldWorks swApp, ModelDoc2 doc, string contextLabel)
        {
            string path = GetAssemblyPath(doc);
            string title = "";
            try { title = doc.GetTitle()?.Trim() ?? ""; } catch { /* ignore */ }

            ModelDoc2? target = doc;
            if (!string.IsNullOrEmpty(path))
            {
                target = swApp.GetOpenDocumentByName(path) as ModelDoc2 ?? target;
            }

            if (!string.IsNullOrEmpty(title))
            {
                target = swApp.GetOpenDocumentByName(title) as ModelDoc2 ?? target;
            }

            if (target == null)
            {
                return null;
            }

            if (!string.IsNullOrEmpty(path)
                && string.IsNullOrEmpty(GetAssemblyPath(target))
                && System.IO.File.Exists(path))
            {
                int openErrors = 0;
                int openWarnings = 0;
                ModelDoc2? opened = swApp.OpenDoc6(
                    path,
                    (int)swDocumentTypes_e.swDocASSEMBLY,
                    (int)swOpenDocOptions_e.swOpenDocOptions_Silent,
                    string.Empty,
                    ref openErrors,
                    ref openWarnings) as ModelDoc2;
                if (opened != null)
                {
                    target = opened;
                    Console.WriteLine($"[delete_broken_mates] 已静默打开子装配体: {contextLabel}");
                }
                else
                {
                    Console.WriteLine(
                        $"[delete_broken_mates] OpenDoc6 失败 [{contextLabel}] errors={openErrors} warnings={openWarnings}");
                }
            }

            string activateTitle = "";
            try { activateTitle = target.GetTitle()?.Trim() ?? title; } catch { activateTitle = title; }
            if (string.IsNullOrEmpty(activateTitle))
            {
                Console.WriteLine($"[delete_broken_mates] 子装配体无标题,跳过: {contextLabel}");
                return null;
            }

            int activateErr = 0;
            swApp.ActivateDoc3(
                activateTitle,
                false,
                (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                ref activateErr);

            ModelDoc2? active = swApp.ActiveDoc as ModelDoc2;
            if (active != null && IsSameDocument(active, target))
            {
                Console.WriteLine($"[delete_broken_mates] 已激活子装配体: {contextLabel}");
                return active;
            }

            Console.WriteLine(
                $"[delete_broken_mates] ActivateDoc3 后活动文档不匹配 [{contextLabel}] activateErr={activateErr}");
            return target;
        }

        private static void RestoreActiveDocument(SldWorks swApp, string originalTitle)
        {
            if (string.IsNullOrWhiteSpace(originalTitle))
            {
                return;
            }

            try
            {
                int activateErr = 0;
                swApp.ActivateDoc3(
                    originalTitle.Trim(),
                    false,
                    (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                    ref activateErr);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[delete_broken_mates] 恢复活动文档失败: {ex.Message}");
            }
        }

        private static bool IsSameDocument(ModelDoc2 a, ModelDoc2 b)
        {
            if (a == b)
            {
                return true;
            }

            string pathA = GetAssemblyPath(a);
            string pathB = GetAssemblyPath(b);
            if (!string.IsNullOrEmpty(pathA) && !string.IsNullOrEmpty(pathB))
            {
                return pathA.Equals(pathB, StringComparison.OrdinalIgnoreCase);
            }

            string titleA = "";
            string titleB = "";
            try { titleA = a.GetTitle()?.Trim() ?? ""; } catch { /* ignore */ }
            try { titleB = b.GetTitle()?.Trim() ?? ""; } catch { /* ignore */ }
            return !string.IsNullOrEmpty(titleA)
                && titleA.Equals(titleB, StringComparison.OrdinalIgnoreCase);
        }

        private static string GetDocumentKey(ModelDoc2 doc)
        {
            string path = GetAssemblyPath(doc);
            if (!string.IsNullOrEmpty(path))
            {
                return path;
            }

            try { return doc.GetTitle()?.Trim() ?? ""; }
            catch { return ""; }
        }

        private static int DeleteMateFeatures(ModelDoc2 doc, List<Feature> toDelete, string contextLabel)
        {
            doc.ClearSelection2(true);
            int deleted = 0;
            foreach (Feature feat in toDelete)
            {
                try
                {
                    if (feat == null)
                    {
                        continue;
                    }

                    string featName = feat.Name ?? "";
                    doc.ClearSelection2(true);
                    bool sel = feat.Select2(false, 0);
                    if (!sel)
                    {
                        Console.WriteLine(
                            $"[delete_broken_mates] [{contextLabel}] 无法选中配合特征,跳过: {featName}");
                        continue;
                    }

                    doc.EditDelete();
                    deleted++;
                    Console.WriteLine($"[delete_broken_mates] [{contextLabel}] 已删除配合: {featName}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine(
                        $"[delete_broken_mates] [{contextLabel}] 删除配合失败: {ex.Message}");
                }
                finally
                {
                    doc.ClearSelection2(true);
                }
            }

            return deleted;
        }

        private static ModelDoc2? TryGetAssemblyDoc(Component2 comp)
        {
            try
            {
                ModelDoc2? doc = comp.GetModelDoc2() as ModelDoc2;
                if (doc == null)
                {
                    return null;
                }

                return doc.GetType() == (int)swDocumentTypes_e.swDocASSEMBLY ? doc : null;
            }
            catch
            {
                return null;
            }
        }

        private static void TryResolveLightweightComponent(Component2? component)
        {
            if (component == null)
            {
                return;
            }

            try
            {
                int sup = component.GetSuppression();
                if (sup == (int)swComponentSuppressionState_e.swComponentLightweight)
                {
                    component.SetSuppression2((int)swComponentSuppressionState_e.swComponentFullyResolved);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(
                    $"[delete_broken_mates] 轻化还原失败 [{component.Name2}]: {ex.Message}");
            }
        }

        private static bool SafeIsSuppressed(Component2 comp)
        {
            try { return comp.IsSuppressed(); }
            catch { return true; }
        }

        private static string GetAssemblyPath(ModelDoc2 asmDoc)
        {
            try { return asmDoc.GetPathName()?.Trim() ?? ""; }
            catch { return ""; }
        }

        private static string BuildComponentLabel(Component2 comp, ModelDoc2 asmDoc)
        {
            string compName = "";
            try { compName = comp.Name2?.Trim() ?? ""; } catch { /* ignore */ }
            string path = GetAssemblyPath(asmDoc);
            if (string.IsNullOrEmpty(path))
            {
                return string.IsNullOrEmpty(compName) ? "子装配体" : compName;
            }

            return string.IsNullOrEmpty(compName)
                ? path
                : $"{compName} ({path})";
        }

        private static void CollectBrokenMatesRecursive(Feature feat, List<Feature> acc, ScanStats stats, int depth)
        {
            Feature f = feat;
            while (f != null)
            {
                stats.FeatureCount++;
                TryAddBrokenMate(f, acc, stats, depth);
                Feature sub = (Feature)f.GetFirstSubFeature();
                if (sub != null)
                {
                    CollectBrokenMatesRecursive(sub, acc, stats, depth + 1);
                }

                f = depth == 0
                    ? (Feature)f.GetNextFeature()
                    : (Feature)f.GetNextSubFeature();
            }
        }

        private static void TryAddBrokenMate(Feature feat, List<Feature> acc, ScanStats stats, int depth)
        {
            try
            {
                string featName = feat.Name ?? "";
                string featType = "";
                try { featType = feat.GetTypeName2() ?? ""; } catch { /* ignore */ }
                string indent = new string(' ', Math.Min(depth, 8) * 2);

                bool suppressed = false;
                try { suppressed = feat.IsSuppressed(); } catch { /* ignore */ }

                int errCode = 0;
                try { errCode = feat.GetErrorCode2(out _); } catch { errCode = 0; }

                object? spec = null;
                try { spec = feat.GetSpecificFeature2(); } catch { /* ignore */ }

                if (spec is Mate2 mate)
                {
                    stats.MateCount++;
                    string mateType = "";
                    try { mateType = ((swMateType_e)mate.Type).ToString(); } catch { /* ignore */ }

                    if (suppressed)
                    {
                        stats.SuppressedMateCount++;
                        Console.WriteLine(
                            $"{indent}[配合·抑制] {featName} type={mateType} err={errCode}");
                        return;
                    }

                    Console.WriteLine(
                        $"{indent}[配合] {featName} type={mateType} err={errCode} featType={featType}");

                    if (errCode != 0)
                    {
                        stats.BrokenMateCount++;
                        acc.Add(feat);
                        Console.WriteLine($"{indent}  → 标记为报错配合 (err={errCode})");
                    }

                    return;
                }

                if (errCode != 0)
                {
                    stats.NonMateErrorCount++;
                    Console.WriteLine(
                        $"{indent}[非配合·报错] {featName} featType={featType} err={errCode}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[delete_broken_mates] 扫描特征异常: {ex.Message}");
            }
        }
    }
}