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);
        }
    }
}
相关推荐
njsgcs6 天前
导出glb可视化让vlm看图找面 自己配合
智能制造·solidworks
花开月满西楼6 天前
零基础速学SolidWorks2026【曲面】曲面缝合
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼6 天前
零基础速学SolidWorks2026【曲面】删除面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼7 天前
零基础速学SolidWorks2026【曲面】加厚曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼10 天前
零基础速学SolidWorks2026【曲面】边界曲面和等距曲面
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼10 天前
零基础速学SolidWorks2026【曲面】平面区域和填充曲面
3d建模·solidworks·机械设计·机械绘图绘制
蓝悦无人机11 天前
SOLIDWORKS高级应用之方程
solidworks·参数化建模·if 判断语句
花开月满西楼11 天前
零基础速学SolidWorks2026【曲面】分割线
3d建模·solidworks·机械设计·机械绘图绘制
花开月满西楼13 天前
零基础速学SolidWorks2026【曲面】建模核心基础
solidworks·机械设计·机械绘图绘制
花开月满西楼14 天前
零基础速学SolidWorks2026【草图】绘制、编辑命令和约束
solidworks·机械设计