From 7e24f4025de19e07398f415a1f589bc8aaf9cb3d Mon Sep 17 00:00:00 2001 From: sladro Date: Sun, 8 Mar 2026 20:45:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/PdmsManager.cs | 954 ++++++++++++++++++++++++++----- Models/ExportIfcRequest.cs | 15 + Models/ModelStatusResponse.cs | 41 +- Models/ShrinkwrapModelRequest.cs | 6 + Network/HttpServer.cs | 11 +- 5 files changed, 876 insertions(+), 151 deletions(-) diff --git a/Core/PdmsManager.cs b/Core/PdmsManager.cs index d1d0698..0192329 100644 --- a/Core/PdmsManager.cs +++ b/Core/PdmsManager.cs @@ -14,6 +14,7 @@ namespace TellmePdmsPluging.Core 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", @@ -174,6 +175,7 @@ namespace TellmePdmsPluging.Core DryRun = effectiveRequest.DryRun, SkipLinkedElements = effectiveRequest.SkipLinkedElements, Padding = effectiveRequest.Padding, + DeletionMode = "conservative", StartedAt = DateTime.Now }; @@ -249,13 +251,13 @@ namespace TellmePdmsPluging.Core if (!effectiveRequest.DryRun && processed && result.RemovedCount > 0) { string persistError; - if (!TryPersistCurrentMdbChanges("TellmePdms shrinkwrap model", out persistError)) + if (!TryPersistCurrentMdbChanges("TellmePdms shrinkwrap model", context.GetTouchedWritableDbs(), result.ProcessedDbNames, out persistError)) { - result.Errors.Add("删除已执行,但保存更改失败: " + persistError); + result.FatalErrors.Add("删除已执行,但保存更改失败: " + persistError); } } - result.Success = processed && result.Errors.Count == 0; + result.Success = processed && result.Errors.Count == 0 && result.FatalErrors.Count == 0; if (!processed) { result.Message = "未找到符合过滤条件的Zone"; @@ -266,7 +268,8 @@ namespace TellmePdmsPluging.Core } else { - var topErrors = result.Errors.Take(3).ToArray(); + 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; } @@ -276,18 +279,23 @@ namespace TellmePdmsPluging.Core 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.Errors.Add(ex.Message); - result.Message = "外壳保留失败"; - 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() { @@ -302,24 +310,26 @@ namespace TellmePdmsPluging.Core }; } - return new ModelStatusResponse - { - ModelLoaded = true, - ProjectInfo = GetProjectInfo(), - ModelStatistics = GetModelStatistics(), - SessionInfo = GetSessionInfo() - }; - } - catch (Exception ex) - { + 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 - }; - } - } + return new ModelStatusResponse + { + ModelLoaded = false, + ExportDiagnostics = GetExportDiagnostics(null) + }; + } + } public OpenProjectResult OpenProject(OpenProjectRequest request) { @@ -477,35 +487,59 @@ namespace TellmePdmsPluging.Core var effectiveRequest = request ?? new ExportIfcRequest(); effectiveRequest.ApplyDefaults(); - var result = new ExportIfcResult - { - StartedAt = DateTime.Now, - ExportPath = effectiveRequest.ExportPath, - FileName = effectiveRequest.FileName, - FullPath = effectiveRequest.GetFullPath() - }; - - try - { - if (!IsPdmsConnected()) - { - result.Success = false; - result.Message = "PDMS 未连接"; - result.CompletedAt = DateTime.Now; - result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; - return result; - } - - var currentMdb = MDB.CurrentMDB; - var designDb = currentMdb?.GetFirstDB(DbType.Design); - if (designDb == null || designDb.World == null) - { - result.Success = false; - result.Message = "未找到有效的设计数据库"; - result.CompletedAt = DateTime.Now; - result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; - return result; - } + 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)) @@ -543,6 +577,7 @@ namespace TellmePdmsPluging.Core 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), @@ -565,27 +600,251 @@ namespace TellmePdmsPluging.Core 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) - { + } + catch (Exception ex) + { result.Success = false; - result.Message = "RVM导出异常: " + ex.Message; + 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) { // 按文档流程构建命令: @@ -908,6 +1167,70 @@ namespace TellmePdmsPluging.Core } } + 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; @@ -950,6 +1273,24 @@ namespace TellmePdmsPluging.Core } } + 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 @@ -1367,6 +1708,109 @@ namespace TellmePdmsPluging.Core 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; @@ -1750,6 +2194,7 @@ namespace TellmePdmsPluging.Core 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) { @@ -1757,6 +2202,7 @@ namespace TellmePdmsPluging.Core Result = result; _keepTypes = new HashSet(request.KeepTypes ?? new List()); _shellBoxes = new List(); + _touchedWritableDbs = new List(); } public ShrinkwrapModelRequest Request { get; } @@ -1791,9 +2237,9 @@ namespace TellmePdmsPluging.Core public void ProcessZone(DbElement zone) { - if (!TryGetLimitsBox(zone, out _zoneBox)) + if (!TryGetZoneBounds(zone, out _zoneBox)) { - Result.Errors.Add($"Zone包围盒计算失败: {BuildElementPath(zone)}"); + SnapshotUnsafe(BuildElementPath(zone), "ZONE", "Zone及其子元素均无法计算包围盒"); return; } @@ -1801,9 +2247,66 @@ namespace TellmePdmsPluging.Core _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)) { - ProcessChildren(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); } } @@ -1832,6 +2335,7 @@ namespace TellmePdmsPluging.Core if (candidates == null || candidates.Length == 0) { + Result.UsedExactSpatial = true; return true; } @@ -1840,15 +2344,16 @@ namespace TellmePdmsPluging.Core return GetOwnerDepth(b).CompareTo(GetOwnerDepth(a)); }); + Result.UsedExactSpatial = true; foreach (var element in candidates) { - ProcessExactCandidate(element); + ProcessExactCandidate(element, zone); } return true; } - private void ProcessExactCandidate(DbElement element) + private void ProcessExactCandidate(DbElement element, DbElement zone) { if (!IsElementValid(element)) { @@ -1879,17 +2384,91 @@ namespace TellmePdmsPluging.Core float[] box; if (TryGetLimitsBox(element, out box) && IsInsideInnerBox(box, _innerDeleteBox, 0f)) { - RemoveElement(element, normalizedType); + RemoveElement(element, normalizedType, zone, "exact-inner"); return; } - Result.KeptCount++; + SnapshotUnsafe(elementPath: BuildElementPath(element), typeName: normalizedType, reason: "无法稳定计算删除核包围盒关系"); return; } if (overlapsDeleteCore) { - RemoveElement(element, normalizedType); + 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; } @@ -1915,65 +2494,7 @@ namespace TellmePdmsPluging.Core return false; } - private 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(); - - if (_keepTypes.Contains(normalizedType)) - { - Result.KeptCount++; - ProcessChildren(element); - return; - } - - if (OverlapsAnyShellBox(element)) - { - Result.ShellKeptCount++; - Result.KeptCount++; - ProcessChildren(element); - return; - } - - float[] box; - if (!TryGetLimitsBox(element, out box)) - { - Result.KeptCount++; - return; - } - - if (IsInsideInnerBox(box, _innerDeleteBox, 0f)) - { - RemoveElement(element, normalizedType); - return; - } - - Result.KeptCount++; - ProcessChildren(element); - } - - private void RemoveElement(DbElement element, string typeName) + private void RemoveElement(DbElement element, string typeName, DbElement zone, string source) { var elementPath = BuildElementPath(element); @@ -1987,6 +2508,14 @@ namespace TellmePdmsPluging.Core 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); @@ -1995,10 +2524,11 @@ namespace TellmePdmsPluging.Core element.Delete(); SnapshotRemoval(elementPath, typeName, false); + TrackTouchedDb(elementDb); } catch (Exception ex) { - Result.Errors.Add($"删除元素 {elementPath} 失败: {ex.Message}"); + Result.Errors.Add("删除元素 " + elementPath + " 失败: " + ex.Message); } } @@ -2023,6 +2553,113 @@ namespace TellmePdmsPluging.Core } } + 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; @@ -2324,6 +2961,59 @@ namespace TellmePdmsPluging.Core (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; } + } } - } + } } diff --git a/Models/ExportIfcRequest.cs b/Models/ExportIfcRequest.cs index 9accb2b..2201b5e 100644 --- a/Models/ExportIfcRequest.cs +++ b/Models/ExportIfcRequest.cs @@ -12,6 +12,7 @@ namespace TellmePdmsPluging.Models public string ExportPath { get; set; } public string FileName { get; set; } public string ExportSystem { get; set; } + public string MdbName { get; set; } public bool? Overwrite { get; set; } public List Selections { get; set; } public List SelectionCommands { get; set; } @@ -32,6 +33,11 @@ namespace TellmePdmsPluging.Models { Overwrite = true; } + + if (!string.IsNullOrEmpty(MdbName)) + { + MdbName = MdbName.Trim(); + } } public string GetFullPath() @@ -83,10 +89,19 @@ namespace TellmePdmsPluging.Models { public bool Success { get; set; } public string Message { get; set; } + public string FailureCategory { get; set; } public string ExportPath { get; set; } public string FileName { get; set; } public string FullPath { get; set; } public long FileSizeBytes { get; set; } + public string ActiveMdbName { get; set; } + public string RequestedMdbName { get; set; } + public bool UsedRequestedMdb { get; set; } + public bool BaseChecksPassed { get; set; } + public string DriverMode { get; set; } + public string RawError { get; set; } + public List ExecutedCommands { get; set; } = new List(); + public List Diagnostics { get; set; } = new List(); public DateTime StartedAt { get; set; } public DateTime CompletedAt { get; set; } public double DurationSeconds { get; set; } diff --git a/Models/ModelStatusResponse.cs b/Models/ModelStatusResponse.cs index a79f5d2..c6ea4d6 100644 --- a/Models/ModelStatusResponse.cs +++ b/Models/ModelStatusResponse.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; namespace TellmePdmsPluging.Models { - public class ModelStatusResponse - { - public bool ModelLoaded { get; set; } - public ProjectInfo ProjectInfo { get; set; } - public ModelStatistics ModelStatistics { get; set; } - public SessionInfo SessionInfo { get; set; } - } + public class ModelStatusResponse + { + public bool ModelLoaded { get; set; } + public ProjectInfo ProjectInfo { get; set; } + public ModelStatistics ModelStatistics { get; set; } + public SessionInfo SessionInfo { get; set; } + public ExportDiagnostics ExportDiagnostics { get; set; } + } public class ProjectInfo { @@ -30,11 +31,23 @@ namespace TellmePdmsPluging.Models public long FeatureCount { get; set; } } - public class SessionInfo - { - public string UserName { get; set; } - public DateTime StartTime { get; set; } - public int DurationMinutes { get; set; } - } - + public class SessionInfo + { + public string UserName { get; set; } + public DateTime StartTime { get; set; } + public int DurationMinutes { get; set; } + } + + public class ExportDiagnostics + { + public string ActiveMdbName { get; set; } + public bool DesignDbAvailable { get; set; } + public bool CurrentMdbMatchesRequested { get; set; } + public string RequestedMdbName { get; set; } + public bool ExportPrecheckPassed { get; set; } + public bool DefinitionsLikelyMissing { get; set; } + public string Message { get; set; } + public string LastExportError { get; set; } + } + } diff --git a/Models/ShrinkwrapModelRequest.cs b/Models/ShrinkwrapModelRequest.cs index 19c3039..c5ee1b8 100644 --- a/Models/ShrinkwrapModelRequest.cs +++ b/Models/ShrinkwrapModelRequest.cs @@ -94,14 +94,20 @@ namespace TellmePdmsPluging.Models public bool DryRun { get; set; } public bool SkipLinkedElements { get; set; } public double Padding { get; set; } + public string DeletionMode { get; set; } + public bool UsedExactSpatial { get; set; } public int TotalVisited { get; set; } public int RemovedCount { get; set; } public int KeptCount { get; set; } public int ShellKeptCount { get; set; } public int SkippedLinkedCount { get; set; } + public int SkippedUnsafeCount { get; set; } public List RemovedElements { get; set; } = new List(); public List SkippedLinkedElements { get; set; } = new List(); + public List SkippedUnsafeElements { get; set; } = new List(); public List Errors { get; set; } = new List(); + public List FatalErrors { get; set; } = new List(); + public List ProcessedDbNames { get; set; } = new List(); public List ZoneSummaries { get; set; } = new List(); public System.DateTime StartedAt { get; set; } public System.DateTime CompletedAt { get; set; } diff --git a/Network/HttpServer.cs b/Network/HttpServer.cs index cae2c77..842cd42 100644 --- a/Network/HttpServer.cs +++ b/Network/HttpServer.cs @@ -725,11 +725,12 @@ namespace TellmePdmsPluging.Network { var parts = new List(); - parts.Add($"\"ModelLoaded\":{model.ModelLoaded.ToString().ToLower()}"); - parts.Add($"\"ProjectInfo\":{SimpleJsonSerialize(model.ProjectInfo)}"); - parts.Add($"\"ModelStatistics\":{SimpleJsonSerialize(model.ModelStatistics)}"); - parts.Add($"\"SessionInfo\":{SimpleJsonSerialize(model.SessionInfo)}"); - + parts.Add($"\"ModelLoaded\":{model.ModelLoaded.ToString().ToLower()}"); + parts.Add($"\"ProjectInfo\":{SimpleJsonSerialize(model.ProjectInfo)}"); + parts.Add($"\"ModelStatistics\":{SimpleJsonSerialize(model.ModelStatistics)}"); + parts.Add($"\"SessionInfo\":{SimpleJsonSerialize(model.SessionInfo)}"); + parts.Add($"\"ExportDiagnostics\":{SimpleJsonSerialize(model.ExportDiagnostics)}"); + return "{" + string.Join(",", parts.ToArray()) + "}"; }