solidworks修改尺寸可以不用方程式直接标记尺寸名称 c#

用方程也得创建方程式,直接改尺寸名称,标记名称,手动改反而更方便

csharp 复制代码
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace tools
{
    /// <summary>
    /// 一条方程式快照(用于机型切换窗格持久化)。
    /// </summary>
    [Serializable]
    public class EquationSnapshotEntry
    {
        public string PartStorageKey { get; set; } = "";
        public string PartDisplayLabel { get; set; } = "";
        public int EquationIndex { get; set; }
        public string VariableName { get; set; } = "";
        /// <summary>方程式右侧(与修改方程式对话框输入一致)</summary>
        public string ValueExpression { get; set; } = "";
    }

    /// <summary>
    /// 方程式管理器:支持单零件或多零件统一窗口;列表项带零件前缀以区分重名变量。
    /// </summary>
    public class EquationModifier
    {
        private sealed class EquationListRow
        {
            public ModelDoc2 Part = null!;
            public int EquationIndex;
            public string PartDisplayLabel = "";
        }

        /// <summary>判断文档是否为零件或装配体(方程式可编辑的文档类型)。</summary>
        private static bool IsPartOrAssembly(ModelDoc2 doc)
        {
            if (doc == null)
            {
                return false;
            }

            int t = doc.GetType();
            return t == (int)swDocumentTypes_e.swDocPART
                || t == (int)swDocumentTypes_e.swDocASSEMBLY;
        }

        /// <summary>返回文档类型中文标签:零件 / 装配体。</summary>
        private static string GetDocTypeLabel(ModelDoc2 doc)
        {
            if (doc != null && doc.GetType() == (int)swDocumentTypes_e.swDocASSEMBLY)
            {
                return "装配体";
            }

            return "零件";
        }

        /// <summary>
        /// 单零件入口(兼容旧调用)。
        /// </summary>
        public static string ModifyEquations(SldWorks swApp, ModelDoc2 swModel)
        {
            return ModifyEquationsForParts(swApp, new List<ModelDoc2> { swModel });
        }

        /// <summary>
        /// 多零件:一个窗口列出所有方程式,每项带 [零件标签] 前缀。
        /// </summary>
        public static string ModifyEquationsForParts(SldWorks swApp, IList<ModelDoc2> parts)
        {
            if (swApp == null || parts == null || parts.Count == 0)
            {
                return "SolidWorks 或零件列表无效";
            }

            var unique = DeduplicateParts(parts);
            if (unique.Count == 0)
            {
                return "没有可编辑的文档";
            }

            var visibleParts = new List<ModelDoc2>();
            var invisiblePartNames = new List<string>();

            foreach (var p in unique)
            {
                try
                {
                    p.Visible = true;
                    if (p.Visible)
                    {
                        visibleParts.Add(p);
                    }
                    else
                    {
                        invisiblePartNames.Add(p.GetTitle());
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"设置零件可见失败: {ex.Message}");
                    invisiblePartNames.Add(p.GetTitle());
                }
            }

            try
            {
                if (visibleParts.Count == 0)
                {
                    swApp.SendMsgToUser("所选文档均不可见,无法修改方程式");
                    return "所选文档均不可见,无法修改方程式";
                }

                if (invisiblePartNames.Count > 0)
                {
                    swApp.SendMsgToUser($"以下文档不可见,已跳过:{string.Join("、", invisiblePartNames.Distinct(StringComparer.OrdinalIgnoreCase))}");
                }

                var rows = BuildEquationRows(visibleParts);
                if (rows.Count == 0)
                {
                    swApp.SendMsgToUser("所选文档中没有「全局变量」方程式(尺寸方程已忽略)");
                    return "所选文档中没有全局变量方程式";
                }

                var applied = new[] { false };
                if (!ShowUnifiedEquationDialog(swApp, rows, applied))
                {
                    return "用户取消了操作";
                }

                return applied[0] ? "方程式修改完成" : "未应用任何修改";
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"修改方程式失败:{ex.Message}");
                swApp?.SendMsgToUser($"修改方程式失败:{ex.Message}");
                return $"修改方程式失败:{ex.Message}";
            }
            finally
            {
                foreach (var p in visibleParts)
                {
                    try
                    {
                        p.Visible = false;
                    }
                    catch
                    {
                        // ignore
                    }
                }
            }
        }

        private static List<ModelDoc2> DeduplicateParts(IEnumerable<ModelDoc2> parts)
        {
            var list = new List<ModelDoc2>();
            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var p in parts)
            {
                if (!IsPartOrAssembly(p))
                {
                    continue;
                }

                string key = p.GetPathName();
                if (string.IsNullOrEmpty(key))
                {
                    key = p.GetTitle();
                }

                if (seen.Add(key))
                {
                    list.Add(p);
                }
            }

            return list;
        }

        private static string GetPartDisplayLabel(ModelDoc2 p, IReadOnlyList<ModelDoc2> allParts)
        {
            string title = p.GetTitle();
            int sameTitle = allParts.Count(x => string.Equals(x.GetTitle(), title, StringComparison.OrdinalIgnoreCase));
            if (sameTitle > 1)
            {
                string path = p.GetPathName();
                return string.IsNullOrEmpty(path)
                    ? title
                    : $"{Path.GetFileName(path)}  |  {path}";
            }

            return title;
        }

        private static List<EquationListRow> BuildEquationRows(List<ModelDoc2> parts)
        {
            var rows = new List<EquationListRow>();

            foreach (var p in parts)
            {
                EquationMgr eqnMgr = null;
                try
                {
                    eqnMgr = (EquationMgr)p.GetEquationMgr();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"获取方程式管理器失败 {p.GetTitle()}: {ex.Message}");
                }

                if (eqnMgr == null)
                {
                    continue;
                }

                int count = eqnMgr.GetCount();
                if (count <= 0)
                {
                    continue;
                }

                string partLabel = GetPartDisplayLabel(p, parts);

                for (int i = 0; i < count; i++)
                {
                    if (!eqnMgr.get_GlobalVariable(i))
                    {
                        continue;
                    }

                    rows.Add(new EquationListRow
                    {
                        Part = p,
                        EquationIndex = i,
                        PartDisplayLabel = partLabel
                    });
                }
            }

            return rows;
        }

        private static string FormatListLine(EquationListRow row, EquationMgr mgr)
        {
            string equation = mgr.get_Equation(row.EquationIndex);
            bool isGlobalVar = mgr.get_GlobalVariable(row.EquationIndex);
            string typeStr = isGlobalVar ? "[全局变量]" : "[尺寸方程]";
            string docLabel = GetDocTypeLabel(row.Part);
            return $"[{docLabel}: {row.PartDisplayLabel}]  #{row.EquationIndex + 1} {typeStr} {equation}";
        }

        private static bool ShowUnifiedEquationDialog(SldWorks swApp, List<EquationListRow> rows, bool[] appliedFlag)
        {
            using (var form = new Form())
            {
                int partCount = rows
                    .Select(r =>
                    {
                        string k = r.Part.GetPathName();
                        return string.IsNullOrEmpty(k) ? r.Part.GetTitle() : k;
                    })
                    .Distinct(StringComparer.OrdinalIgnoreCase)
                    .Count();
                form.Text = rows.Count > 0 ? $"修改全局变量(共 {partCount} 个文档)" : "修改全局变量";
                form.Size = new Size(880, 520);
                form.StartPosition = FormStartPosition.CenterScreen;
                form.MinimumSize = new Size(640, 420);
                form.TopMost = true;

                var mainPanel = new TableLayoutPanel();
                mainPanel.Dock = DockStyle.Fill;
                mainPanel.ColumnCount = 1;
                mainPanel.RowCount = 5;
                mainPanel.Padding = new Padding(10);
                mainPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
                mainPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
                mainPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
                mainPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
                mainPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));

                var titleLabel = new Label
                {
                    Text = "此处仅列出「全局变量」方程式(尺寸驱动方程已隐藏)。同名变量请认准 [零件/装配体: ...] 标签;可多次「应用」,满意后点「完成」关闭。",
                    Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold, GraphicsUnit.Point),
                    AutoSize = true
                };
                mainPanel.Controls.Add(titleLabel, 0, 0);

                var listBox = new ListBox
                {
                    Dock = DockStyle.Fill,
                    IntegralHeight = false,
                    SelectionMode = SelectionMode.One,
                    Font = new Font("Consolas", 9F)
                };

                for (int i = 0; i < rows.Count; i++)
                {
                    var mgr = (EquationMgr)rows[i].Part.GetEquationMgr();
                    listBox.Items.Add(FormatListLine(rows[i], mgr));
                }

                mainPanel.Controls.Add(listBox, 0, 1);

                var inputPanel = new TableLayoutPanel();
                inputPanel.ColumnCount = 1;
                inputPanel.RowCount = 2;
                inputPanel.Dock = DockStyle.Fill;
                inputPanel.Padding = new Padding(0, 10, 0, 0);
                inputPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
                inputPanel.RowStyles.Add(new RowStyle(SizeType.AutoSize));

                var valueLabel = new Label
                {
                    Text = "新值(支持表达式,如 150+231-18):",
                    AutoSize = true,
                    Dock = DockStyle.Fill
                };
                inputPanel.Controls.Add(valueLabel, 0, 0);

                var textBox = new TextBox
                {
                    Dock = DockStyle.Top,
                    MinimumSize = new Size(300, 28),
                    Margin = new Padding(0, 6, 0, 0)
                };
                inputPanel.Controls.Add(textBox, 0, 1);

                var statusLabel = new Label
                {
                    Text = "",
                    AutoSize = true,
                    ForeColor = Color.DimGray
                };

                bool ActivatePartByRow(EquationListRow row)
                {
                    try
                    {
                        if (row?.Part == null)
                        {
                            return false;
                        }

                        row.Part.Visible = true;

                        string title = row.Part.GetTitle();
                        if (!string.IsNullOrEmpty(title))
                        {
                            int errors = 0;
                            swApp.ActivateDoc3(
                                title,
                                true,
                                (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                                ref errors);
                        }

                        return row.Part.Visible;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"切换零件失败: {ex.Message}");
                        return false;
                    }
                }

                void RefreshListLine(int index)
                {
                    if (index < 0 || index >= rows.Count)
                    {
                        return;
                    }

                    var mgr = (EquationMgr)rows[index].Part.GetEquationMgr();
                    listBox.Items[index] = FormatListLine(rows[index], mgr);
                }

                void SyncTextFromSelection()
                {
                    if (listBox.SelectedIndex < 0)
                    {
                        return;
                    }

                    var row = rows[listBox.SelectedIndex];
                    if (!ActivatePartByRow(row))
                    {
                        statusLabel.Text = $"切换文档失败:{row.PartDisplayLabel}";
                        return;
                    }

                    var mgr = (EquationMgr)row.Part.GetEquationMgr();
                    if (mgr == null)
                    {
                        return;
                    }

                    string equation = mgr.get_Equation(row.EquationIndex);
                    textBox.Text = ExtractEquationValue(equation);
                    textBox.Focus();
                    textBox.SelectAll();
                    statusLabel.Text = $"已切换文档:{row.PartDisplayLabel}";
                }

                listBox.SelectedIndexChanged += (s, e) => SyncTextFromSelection();

                mainPanel.Controls.Add(inputPanel, 0, 2);
                mainPanel.Controls.Add(statusLabel, 0, 3);

                var buttonPanel = new FlowLayoutPanel
                {
                    FlowDirection = FlowDirection.LeftToRight,
                    AutoSize = true,
                    WrapContents = false,
                    Padding = new Padding(0, 10, 0, 0)
                };

                var cancelButton = new Button { Text = "取消", Width = 80, DialogResult = DialogResult.Cancel };
                var doneButton = new Button { Text = "完成", Width = 80, DialogResult = DialogResult.OK };
                var applyButton = new Button { Text = "应用", Width = 80 };

                buttonPanel.Controls.Add(applyButton);
                buttonPanel.Controls.Add(doneButton);
                buttonPanel.Controls.Add(cancelButton);

                mainPanel.Controls.Add(buttonPanel, 0, 4);

                form.Controls.Add(mainPanel);
                // Enter 由下方 textBox 专门处理(应用或跳过未改项后跳到下一条),不设 AcceptButton,避免与文本框冲突。
                form.CancelButton = cancelButton;

                void ApplyEnterThenNext()
                {
                    if (listBox.SelectedIndex < 0)
                    {
                        swApp.SendMsgToUser("请先选择一条方程式");
                        return;
                    }

                    string newValue = textBox.Text.Trim();
                    if (string.IsNullOrEmpty(newValue))
                    {
                        swApp.SendMsgToUser("请输入新的方程式值");
                        return;
                    }

                    var row = rows[listBox.SelectedIndex];
                    var mgr = (EquationMgr)row.Part.GetEquationMgr();
                    if (mgr == null)
                    {
                        return;
                    }

                    string originalRhs = ExtractEquationValue(mgr.get_Equation(row.EquationIndex));
                    string calculatedValue = EvaluateExpression(newValue);
                    bool unchanged = IsEquationValueEquivalent(originalRhs, calculatedValue);

                    if (!unchanged)
                    {
                        if (!TryApplyEquationRhs(mgr, row.Part, row.EquationIndex, newValue, swApp, row.PartDisplayLabel))
                        {
                            return;
                        }

                        RefreshListLine(listBox.SelectedIndex);
                        appliedFlag[0] = true;
                        statusLabel.Text =
                            $"已应用:{DateTime.Now:HH:mm:ss}  [{row.PartDisplayLabel}] 第 {row.EquationIndex + 1} 条";
                    }
                    else
                    {
                        statusLabel.Text =
                            $"未改动·Enter 下一项:{DateTime.Now:HH:mm:ss}  [{row.PartDisplayLabel}] 第 {row.EquationIndex + 1} 条";
                    }

                    int next = listBox.SelectedIndex + 1;
                    if (next >= rows.Count)
                    {
                        next = 0;
                    }

                    listBox.SelectedIndex = next;
                }

                void ApplyCurrent()
                {
                    if (listBox.SelectedIndex < 0)
                    {
                        swApp.SendMsgToUser("请先选择一条方程式");
                        return;
                    }

                    string newValue = textBox.Text.Trim();
                    if (string.IsNullOrEmpty(newValue))
                    {
                        swApp.SendMsgToUser("请输入新的方程式值");
                        return;
                    }

                    var row = rows[listBox.SelectedIndex];
                    var mgr = (EquationMgr)row.Part.GetEquationMgr();
                    if (mgr == null)
                    {
                        return;
                    }

                    if (!TryApplyEquationRhs(mgr, row.Part, row.EquationIndex, newValue, swApp, row.PartDisplayLabel))
                    {
                        return;
                    }

                    RefreshListLine(listBox.SelectedIndex);
                    appliedFlag[0] = true;
                    statusLabel.Text = $"已应用:{DateTime.Now:HH:mm:ss}  [{row.PartDisplayLabel}] 第 {row.EquationIndex + 1} 条";
                }

                applyButton.Click += (s, e) => ApplyCurrent();

                textBox.KeyDown += (s, e) =>
                {
                    if (e.KeyCode == Keys.Enter)
                    {
                        e.SuppressKeyPress = true;
                        ApplyEnterThenNext();
                    }
                };

                form.Shown += (s, e) =>
                {
                    if (listBox.Items.Count > 0)
                    {
                        listBox.SelectedIndex = 0;
                    }
                    else
                    {
                        textBox.Focus();
                    }
                };

                bool accepted = form.ShowDialog() == DialogResult.OK;
                RebuildDistinctEquationPartsAndAssembly(swApp, rows);
                return accepted;
            }
        }

        /// <summary>
        /// 关闭方程式窗口后:对涉及零件及其直接上级装配体各重建一次。
        /// </summary>
        private static void RebuildDistinctEquationPartsAndAssembly(SldWorks swApp, List<EquationListRow> rows)
        {
            if (swApp == null || rows == null || rows.Count == 0)
            {
                return;
            }

            TryRebuildDirectParentAssembliesForParts(swApp, rows.Select(r => r.Part));
        }

        /// <summary>
        /// 方程式写入后、重命名前:重建零件,并重建其直接所属装配体(上一级即可)。
        /// </summary>
        private static void TryRebuildPartAndDirectParentAssembly(SldWorks swApp, ModelDoc2 part)
        {
            if (swApp == null || part == null)
            {
                return;
            }

            try
            {
                part.EditRebuild3();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"零件重建失败 {part.GetTitle()}: {ex.Message}");
            }

            string partPath = part.GetPathName() ?? "";
            if (string.IsNullOrWhiteSpace(partPath))
            {
                return;
            }

            if (!TryFindPartComponentForRenameInOpenAssemblies(swApp, partPath, out _, out ModelDoc2 owningAssembly)
                || owningAssembly == null)
            {
                return;
            }

            ModelDoc2 activeBefore = swApp.ActiveDoc as ModelDoc2;
            try
            {
                int errors = 0;
                swApp.ActivateDoc3(
                    owningAssembly.GetTitle() ?? "",
                    true,
                    (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                    ref errors);
                owningAssembly.EditRebuild3();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"直接上级装配体重建失败 {owningAssembly.GetTitle()}: {ex.Message}");
            }
            finally
            {
                RestoreActiveDocument(swApp, activeBefore);
            }
        }

        private static void TryRebuildDirectParentAssembliesForParts(SldWorks swApp, IEnumerable<ModelDoc2> parts)
        {
            if (swApp == null || parts == null)
            {
                return;
            }

            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var part in parts)
            {
                if (part == null)
                {
                    continue;
                }

                string key = GetPartStorageKey(part);
                if (string.IsNullOrWhiteSpace(key) || !seen.Add(key))
                {
                    continue;
                }

                TryRebuildPartAndDirectParentAssembly(swApp, part);
            }
        }

        /// <summary>
        /// 根据索引修改方程式(供内部与一键应用快照使用)。
        /// </summary>
        public static bool TryApplyEquationRhs(
            EquationMgr eqnMgr,
            ModelDoc2 swModel,
            int selectedIndex,
            string newValue,
            SldWorks swApp,
            string? partDisplayLabel,
            string? expectedVariableName = null)
        {
            try
            {
                if (selectedIndex < 0 || selectedIndex >= eqnMgr.GetCount())
                {
                    Debug.WriteLine("选择的方程式索引无效");
                    swApp.SendMsgToUser("选择的方程式索引无效");
                    return false;
                }

                string calculatedValue = EvaluateExpression(newValue);

                int targetIndex = selectedIndex;
                string originalEquation = eqnMgr.get_Equation(targetIndex);
                string[] originalParts = originalEquation.Split(new[] { '=' }, 2);

                if (originalParts.Length < 2)
                {
                    Debug.WriteLine("原始方程式格式错误");
                    swApp.SendMsgToUser("原始方程式格式错误");
                    return false;
                }

                // 用于同步文件名:记录实际被改写的那条方程式在写入前的右侧值(回退到 fallback 索引时用 fallback 的旧值)。
                string previousRhsForRename = NormalizeEquationValue(ExtractEquationValue(originalEquation));

                string equationName = originalParts[0];

                string newEquation = $"{equationName}={calculatedValue}";

                eqnMgr.set_Equation(targetIndex, newEquation);

                // 关键校验:必须回读确认已写入,避免"表里改了、模型没改"的假成功。
                string actualEquation = eqnMgr.get_Equation(targetIndex);
                string actualValue = ExtractEquationValue(actualEquation);
                if (!IsEquationValueEquivalent(actualValue, calculatedValue))
                {
                    int fallbackIndex = FindEquationIndexByVariableName(eqnMgr, expectedVariableName);
                    if (fallbackIndex >= 0 && fallbackIndex != targetIndex)
                    {
                        string fbEq = eqnMgr.get_Equation(fallbackIndex);
                        previousRhsForRename = NormalizeEquationValue(ExtractEquationValue(fbEq));
                        string[] fbParts = fbEq.Split(new[] { '=' }, 2);
                        if (fbParts.Length >= 2)
                        {
                            string fbName = fbParts[0];
                            eqnMgr.set_Equation(fallbackIndex, $"{fbName}={calculatedValue}");
                            string fbActual = ExtractEquationValue(eqnMgr.get_Equation(fallbackIndex));
                            if (IsEquationValueEquivalent(fbActual, calculatedValue))
                            {
                                targetIndex = fallbackIndex;
                                equationName = fbName;
                                actualValue = fbActual;
                            }
                        }
                    }

                    if (!IsEquationValueEquivalent(actualValue, calculatedValue))
                    {
                        string err = $"方程式写入未生效:目标={calculatedValue},实际={actualValue}";
                        Debug.WriteLine(err);
                        swApp?.SendMsgToUser(err);
                        return false;
                    }
                }

                TryRebuildPartAndDirectParentAssembly(swApp, swModel);
                TryRenamePartStemViaRenameDocument(swApp, swModel, previousRhsForRename, calculatedValue ?? "");

                Debug.WriteLine($"方程式已更新: {newEquation}");

                var varDisplay = equationName.Trim().Replace("\r", " ").Replace("\n", " ");
                if (varDisplay.Length > 120)
                {
                    varDisplay = varDisplay.Substring(0, 120) + "...";
                }

                var valDisplay = (calculatedValue ?? "").Trim().Replace("\r", " ").Replace("\n", " ");
                if (valDisplay.Length > 80)
                {
                    valDisplay = valDisplay.Substring(0, 80) + "...";
                }

                string partTag = string.IsNullOrWhiteSpace(partDisplayLabel)
                    ? (swModel?.GetTitle() ?? "")
                    : partDisplayLabel.Trim();
                if (partTag.Length > 80)
                {
                    partTag = partTag.Substring(0, 80) + "...";
                }

                AiOperationBrief.Log($"改方程式:零件={partTag},变量={varDisplay},新值={valDisplay}");

                return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"修改方程式时出错: {ex.Message}");
                swApp?.SendMsgToUser($"修改方程式时出错: {ex.Message}");
                return false;
            }
        }

        private static int FindEquationIndexByVariableName(EquationMgr eqnMgr, string? variableName)
        {
            string v = (variableName ?? "").Trim();
            if (string.IsNullOrWhiteSpace(v) || eqnMgr == null)
            {
                return -1;
            }

            string normalized = NormalizeVariableName(v);
            int count = eqnMgr.GetCount();
            for (int i = 0; i < count; i++)
            {
                if (!eqnMgr.get_GlobalVariable(i))
                {
                    continue;
                }

                string eq = eqnMgr.get_Equation(i);
                string[] parts = eq.Split(new[] { '=' }, 2);
                if (parts.Length < 2)
                {
                    continue;
                }

                if (string.Equals(NormalizeVariableName(parts[0]), normalized, StringComparison.OrdinalIgnoreCase))
                {
                    return i;
                }
            }

            return -1;
        }

        private static string NormalizeVariableName(string variableName)
        {
            return (variableName ?? "")
                .Trim()
                .Replace("\"", "")
                .Replace(""", "")
                .Replace(""", "")
                .Replace(" ", "");
        }

        private static bool IsEquationValueEquivalent(string actualValue, string expectedValue)
        {
            string a = NormalizeEquationValue(actualValue);
            string b = NormalizeEquationValue(expectedValue);
            if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            if (double.TryParse(a, out double ad) && double.TryParse(b, out double bd))
            {
                return Math.Abs(ad - bd) < 1e-9;
            }

            return false;
        }

        private static string NormalizeEquationValue(string value)
        {
            return (value ?? "")
                .Trim()
                .Replace("\"", "")
                .Replace(""", "")
                .Replace(""", "")
                .Replace(" ", "");
        }

        public static string GetPartStorageKey(ModelDoc2 p)
        {
            if (p == null)
            {
                return "";
            }

            string path = p.GetPathName();
            return string.IsNullOrEmpty(path) ? (p.GetTitle() ?? "") : path;
        }

        /// <summary>
        /// 读取多个零件的全部方程式为快照列表。
        /// </summary>
        public static List<EquationSnapshotEntry> CollectEquationSnapshots(SldWorks swApp, IList<ModelDoc2> parts)
        {
            var result = new List<EquationSnapshotEntry>();
            if (parts == null || parts.Count == 0)
            {
                return result;
            }

            var unique = DeduplicateParts(parts);
            if (unique.Count == 0)
            {
                return result;
            }

            foreach (var p in unique)
            {
                EquationMgr mgr = null;
                try
                {
                    mgr = (EquationMgr)p.GetEquationMgr();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"获取方程式管理器失败 {p.GetTitle()}: {ex.Message}");
                }

                if (mgr == null)
                {
                    continue;
                }

                int count = mgr.GetCount();
                if (count <= 0)
                {
                    continue;
                }

                string label = GetPartDisplayLabel(p, unique);
                string storageKey = GetPartStorageKey(p);

                for (int i = 0; i < count; i++)
                {
                    if (!mgr.get_GlobalVariable(i))
                    {
                        continue;
                    }

                    string equation = mgr.get_Equation(i);
                    string[] eqParts = equation.Split(new[] { '=' }, 2);
                    if (eqParts.Length < 2)
                    {
                        continue;
                    }

                    result.Add(new EquationSnapshotEntry
                    {
                        PartStorageKey = storageKey,
                        PartDisplayLabel = label,
                        EquationIndex = i,
                        VariableName = eqParts[0].Trim(),
                        ValueExpression = ExtractEquationValue(equation)
                    });
                }
            }

            return result;
        }

        /// <summary>
        /// 从装配体收集不重复的零件文档(.sldprt),并包含装配体自身(.sldasm)以支持装配体方程式。
        /// </summary>
        public static List<ModelDoc2> CollectDistinctPartDocsFromAssembly(SldWorks swApp, ModelDoc2 assemblyModel)
        {
            var list = new List<ModelDoc2>();
            var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            if (swApp == null || assemblyModel == null || assemblyModel.GetType() != (int)swDocumentTypes_e.swDocASSEMBLY)
            {
                return list;
            }

            // 先加入装配体自身,使其方程式也可被采集/修改
            string asmKey = GetPartStorageKey(assemblyModel);
            if (!string.IsNullOrWhiteSpace(asmKey) && seen.Add(asmKey))
            {
                list.Add(assemblyModel);
            }

            var assy = (AssemblyDoc)assemblyModel;
            object[] comps = (object[])assy.GetComponents(false);
            if (comps == null)
            {
                return list;
            }

            foreach (object obj in comps)
            {
                var comp = obj as Component2;
                if (comp == null)
                {
                    continue;
                }

                try
                {
                    if (!comp.IsLoaded())
                    {
                        continue;
                    }
                }
                catch
                {
                    // ignore
                }

                string path = comp.GetPathName();
                if (string.IsNullOrEmpty(path)
                    || (!path.EndsWith(".SLDPRT", StringComparison.OrdinalIgnoreCase)
                        && !path.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase)))
                {
                    continue;
                }

                ModelDoc2 partDoc = TryGetComponentPartDoc(swApp, comp);
                if (partDoc == null)
                {
                    continue;
                }

                string key = GetPartStorageKey(partDoc);
                if (string.IsNullOrEmpty(key))
                {
                    continue;
                }

                if (seen.Add(key))
                {
                    list.Add(partDoc);
                }
            }

            return list;
        }

        private static ModelDoc2 TryGetComponentPartDoc(SldWorks swApp, Component2 comp)
        {
            ModelDoc2 partDoc = comp.GetModelDoc2() as ModelDoc2;
            if (partDoc == null)
            {
                string path = comp.GetPathName();
                if (string.IsNullOrEmpty(path))
                {
                    return null;
                }

                int docType = path.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase)
                    ? (int)swDocumentTypes_e.swDocASSEMBLY
                    : (int)swDocumentTypes_e.swDocPART;
                try
                {
                    int errors = 0;
                    int warnings = 0;
                    partDoc = swApp.OpenDoc6(
                        path,
                        docType,
                        (int)swOpenDocOptions_e.swOpenDocOptions_Silent,
                        "",
                        ref errors,
                        ref warnings) as ModelDoc2;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"打开文档失败 {path}: {ex.Message}");
                    return null;
                }
            }

            return partDoc;
        }

        /// <summary>
        /// 将快照批量写入对应零件(按零件分组后按索引应用)。
        /// </summary>
        public static string ApplyEquationSnapshots(SldWorks swApp, IList<EquationSnapshotEntry> snapshots)
        {
            if (swApp == null || snapshots == null || snapshots.Count == 0)
            {
                return "没有可应用的方程式记录";
            }

            int ok = 0;
            int fail = 0;
            var touchedParts = new Dictionary<string, ModelDoc2>(StringComparer.OrdinalIgnoreCase);

            try
            {
                foreach (var group in snapshots.GroupBy(s => s.PartStorageKey, StringComparer.OrdinalIgnoreCase))
                {
                    ModelDoc2 part = ResolvePartDocByStorageKey(swApp, group.Key);
                    if (part == null)
                    {
                        fail += group.Count();
                        continue;
                    }

                    string partKey = GetPartStorageKey(part);
                    if (!string.IsNullOrWhiteSpace(partKey) && !touchedParts.ContainsKey(partKey))
                    {
                        touchedParts[partKey] = part;
                    }

                    EquationMgr mgr = null;
                    try
                    {
                        mgr = (EquationMgr)part.GetEquationMgr();
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"获取方程式管理器失败 {group.Key}: {ex.Message}");
                        fail += group.Count();
                        continue;
                    }

                    if (mgr == null)
                    {
                        fail += group.Count();
                        continue;
                    }

                    // 与右键"修改方程式"流程对齐:先确保零件可见并激活,再执行写入。
                    TryEnsurePartVisibleAndActive(swApp, part);

                    foreach (var entry in group.OrderBy(e => e.EquationIndex))
                    {
                        if (TryApplyEquationRhs(mgr, part, entry.EquationIndex, entry.ValueExpression, swApp, entry.PartDisplayLabel, entry.VariableName))
                        {
                            ok++;
                        }
                        else
                        {
                            fail++;
                        }
                    }
                }
            }
            finally
            {
                // 与右键流程一致:参与修改的文档收尾统一恢复为不可见;保存须在隐藏前完成。
                foreach (var part in touchedParts.Values)
                {
                    try
                    {
                        TryEnsurePartVisibleAndActive(swApp, part);
                        try
                        {
                            part.EditRebuild3();
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine($"保存前重建失败 {part.GetTitle()}: {ex.Message}");
                        }

                        PartCustomProperties.TrySavePartSilent(part, out string? saveWarn);
                        if (!string.IsNullOrWhiteSpace(saveWarn))
                        {
                            Debug.WriteLine($"保存文档 {part.GetTitle()}: {saveWarn}");
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"保存文档异常 {part.GetTitle()}: {ex.Message}");
                    }

                    try
                    {
                        part.Visible = false;
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"恢复文档不可见失败 {part.GetTitle()}: {ex.Message}");
                    }
                }
            }

            return $"一键应用完成:成功 {ok} 条,失败 {fail} 条";
        }

        private static void TryEnsurePartVisibleAndActive(SldWorks swApp, ModelDoc2 part)
        {
            if (swApp == null || part == null)
            {
                return;
            }

            try
            {
                part.Visible = true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"设置文档可见失败 {part.GetTitle()}: {ex.Message}");
            }

            try
            {
                string title = part.GetTitle();
                if (!string.IsNullOrWhiteSpace(title))
                {
                    int errors = 0;
                    swApp.ActivateDoc3(
                        title,
                        true,
                        (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                        ref errors);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"激活文档失败 {part.GetTitle()}: {ex.Message}");
            }
        }

        private static ModelDoc2 ResolvePartDocByStorageKey(SldWorks swApp, string storageKey)
        {
            if (string.IsNullOrWhiteSpace(storageKey))
            {
                return null;
            }

            try
            {
                object[] docs = (object[])swApp.GetDocuments();
                if (docs != null)
                {
                    foreach (object d in docs)
                    {
                        var md = d as ModelDoc2;
                        if (md == null)
                        {
                            continue;
                        }

                        string k = GetPartStorageKey(md);
                        if (string.Equals(k, storageKey, StringComparison.OrdinalIgnoreCase))
                        {
                            return md;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"枚举文档失败: {ex.Message}");
            }

            bool looksLikePath = storageKey.IndexOf('\\') >= 0 || storageKey.IndexOf('/') >= 0;
            if (looksLikePath && File.Exists(storageKey))
            {
                int docType = storageKey.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase)
                    ? (int)swDocumentTypes_e.swDocASSEMBLY
                    : (int)swDocumentTypes_e.swDocPART;
                try
                {
                    int errors = 0;
                    int warnings = 0;
                    return swApp.OpenDoc6(
                        storageKey,
                        docType,
                        (int)swOpenDocOptions_e.swOpenDocOptions_Silent,
                        "",
                        ref errors,
                        ref warnings) as ModelDoc2;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"打开文档失败 {storageKey}: {ex.Message}");
                }
            }

            return null;
        }

        /// <summary>
        /// 改方程式后同步装配体组件名:仅当组件名中存在与「修改前方程右侧值」完全对应的独立数字时才替换(1805 只匹配 1805,不匹配 18;18 也不匹配 1805);
        /// 数值容差仅用于格式差异(597.5 与 597.50)。
        /// 零件/装配体改名成功后,若直接所属装配体名为「{名称}装配」,则同步改为「{新名称}装配」。
        /// 在<strong>已打开的装配体</strong>中通过 <see cref="ModelDocExtension.RenameDocument"/> 重命名。
        /// 支持零件(.sldprt)和装配体(.sldasm)文档。
        /// </summary>
        private static void TryRenamePartStemViaRenameDocument(
            SldWorks swApp,
            ModelDoc2 partDoc,
            string previousRhsNormalized,
            string newRhsRaw)
        {
            if (swApp == null || partDoc == null)
            {
                return;
            }

            string newRhsNormalized = NormalizeEquationValue(newRhsRaw);
            if (string.IsNullOrEmpty(previousRhsNormalized) || string.IsNullOrEmpty(newRhsNormalized))
            {
                return;
            }

            if (string.Equals(previousRhsNormalized, newRhsNormalized, StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            try
            {
                string partPath = partDoc.GetPathName() ?? "";
                bool isPart = partPath.EndsWith(".SLDPRT", StringComparison.OrdinalIgnoreCase);
                bool isAsm = partPath.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase);
                if (string.IsNullOrWhiteSpace(partPath) || (!isPart && !isAsm))
                {
                    Debug.WriteLine("同步名称跳过:文档未保存为 .sldprt/.sldasm 路径,无法匹配装配体引用");
                    return;
                }

                string docLabel = isAsm ? "装配体" : "零件";

                Component2 comp = null;
                ModelDoc2 owningAssemblyDoc = null;
                bool found = false;
                if (isPart)
                {
                    found = TryFindPartComponentForRenameInOpenAssemblies(swApp, partPath, out comp, out owningAssemblyDoc);
                }
                else
                {
                    // 装配体:在已打开的上级装配体中查找引用此子装配体的组件
                    found = TryFindAssemblyComponentInOpenAssemblies(swApp, partPath, partDoc, out comp, out owningAssemblyDoc);
                }

                if (!found || comp == null || owningAssemblyDoc == null)
                {
                    Debug.WriteLine($"同步{docLabel}名跳过:未在已打开文档中找到引用该{docLabel}的装配体组件({partPath})");
                    return;
                }

                string oldLeaf = ExtractLeafComponentNameFromComponentPath(comp.Name2 ?? "");
                string newLeaf = TryBuildRenamedComponentLeaf(
                    oldLeaf,
                    previousRhsNormalized,
                    newRhsNormalized);
                if (string.Equals(newLeaf, oldLeaf, StringComparison.Ordinal))
                {
                    Debug.WriteLine(
                        $"同步零件名跳过:组件名中未找到可替换数字(旧值={previousRhsNormalized},新值={newRhsNormalized},组件={oldLeaf})");
                    return;
                }

                string renameTarget = newLeaf;
                if (TryStripComponentInstanceSuffix(newLeaf, out string stripped))
                {
                    renameTarget = stripped;
                }

                if (string.IsNullOrWhiteSpace(renameTarget))
                {
                    return;
                }

                if (TryRenameComponentInAssembly(swApp, owningAssemblyDoc, comp, renameTarget, docLabel))
                {
                    TryRenameOwningAssemblyAfterPartRename(
                        swApp,
                        owningAssemblyDoc,
                        oldLeaf,
                        renameTarget,
                        previousRhsNormalized,
                        newRhsNormalized);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"同步零件名 RenameDocument 异常: {ex.Message}");
            }
        }

        /// <summary>
        /// 零件改名后同步其直接所属装配体:若装配体名为「{零件名}装配」,则改为「{新零件名}装配」;
        /// 装配体若作为子装配体出现在 上级装配体 中则在上级 RenameDocument,否则重命名装配体文档本身。
        /// </summary>
        private static void TryRenameOwningAssemblyAfterPartRename(
            SldWorks swApp,
            ModelDoc2 owningAssemblyDoc,
            string oldPartLeaf,
            string newPartRenameTarget,
            string previousRhsNormalized,
            string newRhsNormalized)
        {
            if (swApp == null || owningAssemblyDoc == null)
            {
                return;
            }

            try
            {
                string assemblyPath = owningAssemblyDoc.GetPathName() ?? "";
                if (string.IsNullOrWhiteSpace(assemblyPath)
                    || !assemblyPath.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase))
                {
                    Debug.WriteLine("同步装配体名跳过:所属装配体未保存为 .sldasm 路径");
                    return;
                }

                string assemblyStem = GetDocumentStemWithoutInstanceSuffix(owningAssemblyDoc);
                string? newAssemblyName = TryBuildAssemblyRenameTarget(
                    assemblyStem,
                    oldPartLeaf,
                    newPartRenameTarget,
                    assemblyPath,
                    previousRhsNormalized,
                    newRhsNormalized);
                if (string.IsNullOrWhiteSpace(newAssemblyName)
                    || string.Equals(newAssemblyName, assemblyStem, StringComparison.Ordinal))
                {
                    Debug.WriteLine(
                        $"同步装配体名跳过:装配体名与零件名无「零件名+装配」对应关系(装配体={assemblyStem},零件={oldPartLeaf})");
                    return;
                }

                if (TryFindAssemblyComponentInOpenAssemblies(
                        swApp,
                        assemblyPath,
                        owningAssemblyDoc,
                        out Component2 asmComp,
                        out ModelDoc2 parentAssemblyDoc)
                    && asmComp != null
                    && parentAssemblyDoc != null)
                {
                    TryRenameComponentInAssembly(swApp, parentAssemblyDoc, asmComp, newAssemblyName, "装配体");
                    return;
                }

                TryRenameAssemblyDocumentDirectly(swApp, owningAssemblyDoc, newAssemblyName);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"同步装配体名异常: {ex.Message}");
            }
        }

        private static string? TryBuildAssemblyRenameTarget(
            string assemblyStem,
            string oldPartLeaf,
            string newPartRenameTarget,
            string assemblyPath,
            string previousRhsNormalized,
            string newRhsNormalized)
        {
            if (string.IsNullOrWhiteSpace(assemblyStem))
            {
                return null;
            }

            string oldPartStem = oldPartLeaf;
            if (TryStripComponentInstanceSuffix(oldPartLeaf, out string oldPartStripped))
            {
                oldPartStem = oldPartStripped;
            }

            string asmStem = assemblyStem;
            if (TryStripComponentInstanceSuffix(assemblyStem, out string asmStripped))
            {
                asmStem = asmStripped;
            }

            const string AssemblySuffix = "装配";
            if (asmStem.EndsWith(AssemblySuffix, StringComparison.Ordinal)
                && asmStem.Length > AssemblySuffix.Length
                && string.Equals(
                    asmStem.Substring(0, asmStem.Length - AssemblySuffix.Length),
                    oldPartStem,
                    StringComparison.Ordinal))
            {
                return newPartRenameTarget + AssemblySuffix;
            }

            string newLeaf = TryBuildRenamedComponentLeaf(
                assemblyStem,
                previousRhsNormalized,
                newRhsNormalized);
            if (string.Equals(newLeaf, assemblyStem, StringComparison.Ordinal))
            {
                return null;
            }

            if (TryStripComponentInstanceSuffix(newLeaf, out string strippedNew))
            {
                return strippedNew;
            }

            return newLeaf;
        }

        private static string GetDocumentStemWithoutInstanceSuffix(ModelDoc2 doc)
        {
            string path = doc?.GetPathName() ?? "";
            string stem = !string.IsNullOrWhiteSpace(path)
                ? Path.GetFileNameWithoutExtension(path)
                : (doc?.GetTitle() ?? "");
            if (TryStripComponentInstanceSuffix(stem, out string stripped))
            {
                return stripped;
            }

            return stem;
        }

        private static bool TryRenameComponentInAssembly(
            SldWorks swApp,
            ModelDoc2 assemblyDoc,
            Component2 comp,
            string renameTarget,
            string logLabel)
        {
            if (swApp == null || assemblyDoc == null || comp == null || string.IsNullOrWhiteSpace(renameTarget))
            {
                return false;
            }

            ModelDoc2 activeBefore = swApp.ActiveDoc as ModelDoc2;
            int activateErrors = 0;
            try
            {
                swApp.ActivateDoc3(
                    assemblyDoc.GetTitle() ?? "",
                    true,
                    (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                    ref activateErrors);

                assemblyDoc.ClearSelection2(true);
                if (!comp.Select4(false, null, false))
                {
                    Debug.WriteLine($"同步{logLabel}名跳过:组件选择失败");
                    return false;
                }

                ModelDocExtension modelExt = assemblyDoc.Extension;
                if (modelExt == null)
                {
                    Debug.WriteLine($"同步{logLabel}名跳过:装配体 Extension 为空");
                    return false;
                }

                int renameError = modelExt.RenameDocument(renameTarget);
                if (renameError == (int)swRenameDocumentError_e.swRenameDocumentError_None)
                {
                    Debug.WriteLine($"方程式驱动 RenameDocument({logLabel}): -> {renameTarget}");
                    return true;
                }

                Debug.WriteLine($"同步{logLabel}名 RenameDocument 失败: error={renameError}, 目标={renameTarget}");
                return false;
            }
            finally
            {
                RestoreActiveDocument(swApp, activeBefore);
            }
        }

        private static bool TryRenameAssemblyDocumentDirectly(
            SldWorks swApp,
            ModelDoc2 assemblyDoc,
            string renameTarget)
        {
            if (swApp == null || assemblyDoc == null || string.IsNullOrWhiteSpace(renameTarget))
            {
                return false;
            }

            ModelDoc2 activeBefore = swApp.ActiveDoc as ModelDoc2;
            int activateErrors = 0;
            try
            {
                swApp.ActivateDoc3(
                    assemblyDoc.GetTitle() ?? "",
                    true,
                    (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                    ref activateErrors);

                assemblyDoc.ClearSelection2(true);
                ModelDocExtension modelExt = assemblyDoc.Extension;
                if (modelExt == null)
                {
                    Debug.WriteLine("同步装配体名跳过:装配体 Extension 为空");
                    return false;
                }

                int renameError = modelExt.RenameDocument(renameTarget);
                if (renameError == (int)swRenameDocumentError_e.swRenameDocumentError_None)
                {
                    Debug.WriteLine($"方程式驱动 RenameDocument(装配体文档): -> {renameTarget}");
                    return true;
                }

                Debug.WriteLine($"同步装配体文档名 RenameDocument 失败: error={renameError}, 目标={renameTarget}");
                return false;
            }
            finally
            {
                RestoreActiveDocument(swApp, activeBefore);
            }
        }

        private static void RestoreActiveDocument(SldWorks swApp, ModelDoc2? activeBefore)
        {
            if (activeBefore == null || swApp == null)
            {
                return;
            }

            try
            {
                int restoreErr = 0;
                swApp.ActivateDoc3(
                    activeBefore.GetTitle() ?? "",
                    true,
                    (int)swRebuildOnActivation_e.swDontRebuildActiveDoc,
                    ref restoreErr);
            }
            catch (Exception exRestore)
            {
                Debug.WriteLine($"恢复活动文档失败: {exRestore.Message}");
            }
        }

        /// <summary>
        /// 在已打开的装配体中递归查找路径匹配的装配体组件,并返回其<strong>直接所属</strong>上级装配体文档。
        /// </summary>
        private static bool TryFindAssemblyComponentInOpenAssemblies(
            SldWorks swApp,
            string assemblyPath,
            ModelDoc2 excludeAssemblyDoc,
            out Component2 foundComp,
            out ModelDoc2 parentAssemblyDoc)
        {
            foundComp = null;
            parentAssemblyDoc = null;
            if (swApp == null || string.IsNullOrWhiteSpace(assemblyPath))
            {
                return false;
            }

            try
            {
                object[] docs = (object[])swApp.GetDocuments();
                if (docs == null)
                {
                    return false;
                }

                foreach (object o in docs)
                {
                    var md = o as ModelDoc2;
                    if (md == null || md.GetType() != (int)swDocumentTypes_e.swDocASSEMBLY)
                    {
                        continue;
                    }

                    if (excludeAssemblyDoc != null
                        && string.Equals(
                            md.GetPathName() ?? "",
                            excludeAssemblyDoc.GetPathName() ?? "",
                            StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    if (TryFindAssemblyComponentRecursive(md, assemblyPath, out foundComp, out parentAssemblyDoc))
                    {
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"查找上级装配体中的子装配体组件失败: {ex.Message}");
            }

            return false;
        }

        private static bool TryFindAssemblyComponentRecursive(
            ModelDoc2 assemblyDoc,
            string assemblyPath,
            out Component2 foundComp,
            out ModelDoc2 parentAssemblyDoc)
        {
            foundComp = null;
            parentAssemblyDoc = null;
            try
            {
                object[] comps = (object[])((AssemblyDoc)assemblyDoc).GetComponents(true);
                if (comps == null)
                {
                    return false;
                }

                foreach (object obj in comps)
                {
                    var comp = obj as Component2;
                    if (comp == null)
                    {
                        continue;
                    }

                    string p = comp.GetPathName() ?? "";
                    if (p.EndsWith(".SLDASM", StringComparison.OrdinalIgnoreCase)
                        && string.Equals(p, assemblyPath, StringComparison.OrdinalIgnoreCase))
                    {
                        foundComp = comp;
                        parentAssemblyDoc = assemblyDoc;
                        return true;
                    }

                    ModelDoc2 refDoc = null;
                    try
                    {
                        refDoc = comp.GetModelDoc2() as ModelDoc2;
                    }
                    catch
                    {
                        refDoc = null;
                    }

                    if (refDoc != null && refDoc.GetType() == (int)swDocumentTypes_e.swDocASSEMBLY)
                    {
                        if (TryFindAssemblyComponentRecursive(refDoc, assemblyPath, out foundComp, out parentAssemblyDoc))
                        {
                            return true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"遍历装配体查找子装配体失败: {ex.Message}");
            }

            return false;
        }

        /// <summary>
        /// 在已打开的装配体中递归查找路径匹配的零件组件,并返回其<strong>直接所属</strong>装配体文档(与 RenameDocument 用法一致)。
        /// </summary>
        private static bool TryFindPartComponentForRenameInOpenAssemblies(
            SldWorks swApp,
            string partPath,
            out Component2 foundComp,
            out ModelDoc2 owningAssemblyDoc)
        {
            foundComp = null;
            owningAssemblyDoc = null;
            if (swApp == null || string.IsNullOrWhiteSpace(partPath))
            {
                return false;
            }

            try
            {
                object[] docs = (object[])swApp.GetDocuments();
                if (docs == null)
                {
                    return false;
                }

                foreach (object o in docs)
                {
                    var md = o as ModelDoc2;
                    if (md == null || md.GetType() != (int)swDocumentTypes_e.swDocASSEMBLY)
                    {
                        continue;
                    }

                    if (TryFindPartComponentRecursive(md, partPath, out foundComp, out owningAssemblyDoc))
                    {
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"查找装配体中的零件组件失败: {ex.Message}");
            }

            return false;
        }

        private static bool TryFindPartComponentRecursive(
            ModelDoc2 assemblyDoc,
            string partPath,
            out Component2 foundComp,
            out ModelDoc2 owningAssemblyDoc)
        {
            foundComp = null;
            owningAssemblyDoc = null;
            try
            {
                object[] comps = (object[])((AssemblyDoc)assemblyDoc).GetComponents(true);
                if (comps == null)
                {
                    return false;
                }

                foreach (object obj in comps)
                {
                    var comp = obj as Component2;
                    if (comp == null)
                    {
                        continue;
                    }

                    string p = comp.GetPathName() ?? "";
                    if (p.EndsWith(".SLDPRT", StringComparison.OrdinalIgnoreCase) &&
                        string.Equals(p, partPath, StringComparison.OrdinalIgnoreCase))
                    {
                        foundComp = comp;
                        owningAssemblyDoc = assemblyDoc;
                        return true;
                    }

                    ModelDoc2 refDoc = null;
                    try
                    {
                        refDoc = comp.GetModelDoc2() as ModelDoc2;
                    }
                    catch
                    {
                        refDoc = null;
                    }

                    if (refDoc != null && refDoc.GetType() == (int)swDocumentTypes_e.swDocASSEMBLY)
                    {
                        if (TryFindPartComponentRecursive(refDoc, partPath, out foundComp, out owningAssemblyDoc))
                        {
                            return true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"遍历装配体失败: {ex.Message}");
            }

            return false;
        }

        private static string ExtractLeafComponentNameFromComponentPath(string componentName)
        {
            if (string.IsNullOrWhiteSpace(componentName))
            {
                return string.Empty;
            }

            int slashIndex = componentName.LastIndexOf('/');
            if (slashIndex >= 0 && slashIndex < componentName.Length - 1)
            {
                return componentName.Substring(slashIndex + 1);
            }

            return componentName;
        }

        private static bool TryStripComponentInstanceSuffix(string leafName, out string nameWithoutSuffix)
        {
            nameWithoutSuffix = string.Empty;
            if (string.IsNullOrWhiteSpace(leafName))
            {
                return false;
            }

            int lastDashIndex = leafName.LastIndexOf('-');
            if (lastDashIndex > 0 && lastDashIndex < leafName.Length - 1)
            {
                string possibleSuffix = leafName.Substring(lastDashIndex + 1);
                if (int.TryParse(possibleSuffix, out _))
                {
                    nameWithoutSuffix = leafName.Substring(0, lastDashIndex);
                    return true;
                }
            }

            return false;
        }

        private static readonly Regex StandaloneNumberRegex = new Regex(
            @"(?<!\d)(\d+(?:\.\d+)?)(?!\d)",
            RegexOptions.Compiled | RegexOptions.CultureInvariant);

        /// <summary>
        /// 按优先级尝试:字面量独立数字 → 数值相等且同为独立数字 token(597.5 与 597.50)。
        /// </summary>
        private static string TryBuildRenamedComponentLeaf(
            string oldLeaf,
            string previousRhsNormalized,
            string newRhsNormalized)
        {
            if (string.IsNullOrEmpty(oldLeaf))
            {
                return oldLeaf;
            }

            string formattedNew = FormatValueForComponentName(newRhsNormalized);

            string newLeaf = ReplaceFirstStandaloneNumberOccurrence(oldLeaf, previousRhsNormalized, formattedNew);
            if (!string.Equals(newLeaf, oldLeaf, StringComparison.Ordinal))
            {
                Debug.WriteLine($"同步零件名:字符串匹配 {previousRhsNormalized} -> {formattedNew}");
                return newLeaf;
            }

            newLeaf = ReplaceFirstStandaloneNumberOccurrenceNumeric(oldLeaf, previousRhsNormalized, formattedNew);
            if (!string.Equals(newLeaf, oldLeaf, StringComparison.Ordinal))
            {
                Debug.WriteLine($"同步零件名:数值匹配旧方程值 {previousRhsNormalized} -> {formattedNew}");
            }

            return newLeaf;
        }

        private static bool ValuesMatchNumerically(string a, string b)
        {
            string na = NormalizeEquationValue(a);
            string nb = NormalizeEquationValue(b);
            if (string.Equals(na, nb, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            return double.TryParse(na, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double ad)
                && double.TryParse(nb, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double bd)
                && Math.Abs(ad - bd) < 1e-6;
        }

        private static string FormatValueForComponentName(string value)
        {
            string v = NormalizeEquationValue(value);
            if (double.TryParse(v, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out double d))
            {
                if (Math.Abs(d - Math.Round(d)) < 1e-9)
                {
                    return ((int)Math.Round(d)).ToString(System.Globalization.CultureInfo.InvariantCulture);
                }

                return d.ToString("G", System.Globalization.CultureInfo.InvariantCulture);
            }

            return v;
        }

        /// <summary>
        /// 将文本中第一次出现的「前后都不是数字」且数值等于 oldNum 的独立数字替换为 newNum。
        /// </summary>
        private static string ReplaceFirstStandaloneNumberOccurrenceNumeric(string text, string oldNum, string newNum)
        {
            if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(oldNum))
            {
                return text;
            }

            Match m = StandaloneNumberRegex.Match(text);
            while (m.Success)
            {
                if (m.Groups.Count >= 2 && ValuesMatchNumerically(m.Groups[1].Value, oldNum))
                {
                    return text.Substring(0, m.Index) + newNum + text.Substring(m.Index + m.Groups[1].Length);
                }

                m = m.NextMatch();
            }

            return text;
        }

        /// <summary>
        /// 将文本中第一次出现的「前后都不是数字」的 oldNum 替换为 newNum(字面量匹配)。
        /// </summary>
        private static string ReplaceFirstStandaloneNumberOccurrence(string text, string oldNum, string newNum)
        {
            if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(oldNum))
            {
                return text;
            }

            string escaped = Regex.Escape(oldNum);
            string pattern = "(?<!\\d)" + escaped + "(?!\\d)";
            Match m = Regex.Match(text, pattern);
            if (!m.Success)
            {
                return text;
            }

            return text.Substring(0, m.Index) + newNum + text.Substring(m.Index + m.Length);
        }

        private static string ExtractEquationValue(string equation)
        {
            if (string.IsNullOrWhiteSpace(equation))
            {
                return "";
            }

            var parts = equation.Split(new[] { '=' }, 2);
            if (parts.Length < 2)
            {
                return equation.Trim();
            }

            return parts[1].Trim();
        }

        private static string EvaluateExpression(string expression)
        {
            try
            {
                if (!expression.Contains('+') && !expression.Contains('-') &&
                    !expression.Contains('*') && !expression.Contains('/') &&
                    !expression.Contains('(') && !expression.Contains(')'))
                {
                    return expression;
                }

                var dataTable = new System.Data.DataTable();
                var result = dataTable.Compute(expression, "");

                if (result is double doubleResult)
                {
                    if (doubleResult == Math.Floor(doubleResult))
                    {
                        return ((int)doubleResult).ToString();
                    }

                    return doubleResult.ToString("F2");
                }

                if (result is int intResult)
                {
                    return intResult.ToString();
                }

                return result.ToString();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"表达式计算失败: {ex.Message},使用原始值");
                return expression;
            }
        }

        /// <summary>桥接:列出单个零件/装配体的全局变量方程式(可选 keyword 过滤变量名或右侧值)。</summary>
        public static List<EquationSnapshotEntry> ListPartEquationSnapshots(ModelDoc2 part, string keyword = "")
        {
            if (part == null)
            {
                return new List<EquationSnapshotEntry>();
            }

            var all = CollectEquationSnapshots(null, new List<ModelDoc2> { part });
            string kw = (keyword ?? "").Trim();
            if (string.IsNullOrEmpty(kw))
            {
                return all;
            }

            return all
                .Where(e =>
                    (e.VariableName ?? "").IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0
                    || (e.ValueExpression ?? "").IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0)
                .ToList();
        }

        /// <summary>
        /// 桥接:定位并应用零件/装配体方程式(支持绝对新值或 delta 加减)。
        /// variableName / keyword 均可选;均未给时尝试用文件名前缀数值(如 597.5装配中间方管 → 597.5)匹配。
        /// </summary>
        public static string ApplyPartEquationFromBridge(
            SldWorks swApp,
            ModelDoc2 part,
            string? variableName,
            string? keyword,
            string? newValueExpression,
            double? deltaMm,
            out string appliedVariable,
            out string oldValue,
            out string newValue)
        {
            appliedVariable = "";
            oldValue = "";
            newValue = "";

            if (swApp == null || part == null)
            {
                return "SolidWorks 或文档无效";
            }

            var entries = CollectEquationSnapshots(swApp, new List<ModelDoc2> { part });
            if (entries.Count == 0)
            {
                return "文档没有全局变量方程式";
            }

            var target = FindBestEquationSnapshotForBridge(
                part,
                entries,
                variableName,
                keyword);
            if (target == null)
            {
                return "未能唯一定位目标方程式(请指定 equationVariable 或 keyword)";
            }

            appliedVariable = target.VariableName ?? "";
            oldValue = target.ValueExpression ?? "";

            string resolvedNewValue;
            if (!string.IsNullOrWhiteSpace(newValueExpression))
            {
                resolvedNewValue = newValueExpression.Trim();
            }
            else if (deltaMm.HasValue)
            {
                string oldEval = EvaluateExpression(oldValue);
                if (!double.TryParse(NormalizeEquationValue(oldEval), NumberStyles.Float, CultureInfo.InvariantCulture, out double oldNum)
                    && !double.TryParse(NormalizeEquationValue(oldEval), NumberStyles.Float, CultureInfo.CurrentCulture, out oldNum))
                {
                    return $"无法将当前方程值「{oldValue}」解析为数值以应用 delta";
                }

                resolvedNewValue = FormatEquationNumeric(oldNum + deltaMm.Value, oldEval);
            }
            else
            {
                return "equationValue 与 equationDelta 至少提供一个";
            }

            newValue = resolvedNewValue;
            target.ValueExpression = resolvedNewValue;
            return ApplyEquationSnapshots(swApp, new List<EquationSnapshotEntry> { target });
        }

        private static EquationSnapshotEntry? FindBestEquationSnapshotForBridge(
            ModelDoc2 part,
            IList<EquationSnapshotEntry> entries,
            string? variableName,
            string? keyword)
        {
            if (entries == null || entries.Count == 0)
            {
                return null;
            }

            IList<EquationSnapshotEntry> candidates = entries;

            string varFilter = (variableName ?? "").Trim();
            if (!string.IsNullOrEmpty(varFilter))
            {
                var exact = candidates.FirstOrDefault(e =>
                    string.Equals(
                        NormalizeVariableName(e.VariableName),
                        NormalizeVariableName(varFilter),
                        StringComparison.OrdinalIgnoreCase));
                if (exact != null)
                {
                    return exact;
                }

                var fuzzy = candidates
                    .Where(e => (e.VariableName ?? "").IndexOf(varFilter, StringComparison.OrdinalIgnoreCase) >= 0)
                    .ToList();
                if (fuzzy.Count == 1)
                {
                    return fuzzy[0];
                }

                if (fuzzy.Count > 1)
                {
                    candidates = fuzzy;
                }
            }

            string kw = (keyword ?? "").Trim();
            if (!string.IsNullOrEmpty(kw))
            {
                var kwHits = candidates
                    .Where(e =>
                        (e.VariableName ?? "").IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0
                        || (e.ValueExpression ?? "").IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0)
                    .ToList();
                if (kwHits.Count == 1)
                {
                    return kwHits[0];
                }

                if (kwHits.Count > 1)
                {
                    candidates = kwHits;
                }
            }

            string stem = Path.GetFileNameWithoutExtension(part.GetPathName() ?? part.GetTitle() ?? "");
            if (TryExtractLeadingNumber(stem, out double stemNum))
            {
                var numHits = candidates
                    .Where(e =>
                    {
                        string ev = EvaluateExpression(e.ValueExpression ?? "");
                        return double.TryParse(NormalizeEquationValue(ev), NumberStyles.Float, CultureInfo.InvariantCulture, out double v)
                            && Math.Abs(v - stemNum) < 0.05;
                    })
                    .ToList();
                if (numHits.Count == 1)
                {
                    return numHits[0];
                }

                if (numHits.Count > 1)
                {
                    var lenPref = numHits.FirstOrDefault(e => (e.VariableName ?? "").Contains("长"));
                    if (lenPref != null)
                    {
                        return lenPref;
                    }

                    candidates = numHits;
                }
            }

            var lengthVars = candidates
                .Where(e => (e.VariableName ?? "").Contains("长"))
                .ToList();
            if (lengthVars.Count == 1)
            {
                return lengthVars[0];
            }

            return candidates.Count == 1 ? candidates[0] : null;
        }

        private static bool TryExtractLeadingNumber(string text, out double value)
        {
            value = 0;
            var match = Regex.Match(text ?? "", @"^(\d+(?:\.\d+)?)");
            if (match.Success
                && double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out value))
            {
                return true;
            }

            return false;
        }

        private static string FormatEquationNumeric(double value, string referenceEvaluated)
        {
            string reference = NormalizeEquationValue(referenceEvaluated);
            if (reference.Contains("."))
            {
                int dot = reference.IndexOf('.');
                int decimals = reference.Length - dot - 1;
                if (decimals < 1)
                {
                    decimals = 1;
                }

                if (decimals > 4)
                {
                    decimals = 4;
                }

                return value.ToString("F" + decimals, CultureInfo.InvariantCulture);
            }

            if (Math.Abs(value - Math.Round(value)) < 1e-9)
            {
                return ((int)Math.Round(value)).ToString(CultureInfo.InvariantCulture);
            }

            return value.ToString("0.##", CultureInfo.InvariantCulture);
        }
    }
}
相关推荐
深蓝冰河4 天前
sw从零件 进行 工程图创建
solidworks·工程图
zlycheng16 天前
高校SolidWorks教育版完整方案,实训室采购与教学落地
solidworks·solidworks教育版
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征9_替换面原理
线性代数·算法·机器学习·3d建模·solidworks
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征8_删除面与修补
3d建模·solidworks
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征10_移动面操作
算法·3d建模·solidworks
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征2_组合实体技巧
3d建模·solidworks
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征4_删除/保留实体
3d建模·solidworks
H178535090961 个月前
SolidWorks第四部分_直接实体建模特征3_分割特征应用
3d建模·solidworks
H178535090961 个月前
SolidWorks_基于草图的实体特征20_特征错误排查
算法·3d建模·solidworks