用方程也得创建方程式,直接改尺寸名称,标记名称,手动改反而更方便
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);
}
}
}