361 lines
12 KiB
C#
361 lines
12 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 薄壳优化执行服务
|
|
/// </summary>
|
|
public class ShellOptimizer
|
|
{
|
|
private readonly ShellAnalyzer _analyzer;
|
|
|
|
public ShellOptimizer()
|
|
{
|
|
_analyzer = new ShellAnalyzer();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行薄壳优化
|
|
/// </summary>
|
|
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. 分析模型获取删除列表
|
|
var analysis = _analyzer.AnalyzeModel(doc, mode);
|
|
var elementsToDelete = GetElementsToDelete(doc, analysis);
|
|
|
|
// 3. 创建备份(如果需要)
|
|
if (backupOriginal)
|
|
{
|
|
result.BackupPath = CreateBackup(doc);
|
|
}
|
|
|
|
// 4. 执行删除操作
|
|
var deletedCount = DeleteElements(doc, elementsToDelete);
|
|
result.RemovedCount = deletedCount;
|
|
|
|
// 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 = stopwatch.Elapsed.TotalSeconds;
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
stopwatch.Stop();
|
|
result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds;
|
|
|
|
// 如果有备份且操作失败,可以考虑恢复备份
|
|
throw new InvalidOperationException($"薄壳优化执行失败: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据分析结果获取需要删除的构件
|
|
/// </summary>
|
|
private List<ElementId> GetElementsToDelete(Document doc, ShellAnalyzeResponse analysis)
|
|
{
|
|
var elementsToDelete = new List<ElementId>();
|
|
|
|
// 重新分析获取详细的构件列表(为了安全性)
|
|
var analyzer = new ShellAnalyzer();
|
|
var detailedAnalysis = analyzer.AnalyzeModel(doc, ShellOptimizeMode.Standard);
|
|
|
|
// 获取所有需要删除的构件
|
|
var allElements = new FilteredElementCollector(doc)
|
|
.WhereElementIsNotElementType()
|
|
.WhereElementIsViewIndependent()
|
|
.ToElements();
|
|
|
|
foreach (var element in allElements)
|
|
{
|
|
if (ShouldDeleteElement(element, analysis))
|
|
{
|
|
elementsToDelete.Add(element.Id);
|
|
}
|
|
}
|
|
|
|
return elementsToDelete;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 判断构件是否应该删除
|
|
/// </summary>
|
|
private bool ShouldDeleteElement(Element element, ShellAnalyzeResponse analysis)
|
|
{
|
|
if (element.Category == null)
|
|
return false;
|
|
|
|
var categoryName = element.Category.Name;
|
|
var categoryStats = analysis.Categories.FirstOrDefault(c => c.Name == categoryName);
|
|
|
|
// 如果该类别没有删除项,则不删除
|
|
if (categoryStats == null || categoryStats.Remove == 0)
|
|
return false;
|
|
|
|
// 这里可以实现更复杂的逻辑,比如优先删除某些构件
|
|
// 目前简化为按类别统计比例删除
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量删除构件
|
|
/// </summary>
|
|
private int DeleteElements(Document doc, List<ElementId> elementIds)
|
|
{
|
|
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);
|
|
}
|
|
|
|
return deletedCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除一批构件
|
|
/// </summary>
|
|
private int DeleteElementBatch(Document doc, List<ElementId> elementIds)
|
|
{
|
|
using (var transaction = new Transaction(doc, "薄壳优化删除构件"))
|
|
{
|
|
transaction.Start();
|
|
|
|
try
|
|
{
|
|
// 过滤出可以删除的构件
|
|
var deletableIds = new List<ElementId>();
|
|
|
|
foreach (var id in elementIds)
|
|
{
|
|
var element = doc.GetElement(id);
|
|
if (element != null && CanDeleteElement(element))
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查构件是否可以安全删除
|
|
/// </summary>
|
|
private bool CanDeleteElement(Element element)
|
|
{
|
|
try
|
|
{
|
|
// 检查构件是否被锁定
|
|
if (element.Pinned)
|
|
return false;
|
|
|
|
// 检查构件是否在工作集中被其他用户编辑
|
|
var worksetId = element.WorksetId;
|
|
if (worksetId != WorksetId.InvalidWorksetId)
|
|
{
|
|
var doc = element.Document;
|
|
var worksetTable = doc.GetWorksetTable();
|
|
var workset = worksetTable.GetWorkset(worksetId);
|
|
if (workset.IsOpen && workset.Owner != doc.Application.Username)
|
|
return false;
|
|
}
|
|
|
|
// 检查是否有依赖关系(如主体构件)
|
|
if (HasCriticalDependencies(element))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
// 如果检查过程出错,为安全起见不删除
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查构件是否有关键依赖关系
|
|
/// </summary>
|
|
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; // 无法确定时,为安全起见认为有依赖
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建文件备份
|
|
/// </summary>
|
|
private string CreateBackup(Document doc)
|
|
{
|
|
try
|
|
{
|
|
var originalPath = doc.PathName;
|
|
if (string.IsNullOrEmpty(originalPath))
|
|
{
|
|
// 如果文档未保存,先保存
|
|
throw new InvalidOperationException("文档必须先保存才能创建备份");
|
|
}
|
|
|
|
var directory = Path.GetDirectoryName(originalPath);
|
|
var fileName = Path.GetFileNameWithoutExtension(originalPath);
|
|
var extension = Path.GetExtension(originalPath);
|
|
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
|
|
|
var backupFileName = $"{fileName}_backup_{timestamp}{extension}";
|
|
var backupPath = Path.Combine(directory, backupFileName);
|
|
|
|
// 复制文件
|
|
File.Copy(originalPath, backupPath, false);
|
|
|
|
return backupPath;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException($"创建备份失败: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存文档
|
|
/// </summary>
|
|
private void SaveDocument(Document doc)
|
|
{
|
|
try
|
|
{
|
|
if (doc.IsModified)
|
|
{
|
|
doc.Save();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException($"保存文档失败: {ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取文档文件大小
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 格式化文件大小显示
|
|
/// </summary>
|
|
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]}";
|
|
}
|
|
}
|
|
}
|