TellmePdmsPluging/Core/PdmsManager.cs
2026-03-08 20:45:18 +08:00

3020 lines
104 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, DbAttribute> _attributeCache = new Dictionary<string, DbAttribute>(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 <driver> (可选)
// EXPORT FILE <name> OVERWRITE
// EXPORT <selection>
// EXPORT FINISH
string fullPath = request.GetFullPath().Replace("\\", "/");
var commands = new List<string>();
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<string> values, out DbType[] types, out string error)
{
var list = new List<DbType>();
var invalid = new List<string>();
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<Db> touchedDbs, IList<string> 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<Db>();
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<Db> 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<string, int>
{
{"SITE", 0}, {"ZONE", 0}, {"PIPE", 0}, {"EQUI", 0},
{"STRU", 0}, {"VALVE", 0}, {"FITT", 0}, {"NOZZ", 0}
};
int totalElements = 0;
var activeZones = new List<string>();
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<string, int>(),
ZoneCount = 0,
ActiveZones = new List<string>(),
FileSize = -1,
PolygonCount = -1,
FeatureCount = 0
};
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"获取模型统计信息失败: {ex.Message}");
return new ModelStatistics
{
TotalElements = 0,
ElementCounts = new Dictionary<string, int>(),
ZoneCount = 0,
ActiveZones = new List<string>(),
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<string, int> 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<string> 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<string>();
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<string>(request.KeepTypes ?? new List<string>());
_removeTypes = new HashSet<string>(request.RemoveTypes ?? new List<string>());
}
public SimplifyModelRequest Request { get; }
public SimplifyModelResult Result { get; }
public string CurrentZoneName { get; private set; }
private readonly HashSet<string> _keepTypes;
private readonly HashSet<string> _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<Db> _touchedWritableDbs;
public ShrinkwrapContext(ShrinkwrapModelRequest request, ShrinkwrapModelResult result)
{
Request = request;
Result = result;
_keepTypes = new HashSet<string>(request.KeepTypes ?? new List<string>());
_shellBoxes = new List<float[]>();
_touchedWritableDbs = new List<Db>();
}
public ShrinkwrapModelRequest Request { get; }
public ShrinkwrapModelResult Result { get; }
public string CurrentZoneName { get; private set; }
private readonly HashSet<string> _keepTypes;
private float[] _zoneBox;
private float[] _innerBox;
private float[] _innerDeleteBox;
private List<float[]> _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<Db> 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<float[]>();
CollectDescendantBoxes(zone, aggregate);
if (aggregate.Count == 0)
{
return false;
}
box = MergeBoxes(aggregate);
return box != null;
}
private void CollectDescendantBoxes(DbElement parent, List<float[]> 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<ShrinkwrapCandidate>();
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<ShrinkwrapCandidate> 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<DbElement> SnapshotMembers(DbElement parent)
{
var snapshot = new List<DbElement>();
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<float[]> BuildShellBoxes(float[] outerBox, float[] innerDeleteBox)
{
var boxes = new List<float[]>();
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<float[]> 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<float[]> 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; }
}
}
}
}