1011 lines
39 KiB
C#
1011 lines
39 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using Autodesk.Revit.DB;
|
||
using Autodesk.Revit.UI;
|
||
using RevitHttpControl.Models;
|
||
|
||
namespace RevitHttpControl.Services
|
||
{
|
||
/// <summary>
|
||
/// Revit API 操作服务
|
||
/// </summary>
|
||
public static class RevitService
|
||
{
|
||
/// <summary>
|
||
/// 获取当前文档信息
|
||
/// </summary>
|
||
/// <returns>当前文档信息</returns>
|
||
public static CurrentDocumentInfo GetCurrentDocumentInfo()
|
||
{
|
||
var documentInfo = new CurrentDocumentInfo
|
||
{
|
||
IsOpen = false,
|
||
FileName = null,
|
||
FilePath = null
|
||
};
|
||
|
||
try
|
||
{
|
||
// 尝试获取当前活动文档信息
|
||
if (App.Instance != null)
|
||
{
|
||
// 通过队列执行获取文档信息的操作
|
||
var docInfoResult = new CurrentDocumentInfo
|
||
{
|
||
IsOpen = false,
|
||
FileName = null,
|
||
FilePath = null
|
||
};
|
||
var completed = false;
|
||
Exception executionException = null;
|
||
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("RevitService: Getting document info...");
|
||
|
||
var activeDoc = uiApp.ActiveUIDocument?.Document;
|
||
if (activeDoc != null)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Found active document: {activeDoc.Title}");
|
||
|
||
docInfoResult.IsOpen = true;
|
||
|
||
// 处理文档路径
|
||
var pathName = activeDoc.PathName;
|
||
if (!string.IsNullOrEmpty(pathName))
|
||
{
|
||
docInfoResult.FileName = Path.GetFileName(pathName);
|
||
docInfoResult.FilePath = pathName;
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Document path: {pathName}");
|
||
}
|
||
else
|
||
{
|
||
// 对于未保存的文档,使用Title
|
||
docInfoResult.FileName = activeDoc.Title;
|
||
docInfoResult.FilePath = null;
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Unsaved document: {activeDoc.Title}");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("RevitService: No active document found");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Exception getting document info: {ex.Message}");
|
||
executionException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成(最多等待3秒)
|
||
var timeout = DateTime.Now.AddSeconds(3);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(50);
|
||
}
|
||
|
||
if (!completed)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("RevitService: Timeout waiting for document info");
|
||
return documentInfo; // 返回默认值
|
||
}
|
||
|
||
if (executionException != null)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Execution failed: {executionException.Message}");
|
||
return documentInfo; // 返回默认值
|
||
}
|
||
|
||
return docInfoResult;
|
||
}
|
||
else
|
||
{
|
||
System.Diagnostics.Debug.WriteLine("RevitService: App.Instance is null");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"RevitService: Outer exception: {ex.Message}");
|
||
}
|
||
|
||
return documentInfo;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取元素统计数量
|
||
/// </summary>
|
||
/// <param name="type">统计类型</param>
|
||
/// <returns>元素数量</returns>
|
||
public static int GetElementCount(StatsType type)
|
||
{
|
||
try
|
||
{
|
||
var count = 0;
|
||
var completed = false;
|
||
Exception capturedException = null;
|
||
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc != null)
|
||
{
|
||
var collector = new FilteredElementCollector(doc);
|
||
var category = GetBuiltInCategory(type);
|
||
count = collector.OfCategory(category).Count();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
capturedException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成(最多等待5秒)
|
||
var timeout = DateTime.Now.AddSeconds(5);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(50);
|
||
}
|
||
|
||
if (capturedException != null)
|
||
throw capturedException;
|
||
|
||
return count;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"获取 {type} 统计数据失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步获取元素统计数量
|
||
/// </summary>
|
||
/// <param name="type">统计类型</param>
|
||
/// <param name="taskId">任务ID</param>
|
||
/// <param name="taskManager">任务管理器</param>
|
||
public static void GetElementCountAsync(StatsType type, Guid taskId, TaskManager taskManager)
|
||
{
|
||
try
|
||
{
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc != null)
|
||
{
|
||
var collector = new FilteredElementCollector(doc);
|
||
var category = GetBuiltInCategory(type);
|
||
var count = collector.OfCategory(category).Count();
|
||
|
||
// 构建完整的统计响应
|
||
var statsResponse = new SyncStatsResponse
|
||
{
|
||
ElementType = type.ToString(),
|
||
Count = count,
|
||
Details = new StatsDetails
|
||
{
|
||
TypeName = GetStatsTypeDisplayName(type),
|
||
TypeId = (int)category
|
||
}
|
||
};
|
||
|
||
// 将完整结果存储到任务管理器
|
||
taskManager.CompleteTask(taskId, statsResponse);
|
||
}
|
||
else
|
||
{
|
||
taskManager.FailTask(taskId, "没有打开的文档");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskManager.FailTask(taskId, ex.Message);
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskManager.FailTask(taskId, ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统计类型到 BuiltInCategory 的映射
|
||
/// </summary>
|
||
/// <param name="type">统计类型</param>
|
||
/// <returns>对应的 BuiltInCategory</returns>
|
||
public static BuiltInCategory GetBuiltInCategory(StatsType type)
|
||
{
|
||
switch (type)
|
||
{
|
||
case StatsType.Wall:
|
||
return BuiltInCategory.OST_Walls;
|
||
case StatsType.Door:
|
||
return BuiltInCategory.OST_Doors;
|
||
case StatsType.Window:
|
||
return BuiltInCategory.OST_Windows;
|
||
case StatsType.Floor:
|
||
return BuiltInCategory.OST_Floors;
|
||
case StatsType.Ceiling:
|
||
return BuiltInCategory.OST_Ceilings;
|
||
case StatsType.Roof:
|
||
return BuiltInCategory.OST_Roofs;
|
||
case StatsType.Column:
|
||
return BuiltInCategory.OST_Columns;
|
||
case StatsType.Beam:
|
||
return BuiltInCategory.OST_StructuralFraming;
|
||
case StatsType.Furniture:
|
||
return BuiltInCategory.OST_Furniture;
|
||
case StatsType.Room:
|
||
return BuiltInCategory.OST_Rooms;
|
||
default:
|
||
throw new ArgumentException($"不支持的统计类型: {type}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取元素统计详细信息
|
||
/// </summary>
|
||
/// <param name="type">统计类型</param>
|
||
/// <returns>详细统计信息</returns>
|
||
public static SyncStatsResponse GetElementStatsDetail(StatsType type)
|
||
{
|
||
try
|
||
{
|
||
var count = 0;
|
||
var completed = false;
|
||
Exception capturedException = null;
|
||
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc != null)
|
||
{
|
||
var collector = new FilteredElementCollector(doc);
|
||
var category = GetBuiltInCategory(type);
|
||
count = collector.OfCategory(category).Count();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
capturedException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成(最多等待5秒)
|
||
var timeout = DateTime.Now.AddSeconds(5);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(50);
|
||
}
|
||
|
||
if (capturedException != null)
|
||
throw capturedException;
|
||
|
||
// 构建完整的响应
|
||
return new SyncStatsResponse
|
||
{
|
||
ElementType = type.ToString(),
|
||
Count = count,
|
||
Details = new StatsDetails
|
||
{
|
||
TypeName = GetStatsTypeDisplayName(type),
|
||
TypeId = (int)GetBuiltInCategory(type)
|
||
}
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"获取 {type} 统计数据失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取统计类型的中文显示名称
|
||
/// </summary>
|
||
/// <param name="type">统计类型</param>
|
||
/// <returns>中文名称</returns>
|
||
public static string GetStatsTypeDisplayName(StatsType type)
|
||
{
|
||
switch (type)
|
||
{
|
||
case StatsType.Wall:
|
||
return "墙体";
|
||
case StatsType.Door:
|
||
return "门";
|
||
case StatsType.Window:
|
||
return "窗";
|
||
case StatsType.Floor:
|
||
return "楼板";
|
||
case StatsType.Ceiling:
|
||
return "天花板";
|
||
case StatsType.Roof:
|
||
return "屋顶";
|
||
case StatsType.Column:
|
||
return "柱";
|
||
case StatsType.Beam:
|
||
return "梁";
|
||
case StatsType.Furniture:
|
||
return "家具";
|
||
case StatsType.Room:
|
||
return "房间";
|
||
default:
|
||
return type.ToString();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 快速获取模型总览信息
|
||
/// </summary>
|
||
/// <returns>模型总览信息</returns>
|
||
public static ModelOverviewResponse GetModelOverview()
|
||
{
|
||
try
|
||
{
|
||
ModelOverviewResponse overview = null;
|
||
var completed = false;
|
||
Exception capturedException = null;
|
||
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc == null)
|
||
{
|
||
throw new InvalidOperationException("没有打开的文档");
|
||
}
|
||
|
||
// 1. 项目基本信息
|
||
var filePath = doc.IsWorkshared ?
|
||
(doc.GetWorksharingCentralModelPath()?.ToString() ?? "中心文件路径未知") :
|
||
(!string.IsNullOrEmpty(doc.PathName) ? doc.PathName : "未保存");
|
||
|
||
// 获取文件大小
|
||
long fileSizeBytes = 0;
|
||
string fileSizeDisplay = "未知";
|
||
|
||
try
|
||
{
|
||
if (!string.IsNullOrEmpty(doc.PathName) && System.IO.File.Exists(doc.PathName))
|
||
{
|
||
var fileInfo = new System.IO.FileInfo(doc.PathName);
|
||
fileSizeBytes = fileInfo.Length;
|
||
fileSizeDisplay = FormatFileSize(fileSizeBytes);
|
||
}
|
||
else if (doc.IsWorkshared)
|
||
{
|
||
var centralPath = doc.GetWorksharingCentralModelPath();
|
||
if (centralPath != null)
|
||
{
|
||
var centralPathString = centralPath.ToString();
|
||
if (!string.IsNullOrEmpty(centralPathString) && System.IO.File.Exists(centralPathString))
|
||
{
|
||
var fileInfo = new System.IO.FileInfo(centralPathString);
|
||
fileSizeBytes = fileInfo.Length;
|
||
fileSizeDisplay = FormatFileSize(fileSizeBytes);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
fileSizeDisplay = "未保存";
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
fileSizeDisplay = "无法获取";
|
||
}
|
||
|
||
var projectInfo = new Models.ProjectInfo
|
||
{
|
||
Name = string.IsNullOrEmpty(doc.Title) ? "未命名项目" : doc.Title,
|
||
FilePath = filePath,
|
||
RevitVersion = uiApp.Application.VersionName,
|
||
IsCentralFile = doc.IsWorkshared,
|
||
Status = doc.IsModified ? "已修改" : "已保存",
|
||
FileSizeBytes = fileSizeBytes,
|
||
FileSizeDisplay = fileSizeDisplay,
|
||
PolygonCount = CalculatePolygonCountFast(doc, maxElements: 200, maxMilliseconds: 1200),
|
||
FeatureCount = CalculateFeatureCount(doc)
|
||
};
|
||
|
||
// 2. 快速统计主要构件(批量获取避免多次调用)
|
||
var elementCounts = new ElementCounts
|
||
{
|
||
Walls = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).Count(),
|
||
Doors = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).Count(),
|
||
Windows = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Windows).Count(),
|
||
Floors = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Floors).Count(),
|
||
Ceilings = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Ceilings).Count(),
|
||
Columns = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Columns).Count(),
|
||
Beams = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_StructuralFraming).Count(),
|
||
Rooms = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Rooms).Count()
|
||
};
|
||
|
||
// 3. 模型组织结构信息
|
||
var structure = new ModelStructure
|
||
{
|
||
Levels = new FilteredElementCollector(doc).OfClass(typeof(Level)).Count(),
|
||
FloorPlans = new FilteredElementCollector(doc).OfClass(typeof(ViewPlan)).Count(),
|
||
Views3D = new FilteredElementCollector(doc).OfClass(typeof(View3D)).Count(),
|
||
Elevations = new FilteredElementCollector(doc).OfClass(typeof(View))
|
||
.Cast<View>().Count(v => v.ViewType == ViewType.Elevation),
|
||
Sections = new FilteredElementCollector(doc).OfClass(typeof(ViewSection)).Count(),
|
||
Sheets = new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)).Count()
|
||
};
|
||
|
||
// 4. 模型层级信息
|
||
var hierarchy = new ModelHierarchy
|
||
{
|
||
Levels = new List<LevelInfo>(),
|
||
Groups = new FilteredElementCollector(doc).OfClass(typeof(Group)).Count(),
|
||
Worksets = doc.IsWorkshared ? new FilteredWorksetCollector(doc).Count() : 0,
|
||
Phases = new FilteredElementCollector(doc).OfClass(typeof(Phase)).Count(),
|
||
DesignOptions = new FilteredElementCollector(doc).OfClass(typeof(DesignOption)).Count()
|
||
};
|
||
|
||
// 获取楼层详细信息
|
||
var levels = new FilteredElementCollector(doc)
|
||
.OfClass(typeof(Level))
|
||
.Cast<Level>()
|
||
.OrderBy(l => l.Elevation)
|
||
.ToList();
|
||
|
||
foreach (var level in levels)
|
||
{
|
||
hierarchy.Levels.Add(new LevelInfo
|
||
{
|
||
Name = level.Name,
|
||
Elevation = level.Elevation * 304.8 // 转换为毫米
|
||
});
|
||
}
|
||
|
||
// 5. 链接文件统计
|
||
var links = new LinkFiles
|
||
{
|
||
RevitLinks = new LinkCategory(),
|
||
CadLinks = new LinkCategory(),
|
||
PointClouds = 0,
|
||
Images = 0
|
||
};
|
||
|
||
// Revit链接统计
|
||
// 使用OfClass确保只获取RevitLinkInstance类型的元素
|
||
var revitLinkInstances = new FilteredElementCollector(doc)
|
||
.OfClass(typeof(RevitLinkInstance))
|
||
.Cast<RevitLinkInstance>();
|
||
|
||
links.RevitLinks.Total = revitLinkInstances.Count();
|
||
links.RevitLinks.Loaded = revitLinkInstances.Count(link => link.GetLinkDocument() != null);
|
||
links.RevitLinks.Unloaded = links.RevitLinks.Total - links.RevitLinks.Loaded;
|
||
|
||
// CAD链接统计
|
||
var cadLinkTypes = new FilteredElementCollector(doc)
|
||
.OfClass(typeof(CADLinkType))
|
||
.Cast<CADLinkType>();
|
||
|
||
links.CadLinks.Total = cadLinkTypes.Count();
|
||
|
||
// CAD链接状态检查(通过实例检查)
|
||
var cadInstances = new FilteredElementCollector(doc)
|
||
.OfClass(typeof(ImportInstance))
|
||
.Cast<ImportInstance>();
|
||
|
||
var loadedCadCount = 0;
|
||
foreach (var cadType in cadLinkTypes)
|
||
{
|
||
var hasInstance = cadInstances.Any(inst => inst.GetTypeId() == cadType.Id);
|
||
if (hasInstance) loadedCadCount++;
|
||
}
|
||
|
||
links.CadLinks.Loaded = loadedCadCount;
|
||
links.CadLinks.Unloaded = links.CadLinks.Total - links.CadLinks.Loaded;
|
||
|
||
// 点云链接
|
||
links.PointClouds = new FilteredElementCollector(doc)
|
||
.OfCategory(BuiltInCategory.OST_PointClouds).Count();
|
||
|
||
// 图像链接
|
||
links.Images = new FilteredElementCollector(doc)
|
||
.OfClass(typeof(ImageType)).Count();
|
||
|
||
// 生成链接文件总结
|
||
var totalLinks = links.RevitLinks.Total + links.CadLinks.Total + links.PointClouds + links.Images;
|
||
var loadedLinks = links.RevitLinks.Loaded + links.CadLinks.Loaded + links.PointClouds + links.Images;
|
||
links.Summary = totalLinks > 0 ?
|
||
$"{totalLinks}个链接文件,{loadedLinks}个已加载" :
|
||
"无链接文件";
|
||
|
||
// 组装总览响应
|
||
overview = new ModelOverviewResponse
|
||
{
|
||
Project = projectInfo,
|
||
Elements = elementCounts,
|
||
Structure = structure,
|
||
Hierarchy = hierarchy,
|
||
Links = links,
|
||
GeneratedAt = DateTime.Now
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
capturedException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成(最多等待10秒,因为信息较多)
|
||
var timeout = DateTime.Now.AddSeconds(10);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(50);
|
||
}
|
||
|
||
if (capturedException != null)
|
||
throw capturedException;
|
||
|
||
if (!completed)
|
||
throw new TimeoutException("获取模型总览信息超时");
|
||
|
||
return overview;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"获取模型总览失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化文件大小显示
|
||
/// </summary>
|
||
/// <param name="bytes">文件大小(字节)</param>
|
||
/// <returns>格式化的文件大小字符串</returns>
|
||
private static string FormatFileSize(long bytes)
|
||
{
|
||
const long KB = 1024;
|
||
const long MB = KB * 1024;
|
||
const long GB = MB * 1024;
|
||
|
||
if (bytes >= GB)
|
||
{
|
||
return $"{bytes / (double)GB:F1} GB";
|
||
}
|
||
else if (bytes >= MB)
|
||
{
|
||
return $"{bytes / (double)MB:F1} MB";
|
||
}
|
||
else if (bytes >= KB)
|
||
{
|
||
return $"{bytes / (double)KB:F1} KB";
|
||
}
|
||
else
|
||
{
|
||
return $"{bytes} 字节";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算模型面片总数(基于可三角化几何)
|
||
/// </summary>
|
||
/// <param name="doc">Revit文档</param>
|
||
/// <returns>面片总数</returns>
|
||
private static long CalculatePolygonCountFast(Document doc, int maxElements, int maxMilliseconds)
|
||
{
|
||
long totalTriangles = 0;
|
||
var processedElements = 0;
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
try
|
||
{
|
||
var options = new Options
|
||
{
|
||
ComputeReferences = false,
|
||
IncludeNonVisibleObjects = false,
|
||
DetailLevel = ViewDetailLevel.Medium
|
||
};
|
||
|
||
var elements = new FilteredElementCollector(doc)
|
||
.WhereElementIsNotElementType()
|
||
.Where(e => e.Category != null && e.Category.CategoryType == CategoryType.Model);
|
||
|
||
foreach (var element in elements)
|
||
{
|
||
if (processedElements >= maxElements || stopwatch.ElapsedMilliseconds >= maxMilliseconds)
|
||
{
|
||
break;
|
||
}
|
||
|
||
GeometryElement geometry = null;
|
||
try
|
||
{
|
||
geometry = element.get_Geometry(options);
|
||
}
|
||
catch
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (geometry == null)
|
||
continue;
|
||
|
||
totalTriangles += CountTrianglesFromGeometry(geometry);
|
||
processedElements++;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略计算异常并返回已累计值
|
||
}
|
||
|
||
return totalTriangles;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统计模型特征数(按模型类别去重)
|
||
/// </summary>
|
||
/// <param name="doc">Revit文档</param>
|
||
/// <returns>模型特征数</returns>
|
||
private static int CalculateFeatureCount(Document doc)
|
||
{
|
||
try
|
||
{
|
||
return new FilteredElementCollector(doc)
|
||
.WhereElementIsNotElementType()
|
||
.Where(e => e.Category != null && e.Category.CategoryType == CategoryType.Model)
|
||
.Select(e => e.Category.Id.IntegerValue)
|
||
.Distinct()
|
||
.Count();
|
||
}
|
||
catch
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 递归统计几何中的三角面片数量
|
||
/// </summary>
|
||
/// <param name="geometry">几何对象集合</param>
|
||
/// <returns>三角面片数量</returns>
|
||
private static long CountTrianglesFromGeometry(GeometryElement geometry)
|
||
{
|
||
long triangles = 0;
|
||
|
||
foreach (var obj in geometry)
|
||
{
|
||
if (obj is Solid solid && solid.Faces != null && solid.Faces.Size > 0)
|
||
{
|
||
foreach (Face face in solid.Faces)
|
||
{
|
||
try
|
||
{
|
||
var mesh = face.Triangulate();
|
||
if (mesh != null)
|
||
triangles += mesh.NumTriangles;
|
||
}
|
||
catch
|
||
{
|
||
// 忽略单个面三角化失败
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (obj is Mesh meshObj)
|
||
{
|
||
triangles += meshObj.NumTriangles;
|
||
continue;
|
||
}
|
||
|
||
if (obj is GeometryInstance instance)
|
||
{
|
||
try
|
||
{
|
||
var instanceGeometry = instance.GetInstanceGeometry();
|
||
if (instanceGeometry != null)
|
||
triangles += CountTrianglesFromGeometry(instanceGeometry);
|
||
}
|
||
catch
|
||
{
|
||
// 忽略单个实例解析失败
|
||
}
|
||
}
|
||
}
|
||
|
||
return triangles;
|
||
}
|
||
|
||
/// <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>
|
||
/// <param name="mode">优化模式</param>
|
||
/// <returns>分析结果</returns>
|
||
public static ShellAnalyzeResponse AnalyzeShellOptimization(ShellOptimizeMode mode)
|
||
{
|
||
ShellAnalyzeResponse 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 analyzer = new ShellAnalyzer();
|
||
result = analyzer.AnalyzeModel(doc, mode);
|
||
ApplyDynamicSizeEstimate(doc, result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
capturedException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成(最多等待30秒,分析可能需要较长时间)
|
||
var timeout = DateTime.Now.AddSeconds(30);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(100);
|
||
}
|
||
|
||
if (capturedException != null)
|
||
throw capturedException;
|
||
|
||
if (!completed)
|
||
throw new TimeoutException("薄壳分析超时");
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"薄壳分析失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行薄壳优化(同步)
|
||
/// </summary>
|
||
/// <param name="mode">优化模式</param>
|
||
/// <param name="backupOriginal">是否备份原文件</param>
|
||
/// <returns>执行结果</returns>
|
||
public static ShellOptimizeResult ExecuteShellOptimization(ShellOptimizeMode mode, 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.ExecuteOptimization(doc, mode, 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>
|
||
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>
|
||
/// <param name="mode">优化模式</param>
|
||
/// <param name="backupOriginal">是否备份原文件</param>
|
||
/// <param name="taskId">任务ID</param>
|
||
/// <param name="taskManager">任务管理器</param>
|
||
public static void ExecuteShellOptimizationAsync(ShellOptimizeMode mode, bool backupOriginal, Guid taskId, TaskManager taskManager)
|
||
{
|
||
try
|
||
{
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc == null)
|
||
{
|
||
taskManager.FailTask(taskId, "没有打开的Revit文档");
|
||
return;
|
||
}
|
||
|
||
var optimizer = new ShellOptimizer();
|
||
|
||
// 更新任务状态为进行中
|
||
taskManager.SetTaskRunning(taskId);
|
||
|
||
// 执行优化
|
||
var result = optimizer.ExecuteOptimization(doc, mode, backupOriginal);
|
||
|
||
// 设置任务完成
|
||
taskManager.CompleteTask(taskId, result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskManager.FailTask(taskId, $"薄壳优化执行失败: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskManager.FailTask(taskId, $"启动薄壳优化任务失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
}
|