diff --git a/Controllers/ShellController.cs b/Controllers/ShellController.cs
index a18c0a2..6b17506 100644
--- a/Controllers/ShellController.cs
+++ b/Controllers/ShellController.cs
@@ -289,6 +289,14 @@ namespace RevitHttpControl.Controllers
Description = "删除所有内部元素,减少约80%文件大小",
EstimatedReduction = "80%",
DeletedElements = new[] { "家具", "设备", "天花板", "配景", "植物", "灯具", "内部装饰" }
+ },
+ new
+ {
+ Mode = ShellOptimizeMode.EnvelopeOnly,
+ Name = "仅保留围护+场地",
+ Description = "严格只保留外壳(外墙/屋顶/外门窗等)与场地地形,删除所有室内与链接模型",
+ EstimatedReduction = "-",
+ DeletedElements = new[] { "所有室内构件", "结构构件", "链接模型" }
}
};
diff --git a/Models/ShellModels.cs b/Models/ShellModels.cs
index 5e520f9..9865d11 100644
--- a/Models/ShellModels.cs
+++ b/Models/ShellModels.cs
@@ -171,7 +171,12 @@ namespace RevitHttpControl.Models
///
/// 激进模式 - 删除所有内部元素(减少约80%)
///
- Aggressive
+ Aggressive,
+
+ ///
+ /// 仅保留围护+场地 - 严格只保留外壳(外墙/屋顶/外门窗等)与场地地形,删除所有室内与链接模型
+ ///
+ EnvelopeOnly
}
///
diff --git a/Readme.md b/Readme.md
index d6fa0fa..7c14f37 100644
--- a/Readme.md
+++ b/Readme.md
@@ -502,4 +502,7 @@ GET http://localhost:9000/api/shell/modes
- **智能分析**:25种构件类别支持,外墙外门窗智能识别
- **完整统计**:文件大小对比、删除统计、处理时间记录
-项目现已集成完整的薄壳优化功能,代码质量达到生产级标准。
\ No newline at end of file
+### 变更
+
+ • POST /api/shell/execute 或 POST /api/shell/execute/sync
+ • body 里把 mode 设为 EnvelopeOnly
\ No newline at end of file
diff --git a/Services/ShellAnalyzer.cs b/Services/ShellAnalyzer.cs
index e2fdc04..81553ac 100644
--- a/Services/ShellAnalyzer.cs
+++ b/Services/ShellAnalyzer.cs
@@ -12,6 +12,14 @@ namespace RevitHttpControl.Services
///
public class ShellAnalyzer
{
+ public ElementAction GetElementAction(Element element, ShellOptimizeMode mode)
+ {
+ if (element?.Category == null)
+ return ElementAction.Keep;
+
+ return DetermineElementAction(element, mode);
+ }
+
///
/// 分析模型构件,确定保留和删除的元素
///
@@ -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;
+ }
+
///
/// 判断是否为结构元素(始终保留)
///
@@ -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:
diff --git a/Services/ShellOptimizer.cs b/Services/ShellOptimizer.cs
index 56db91f..ff68f52 100644
--- a/Services/ShellOptimizer.cs
+++ b/Services/ShellOptimizer.cs
@@ -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
///
/// 根据分析结果获取需要删除的构件
///
- private List GetElementsToDelete(Document doc, ShellAnalyzeResponse analysis)
+ private List GetElementsToDelete(Document doc, ShellOptimizeMode mode)
{
var elementsToDelete = new List();
- // 重新分析获取详细的构件列表(为了安全性)
- 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;
}
- ///
- /// 判断构件是否应该删除
- ///
- 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;
- }
-
///
/// 批量删除构件
///
- private int DeleteElements(Document doc, List elementIds)
+ private int DeleteElements(Document doc, List 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
///
/// 删除一批构件
///
- private int DeleteElementBatch(Document doc, List elementIds)
+ private int DeleteElementBatch(Document doc, List 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
///
/// 检查构件是否可以安全删除
///
- 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;