diff --git a/Models/ShellModels.cs b/Models/ShellModels.cs index 90dac1c..213000c 100644 --- a/Models/ShellModels.cs +++ b/Models/ShellModels.cs @@ -194,7 +194,7 @@ namespace RevitHttpControl.Models /// /// 处理耗时(秒) /// - public double ProcessingTimeSeconds { get; set; } + public int ProcessingTimeSeconds { get; set; } } /// diff --git a/Services/ShellOptimizer.cs b/Services/ShellOptimizer.cs index f8cc17b..f01be99 100644 --- a/Services/ShellOptimizer.cs +++ b/Services/ShellOptimizer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -38,7 +38,7 @@ namespace RevitHttpControl.Services result.OriginalSize = FormatFileSize(originalSize); // 2. 分析模型获取删除列表 - var analysis = _analyzer.AnalyzeModel(doc, mode); + _analyzer.AnalyzeModel(doc, mode); var elementsToDelete = GetElementsToDelete(doc, mode); // 3. 创建备份(如果需要) @@ -49,7 +49,8 @@ namespace RevitHttpControl.Services // 4. 执行删除操作 var deletedCount = DeleteElements(doc, elementsToDelete, mode); - result.RemovedCount = deletedCount; + var purgedCount = PurgeUnusedFamilyData(doc); + result.RemovedCount = deletedCount + purgedCount; // 5. 保存文档并计算优化后大小 SaveDocument(doc); @@ -68,14 +69,14 @@ namespace RevitHttpControl.Services } stopwatch.Stop(); - result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds; + result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); return result; } catch (Exception ex) { stopwatch.Stop(); - result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds; + result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); // 如果有备份且操作失败,可以考虑恢复备份 throw new InvalidOperationException($"薄壳优化执行失败: {ex.Message}", ex); @@ -111,9 +112,10 @@ namespace RevitHttpControl.Services result.BackupPath = CreateBackup(doc); } - // 自定义删除沿用标准安全检查逻辑(非 EnvelopeOnly) + // Reuse Standard safety checks for custom category deletion. var deletedCount = DeleteElements(doc, elementsToDelete, ShellOptimizeMode.Standard); - result.RemovedCount = deletedCount; + var purgedCount = PurgeUnusedFamilyData(doc); + result.RemovedCount = deletedCount + purgedCount; SaveDocument(doc); var optimizedSize = GetDocumentFileSize(doc); @@ -130,14 +132,14 @@ namespace RevitHttpControl.Services } stopwatch.Stop(); - result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds; + result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); return result; } catch (Exception ex) { stopwatch.Stop(); - result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds; + result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); throw new InvalidOperationException($"自定义类别删除执行失败: {ex.Message}", ex); } } @@ -219,6 +221,7 @@ namespace RevitHttpControl.Services using (var transaction = new Transaction(doc, "薄壳优化删除构件")) { transaction.Start(); + ApplyWarningSuppression(transaction); try { @@ -404,6 +407,12 @@ namespace RevitHttpControl.Services return; } + // Prefer compact save for normal local documents to reclaim free space. + if (TryCompactSaveInPlace(doc)) + { + return; + } + try { doc.Save(); @@ -421,6 +430,38 @@ namespace RevitHttpControl.Services } } + private bool TryCompactSaveInPlace(Document doc) + { + if (doc == null) + return false; + + if (doc.IsWorkshared) + return false; + + var originalPath = doc.PathName; + if (string.IsNullOrWhiteSpace(originalPath)) + return false; + + if (!File.Exists(originalPath)) + return false; + + try + { + var options = new SaveAsOptions + { + OverwriteExistingFile = true, + Compact = true + }; + + doc.SaveAs(originalPath, options); + return true; + } + catch + { + return false; + } + } + private bool IsReadOnlySaveError(Exception ex) { if (ex == null) return false; @@ -453,6 +494,118 @@ namespace RevitHttpControl.Services doc.SaveAs(outputPath, saveAsOptions); } + /// + /// Remove unused family symbols and empty families after instance deletion. + /// + private int PurgeUnusedFamilyData(Document doc) + { + if (doc == null) + return 0; + + var usedSymbolIds = new HashSet(); + var instances = new FilteredElementCollector(doc) + .OfClass(typeof(FamilyInstance)) + .WhereElementIsNotElementType() + .Cast(); + + foreach (var instance in instances) + { + var symbolId = instance?.Symbol?.Id?.IntegerValue; + if (symbolId.HasValue) + { + usedSymbolIds.Add(symbolId.Value); + } + } + + var unusedSymbolIds = new FilteredElementCollector(doc) + .OfClass(typeof(FamilySymbol)) + .Cast() + .Where(symbol => symbol != null && !usedSymbolIds.Contains(symbol.Id.IntegerValue)) + .Select(symbol => symbol.Id) + .ToList(); + + var removedSymbolCount = DeleteElementIdsInBatches(doc, unusedSymbolIds, "清理未使用族类型"); + + var emptyFamilyIds = new FilteredElementCollector(doc) + .OfClass(typeof(Family)) + .Cast() + .Where(family => family != null && !family.GetFamilySymbolIds().Any()) + .Select(family => family.Id) + .ToList(); + + var removedFamilyCount = DeleteElementIdsInBatches(doc, emptyFamilyIds, "清理空族定义"); + return removedSymbolCount + removedFamilyCount; + } + + private int DeleteElementIdsInBatches(Document doc, List elementIds, string transactionName) + { + if (doc == null || elementIds == null || elementIds.Count == 0) + return 0; + + const int batchSize = 100; + int totalRemoved = 0; + + for (int i = 0; i < elementIds.Count; i += batchSize) + { + var batchIds = elementIds.Skip(i).Take(batchSize).ToList(); + if (batchIds.Count == 0) + continue; + + using (var transaction = new Transaction(doc, transactionName)) + { + transaction.Start(); + ApplyWarningSuppression(transaction); + + try + { + var deletedIds = doc.Delete(batchIds); + transaction.Commit(); + totalRemoved += deletedIds.Count; + } + catch + { + transaction.RollBack(); + } + } + } + + return totalRemoved; + } + + private void ApplyWarningSuppression(Transaction transaction) + { + if (transaction == null) + return; + + var options = transaction.GetFailureHandlingOptions(); + options.SetFailuresPreprocessor(new WarningFailurePreprocessor()); + options.SetClearAfterRollback(true); + transaction.SetFailureHandlingOptions(options); + } + + private class WarningFailurePreprocessor : IFailuresPreprocessor + { + public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) + { + if (failuresAccessor == null) + return FailureProcessingResult.Continue; + + var failures = failuresAccessor.GetFailureMessages(); + if (failures == null || failures.Count == 0) + return FailureProcessingResult.Continue; + + foreach (var failure in failures) + { + if (failure != null && failure.GetSeverity() == FailureSeverity.Warning) + { + failuresAccessor.DeleteWarning(failure); + } + } + + return FailureProcessingResult.Continue; + } + } + /// /// 获取文档文件大小 /// diff --git a/app.config b/app.config index 1729791..1ec619e 100644 --- a/app.config +++ b/app.config @@ -1,8 +1,8 @@ - - + +