feat: implement file closing and custom category deletion functionality

This commit is contained in:
sladro 2026-03-01 08:04:54 +08:00
parent 58d91786ea
commit 660c3bcd6f
8 changed files with 483 additions and 23 deletions

View File

@ -188,5 +188,66 @@ namespace RevitHttpControl.Controllers
return Request.CreateResponse(HttpStatusCode.InternalServerError, errorResponse);
}
}
/// <summary>
/// 关闭当前打开文件
/// </summary>
/// <returns>关闭文件响应</returns>
[HttpPost]
[Route("close")]
public HttpResponseMessage CloseFile()
{
try
{
var result = DocumentService.CloseCurrentDocument();
var successResponse = new ApiResponse<CloseFileResponse>
{
Success = true,
Code = 200,
Message = "文件关闭成功",
Data = result
};
return Request.CreateResponse(HttpStatusCode.OK, successResponse);
}
catch (InvalidOperationException ex)
{
var conflictResponse = new ApiResponse<object>
{
Success = false,
Code = 409,
Message = ex.Message,
Data = new { error = "NO_DOCUMENT_OPEN" }
};
return Request.CreateResponse(HttpStatusCode.Conflict, conflictResponse);
}
catch (TimeoutException ex)
{
var timeoutResponse = new ApiResponse<object>
{
Success = false,
Code = 408,
Message = ex.Message,
Data = new { error = "OPERATION_TIMEOUT" }
};
return Request.CreateResponse(HttpStatusCode.RequestTimeout, timeoutResponse);
}
catch (Exception ex)
{
var errorResponse = new ApiResponse<object>
{
Success = false,
Code = 500,
Message = "关闭文件时发生错误",
Data = new
{
error = "INTERNAL_ERROR",
errorDescription = ex.Message
}
};
return Request.CreateResponse(HttpStatusCode.InternalServerError, errorResponse);
}
}
}
}

View File

@ -254,6 +254,89 @@ namespace RevitHttpControl.Controllers
}
}
/// <summary>
/// 按类别自定义执行删除(同步)
/// </summary>
/// <param name="request">自定义删除请求</param>
/// <returns>执行结果</returns>
[HttpPost]
[Route("execute/custom")]
public HttpResponseMessage ExecuteShellCustom([FromBody] ShellCustomExecuteRequest request)
{
try
{
if (request == null)
{
return this.CreateErrorResponse(
ErrorCodes.INVALID_REQUEST_PARAMETERS,
"请求参数不能为空",
HttpStatusCode.BadRequest
);
}
if (request.CategoryIds == null || request.CategoryIds.Count == 0)
{
return this.CreateErrorResponse(
ErrorCodes.INVALID_REQUEST_PARAMETERS,
"CategoryIds 不能为空且至少包含一个类别ID",
HttpStatusCode.BadRequest
);
}
var result = RevitService.ExecuteShellOptimizationByCategories(request.CategoryIds, request.BackupOriginal);
return this.CreateSuccessResponse(result, "自定义类别删除执行完成");
}
catch (InvalidOperationException ex)
{
return this.CreateErrorResponse(
ErrorCodes.NO_DOCUMENT_OPEN,
ex.Message,
HttpStatusCode.BadRequest
);
}
catch (ArgumentException ex)
{
return this.CreateErrorResponse(
ErrorCodes.INVALID_REQUEST_PARAMETERS,
ex.Message,
HttpStatusCode.BadRequest
);
}
catch (TimeoutException ex)
{
return this.CreateErrorResponse(
ErrorCodes.OPERATION_TIMEOUT,
ex.Message,
HttpStatusCode.RequestTimeout
);
}
catch (UnauthorizedAccessException ex)
{
return this.CreateErrorResponse(
ErrorCodes.FILE_WRITE_PERMISSION_DENIED,
"文件写入权限不足:" + ex.Message,
HttpStatusCode.Forbidden
);
}
catch (System.IO.IOException ex)
{
return this.CreateErrorResponse(
ErrorCodes.FILE_IO_ERROR,
"文件操作失败:" + ex.Message,
HttpStatusCode.InternalServerError
);
}
catch (Exception ex)
{
return this.CreateErrorResponse(
ErrorCodes.REVIT_API_ERROR,
"自定义类别删除执行失败",
HttpStatusCode.InternalServerError,
ex.Message
);
}
}
/// <summary>
/// 获取薄壳优化预设配置
/// </summary>

View File

@ -38,4 +38,25 @@ namespace RevitHttpControl.Models
/// </summary>
public string FilePath { get; set; }
}
/// <summary>
/// 关闭文件响应
/// </summary>
public class CloseFileResponse
{
/// <summary>
/// 操作结果
/// </summary>
public string Result { get; set; }
/// <summary>
/// 关闭前的文件名
/// </summary>
public string FileName { get; set; }
/// <summary>
/// 关闭前的文件路径
/// </summary>
public string FilePath { get; set; }
}
}

View File

@ -49,6 +49,22 @@ namespace RevitHttpControl.Models
public bool BackupOriginal { get; set; } = true;
}
/// <summary>
/// 按类别自定义删除请求
/// </summary>
public class ShellCustomExecuteRequest
{
/// <summary>
/// 要删除的类别ID列表BuiltInCategory 的 int 值)
/// </summary>
public List<int> CategoryIds { get; set; } = new List<int>();
/// <summary>
/// 是否备份原文件
/// </summary>
public bool BackupOriginal { get; set; } = true;
}
/// <summary>
/// 薄壳执行响应
/// </summary>
@ -84,6 +100,16 @@ namespace RevitHttpControl.Models
/// 预估文件大小减少百分比
/// </summary>
public string EstimatedReduction { get; set; }
/// <summary>
/// 当前文件大小(格式化显示)
/// </summary>
public string OriginalSize { get; set; }
/// <summary>
/// 预估优化后文件大小(格式化显示)
/// </summary>
public string EstimatedOptimizedSize { get; set; }
}
/// <summary>
@ -91,6 +117,11 @@ namespace RevitHttpControl.Models
/// </summary>
public class CategoryStatistics
{
/// <summary>
/// 分类IDBuiltInCategory 的 int 值)
/// </summary>
public int CategoryId { get; set; }
/// <summary>
/// 分类名称
/// </summary>
@ -205,6 +236,11 @@ namespace RevitHttpControl.Models
/// </summary>
public int ElementId { get; set; }
/// <summary>
/// 构件分类IDBuiltInCategory 的 int 值)
/// </summary>
public int CategoryId { get; set; }
/// <summary>
/// 构件分类名称
/// </summary>

View File

@ -84,6 +84,81 @@ namespace RevitHttpControl.Services
}
}
/// <summary>
/// 关闭当前活动文档
/// </summary>
/// <returns>关闭文件响应</returns>
public static CloseFileResponse CloseCurrentDocument()
{
try
{
var completed = false;
Exception capturedException = null;
CloseFileResponse response = null;
App.Instance.EnqueueCommand(uiApp =>
{
try
{
var activeDoc = uiApp.ActiveUIDocument?.Document;
if (activeDoc == null)
{
throw new InvalidOperationException("没有打开的文档");
}
var fileName = string.IsNullOrEmpty(activeDoc.PathName)
? activeDoc.Title
: Path.GetFileName(activeDoc.PathName);
var filePath = string.IsNullOrEmpty(activeDoc.PathName) ? null : activeDoc.PathName;
activeDoc.Close(false);
response = new CloseFileResponse
{
Result = "文件关闭成功",
FileName = fileName,
FilePath = filePath
};
}
catch (Exception ex)
{
capturedException = ex;
}
finally
{
completed = true;
}
});
// 等待命令执行完成最多等待10秒
var timeout = DateTime.Now.AddSeconds(10);
while (!completed && DateTime.Now < timeout)
{
System.Threading.Thread.Sleep(100);
}
if (!completed)
throw new TimeoutException("关闭文件操作超时");
if (capturedException != null)
throw capturedException;
return response;
}
catch (TimeoutException)
{
throw;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception ex)
{
throw new InvalidOperationException($"关闭文件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 异步打开文档文件
/// </summary>

View File

@ -609,6 +609,61 @@ namespace RevitHttpControl.Services
}
}
/// <summary>
/// 获取文档文件大小(字节)
/// </summary>
private static long GetDocumentFileSize(Document doc)
{
try
{
if (doc == null)
return 0;
if (!string.IsNullOrEmpty(doc.PathName) && File.Exists(doc.PathName))
return new FileInfo(doc.PathName).Length;
if (doc.IsWorkshared)
{
var centralPath = doc.GetWorksharingCentralModelPath();
var centralPathString = centralPath?.ToString();
if (!string.IsNullOrEmpty(centralPathString) && File.Exists(centralPathString))
return new FileInfo(centralPathString).Length;
}
}
catch
{
// 忽略文件大小获取异常,保持分析结果可用
}
return 0;
}
/// <summary>
/// 基于真实文件大小和可删除比例填充动态估算信息
/// </summary>
private static void ApplyDynamicSizeEstimate(Document doc, ShellAnalyzeResponse response)
{
if (doc == null || response?.Analysis == null)
return;
var summary = response.Analysis;
var originalSize = GetDocumentFileSize(doc);
if (originalSize <= 0)
return;
var removeRatio = summary.TotalElements > 0
? Math.Max(0.0, Math.Min(1.0, summary.RemoveElements / (double)summary.TotalElements))
: 0.0;
var estimatedOptimizedBytes = (long)Math.Round(originalSize * (1.0 - removeRatio));
if (estimatedOptimizedBytes < 0)
estimatedOptimizedBytes = 0;
summary.OriginalSize = FormatFileSize(originalSize);
summary.EstimatedOptimizedSize = FormatFileSize(estimatedOptimizedBytes);
summary.EstimatedReduction = $"{(removeRatio * 100.0):F1}%";
}
/// <summary>
/// 分析薄壳优化方案
/// </summary>
@ -632,6 +687,7 @@ namespace RevitHttpControl.Services
var analyzer = new ShellAnalyzer();
result = analyzer.AnalyzeModel(doc, mode);
ApplyDynamicSizeEstimate(doc, result);
}
catch (Exception ex)
{
@ -720,6 +776,59 @@ namespace RevitHttpControl.Services
}
}
/// <summary>
/// 按类别执行自定义删除(同步)
/// </summary>
public static ShellOptimizeResult ExecuteShellOptimizationByCategories(IEnumerable<int> categoryIds, bool backupOriginal = true)
{
ShellOptimizeResult result = null;
Exception capturedException = null;
bool completed = false;
try
{
App.Instance.EnqueueCommand(uiApp =>
{
try
{
var doc = uiApp.ActiveUIDocument?.Document;
if (doc == null)
throw new InvalidOperationException("没有打开的Revit文档");
var optimizer = new ShellOptimizer();
result = optimizer.ExecuteOptimizationByCategories(doc, categoryIds, backupOriginal);
}
catch (Exception ex)
{
capturedException = ex;
}
finally
{
completed = true;
}
});
// 等待命令执行完成最多等待5分钟
var timeout = DateTime.Now.AddMinutes(5);
while (!completed && DateTime.Now < timeout)
{
System.Threading.Thread.Sleep(200);
}
if (capturedException != null)
throw capturedException;
if (!completed)
throw new TimeoutException("自定义类别删除执行超时");
return result;
}
catch (Exception ex)
{
throw new InvalidOperationException($"自定义类别删除执行失败: {ex.Message}", ex);
}
}
/// <summary>
/// 执行薄壳优化(异步)
/// </summary>

View File

@ -40,7 +40,7 @@ namespace RevitHttpControl.Services
}
// 统计结果
var summary = CreateAnalysisSummary(analysisResults, mode);
var summary = CreateAnalysisSummary(analysisResults);
var categories = CreateCategoryStatistics(analysisResults);
return new ShellAnalyzeResponse
@ -122,6 +122,7 @@ namespace RevitHttpControl.Services
/// </summary>
private ElementAnalysisResult AnalyzeElement(Element element, ShellOptimizeMode mode)
{
var categoryId = element.Category?.Id?.IntegerValue ?? 0;
var categoryName = element.Category?.Name ?? "未知类别";
var action = DetermineElementAction(element, mode);
var reason = GetActionReason(element, action, mode);
@ -129,6 +130,7 @@ namespace RevitHttpControl.Services
return new ElementAnalysisResult
{
ElementId = element.Id.IntegerValue,
CategoryId = categoryId,
CategoryName = categoryName,
Action = action,
Reason = reason
@ -416,36 +418,24 @@ namespace RevitHttpControl.Services
/// <summary>
/// 创建分析摘要
/// </summary>
private ShellAnalysisSummary CreateAnalysisSummary(List<ElementAnalysisResult> results, ShellOptimizeMode mode)
private ShellAnalysisSummary CreateAnalysisSummary(List<ElementAnalysisResult> results)
{
var total = results.Count;
var keep = results.Count(r => r.Action == ElementAction.Keep);
var remove = results.Count(r => r.Action == ElementAction.Remove);
// 根据模式估算文件大小减少百分比
string estimatedReduction;
switch (mode)
{
case ShellOptimizeMode.Conservative:
estimatedReduction = "30%";
break;
case ShellOptimizeMode.Standard:
estimatedReduction = "65%";
break;
case ShellOptimizeMode.Aggressive:
estimatedReduction = "80%";
break;
default:
estimatedReduction = "50%";
break;
}
var estimatedReduction = total > 0
? $"{(remove * 100.0 / total):F1}%"
: "0%";
return new ShellAnalysisSummary
{
TotalElements = total,
KeepElements = keep,
RemoveElements = remove,
EstimatedReduction = estimatedReduction
EstimatedReduction = estimatedReduction,
OriginalSize = "未知",
EstimatedOptimizedSize = "未知"
};
}
@ -454,10 +444,10 @@ namespace RevitHttpControl.Services
/// </summary>
private List<CategoryStatistics> CreateCategoryStatistics(List<ElementAnalysisResult> results)
{
var categoryGroups = results.GroupBy(r => r.CategoryName);
var categoryGroups = results.GroupBy(r => new { r.CategoryId, r.CategoryName });
var statistics = new List<CategoryStatistics>();
foreach (var group in categoryGroups.OrderBy(g => g.Key))
foreach (var group in categoryGroups.OrderBy(g => g.Key.CategoryName))
{
var total = group.Count();
var keep = group.Count(r => r.Action == ElementAction.Keep);
@ -465,7 +455,8 @@ namespace RevitHttpControl.Services
statistics.Add(new CategoryStatistics
{
Name = group.Key,
CategoryId = group.Key.CategoryId,
Name = group.Key.CategoryName,
Total = total,
Keep = keep,
Remove = remove

View File

@ -82,6 +82,66 @@ namespace RevitHttpControl.Services
}
}
/// <summary>
/// 按类别执行自定义删除
/// </summary>
public ShellOptimizeResult ExecuteOptimizationByCategories(Document doc, IEnumerable<int> categoryIds, bool backupOriginal = true)
{
if (doc == null)
throw new ArgumentNullException(nameof(doc));
if (categoryIds == null)
throw new ArgumentNullException(nameof(categoryIds));
var categorySet = new HashSet<int>(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);
}
// 自定义删除沿用标准安全检查逻辑(非 EnvelopeOnly
var deletedCount = DeleteElements(doc, elementsToDelete, ShellOptimizeMode.Standard);
result.RemovedCount = deletedCount;
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 = stopwatch.Elapsed.TotalSeconds;
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
result.ProcessingTimeSeconds = stopwatch.Elapsed.TotalSeconds;
throw new InvalidOperationException($"自定义类别删除执行失败: {ex.Message}", ex);
}
}
/// <summary>
/// 根据分析结果获取需要删除的构件
/// </summary>
@ -106,6 +166,30 @@ namespace RevitHttpControl.Services
return elementsToDelete;
}
/// <summary>
/// 按类别获取待删除构件
/// </summary>
private List<ElementId> GetElementsToDeleteByCategories(Document doc, HashSet<int> categoryIds)
{
var elementsToDelete = new List<ElementId>();
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;
}
/// <summary>
/// 批量删除构件
/// </summary>