feat: add EnvelopeOnly mode for shell optimization

This commit is contained in:
sladro 2026-01-29 11:21:58 +08:00
parent 80c5e73ab1
commit 52189448dd
5 changed files with 138 additions and 36 deletions

View File

@ -289,6 +289,14 @@ namespace RevitHttpControl.Controllers
Description = "删除所有内部元素减少约80%文件大小",
EstimatedReduction = "80%",
DeletedElements = new[] { "家具", "设备", "天花板", "配景", "植物", "灯具", "内部装饰" }
},
new
{
Mode = ShellOptimizeMode.EnvelopeOnly,
Name = "仅保留围护+场地",
Description = "严格只保留外壳(外墙/屋顶/外门窗等)与场地地形,删除所有室内与链接模型",
EstimatedReduction = "-",
DeletedElements = new[] { "所有室内构件", "结构构件", "链接模型" }
}
};

View File

@ -171,7 +171,12 @@ namespace RevitHttpControl.Models
/// <summary>
/// 激进模式 - 删除所有内部元素减少约80%
/// </summary>
Aggressive
Aggressive,
/// <summary>
/// 仅保留围护+场地 - 严格只保留外壳(外墙/屋顶/外门窗等)与场地地形,删除所有室内与链接模型
/// </summary>
EnvelopeOnly
}
/// <summary>

View File

@ -502,4 +502,7 @@ GET http://localhost:9000/api/shell/modes
- **智能分析**25种构件类别支持外墙外门窗智能识别
- **完整统计**:文件大小对比、删除统计、处理时间记录
项目现已集成完整的薄壳优化功能,代码质量达到生产级标准。
### 变更
• POST /api/shell/execute 或 POST /api/shell/execute/sync
• body 里把 mode 设为 EnvelopeOnly

View File

@ -12,6 +12,14 @@ namespace RevitHttpControl.Services
/// </summary>
public class ShellAnalyzer
{
public ElementAction GetElementAction(Element element, ShellOptimizeMode mode)
{
if (element?.Category == null)
return ElementAction.Keep;
return DetermineElementAction(element, mode);
}
/// <summary>
/// 分析模型构件,确定保留和删除的元素
/// </summary>
@ -134,6 +142,17 @@ namespace RevitHttpControl.Services
{
var category = element.Category.Id.IntegerValue;
if (mode == ShellOptimizeMode.EnvelopeOnly)
{
if (IsSiteElement(category))
return ElementAction.Keep;
if (element is RevitLinkInstance)
return ElementAction.Remove;
return IsEnvelopeElement(element) ? ElementAction.Keep : ElementAction.Remove;
}
// 1. 始终保留的结构元素
if (IsStructuralElement(category))
return ElementAction.Keep;
@ -156,6 +175,82 @@ namespace RevitHttpControl.Services
}
}
private bool IsSiteElement(int categoryId)
{
var siteCategories = new[]
{
(int)BuiltInCategory.OST_Topography,
(int)BuiltInCategory.OST_Site,
(int)BuiltInCategory.OST_Parking,
(int)BuiltInCategory.OST_Planting,
};
return siteCategories.Contains(categoryId);
}
private bool IsEnvelopeElement(Element element)
{
if (element?.Category == null)
return false;
var categoryId = element.Category.Id.IntegerValue;
// Curtain系统构件
if (categoryId == (int)BuiltInCategory.OST_CurtainWallPanels ||
categoryId == (int)BuiltInCategory.OST_CurtainWallMullions)
return true;
// 外墙
if (element is Wall wall)
return IsExteriorWall(wall);
// 屋顶
if (element is RoofBase)
return true;
// 外门窗Host 为外墙
if (element is FamilyInstance fi)
{
if (categoryId == (int)BuiltInCategory.OST_Doors || categoryId == (int)BuiltInCategory.OST_Windows)
{
try
{
return fi.Host is Wall hostWall && IsExteriorWall(hostWall);
}
catch
{
return false;
}
}
}
return false;
}
private bool IsExteriorWall(Wall wall)
{
try
{
var wallType = wall.WallType;
if (wallType != null)
{
if (wallType.Function == WallFunction.Exterior)
return true;
// 少量模型不设置Function时的兜底幕墙通常属于围护
var typeName = wallType.Name?.ToLower() ?? "";
if (typeName.Contains("curtain") || typeName.Contains("幕墙"))
return true;
}
}
catch
{
return false;
}
return false;
}
/// <summary>
/// 判断是否为结构元素(始终保留)
/// </summary>
@ -282,6 +377,13 @@ namespace RevitHttpControl.Services
if (action == ElementAction.Keep)
{
if (mode == ShellOptimizeMode.EnvelopeOnly)
{
if (IsSiteElement(category))
return "场地/地形构件,必须保留";
return "围护构件,必须保留";
}
if (IsStructuralElement(category))
return "结构元素,必须保留";
if (IsExteriorElement(element))
@ -290,6 +392,13 @@ namespace RevitHttpControl.Services
}
else
{
if (mode == ShellOptimizeMode.EnvelopeOnly)
{
if (element is RevitLinkInstance)
return "链接模型,必须删除";
return "室内/非围护构件,删除";
}
switch (mode)
{
case ShellOptimizeMode.Conservative:

View File

@ -39,7 +39,7 @@ namespace RevitHttpControl.Services
// 2. 分析模型获取删除列表
var analysis = _analyzer.AnalyzeModel(doc, mode);
var elementsToDelete = GetElementsToDelete(doc, analysis);
var elementsToDelete = GetElementsToDelete(doc, mode);
// 3. 创建备份(如果需要)
if (backupOriginal)
@ -48,7 +48,7 @@ namespace RevitHttpControl.Services
}
// 4. 执行删除操作
var deletedCount = DeleteElements(doc, elementsToDelete);
var deletedCount = DeleteElements(doc, elementsToDelete, mode);
result.RemovedCount = deletedCount;
// 5. 保存文档并计算优化后大小
@ -85,14 +85,10 @@ namespace RevitHttpControl.Services
/// <summary>
/// 根据分析结果获取需要删除的构件
/// </summary>
private List<ElementId> GetElementsToDelete(Document doc, ShellAnalyzeResponse analysis)
private List<ElementId> GetElementsToDelete(Document doc, ShellOptimizeMode mode)
{
var elementsToDelete = new List<ElementId>();
// 重新分析获取详细的构件列表(为了安全性)
var analyzer = new ShellAnalyzer();
var detailedAnalysis = analyzer.AnalyzeModel(doc, ShellOptimizeMode.Standard);
// 获取所有需要删除的构件
var allElements = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
@ -101,7 +97,7 @@ namespace RevitHttpControl.Services
foreach (var element in allElements)
{
if (ShouldDeleteElement(element, analysis))
if (_analyzer.GetElementAction(element, mode) == ElementAction.Remove)
{
elementsToDelete.Add(element.Id);
}
@ -110,30 +106,10 @@ namespace RevitHttpControl.Services
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)
private int DeleteElements(Document doc, List<ElementId> elementIds, ShellOptimizeMode mode)
{
if (elementIds == null || elementIds.Count == 0)
return 0;
@ -145,7 +121,7 @@ namespace RevitHttpControl.Services
for (int i = 0; i < elementIds.Count; i += batchSize)
{
var batch = elementIds.Skip(i).Take(batchSize).ToList();
deletedCount += DeleteElementBatch(doc, batch);
deletedCount += DeleteElementBatch(doc, batch, mode);
}
return deletedCount;
@ -154,7 +130,7 @@ namespace RevitHttpControl.Services
/// <summary>
/// 删除一批构件
/// </summary>
private int DeleteElementBatch(Document doc, List<ElementId> elementIds)
private int DeleteElementBatch(Document doc, List<ElementId> elementIds, ShellOptimizeMode mode)
{
using (var transaction = new Transaction(doc, "薄壳优化删除构件"))
{
@ -168,7 +144,7 @@ namespace RevitHttpControl.Services
foreach (var id in elementIds)
{
var element = doc.GetElement(id);
if (element != null && CanDeleteElement(element))
if (element != null && CanDeleteElement(element, mode))
{
deletableIds.Add(id);
}
@ -201,7 +177,7 @@ namespace RevitHttpControl.Services
/// <summary>
/// 检查构件是否可以安全删除
/// </summary>
private bool CanDeleteElement(Element element)
private bool CanDeleteElement(Element element, ShellOptimizeMode mode)
{
try
{
@ -221,7 +197,8 @@ namespace RevitHttpControl.Services
}
// 检查是否有依赖关系(如主体构件)
if (HasCriticalDependencies(element))
// EnvelopeOnly: 允许删除“宿主”类元素如内墙由Revit级联删除其托管构件
if (mode != ShellOptimizeMode.EnvelopeOnly && HasCriticalDependencies(element))
return false;
return true;