using System; using System.Collections.Generic; using TellmePdmsPluging.Models; using Aveva.ApplicationFramework; using Aveva.Pdms.Database; using Aveva.ApplicationFramework.Presentation; using System.Linq; namespace TellmePdmsPluging.Core { public class PdmsManager { private static PdmsManager _instance; private static readonly object _lock = new object(); private static readonly object _attributeCacheLock = new object(); private static readonly Dictionary _attributeCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static string _lastExportErrorMessage; private static readonly string[] LinkedAttributeCandidates = new[] { "ATCONN", "BRCON", "DEPREF", "ECRFA", "CONNE", "CONNS", "ATTC", "CTYA", "CTYE", "CTYO", "CTYS", "ENDPOI", "CLNK", "CONN", "DCON", "DPCO" }; public static PdmsManager Instance { get { if (_instance == null) { lock (_lock) { if (_instance == null) _instance = new PdmsManager(); } } return _instance; } } private PdmsManager() { } public SimplifyModelResult SimplifyModel(SimplifyModelRequest request) { var effectiveRequest = request ?? new SimplifyModelRequest(); effectiveRequest.ApplyDefaults(); var result = new SimplifyModelResult { DryRun = effectiveRequest.DryRun, SkipLinkedElements = effectiveRequest.SkipLinkedElements, StartedAt = DateTime.Now }; try { if (!IsPdmsConnected()) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "PDMS 未连接"; result.Errors.Add("PDMS 未连接"); return result; } var currentMdb = MDB.CurrentMDB; var designDb = currentMdb?.GetFirstDB(DbType.Design); if (designDb == null || designDb.World == null) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "未找到有效的设计数据库"; result.Errors.Add("未找到有效的设计数据库"); return result; } if (!effectiveRequest.DryRun && IsDbReadOnly(designDb)) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "当前Design DB为只读,无法执行真实删除"; result.Errors.Add("当前Design DB为只读,无法执行真实删除"); return result; } var context = new SimplifyContext(effectiveRequest, result); var sites = designDb.World.Members(); bool processed = false; if (sites != null) { foreach (var site in sites) { if (!IsElementValid(site) || !string.Equals(GetElementTypeName(site), "SITE", StringComparison.OrdinalIgnoreCase)) { continue; } var zones = site.Members(); if (zones == null) { continue; } foreach (var zone in zones) { if (!IsElementValid(zone) || !string.Equals(GetElementTypeName(zone), "ZONE", StringComparison.OrdinalIgnoreCase)) { continue; } if (!context.ShouldProcessZone(zone)) { continue; } processed = true; context.EnterZone(zone); context.ProcessChildren(zone); } } } if (!effectiveRequest.DryRun && processed && result.RemovedCount > 0) { string persistError; if (!TryPersistCurrentMdbChanges("TellmePdms simplify model", out persistError)) { result.Errors.Add("删除已执行,但保存更改失败: " + persistError); } } result.Success = processed && result.Errors.Count == 0; if (!processed) { result.Message = "未找到符合过滤条件的Zone"; } else if (result.Success) { result.Message = result.DryRun ? "模型轻量化干跑完成" : "模型轻量化完成"; } else { var topErrors = result.Errors.Take(3).ToArray(); var errorSummary = topErrors.Length == 0 ? "UNKNOWN_ERROR" : string.Join(" | ", topErrors); result.Message = (result.DryRun ? "模型轻量化干跑完成,但存在错误: " : "模型轻量化完成,但存在错误: ") + errorSummary; } if (effectiveRequest.SkipLinkedElements && result.SkippedLinkedCount > 0) { result.Message += ";已跳过连接元素 " + result.SkippedLinkedCount + " 个"; } result.CompletedAt = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.Errors.Add(ex.Message); result.Message = "模型轻量化失败"; result.CompletedAt = DateTime.Now; return result; } } public ShrinkwrapModelResult ShrinkwrapModel(ShrinkwrapModelRequest request) { var effectiveRequest = request ?? new ShrinkwrapModelRequest(); effectiveRequest.ApplyDefaults(); var result = new ShrinkwrapModelResult { DryRun = effectiveRequest.DryRun, SkipLinkedElements = effectiveRequest.SkipLinkedElements, Padding = effectiveRequest.Padding, DeletionMode = "conservative", StartedAt = DateTime.Now }; try { if (!IsPdmsConnected()) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "PDMS 未连接"; result.Errors.Add("PDMS 未连接"); return result; } var currentMdb = MDB.CurrentMDB; var designDb = currentMdb?.GetFirstDB(DbType.Design); if (designDb == null || designDb.World == null) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "未找到有效的设计数据库"; result.Errors.Add("未找到有效的设计数据库"); return result; } if (!effectiveRequest.DryRun && IsDbReadOnly(designDb)) { result.Success = false; result.CompletedAt = DateTime.Now; result.Message = "当前Design DB为只读,无法执行真实删除"; result.Errors.Add("当前Design DB为只读,无法执行真实删除"); return result; } var context = new ShrinkwrapContext(effectiveRequest, result); var sites = designDb.World.Members(); bool processed = false; if (sites != null) { foreach (var site in sites) { if (!IsElementValid(site) || !string.Equals(GetElementTypeName(site), "SITE", StringComparison.OrdinalIgnoreCase)) { continue; } var zones = site.Members(); if (zones == null) { continue; } foreach (var zone in zones) { if (!IsElementValid(zone) || !string.Equals(GetElementTypeName(zone), "ZONE", StringComparison.OrdinalIgnoreCase)) { continue; } if (!context.ShouldProcessZone(zone)) { continue; } processed = true; context.EnterZone(zone); context.ProcessZone(zone); } } } if (!effectiveRequest.DryRun && processed && result.RemovedCount > 0) { string persistError; if (!TryPersistCurrentMdbChanges("TellmePdms shrinkwrap model", context.GetTouchedWritableDbs(), result.ProcessedDbNames, out persistError)) { result.FatalErrors.Add("删除已执行,但保存更改失败: " + persistError); } } result.Success = processed && result.Errors.Count == 0 && result.FatalErrors.Count == 0; if (!processed) { result.Message = "未找到符合过滤条件的Zone"; } else if (result.Success) { result.Message = result.DryRun ? "外壳保留干跑完成" : "外壳保留完成"; } else { var mergedErrors = result.FatalErrors.Concat(result.Errors).Take(3).ToArray(); var topErrors = mergedErrors; var errorSummary = topErrors.Length == 0 ? "UNKNOWN_ERROR" : string.Join(" | ", topErrors); result.Message = (result.DryRun ? "外壳保留干跑完成,但存在错误: " : "外壳保留完成,但存在错误: ") + errorSummary; } if (effectiveRequest.SkipLinkedElements && result.SkippedLinkedCount > 0) { result.Message += ";已跳过连接元素 " + result.SkippedLinkedCount + " 个"; } if (result.SkippedUnsafeCount > 0) { result.Message += ";已跳过不安全元素 " + result.SkippedUnsafeCount + " 个"; } result.CompletedAt = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.FatalErrors.Add(ex.Message); result.Message = "外壳保留失败"; result.CompletedAt = DateTime.Now; return result; } } public ModelStatusResponse GetModelStatus() { try { // 检查PDMS是否连接 if (!IsPdmsConnected()) { return new ModelStatusResponse { ModelLoaded = false }; } return new ModelStatusResponse { ModelLoaded = true, ProjectInfo = GetProjectInfo(), ModelStatistics = GetModelStatistics(), SessionInfo = GetSessionInfo(), ExportDiagnostics = GetExportDiagnostics(null) }; } catch (Exception ex) { // 记录错误日志 System.Diagnostics.Debug.WriteLine($"获取PDMS模型状态失败: {ex.Message}"); return new ModelStatusResponse { ModelLoaded = false, ExportDiagnostics = GetExportDiagnostics(null) }; } } public OpenProjectResult OpenProject(OpenProjectRequest request) { var effectiveRequest = request ?? new OpenProjectRequest(); effectiveRequest.ApplyDefaults(); var result = new OpenProjectResult { ProjectName = effectiveRequest.ProjectName, FileSize = -1, PolygonCount = -1, FeatureCount = -1, CompletedAt = DateTime.Now }; try { var currentProject = Project.CurrentProject; if (currentProject == null) { result.Success = false; result.Message = "Project.CurrentProject 为空,无法打开项目"; return result; } bool alreadyOpen = false; try { alreadyOpen = currentProject.IsOpen(); } catch { alreadyOpen = false; } result.WasAlreadyOpen = alreadyOpen; // 如果已经打开且未指定目标项目名,则直接返回成功 if (alreadyOpen && string.IsNullOrEmpty(effectiveRequest.ProjectName)) { result.Success = true; result.Message = "项目已打开"; PopulateOpenProjectMetrics(result); return result; } if (string.IsNullOrEmpty(effectiveRequest.ProjectName)) { result.Success = false; result.Message = "ProjectName 不能为空"; return result; } bool opened = currentProject.Open( effectiveRequest.ProjectName, effectiveRequest.UserName ?? string.Empty, effectiveRequest.Password ?? string.Empty); result.Success = opened; result.Message = opened ? "项目打开成功" : "项目打开失败(Open 返回 false)"; if (opened) { PopulateOpenProjectMetrics(result); } result.CompletedAt = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.Message = "项目打开异常: " + ex.Message; result.CompletedAt = DateTime.Now; return result; } } public CloseProjectResult CloseProject(CloseProjectRequest request) { var effectiveRequest = request ?? new CloseProjectRequest(); effectiveRequest.ApplyDefaults(); var result = new CloseProjectResult { CompletedAt = DateTime.Now }; try { var currentProject = Project.CurrentProject; if (currentProject == null) { result.Success = false; result.Message = "Project.CurrentProject 为空,无法关闭项目"; return result; } bool isOpen = false; try { isOpen = currentProject.IsOpen(); } catch { isOpen = false; } result.WasOpen = isOpen; result.ProjectName = GetProjectNameSafe(currentProject); if (!isOpen) { result.Success = true; result.Message = "当前没有已打开项目"; result.CompletedAt = DateTime.Now; return result; } var projectType = currentProject.GetType(); var closeWithForce = projectType.GetMethod("Close", new[] { typeof(bool) }); if (closeWithForce != null) { closeWithForce.Invoke(currentProject, new object[] { effectiveRequest.Force }); } else { var closeNoArg = projectType.GetMethod("Close", Type.EmptyTypes); if (closeNoArg == null) { result.Success = false; result.Message = "未找到可用的 Project.Close 方法"; result.CompletedAt = DateTime.Now; return result; } closeNoArg.Invoke(currentProject, null); } result.Success = true; result.Message = "项目关闭成功"; result.CompletedAt = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.Message = "关闭项目异常: " + ex.Message; result.CompletedAt = DateTime.Now; return result; } } public ExportIfcResult ExportIfc(ExportIfcRequest request) { var effectiveRequest = request ?? new ExportIfcRequest(); effectiveRequest.ApplyDefaults(); var result = new ExportIfcResult { StartedAt = DateTime.Now, ExportPath = effectiveRequest.ExportPath, FileName = effectiveRequest.FileName, FullPath = effectiveRequest.GetFullPath(), RequestedMdbName = effectiveRequest.MdbName, DriverMode = "macro-export" }; try { if (!IsPdmsConnected()) { result.Success = false; result.Message = "PDMS 未连接"; result.FailureCategory = "pdms-connection"; result.RawError = result.Message; SetLastExportError(result.Message); result.CompletedAt = DateTime.Now; result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; return result; } string mdbValidationError; var currentMdb = ResolveExportMdb(effectiveRequest, result, out mdbValidationError); if (currentMdb == null) { result.Success = false; result.Message = mdbValidationError ?? "未找到可用于导出的MDB"; result.FailureCategory = "mdb-validation"; result.RawError = result.Message; SetLastExportError(result.Message); result.CompletedAt = DateTime.Now; result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; return result; } result.ActiveMdbName = GetMdbNameSafe(currentMdb); result.BaseChecksPassed = true; var designDb = currentMdb?.GetFirstDB(DbType.Design); if (designDb == null || designDb.World == null) { result.Success = false; result.Message = "未找到有效的设计数据库"; result.FailureCategory = "design-db"; result.RawError = result.Message; SetLastExportError(result.Message); result.CompletedAt = DateTime.Now; result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; return result; } // 确保导出目录存在 if (!System.IO.Directory.Exists(effectiveRequest.ExportPath)) { System.IO.Directory.CreateDirectory(effectiveRequest.ExportPath); } // 执行PML命令 - 通过MacroCommand类型反射构造并执行 var macroType = CommandManager.Instance.MacroCommand; if (macroType == null) throw new InvalidOperationException("MacroCommand类型未注册"); var ctor = macroType.GetConstructor( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, new[] { typeof(string), typeof(string), typeof(string) }, null); if (ctor == null) { var ctors = macroType.GetConstructors( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var ctorInfo = new System.Text.StringBuilder(); foreach (var c in ctors) { ctorInfo.Append(c.ToString()).Append(" | "); } throw new InvalidOperationException( "MacroCommand未找到(string,string,string)构造函数,类型: " + macroType.FullName + " 构造函数: " + ctorInfo); } var exportCommands = BuildExportCommands(effectiveRequest); for (int i = 0; i < exportCommands.Length; i++) { result.ExecutedCommands.Add(exportCommands[i]); var macroCommand = ctor.Invoke(new object[] { "TellmePdmsPluging.ExportIfc." + (i + 1), exportCommands[i], string.Empty }) as Command; if (macroCommand == null) { throw new InvalidOperationException("MacroCommand实例创建失败,类型: " + macroType.FullName); } macroCommand.Execute(); } // 检查文件是否生成 if (System.IO.File.Exists(result.FullPath)) { var fileInfo = new System.IO.FileInfo(result.FullPath); result.FileSizeBytes = fileInfo.Length; result.Success = true; result.Message = "RVM导出成功"; result.FailureCategory = null; result.RawError = null; SetLastExportError(null); } else { result.Success = false; result.Message = "RVM导出失败,未生成文件"; result.FailureCategory = "output-path"; result.RawError = result.Message; result.Diagnostics.Add("导出命令已执行,但目标文件未生成"); SetLastExportError(result.Message); } result.CompletedAt = DateTime.Now; result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; return result; } catch (Exception ex) { result.Success = false; result.RawError = ex.Message; result.FailureCategory = ClassifyExportFailure(ex.Message); result.Message = TranslateExportException(ex, result.ActiveMdbName, effectiveRequest.MdbName); result.Diagnostics.Add("导出命令执行失败,分类=" + result.FailureCategory); SetLastExportError(result.Message); result.CompletedAt = DateTime.Now; result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; return result; } } private ExportDiagnostics GetExportDiagnostics(string requestedMdbName) { var diagnostics = new ExportDiagnostics { RequestedMdbName = NormalizeMdbName(requestedMdbName), LastExportError = _lastExportErrorMessage }; try { var currentMdb = MDB.CurrentMDB; diagnostics.ActiveMdbName = GetMdbNameSafe(currentMdb); diagnostics.CurrentMdbMatchesRequested = string.IsNullOrEmpty(diagnostics.RequestedMdbName) || IsSameMdbName(diagnostics.ActiveMdbName, diagnostics.RequestedMdbName); var designDb = currentMdb == null ? null : currentMdb.GetFirstDB(DbType.Design); diagnostics.DesignDbAvailable = designDb != null && designDb.World != null; diagnostics.DefinitionsLikelyMissing = IsDefinitionsError(_lastExportErrorMessage); if (currentMdb == null) { diagnostics.ExportPrecheckPassed = false; diagnostics.Message = "当前没有活动MDB"; return diagnostics; } if (!diagnostics.CurrentMdbMatchesRequested) { diagnostics.ExportPrecheckPassed = false; diagnostics.Message = "当前活动MDB与请求的MDB不一致"; return diagnostics; } if (!diagnostics.DesignDbAvailable) { diagnostics.ExportPrecheckPassed = false; diagnostics.Message = "当前MDB未挂接有效的Design DB"; return diagnostics; } if (diagnostics.DefinitionsLikelyMissing) { diagnostics.ExportPrecheckPassed = false; diagnostics.Message = "当前MDB疑似缺少导出所需的UDET/UDA definitions"; return diagnostics; } diagnostics.ExportPrecheckPassed = true; diagnostics.Message = "导出前置检查通过"; return diagnostics; } catch (Exception ex) { diagnostics.ExportPrecheckPassed = false; diagnostics.Message = "导出检查异常: " + ex.Message; return diagnostics; } } private MDB ResolveExportMdb(ExportIfcRequest request, ExportIfcResult result, out string error) { error = null; try { var currentMdb = MDB.CurrentMDB; var requestedMdbName = NormalizeMdbName(request.MdbName); request.MdbName = requestedMdbName; result.RequestedMdbName = requestedMdbName; result.ActiveMdbName = GetMdbNameSafe(currentMdb); if (string.IsNullOrEmpty(requestedMdbName)) { result.UsedRequestedMdb = false; result.Diagnostics.Add("未提供mdbName,直接使用当前活动MDB"); return currentMdb; } if (currentMdb != null && IsSameMdbName(currentMdb.Name, requestedMdbName)) { result.UsedRequestedMdb = true; result.Diagnostics.Add("请求的MDB与当前活动MDB一致,直接使用当前MDB"); return currentMdb; } result.UsedRequestedMdb = false; result.Diagnostics.Add("请求的MDB与当前活动MDB不一致,插件不会自动切换MDB"); error = "当前活动MDB " + (GetMdbNameSafe(currentMdb) ?? "NULL") + " 与请求的MDB " + requestedMdbName + " 不一致,请先在PDMS中手动切换后重试"; return null; } catch (Exception ex) { error = "校验导出MDB异常: " + ex.Message; return null; } } private static string GetMdbNameSafe(MDB mdb) { if (mdb == null) { return null; } try { return mdb.Name; } catch { return null; } } private static string NormalizeMdbName(string value) { if (string.IsNullOrEmpty(value)) { return null; } var trimmed = value.Trim(); if (trimmed.Length == 0) { return null; } if (!trimmed.StartsWith("/", StringComparison.Ordinal)) { trimmed = "/" + trimmed.TrimStart('/'); } return trimmed; } private static bool IsSameMdbName(string left, string right) { var normalizedLeft = NormalizeMdbName(left); var normalizedRight = NormalizeMdbName(right); if (string.IsNullOrEmpty(normalizedLeft) || string.IsNullOrEmpty(normalizedRight)) { return false; } return string.Equals(normalizedLeft, normalizedRight, StringComparison.OrdinalIgnoreCase); } private static string TranslateExportException(Exception ex, string activeMdbName, string requestedMdbName) { var rawMessage = ex == null ? string.Empty : ex.Message ?? string.Empty; var mdbName = !string.IsNullOrEmpty(activeMdbName) ? activeMdbName : requestedMdbName; if (IsDefinitionsError(rawMessage)) { if (!string.IsNullOrEmpty(mdbName)) { return "RVM导出失败: 当前MDB " + mdbName + " 缺少导出所需的UDET/UDA定义,请切换到包含定义库的MDB后重试"; } return "RVM导出失败: 当前MDB缺少导出所需的UDET/UDA定义,请切换到包含定义库的MDB后重试"; } if (rawMessage.IndexOf("找不到文件", StringComparison.OrdinalIgnoreCase) >= 0 || rawMessage.IndexOf("could not find file", StringComparison.OrdinalIgnoreCase) >= 0) { return "RVM导出失败: Review driver/template 路径或输入文件无效"; } return "RVM导出异常: " + rawMessage; } private static string ClassifyExportFailure(string rawMessage) { if (string.IsNullOrEmpty(rawMessage)) { return "command"; } if (IsDefinitionsError(rawMessage)) { return "definitions"; } if (rawMessage.IndexOf("找不到文件", StringComparison.OrdinalIgnoreCase) >= 0 || rawMessage.IndexOf("could not find file", StringComparison.OrdinalIgnoreCase) >= 0) { return "driver-template"; } if (rawMessage.IndexOf("应用程序中的错误", StringComparison.OrdinalIgnoreCase) >= 0) { return "mdb-switch"; } return "command"; } private static bool IsDefinitionsError(string message) { if (string.IsNullOrEmpty(message)) { return false; } return message.IndexOf("Undefined UDA or UDET name", StringComparison.OrdinalIgnoreCase) >= 0; } private static void SetLastExportError(string message) { _lastExportErrorMessage = message; } private string[] BuildExportCommands(ExportIfcRequest request) { // 按文档流程构建命令: // EXPORT SYSTEM (可选) // EXPORT FILE OVERWRITE // EXPORT // EXPORT FINISH string fullPath = request.GetFullPath().Replace("\\", "/"); var commands = new List(); if (!string.IsNullOrEmpty(request.ExportSystem)) { commands.Add("EXPORT SYSTEM " + request.ExportSystem); } commands.Add(string.Format("EXPORT FILE {0} {1}", fullPath, request.Overwrite.GetValueOrDefault(true) ? "OVERWRITE" : "READ")); var selections = request.GetEffectiveSelections(); foreach (var selectionCommand in selections) { commands.Add(selectionCommand); } commands.Add("EXPORT FINISH"); return commands.ToArray(); } public OpenMdbResult OpenMdb(OpenMdbRequest request) { var effectiveRequest = request ?? new OpenMdbRequest(); effectiveRequest.ApplyDefaults(); var result = new OpenMdbResult { MdbName = effectiveRequest.MdbName, ReadOnly = effectiveRequest.ReadOnly, DefaultType = effectiveRequest.DefaultType, FileSize = -1, PolygonCount = -1, FeatureCount = -1, CompletedAt = DateTime.Now }; try { if (string.IsNullOrEmpty(effectiveRequest.MdbName)) { result.Success = false; result.Message = "MdbName 不能为空"; return result; } // 如果当前MDB已经是目标,则直接返回成功 try { var current = MDB.CurrentMDB; if (current != null && string.Equals(current.Name, effectiveRequest.MdbName, StringComparison.OrdinalIgnoreCase)) { result.Success = true; result.WasAlreadyOpen = true; result.Message = "MDB 已经打开"; PopulateOpenMdbMetrics(result); result.CompletedAt = DateTime.Now; return result; } } catch { // ignore, continue to open } var setup = MDBSetup.CreateMDBSetup(effectiveRequest.MdbName); if (setup == null) { result.Success = false; result.Message = "创建 MDBSetup 失败"; return result; } setup.ReadOnly = effectiveRequest.ReadOnly; if (effectiveRequest.Subtype.HasValue) { setup.Subtype = effectiveRequest.Subtype.Value; } if (!string.IsNullOrEmpty(effectiveRequest.DefaultType)) { DbType defaultType; if (!TryParseDbType(effectiveRequest.DefaultType, out defaultType)) { result.Success = false; result.Message = "DefaultType 无效: " + effectiveRequest.DefaultType; return result; } setup.DefaultType = defaultType; } if (effectiveRequest.ReadTypes != null && effectiveRequest.ReadTypes.Count > 0) { DbType[] readTypes; string error; if (!TryParseDbTypes(effectiveRequest.ReadTypes, out readTypes, out error)) { result.Success = false; result.Message = "ReadTypes 无效: " + error; return result; } setup.ReadTypes = readTypes; } if (effectiveRequest.WriteTypes != null && effectiveRequest.WriteTypes.Count > 0) { DbType[] writeTypes; string error; if (!TryParseDbTypes(effectiveRequest.WriteTypes, out writeTypes, out error)) { result.Success = false; result.Message = "WriteTypes 无效: " + error; return result; } setup.WriteTypes = writeTypes; } var openedMdb = Project.OpenMDB(setup); if (openedMdb == null) { result.Success = false; result.Message = "打开 MDB 失败(OpenMDB 返回 null)"; result.CompletedAt = DateTime.Now; return result; } result.Success = true; result.WasAlreadyOpen = false; result.MdbName = openedMdb.Name; result.Message = "MDB 打开成功"; PopulateOpenMdbMetrics(result); result.CompletedAt = DateTime.Now; return result; } catch (Exception ex) { result.Success = false; result.Message = "打开 MDB 异常: " + ex.Message; result.CompletedAt = DateTime.Now; return result; } } private static bool TryParseDbTypes(IEnumerable values, out DbType[] types, out string error) { var list = new List(); var invalid = new List(); foreach (var raw in values) { if (string.IsNullOrEmpty(raw)) { continue; } DbType parsed; if (TryParseDbType(raw, out parsed)) { if (!list.Contains(parsed)) { list.Add(parsed); } } else { invalid.Add(raw); } } if (invalid.Count > 0) { types = null; error = string.Join(",", invalid.ToArray()); return false; } types = list.ToArray(); error = null; return true; } private static bool TryParseDbType(string value, out DbType dbType) { dbType = DbType.Design; if (string.IsNullOrEmpty(value)) { return false; } try { dbType = (DbType)Enum.Parse(typeof(DbType), value.Trim(), true); return true; } catch { return false; } } private static string GetProjectNameSafe(Project project) { if (project == null) { return null; } try { var nameProperty = project.GetType().GetProperty("Name"); if (nameProperty != null) { var value = nameProperty.GetValue(project, null) as string; if (!string.IsNullOrEmpty(value)) { return value; } } } catch { // ignore and fallback } return null; } private static bool IsDbReadOnly(Db db) { if (db == null) { return true; } try { var property = db.GetType().GetProperty("IsReadOnly"); if (property == null) { return false; } var value = property.GetValue(db, null); if (value is bool) { return (bool)value; } } catch { // ignore and treat as writable } return false; } private static bool TryPersistCurrentMdbChanges(string comment, out string error) { error = null; try { var mdb = MDB.CurrentMDB; if (mdb == null) { error = "当前MDB为空"; return false; } bool saveResult; if (TryInvokeSaveWorkByComment(mdb, comment, out saveResult)) { if (saveResult) { return true; } error = "SaveWork(string) 返回 false"; return false; } var designDb = mdb.GetFirstDB(DbType.Design); if (designDb == null) { error = "未找到Design DB"; return false; } if (IsDbReadOnly(designDb)) { error = "Design DB为只读"; return false; } if (TryInvokeSaveWorkByDbs(mdb, new[] { designDb }, comment, out saveResult)) { if (saveResult) { return true; } error = "SaveWork(Db[], string) 返回 false"; return false; } error = "未找到可用的SaveWork重载"; return false; } catch (Exception ex) { error = ex.Message; return false; } } private static bool TryPersistCurrentMdbChanges(string comment, IList touchedDbs, IList processedDbNames, out string error) { error = null; if (processedDbNames != null) { processedDbNames.Clear(); } try { var mdb = MDB.CurrentMDB; if (mdb == null) { error = "当前MDB为空"; return false; } var writableDbs = new List(); if (touchedDbs != null) { for (int i = 0; i < touchedDbs.Count; i++) { var db = touchedDbs[i]; if (db == null || IsDbReadOnly(db)) { continue; } if (!ContainsDb(writableDbs, db)) { writableDbs.Add(db); if (processedDbNames != null) { processedDbNames.Add(GetDbNameSafe(db)); } } } } if (writableDbs.Count > 0) { bool saveResult; if (TryInvokeSaveWorkByDbs(mdb, writableDbs.ToArray(), comment, out saveResult)) { if (saveResult) { return true; } error = "SaveWork(Db[], string) 返回 false"; return false; } } return TryPersistCurrentMdbChanges(comment, out error); } catch (Exception ex) { error = ex.Message; return false; } } private static bool TryInvokeSaveWorkByComment(MDB mdb, string comment, out bool saveResult) { saveResult = false; try { var method = mdb.GetType().GetMethod("SaveWork", new[] { typeof(string) }); if (method == null) { return false; } var result = method.Invoke(mdb, new object[] { comment ?? string.Empty }); saveResult = !(result is bool) || (bool)result; return true; } catch { return false; } } private static bool TryInvokeSaveWorkByDbs(MDB mdb, Db[] dbs, string comment, out bool saveResult) { saveResult = false; try { var method = mdb.GetType().GetMethod("SaveWork", new[] { typeof(Db[]), typeof(string) }); if (method == null) { return false; } var result = method.Invoke(mdb, new object[] { dbs, comment ?? string.Empty }); saveResult = !(result is bool) || (bool)result; return true; } catch { return false; } } private static bool ContainsDb(IList dbs, Db candidate) { if (dbs == null || candidate == null) { return false; } for (int i = 0; i < dbs.Count; i++) { if (object.ReferenceEquals(dbs[i], candidate)) { return true; } } return false; } private bool IsPdmsConnected() { try { // 使用MDB.CurrentMDB检查PDMS连接状态 var currentMdb = MDB.CurrentMDB; return currentMdb != null; } catch { return false; } } private ProjectInfo GetProjectInfo() { try { // 使用MDB.CurrentMDB和Project.CurrentProject获取真实项目信息 var currentMdb = MDB.CurrentMDB; var currentProject = Project.CurrentProject; if (currentMdb != null) { var designDb = currentMdb.GetFirstDB(DbType.Design); if (designDb != null) { return new ProjectInfo { ProjectName = designDb.Name ?? "Unknown", MdsName = currentMdb.Name ?? "Unknown", PdmsVersion = "12.1.SP4" }; } } return new ProjectInfo { ProjectName = "Unknown", MdsName = "Unknown", PdmsVersion = "12.1.SP4" }; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"获取项目信息失败: {ex.Message}"); return new ProjectInfo { ProjectName = "Unknown", MdsName = "Unknown", PdmsVersion = "12.1.SP4" }; } } private ModelStatistics GetModelStatistics() { try { // 使用MDB.CurrentMDB获取真实模型统计信息 var currentMdb = MDB.CurrentMDB; if (currentMdb != null) { var designDb = currentMdb.GetFirstDB(DbType.Design); if (designDb?.World != null) { // 初始化统计计数器 var elementCounts = new Dictionary { {"SITE", 0}, {"ZONE", 0}, {"PIPE", 0}, {"EQUI", 0}, {"STRU", 0}, {"VALVE", 0}, {"FITT", 0}, {"NOZZ", 0} }; int totalElements = 0; var activeZones = new List(); int zoneCount = 0; // 递归统计所有元素 CountElementsByType(designDb.World, elementCounts, ref totalElements); // 专门统计Zone信息 CountZones(designDb.World, ref zoneCount, activeZones); return new ModelStatistics { TotalElements = totalElements, ElementCounts = elementCounts, ZoneCount = zoneCount, ActiveZones = activeZones, FileSize = TryGetCurrentModelFileSize(), PolygonCount = -1, FeatureCount = totalElements }; } } return new ModelStatistics { TotalElements = 0, ElementCounts = new Dictionary(), ZoneCount = 0, ActiveZones = new List(), FileSize = -1, PolygonCount = -1, FeatureCount = 0 }; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"获取模型统计信息失败: {ex.Message}"); return new ModelStatistics { TotalElements = 0, ElementCounts = new Dictionary(), ZoneCount = 0, ActiveZones = new List(), FileSize = -1, PolygonCount = -1, FeatureCount = 0 }; } } private void PopulateOpenProjectMetrics(OpenProjectResult result) { var statistics = GetModelStatistics(); if (statistics == null) { return; } result.FileSize = statistics.FileSize; result.PolygonCount = statistics.PolygonCount; result.FeatureCount = statistics.FeatureCount; } private void PopulateOpenMdbMetrics(OpenMdbResult result) { var statistics = GetModelStatistics(); if (statistics == null) { return; } result.FileSize = statistics.FileSize; result.PolygonCount = statistics.PolygonCount; result.FeatureCount = statistics.FeatureCount; } private long TryGetCurrentModelFileSize() { try { var currentMdb = MDB.CurrentMDB; if (currentMdb == null) { return -1; } var designDb = currentMdb.GetFirstDB(DbType.Design); string path = TryGetCandidatePath(designDb) ?? TryGetCandidatePath(currentMdb); if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) { return -1; } return new System.IO.FileInfo(path).Length; } catch { return -1; } } private static string TryGetCandidatePath(object source) { if (source == null) { return null; } var type = source.GetType(); var names = new[] { "FileName", "Filename", "FullName", "Path" }; foreach (var name in names) { var prop = type.GetProperty(name); if (prop != null && prop.PropertyType == typeof(string)) { var value = prop.GetValue(source, null) as string; if (LooksLikeFilePath(value)) { return value; } } var field = type.GetField(name); if (field != null && field.FieldType == typeof(string)) { var value = field.GetValue(source) as string; if (LooksLikeFilePath(value)) { return value; } } } return null; } private static bool LooksLikeFilePath(string value) { if (string.IsNullOrEmpty(value)) { return false; } var trimmed = value.Trim(); if (trimmed.Length < 3) { return false; } return trimmed.IndexOf('\\') >= 0 || trimmed.IndexOf('/') >= 0; } private void CountElementsByType(DbElement parentElement, Dictionary elementCounts, ref int totalElements) { try { if (parentElement == null || parentElement.IsNull || !parentElement.IsValid) return; // 获取所有子元素 var members = parentElement.Members(); if (members != null) { foreach (var element in members) { if (element != null && !element.IsNull && element.IsValid) { totalElements++; // 获取元素的实际类型 var elementType = element.GetActualType(); if (elementType != null) { string typeName = elementType.Name; if (elementCounts.ContainsKey(typeName)) { elementCounts[typeName]++; } } // 递归统计子元素 CountElementsByType(element, elementCounts, ref totalElements); } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"统计元素类型时出错: {ex.Message}"); } } private void CountZones(DbElement worldElement, ref int zoneCount, List activeZones) { try { if (worldElement == null || worldElement.IsNull || !worldElement.IsValid) return; // 查找SITE元素 var sites = worldElement.Members(); if (sites != null) { foreach (var site in sites) { if (site != null && !site.IsNull && site.IsValid) { var siteType = site.GetActualType(); if (siteType != null && siteType.Name == "SITE") { // 在SITE下查找ZONE元素 var zones = site.Members(); if (zones != null) { foreach (var zone in zones) { if (zone != null && !zone.IsNull && zone.IsValid) { var zoneType = zone.GetActualType(); if (zoneType != null && zoneType.Name == "ZONE") { zoneCount++; // 获取Zone名称 try { string zoneName = ""; if (zone.GetValidString(DbAttributeInstance.NAME, ref zoneName)) { if (!string.IsNullOrEmpty(zoneName)) { activeZones.Add(zoneName); } } } catch { // 如果无法获取名称,跳过 } } } } } } } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"统计Zone信息时出错: {ex.Message}"); } } private SessionInfo GetSessionInfo() { try { // 使用MDB.CurrentMDB获取真实会话信息 var currentMdb = MDB.CurrentMDB; if (currentMdb != null) { var designDb = currentMdb.GetFirstDB(DbType.Design); if (designDb?.CurrentSession != null) { // 从真实的数据库会话获取信息 var session = designDb.CurrentSession; return new SessionInfo { UserName = session.User ?? Environment.UserName, // 从DbSession获取用户名 StartTime = session.Date, // 从DbSession获取会话创建时间 DurationMinutes = (int)(DateTime.Now - session.Date).TotalMinutes // 计算会话持续时间 }; } } return new SessionInfo { UserName = Environment.UserName, StartTime = DateTime.Now, DurationMinutes = 0 }; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"获取会话信息失败: {ex.Message}"); return new SessionInfo { UserName = Environment.UserName, StartTime = DateTime.Now, DurationMinutes = 0 }; } } private static bool IsElementValid(DbElement element) { return element != null && !element.IsNull && element.IsValid; } private static string GetElementTypeName(DbElement element) { try { var type = element?.GetActualType(); return type?.Name ?? string.Empty; } catch { return string.Empty; } } private static string BuildElementPath(DbElement element) { var segments = new List(); var current = element; int guard = 0; while (IsElementValid(current) && guard < 128) { string name = string.Empty; try { if (!current.GetValidString(DbAttributeInstance.NAME, ref name) || string.IsNullOrEmpty(name)) { name = current.ToString(); } } catch { name = current.ToString(); } segments.Add(name.Trim()); current = current.Owner; guard++; } segments.Reverse(); return "/" + string.Join("/", segments.ToArray()); } private static bool TryGetElementDb(DbElement element, out Db db) { db = null; if (!IsElementValid(element)) { return false; } var names = new[] { "DB", "Db", "Database", "OwningDB", "OwnerDB" }; var type = element.GetType(); for (int i = 0; i < names.Length; i++) { var prop = type.GetProperty(names[i]); if (prop != null && typeof(Db).IsAssignableFrom(prop.PropertyType)) { try { db = prop.GetValue(element, null) as Db; if (db != null) { return true; } } catch { // ignore } } } return false; } private static string GetDbNameSafe(Db db) { if (db == null) { return "UNKNOWN_DB"; } try { return db.Name ?? db.ToString(); } catch { return "UNKNOWN_DB"; } } private static bool IsDesignDb(Db db) { if (db == null) { return false; } try { var property = db.GetType().GetProperty("Type"); if (property == null) { return false; } var value = property.GetValue(db, null); if (value is DbType) { return (DbType)value == DbType.Design; } } catch { // ignore } return false; } private static bool IsElementUnderOwner(DbElement element, DbElement expectedOwner) { if (!IsElementValid(element) || !IsElementValid(expectedOwner)) { return false; } var current = element; int guard = 0; while (IsElementValid(current) && guard < 512) { if (object.ReferenceEquals(current, expectedOwner)) { return true; } current = current.Owner; guard++; } return false; } private static bool ShouldSkipLinkedElement(DbElement element, bool skipLinkedElements, out string reason) { reason = null; if (!skipLinkedElements || !IsElementValid(element)) { return false; } try { if (!element.IsDeleteable) { reason = "IsDeleteable=false"; return true; } } catch { // ignore } string attributeName; string attributeValue; if (TryFindLinkedAttributeValue(element, out attributeName, out attributeValue)) { reason = string.IsNullOrEmpty(attributeValue) ? attributeName : (attributeName + "=" + attributeValue); return true; } return false; } private static bool TryFindLinkedAttributeValue(DbElement element, out string attributeName, out string attributeValue) { attributeName = null; attributeValue = null; if (!IsElementValid(element)) { return false; } foreach (var candidate in LinkedAttributeCandidates) { DbAttribute attribute; if (!TryGetDbAttributeByName(candidate, out attribute) || attribute == null) { continue; } bool isValid; try { isValid = element.IsAttributeValid(attribute); } catch { continue; } if (!isValid) { continue; } string textValue = string.Empty; try { if (element.GetValidAsString(attribute, ref textValue) && HasMeaningfulLinkedValue(textValue)) { attributeName = candidate; attributeValue = SummarizeValue(textValue); return true; } } catch { // ignore and fallback to array check } try { var refs = element.GetElementArray(attribute); if (refs != null) { for (int i = 0; i < refs.Length; i++) { if (IsElementValid(refs[i])) { attributeName = candidate; attributeValue = "REFS>0"; return true; } } } } catch { // ignore } } return false; } private static bool TryGetDbAttributeByName(string name, out DbAttribute attribute) { attribute = null; if (string.IsNullOrEmpty(name)) { return false; } lock (_attributeCacheLock) { if (_attributeCache.TryGetValue(name, out attribute)) { return attribute != null; } } DbAttribute resolved = null; try { var flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.IgnoreCase; var field = typeof(DbAttributeInstance).GetField(name, flags); if (field != null) { resolved = field.GetValue(null) as DbAttribute; } if (resolved == null) { var property = typeof(DbAttributeInstance).GetProperty(name, flags); if (property != null) { resolved = property.GetValue(null, null) as DbAttribute; } } } catch { resolved = null; } lock (_attributeCacheLock) { _attributeCache[name] = resolved; } attribute = resolved; return attribute != null; } private static bool HasMeaningfulLinkedValue(string value) { if (IsNullOrWhiteSpace(value)) { return false; } var normalized = value.Trim().ToUpperInvariant(); switch (normalized) { case "0": case "FALSE": case "NONE": case "NO": case "NULREF": case "NULL": case "UNSET": case "UNDEFINED": case "[]": case "{}": return false; } if (normalized.IndexOf("NULREF", StringComparison.Ordinal) >= 0 || normalized.IndexOf("UNSET", StringComparison.Ordinal) >= 0 || normalized.IndexOf("UNDEFINED", StringComparison.Ordinal) >= 0) { return false; } bool hasLetter = false; bool hasSlash = false; for (int i = 0; i < normalized.Length; i++) { char ch = normalized[i]; if (char.IsLetter(ch)) { hasLetter = true; break; } if (ch == '/') { hasSlash = true; } } if (!hasLetter && !hasSlash) { return false; } return true; } private static string SummarizeValue(string value) { if (string.IsNullOrEmpty(value)) { return value; } var trimmed = value.Trim(); const int maxLength = 64; if (trimmed.Length <= maxLength) { return trimmed; } return trimmed.Substring(0, maxLength) + "..."; } private static bool IsNullOrWhiteSpace(string value) { if (value == null) { return true; } for (int i = 0; i < value.Length; i++) { if (!char.IsWhiteSpace(value[i])) { return false; } } return true; } private class SimplifyContext { private const int MaxRemovedSnapshots = 200; private const int MaxSkippedSnapshots = 200; public SimplifyContext(SimplifyModelRequest request, SimplifyModelResult result) { Request = request; Result = result; _keepTypes = new HashSet(request.KeepTypes ?? new List()); _removeTypes = new HashSet(request.RemoveTypes ?? new List()); } public SimplifyModelRequest Request { get; } public SimplifyModelResult Result { get; } public string CurrentZoneName { get; private set; } private readonly HashSet _keepTypes; private readonly HashSet _removeTypes; public bool ShouldProcessZone(DbElement zone) { if (Request.ZoneFilters == null || Request.ZoneFilters.Count == 0) { return true; } var zonePath = BuildElementPath(zone).ToUpperInvariant(); return Request.ZoneFilters.Any(filter => zonePath.Contains(filter)); } public void EnterZone(DbElement zone) { CurrentZoneName = BuildElementPath(zone); if (!Result.ZoneSummaries.Contains(CurrentZoneName)) { Result.ZoneSummaries.Add(CurrentZoneName); } } public void ProcessChildren(DbElement parent) { var members = parent.Members(); if (members == null) { return; } foreach (var child in members) { ProcessElement(child); } } private void ProcessElement(DbElement element) { if (!IsElementValid(element)) { return; } Result.TotalVisited++; var typeName = GetElementTypeName(element); var normalizedType = string.IsNullOrEmpty(typeName) ? string.Empty : typeName.ToUpperInvariant(); bool keep = _keepTypes.Contains(normalizedType); bool remove = _removeTypes.Contains(normalizedType) && !keep; if (remove) { RemoveElement(element, normalizedType); return; } Result.KeptCount++; ProcessChildren(element); } private void RemoveElement(DbElement element, string typeName) { var elementPath = BuildElementPath(element); try { string linkedReason; if (ShouldSkipLinkedElement(element, Request.SkipLinkedElements, out linkedReason)) { Result.KeptCount++; SnapshotSkipped(elementPath, typeName, linkedReason); return; } if (Request.DryRun) { SnapshotRemoval(elementPath, typeName, true); return; } element.Delete(); SnapshotRemoval(elementPath, typeName, false); } catch (Exception ex) { Result.Errors.Add($"删除元素 {elementPath} 失败: {ex.Message}"); } } private void SnapshotRemoval(string elementPath, string typeName, bool dryRun) { Result.RemovedCount++; if (Result.RemovedElements.Count < MaxRemovedSnapshots) { Result.RemovedElements.Add((dryRun ? "[DRYRUN-DEL] " : "[DEL] ") + elementPath + " (" + typeName + ")"); } } private void SnapshotSkipped(string elementPath, string typeName, string reason) { Result.SkippedLinkedCount++; if (Result.SkippedLinkedElements.Count < MaxSkippedSnapshots) { var suffix = string.IsNullOrEmpty(reason) ? string.Empty : (" [" + reason + "]"); Result.SkippedLinkedElements.Add("[SKIP-LINKED] " + elementPath + " (" + typeName + ")" + suffix); } } } private class ShrinkwrapContext { private const int MaxRemovedSnapshots = 200; private const int MaxSkippedSnapshots = 200; private const float MinBoxEdge = 0.001f; private readonly List _touchedWritableDbs; public ShrinkwrapContext(ShrinkwrapModelRequest request, ShrinkwrapModelResult result) { Request = request; Result = result; _keepTypes = new HashSet(request.KeepTypes ?? new List()); _shellBoxes = new List(); _touchedWritableDbs = new List(); } public ShrinkwrapModelRequest Request { get; } public ShrinkwrapModelResult Result { get; } public string CurrentZoneName { get; private set; } private readonly HashSet _keepTypes; private float[] _zoneBox; private float[] _innerBox; private float[] _innerDeleteBox; private List _shellBoxes; public bool ShouldProcessZone(DbElement zone) { if (Request.ZoneFilters == null || Request.ZoneFilters.Count == 0) { return true; } var zonePath = BuildElementPath(zone).ToUpperInvariant(); return Request.ZoneFilters.Any(filter => zonePath.Contains(filter)); } public void EnterZone(DbElement zone) { CurrentZoneName = BuildElementPath(zone); if (!Result.ZoneSummaries.Contains(CurrentZoneName)) { Result.ZoneSummaries.Add(CurrentZoneName); } } public void ProcessZone(DbElement zone) { if (!TryGetZoneBounds(zone, out _zoneBox)) { SnapshotUnsafe(BuildElementPath(zone), "ZONE", "Zone及其子元素均无法计算包围盒"); return; } _innerBox = BuildInnerBox(_zoneBox, (float)Math.Max(0d, Request.Padding)); _innerDeleteBox = BuildDeleteCoreBox(_innerBox, (float)Math.Max(0d, Request.TouchTolerance)); _shellBoxes = BuildShellBoxes(_zoneBox, _innerDeleteBox); if (_innerDeleteBox == null || _shellBoxes == null || _shellBoxes.Count == 0) { SnapshotUnsafe(BuildElementPath(zone), "ZONE", "Zone几何边界不足以构建保壳删除区域"); return; } if (!ProcessZoneWithExactSpatial(zone)) { Result.UsedExactSpatial = false; ProcessFallbackCandidates(zone); } } public IList GetTouchedWritableDbs() { return _touchedWritableDbs; } private bool TryGetZoneBounds(DbElement zone, out float[] box) { box = null; if (!IsElementValid(zone)) { return false; } if (TryGetLimitsBox(zone, out box)) { return true; } var aggregate = new List(); CollectDescendantBoxes(zone, aggregate); if (aggregate.Count == 0) { return false; } box = MergeBoxes(aggregate); return box != null; } private void CollectDescendantBoxes(DbElement parent, List boxes) { var members = SnapshotMembers(parent); for (int i = 0; i < members.Count; i++) { var child = members[i]; if (!IsElementValid(child)) { continue; } float[] childBox; if (TryGetLimitsBox(child, out childBox)) { boxes.Add(childBox); } CollectDescendantBoxes(child, boxes); } } private bool ProcessZoneWithExactSpatial(DbElement zone) { if (_shellBoxes == null || _shellBoxes.Count == 0 || _innerDeleteBox == null) { return false; } var spatial = Spatial.Instance; if (spatial == null) { return false; } DbElement[] candidates; try { candidates = spatial.ElementsInElementExact(zone, true); } catch { return false; } if (candidates == null || candidates.Length == 0) { Result.UsedExactSpatial = true; return true; } Array.Sort(candidates, delegate (DbElement a, DbElement b) { return GetOwnerDepth(b).CompareTo(GetOwnerDepth(a)); }); Result.UsedExactSpatial = true; foreach (var element in candidates) { ProcessExactCandidate(element, zone); } return true; } private void ProcessExactCandidate(DbElement element, DbElement zone) { if (!IsElementValid(element)) { return; } Result.TotalVisited++; var typeName = GetElementTypeName(element); var normalizedType = string.IsNullOrEmpty(typeName) ? string.Empty : typeName.ToUpperInvariant(); if (_keepTypes.Contains(normalizedType)) { Result.KeptCount++; return; } if (OverlapsAnyShellBox(element)) { Result.ShellKeptCount++; Result.KeptCount++; return; } bool overlapsDeleteCore; if (!TryElementInBox(element, _innerDeleteBox, out overlapsDeleteCore)) { float[] box; if (TryGetLimitsBox(element, out box) && IsInsideInnerBox(box, _innerDeleteBox, 0f)) { RemoveElement(element, normalizedType, zone, "exact-inner"); return; } SnapshotUnsafe(elementPath: BuildElementPath(element), typeName: normalizedType, reason: "无法稳定计算删除核包围盒关系"); return; } if (overlapsDeleteCore) { RemoveElement(element, normalizedType, zone, "exact-overlap"); return; } Result.KeptCount++; } private void ProcessFallbackCandidates(DbElement zone) { var candidates = new List(); CollectFallbackCandidates(zone, candidates, zone); candidates.Sort(delegate (ShrinkwrapCandidate a, ShrinkwrapCandidate b) { return b.Depth.CompareTo(a.Depth); }); for (int i = 0; i < candidates.Count; i++) { ProcessFallbackCandidate(candidates[i], zone); } } private void CollectFallbackCandidates(DbElement parent, List candidates, DbElement zone) { var members = SnapshotMembers(parent); for (int i = 0; i < members.Count; i++) { var child = members[i]; if (!IsElementValid(child)) { continue; } candidates.Add(new ShrinkwrapCandidate(child, GetOwnerDepth(child))); CollectFallbackCandidates(child, candidates, zone); } } private void ProcessFallbackCandidate(ShrinkwrapCandidate candidate, DbElement zone) { var element = candidate.Element; if (!IsElementValid(element)) { return; } Result.TotalVisited++; var typeName = GetElementTypeName(element); var normalizedType = string.IsNullOrEmpty(typeName) ? string.Empty : typeName.ToUpperInvariant(); if (_keepTypes.Contains(normalizedType)) { Result.KeptCount++; return; } if (OverlapsAnyShellBox(element)) { Result.ShellKeptCount++; Result.KeptCount++; return; } float[] box; if (!TryGetLimitsBox(element, out box)) { SnapshotUnsafe(BuildElementPath(element), normalizedType, "无法计算包围盒"); return; } if (IsInsideInnerBox(box, _innerDeleteBox, 0f)) { RemoveElement(element, normalizedType, zone, "fallback-inner"); return; } Result.KeptCount++; } private bool OverlapsAnyShellBox(DbElement element) { if (_shellBoxes == null || _shellBoxes.Count == 0) { return false; } foreach (var shellBox in _shellBoxes) { bool overlaps; if (TryElementInBox(element, shellBox, out overlaps) && overlaps) { return true; } } return false; } private void RemoveElement(DbElement element, string typeName, DbElement zone, string source) { var elementPath = BuildElementPath(element); try { string linkedReason; if (ShouldSkipLinkedElement(element, Request.SkipLinkedElements, out linkedReason)) { Result.KeptCount++; SnapshotSkipped(elementPath, typeName, linkedReason); return; } string unsafeReason; Db elementDb; if (!IsSafeToDelete(element, zone, out elementDb, out unsafeReason)) { SnapshotUnsafe(elementPath, typeName, unsafeReason); return; } if (Request.DryRun) { SnapshotRemoval(elementPath, typeName, true); return; } element.Delete(); SnapshotRemoval(elementPath, typeName, false); TrackTouchedDb(elementDb); } catch (Exception ex) { Result.Errors.Add("删除元素 " + elementPath + " 失败: " + ex.Message); } } private void SnapshotRemoval(string elementPath, string typeName, bool dryRun) { Result.RemovedCount++; if (Result.RemovedElements.Count < MaxRemovedSnapshots) { Result.RemovedElements.Add((dryRun ? "[DRYRUN-DEL] " : "[DEL] ") + elementPath + " (" + typeName + ")"); } } private void SnapshotSkipped(string elementPath, string typeName, string reason) { Result.SkippedLinkedCount++; if (Result.SkippedLinkedElements.Count < MaxSkippedSnapshots) { var suffix = string.IsNullOrEmpty(reason) ? string.Empty : (" [" + reason + "]"); Result.SkippedLinkedElements.Add("[SKIP-LINKED] " + elementPath + " (" + typeName + ")" + suffix); } } private void SnapshotUnsafe(string elementPath, string typeName, string reason) { Result.KeptCount++; Result.SkippedUnsafeCount++; if (Result.SkippedUnsafeElements.Count < MaxSkippedSnapshots) { var suffix = string.IsNullOrEmpty(reason) ? string.Empty : (" [" + reason + "]"); Result.SkippedUnsafeElements.Add("[SKIP-UNSAFE] " + elementPath + " (" + typeName + ")" + suffix); } } private bool IsSafeToDelete(DbElement element, DbElement zone, out Db elementDb, out string reason) { elementDb = null; reason = null; if (!IsElementValid(element)) { reason = "元素无效"; return false; } if (!IsElementValid(zone)) { reason = "Zone无效"; return false; } var owner = element.Owner; if (!IsElementValid(owner)) { reason = "Owner无效"; return false; } if (!IsElementUnderOwner(element, zone)) { reason = "元素已不在目标Zone下"; return false; } if (!TryGetElementDb(element, out elementDb) || elementDb == null) { reason = "无法确认元素所属DB"; return false; } if (!IsDesignDb(elementDb)) { reason = "元素所属DB不是Design DB"; return false; } if (IsDbReadOnly(elementDb)) { reason = "元素所属DB为只读"; return false; } return true; } private void TrackTouchedDb(Db db) { if (db == null || IsDbReadOnly(db)) { return; } if (!ContainsDb(_touchedWritableDbs, db)) { _touchedWritableDbs.Add(db); } } private static List SnapshotMembers(DbElement parent) { var snapshot = new List(); if (!IsElementValid(parent)) { return snapshot; } DbElement[] members = null; try { members = parent.Members(); } catch { return snapshot; } if (members == null) { return snapshot; } for (int i = 0; i < members.Length; i++) { snapshot.Add(members[i]); } return snapshot; } private static int GetOwnerDepth(DbElement element) { var current = element; int depth = 0; int guard = 0; while (IsElementValid(current) && guard < 512) { depth++; current = current.Owner; guard++; } return depth; } private static bool TryElementInBox(DbElement element, float[] box, out bool overlaps) { overlaps = false; if (!IsElementValid(element) || box == null || box.Length < 6) { return false; } try { float[] elementBox; if (!TryGetLimitsBox(element, out elementBox)) { return false; } overlaps = BoxesOverlap(elementBox, box); return true; } catch { return false; } } private static bool TryGetLimitsBox(DbElement element, out float[] box) { box = null; if (!IsElementValid(element)) { return false; } try { var spatial = Spatial.Instance; if (spatial == null) { return false; } var spatialType = spatial.GetType(); var arrayMethod = spatialType.GetMethod("LimitsBox", new[] { typeof(DbElement), typeof(float[]) }); if (arrayMethod != null) { var args = new object[] { element, new float[6] }; var okObj = arrayMethod.Invoke(spatial, args); if (okObj is bool && (bool)okObj) { if (TryNormalizeBox(args[1] as float[], out box)) { return true; } } } var methods = spatialType.GetMethods().Where(m => m.Name == "LimitsBox").ToArray(); foreach (var method in methods) { var parameters = method.GetParameters(); if (parameters.Length != 2 || parameters[0].ParameterType != typeof(DbElement) || !parameters[1].ParameterType.IsByRef) { continue; } object boxValue; try { boxValue = Activator.CreateInstance(parameters[1].ParameterType.GetElementType()); } catch { continue; } var args = new object[] { element, boxValue }; var okObj = method.Invoke(spatial, args); if (!(okObj is bool) || !(bool)okObj) { continue; } var converted = ConvertLimitsBoxObject(args[1]); if (TryNormalizeBox(converted, out box)) { return true; } } } catch { // ignore } return false; } private static float[] ConvertLimitsBoxObject(object limits) { if (limits == null) { return null; } var type = limits.GetType(); try { float xmin = ReadFloat(limits, type, new[] { "XMIN", "XMin", "MinX", "Xmin" }); float ymin = ReadFloat(limits, type, new[] { "YMIN", "YMin", "MinY", "Ymin" }); float zmin = ReadFloat(limits, type, new[] { "ZMIN", "ZMin", "MinZ", "Zmin" }); float xmax = ReadFloat(limits, type, new[] { "XMAX", "XMax", "MaxX", "Xmax" }); float ymax = ReadFloat(limits, type, new[] { "YMAX", "YMax", "MaxY", "Ymax" }); float zmax = ReadFloat(limits, type, new[] { "ZMAX", "ZMax", "MaxZ", "Zmax" }); return new[] { xmin, ymin, zmin, xmax, ymax, zmax }; } catch { return null; } } private static float ReadFloat(object boxed, Type type, string[] names) { foreach (var name in names) { var prop = type.GetProperty(name); if (prop != null) { var value = prop.GetValue(boxed, null); if (value != null) { return Convert.ToSingle(value); } } var field = type.GetField(name); if (field != null) { var value = field.GetValue(boxed); if (value != null) { return Convert.ToSingle(value); } } } throw new InvalidOperationException("LimitsBox缺少必要字段/属性"); } private static bool TryNormalizeBox(float[] source, out float[] normalized) { normalized = null; if (source == null || source.Length < 6) { return false; } float xmin = Math.Min(source[0], source[3]); float ymin = Math.Min(source[1], source[4]); float zmin = Math.Min(source[2], source[5]); float xmax = Math.Max(source[0], source[3]); float ymax = Math.Max(source[1], source[4]); float zmax = Math.Max(source[2], source[5]); if ((xmax - xmin) < MinBoxEdge || (ymax - ymin) < MinBoxEdge || (zmax - zmin) < MinBoxEdge) { return false; } normalized = new[] { xmin, ymin, zmin, xmax, ymax, zmax }; return true; } private static float[] BuildInnerBox(float[] outerBox, float padding) { if (outerBox == null || outerBox.Length < 6) { return null; } var inner = new float[6]; inner[0] = outerBox[0] + padding; inner[1] = outerBox[1] + padding; inner[2] = outerBox[2] + padding; inner[3] = outerBox[3] - padding; inner[4] = outerBox[4] - padding; inner[5] = outerBox[5] - padding; if (inner[3] < inner[0]) { inner[0] = outerBox[0]; inner[3] = outerBox[3]; } if (inner[4] < inner[1]) { inner[1] = outerBox[1]; inner[4] = outerBox[4]; } if (inner[5] < inner[2]) { inner[2] = outerBox[2]; inner[5] = outerBox[5]; } return inner; } private static float[] BuildDeleteCoreBox(float[] innerBox, float tolerance) { if (innerBox == null || innerBox.Length < 6) { return null; } var core = new float[6]; var offset = Math.Max(0f, tolerance); core[0] = innerBox[0] + offset; core[1] = innerBox[1] + offset; core[2] = innerBox[2] + offset; core[3] = innerBox[3] - offset; core[4] = innerBox[4] - offset; core[5] = innerBox[5] - offset; float[] normalized; return TryNormalizeBox(core, out normalized) ? normalized : null; } private static List BuildShellBoxes(float[] outerBox, float[] innerDeleteBox) { var boxes = new List(); if (outerBox == null || innerDeleteBox == null) { return boxes; } AddShellBox(boxes, outerBox[0], outerBox[1], outerBox[2], innerDeleteBox[0], outerBox[4], outerBox[5]); // Left AddShellBox(boxes, innerDeleteBox[3], outerBox[1], outerBox[2], outerBox[3], outerBox[4], outerBox[5]); // Right AddShellBox(boxes, innerDeleteBox[0], outerBox[1], outerBox[2], innerDeleteBox[3], innerDeleteBox[1], outerBox[5]); // Front AddShellBox(boxes, innerDeleteBox[0], innerDeleteBox[4], outerBox[2], innerDeleteBox[3], outerBox[4], outerBox[5]); // Back AddShellBox(boxes, innerDeleteBox[0], innerDeleteBox[1], outerBox[2], innerDeleteBox[3], innerDeleteBox[4], innerDeleteBox[2]); // Bottom AddShellBox(boxes, innerDeleteBox[0], innerDeleteBox[1], innerDeleteBox[5], innerDeleteBox[3], innerDeleteBox[4], outerBox[5]); // Top return boxes; } private static void AddShellBox(List boxes, float xmin, float ymin, float zmin, float xmax, float ymax, float zmax) { var candidate = new[] { xmin, ymin, zmin, xmax, ymax, zmax }; float[] normalized; if (TryNormalizeBox(candidate, out normalized)) { boxes.Add(normalized); } } private static bool IsInsideInnerBox(float[] box, float[] innerBox, float tolerance) { if (box == null || innerBox == null || box.Length < 6 || innerBox.Length < 6) { return false; } return (box[0] > innerBox[0] + tolerance) && (box[1] > innerBox[1] + tolerance) && (box[2] > innerBox[2] + tolerance) && (box[3] < innerBox[3] - tolerance) && (box[4] < innerBox[4] - tolerance) && (box[5] < innerBox[5] - tolerance); } private static bool BoxesOverlap(float[] a, float[] b) { if (a == null || b == null || a.Length < 6 || b.Length < 6) { return false; } return (a[0] <= b[3]) && (a[3] >= b[0]) && (a[1] <= b[4]) && (a[4] >= b[1]) && (a[2] <= b[5]) && (a[5] >= b[2]); } private static float[] MergeBoxes(IList boxes) { if (boxes == null || boxes.Count == 0) { return null; } float xmin = float.MaxValue; float ymin = float.MaxValue; float zmin = float.MaxValue; float xmax = float.MinValue; float ymax = float.MinValue; float zmax = float.MinValue; bool found = false; for (int i = 0; i < boxes.Count; i++) { var box = boxes[i]; if (box == null || box.Length < 6) { continue; } xmin = Math.Min(xmin, box[0]); ymin = Math.Min(ymin, box[1]); zmin = Math.Min(zmin, box[2]); xmax = Math.Max(xmax, box[3]); ymax = Math.Max(ymax, box[4]); zmax = Math.Max(zmax, box[5]); found = true; } if (!found) { return null; } float[] merged; return TryNormalizeBox(new[] { xmin, ymin, zmin, xmax, ymax, zmax }, out merged) ? merged : null; } private class ShrinkwrapCandidate { public ShrinkwrapCandidate(DbElement element, int depth) { Element = element; Depth = depth; } public DbElement Element { get; private set; } public int Depth { get; private set; } } } } }