using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using Autodesk.Revit.DB; using RevitHttpControl.Models; namespace RevitHttpControl.Services { /// /// 薄壳优化执行服务 /// public class ShellOptimizer { private readonly ShellAnalyzer _analyzer; public ShellOptimizer() { _analyzer = new ShellAnalyzer(); } /// /// 执行薄壳优化 /// public ShellOptimizeResult ExecuteOptimization(Document doc, ShellOptimizeMode mode, bool backupOriginal = true) { if (doc == null) throw new ArgumentNullException(nameof(doc)); var stopwatch = Stopwatch.StartNew(); var result = new ShellOptimizeResult(); try { // 1. 获取原始文件大小 var originalSize = GetDocumentFileSize(doc); result.OriginalSize = FormatFileSize(originalSize); // 2. 分析模型获取删除列表 _analyzer.AnalyzeModel(doc, mode); var elementsToDelete = GetElementsToDelete(doc, mode); // 3. 创建备份(如果需要) if (backupOriginal) { result.BackupPath = CreateBackup(doc); } // 4. 执行删除操作 var deletedCount = DeleteElements(doc, elementsToDelete, mode); var purgedCount = PurgeUnusedFamilyData(doc); result.RemovedCount = deletedCount + purgedCount; // 5. 保存文档并计算优化后大小 SaveDocument(doc); var optimizedSize = GetDocumentFileSize(doc); result.OptimizedSize = FormatFileSize(optimizedSize); // 6. 计算减少百分比 if (originalSize > 0) { var reduction = ((double)(originalSize - optimizedSize) / originalSize) * 100; result.Reduction = $"{reduction:F1}%"; } else { result.Reduction = "0%"; } stopwatch.Stop(); result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); return result; } catch (Exception ex) { stopwatch.Stop(); result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); // 如果有备份且操作失败,可以考虑恢复备份 throw new InvalidOperationException($"薄壳优化执行失败: {ex.Message}", ex); } } /// /// 按类别执行自定义删除 /// public ShellOptimizeResult ExecuteOptimizationByCategories(Document doc, IEnumerable categoryIds, bool backupOriginal = true) { if (doc == null) throw new ArgumentNullException(nameof(doc)); if (categoryIds == null) throw new ArgumentNullException(nameof(categoryIds)); var categorySet = new HashSet(categoryIds); if (categorySet.Count == 0) throw new ArgumentException("至少需要指定一个要删除的类别", nameof(categoryIds)); var stopwatch = Stopwatch.StartNew(); var result = new ShellOptimizeResult(); try { var originalSize = GetDocumentFileSize(doc); result.OriginalSize = FormatFileSize(originalSize); var elementsToDelete = GetElementsToDeleteByCategories(doc, categorySet); if (backupOriginal) { result.BackupPath = CreateBackup(doc); } // Reuse Standard safety checks for custom category deletion. var deletedCount = DeleteElements(doc, elementsToDelete, ShellOptimizeMode.Standard); var purgedCount = PurgeUnusedFamilyData(doc); result.RemovedCount = deletedCount + purgedCount; SaveDocument(doc); var optimizedSize = GetDocumentFileSize(doc); result.OptimizedSize = FormatFileSize(optimizedSize); if (originalSize > 0) { var reduction = ((double)(originalSize - optimizedSize) / originalSize) * 100; result.Reduction = $"{reduction:F1}%"; } else { result.Reduction = "0%"; } stopwatch.Stop(); result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); return result; } catch (Exception ex) { stopwatch.Stop(); result.ProcessingTimeSeconds = (int)Math.Round(stopwatch.Elapsed.TotalSeconds, MidpointRounding.AwayFromZero); throw new InvalidOperationException($"自定义类别删除执行失败: {ex.Message}", ex); } } /// /// 根据分析结果获取需要删除的构件 /// private List GetElementsToDelete(Document doc, ShellOptimizeMode mode) { var elementsToDelete = new List(); // 获取所有需要删除的构件 var allElements = new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent() .ToElements(); foreach (var element in allElements) { if (_analyzer.GetElementAction(element, mode) == ElementAction.Remove) { elementsToDelete.Add(element.Id); } } return elementsToDelete; } /// /// 按类别获取待删除构件 /// private List GetElementsToDeleteByCategories(Document doc, HashSet categoryIds) { var elementsToDelete = new List(); var allElements = new FilteredElementCollector(doc) .WhereElementIsNotElementType() .WhereElementIsViewIndependent() .ToElements(); foreach (var element in allElements) { var categoryId = element?.Category?.Id?.IntegerValue; if (categoryId.HasValue && categoryIds.Contains(categoryId.Value)) { elementsToDelete.Add(element.Id); } } return elementsToDelete; } /// /// 批量删除构件 /// private int DeleteElements(Document doc, List elementIds, ShellOptimizeMode mode) { if (elementIds == null || elementIds.Count == 0) return 0; int deletedCount = 0; const int batchSize = 100; // 批量处理大小 // 分批删除以提高性能和稳定性 for (int i = 0; i < elementIds.Count; i += batchSize) { var batch = elementIds.Skip(i).Take(batchSize).ToList(); deletedCount += DeleteElementBatch(doc, batch, mode); } return deletedCount; } /// /// 删除一批构件 /// private int DeleteElementBatch(Document doc, List elementIds, ShellOptimizeMode mode) { using (var transaction = new Transaction(doc, "薄壳优化删除构件")) { transaction.Start(); ApplyWarningSuppression(transaction); try { // 过滤出可以删除的构件 var deletableIds = new List(); foreach (var id in elementIds) { var element = doc.GetElement(id); if (element != null && CanDeleteElement(element, mode)) { deletableIds.Add(id); } } // 执行删除 if (deletableIds.Count > 0) { var deletedIds = doc.Delete(deletableIds); transaction.Commit(); return deletedIds.Count; } else { transaction.RollBack(); return 0; } } catch (Exception ex) { transaction.RollBack(); // 记录错误但继续处理其他构件 System.Diagnostics.Debug.WriteLine($"删除构件批次失败: {ex.Message}"); return 0; } } } /// /// 检查构件是否可以安全删除 /// private bool CanDeleteElement(Element element, ShellOptimizeMode mode) { try { // 检查构件是否被锁定 if (element.Pinned) return false; // 检查构件是否在工作集中被其他用户编辑 var worksetId = element.WorksetId; if (worksetId != WorksetId.InvalidWorksetId) { var doc = element.Document; // 仅对工作共享文档执行“他人占用”校验,避免本地/样例文件误判。 if (doc.IsWorkshared) { var worksetTable = doc.GetWorksetTable(); var workset = worksetTable.GetWorkset(worksetId); var owner = workset?.Owner; var currentUser = doc.Application?.Username; if (workset != null && workset.IsOpen && !string.IsNullOrWhiteSpace(owner) && !string.Equals(owner, currentUser, StringComparison.OrdinalIgnoreCase)) { return false; } } } // 检查是否有依赖关系(如主体构件) // EnvelopeOnly: 允许删除“宿主”类元素(如内墙),由Revit级联删除其托管构件 if (mode != ShellOptimizeMode.EnvelopeOnly && HasCriticalDependencies(element)) return false; return true; } catch { // 如果检查过程出错,为安全起见不删除 return false; } } /// /// 检查构件是否有关键依赖关系 /// private bool HasCriticalDependencies(Element element) { try { // 对于墙体,检查是否有门窗等构件托管在上面 if (element is Wall wall) { var doc = wall.Document; var collector = new FilteredElementCollector(doc); var familyInstances = collector.OfClass(typeof(FamilyInstance)).ToElements(); // 检查是否有门窗托管在这面墙上 foreach (Element e in familyInstances) { var familyInstance = e as FamilyInstance; if (familyInstance?.Host?.Id == wall.Id) { return true; // 有构件托管在这面墙上,不能删除 } } } return false; } catch { return true; // 无法确定时,为安全起见认为有依赖 } } /// /// 创建文件备份 /// private string CreateBackup(Document doc) { try { var originalPath = doc.PathName; if (string.IsNullOrEmpty(originalPath)) { // 如果文档未保存,先保存 throw new InvalidOperationException("文档必须先保存才能创建备份"); } var fileName = Path.GetFileNameWithoutExtension(originalPath); var extension = Path.GetExtension(originalPath); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var backupFileName = $"{fileName}_backup_{timestamp}{extension}"; // 先尝试写入原目录;若无权限则回退到用户本地目录。 var sourceDirectory = Path.GetDirectoryName(originalPath); if (!string.IsNullOrWhiteSpace(sourceDirectory)) { var backupPathInSource = Path.Combine(sourceDirectory, backupFileName); try { File.Copy(originalPath, backupPathInSource, false); return backupPathInSource; } catch (UnauthorizedAccessException) { // Ignore and fallback. } catch (IOException ex) when (File.Exists(backupPathInSource)) { throw new InvalidOperationException($"备份文件已存在: {backupPathInSource}", ex); } } var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var fallbackDirectory = Path.Combine(localAppData, "RevitHttpControl", "Backups"); Directory.CreateDirectory(fallbackDirectory); var backupPathInFallback = Path.Combine(fallbackDirectory, backupFileName); File.Copy(originalPath, backupPathInFallback, false); return backupPathInFallback; } catch (Exception ex) { throw new InvalidOperationException($"创建备份失败: {ex.Message}", ex); } } /// /// 保存文档 /// private void SaveDocument(Document doc) { try { if (!doc.IsModified) { return; } // Prefer compact save for normal local documents to reclaim free space. if (TryCompactSaveInPlace(doc)) { return; } try { doc.Save(); return; } catch (Exception ex) when (IsReadOnlySaveError(ex)) { SaveDocumentAsWritableCopy(doc); return; } } catch (Exception ex) { throw new InvalidOperationException($"保存文档失败: {ex.Message}", ex); } } 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; var message = ex.Message ?? string.Empty; return message.IndexOf("read-only", StringComparison.OrdinalIgnoreCase) >= 0 || message.IndexOf("只读", StringComparison.OrdinalIgnoreCase) >= 0; } private void SaveDocumentAsWritableCopy(Document doc) { var originalPath = doc.PathName; if (string.IsNullOrWhiteSpace(originalPath)) throw new InvalidOperationException("文档为只读且无法获取原始路径,不能自动另存为"); var fileName = Path.GetFileNameWithoutExtension(originalPath); var extension = Path.GetExtension(originalPath); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var outputDirectory = Path.Combine(localAppData, "RevitHttpControl", "Optimized"); Directory.CreateDirectory(outputDirectory); var outputPath = Path.Combine(outputDirectory, $"{fileName}_optimized_{timestamp}{extension}"); var saveAsOptions = new SaveAsOptions { OverwriteExistingFile = false, Compact = true }; 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; } } /// /// 获取文档文件大小 /// private long GetDocumentFileSize(Document doc) { try { var filePath = doc.PathName; if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) return 0; var fileInfo = new FileInfo(filePath); return fileInfo.Length; } catch { return 0; } } /// /// 格式化文件大小显示 /// private string FormatFileSize(long bytes) { if (bytes == 0) return "0 字节"; string[] sizes = { "字节", "KB", "MB", "GB", "TB" }; int order = 0; double size = bytes; while (size >= 1024 && order < sizes.Length - 1) { order++; size /= 1024; } return $"{size:F2} {sizes[order]}"; } } }