469 lines
17 KiB
C#
469 lines
17 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using Autodesk.Revit.DB;
|
||
using RevitHttpControl.Models;
|
||
using RevitHttpControl.Common;
|
||
using ExportRange = RevitHttpControl.Models.ExportRange;
|
||
|
||
namespace RevitHttpControl.Services
|
||
{
|
||
/// <summary>
|
||
/// 导出服务配置常量
|
||
/// </summary>
|
||
internal static class ExportConstants
|
||
{
|
||
/// <summary>
|
||
/// 同步导出超时时间(分钟)
|
||
/// </summary>
|
||
public const int SYNC_EXPORT_TIMEOUT_MINUTES = 5;
|
||
|
||
/// <summary>
|
||
/// 轮询间隔(毫秒)
|
||
/// </summary>
|
||
public const int POLLING_INTERVAL_MS = 200;
|
||
|
||
/// <summary>
|
||
/// 默认IFC文件扩展名
|
||
/// </summary>
|
||
public const string IFC_FILE_EXTENSION = ".ifc";
|
||
|
||
/// <summary>
|
||
/// 临时文件前缀
|
||
/// </summary>
|
||
public const string TEMP_FILE_PREFIX = "revit_export_test_";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出服务类
|
||
/// </summary>
|
||
public static class ExportService
|
||
{
|
||
/// <summary>
|
||
/// 同步导出IFC文件
|
||
/// </summary>
|
||
/// <param name="request">导出请求参数</param>
|
||
/// <returns>导出结果</returns>
|
||
public static ExportIfcResponse ExportToIfc(ExportIfcRequest request)
|
||
{
|
||
if (request == null)
|
||
throw new ArgumentNullException(nameof(request));
|
||
|
||
var startTime = DateTime.Now;
|
||
ExportIfcResponse result = null;
|
||
Exception capturedException = null;
|
||
var completed = false;
|
||
|
||
try
|
||
{
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
result = ExecuteIfcExportCore(request, uiApp, startTime);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
capturedException = ex;
|
||
}
|
||
finally
|
||
{
|
||
completed = true;
|
||
}
|
||
});
|
||
|
||
// 等待命令执行完成
|
||
var timeout = DateTime.Now.AddMinutes(ExportConstants.SYNC_EXPORT_TIMEOUT_MINUTES);
|
||
while (!completed && DateTime.Now < timeout)
|
||
{
|
||
System.Threading.Thread.Sleep(ExportConstants.POLLING_INTERVAL_MS);
|
||
}
|
||
|
||
if (capturedException != null)
|
||
throw capturedException;
|
||
|
||
if (!completed)
|
||
throw new TimeoutException(ErrorMessages.OPERATION_TIMEOUT_MSG);
|
||
|
||
return result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"{ErrorMessages.IFC_EXPORT_FAILED_MSG}: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 异步导出IFC文件
|
||
/// </summary>
|
||
/// <param name="request">导出请求参数</param>
|
||
/// <param name="taskId">任务ID</param>
|
||
/// <param name="taskManager">任务管理器</param>
|
||
public static void ExportToIfcAsync(ExportIfcRequest request, string taskId, TaskManager taskManager)
|
||
{
|
||
if (request == null)
|
||
throw new ArgumentNullException(nameof(request));
|
||
|
||
var startTime = DateTime.Now;
|
||
|
||
App.Instance.EnqueueCommand(uiApp =>
|
||
{
|
||
try
|
||
{
|
||
var result = ExecuteIfcExportCore(request, uiApp, startTime);
|
||
taskManager.CompleteTask(new Guid(taskId), result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
taskManager.FailTask(new Guid(taskId), $"{ErrorMessages.IFC_EXPORT_FAILED_MSG}: {ex.Message}");
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 核心IFC导出逻辑(同步和异步共用)
|
||
/// </summary>
|
||
/// <param name="request">导出请求参数</param>
|
||
/// <param name="uiApp">Revit UI应用程序</param>
|
||
/// <param name="startTime">开始时间</param>
|
||
/// <returns>导出结果</returns>
|
||
private static ExportIfcResponse ExecuteIfcExportCore(ExportIfcRequest request, Autodesk.Revit.UI.UIApplication uiApp, DateTime startTime)
|
||
{
|
||
var doc = uiApp.ActiveUIDocument?.Document;
|
||
if (doc == null)
|
||
throw new InvalidOperationException(ErrorMessages.NO_DOCUMENT_OPEN_MSG);
|
||
|
||
// 生成输出路径
|
||
var outputPath = GenerateOutputPath(request, doc);
|
||
|
||
// 验证输出路径
|
||
ValidateOutputPath(outputPath);
|
||
|
||
// 配置IFC导出选项
|
||
var ifcExportOptions = ConfigureIfcExportOptions(request);
|
||
|
||
// 执行IFC导出
|
||
var exportResult = ExecuteIfcExport(doc, outputPath, ifcExportOptions);
|
||
|
||
// 生成响应
|
||
return CreateExportResponse(outputPath, exportResult, startTime, request);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取支持的导出格式
|
||
/// </summary>
|
||
/// <returns>支持的格式列表</returns>
|
||
public static List<ExportFormat> GetSupportedFormats()
|
||
{
|
||
return new List<ExportFormat>
|
||
{
|
||
new ExportFormat
|
||
{
|
||
Format = "IFC",
|
||
Name = "Industry Foundation Classes",
|
||
Description = "建筑信息模型标准交换格式",
|
||
Extensions = new List<string> { ".ifc" },
|
||
SupportsAsync = true,
|
||
IsExperimental = false
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成输出文件路径
|
||
/// </summary>
|
||
/// <param name="request">导出请求</param>
|
||
/// <param name="doc">Revit文档</param>
|
||
/// <returns>输出文件路径</returns>
|
||
private static string GenerateOutputPath(ExportIfcRequest request, Document doc)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(request.OutputPath))
|
||
{
|
||
// 确保路径有正确的扩展名
|
||
if (!request.OutputPath.EndsWith(".ifc", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
request.OutputPath += ".ifc";
|
||
}
|
||
return request.OutputPath;
|
||
}
|
||
|
||
// 自动生成路径
|
||
var docPath = doc.PathName;
|
||
var directory = string.IsNullOrEmpty(docPath)
|
||
? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
|
||
: Path.GetDirectoryName(docPath);
|
||
|
||
var fileName = string.IsNullOrEmpty(docPath)
|
||
? doc.Title
|
||
: Path.GetFileNameWithoutExtension(docPath);
|
||
|
||
var prefix = string.IsNullOrWhiteSpace(request.FileNamePrefix)
|
||
? "Export"
|
||
: request.FileNamePrefix;
|
||
|
||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
var outputFileName = $"{prefix}_{fileName}_{timestamp}.ifc";
|
||
|
||
return Path.Combine(directory, outputFileName);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证输出路径
|
||
/// </summary>
|
||
/// <param name="outputPath">输出路径</param>
|
||
private static void ValidateOutputPath(string outputPath)
|
||
{
|
||
try
|
||
{
|
||
// 基本路径验证
|
||
if (string.IsNullOrWhiteSpace(outputPath))
|
||
throw new ArgumentException(ErrorMessages.EXPORT_PATH_INVALID_MSG);
|
||
|
||
// 检查路径安全性(防止路径遍历攻击)
|
||
var fullPath = Path.GetFullPath(outputPath);
|
||
if (!fullPath.Equals(outputPath, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
// 如果规范化后的路径与原路径不同,可能存在路径遍历
|
||
throw new ArgumentException(ErrorMessages.EXPORT_PATH_INVALID_MSG);
|
||
}
|
||
|
||
// 检查文件扩展名
|
||
var extension = Path.GetExtension(outputPath);
|
||
if (!extension.Equals(ExportConstants.IFC_FILE_EXTENSION, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
throw new ArgumentException($"输出文件必须是{ExportConstants.IFC_FILE_EXTENSION}格式");
|
||
}
|
||
|
||
var directory = Path.GetDirectoryName(outputPath);
|
||
if (!Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
|
||
// 检查写入权限(使用更安全的方式)
|
||
var testFile = Path.Combine(directory, $"{ExportConstants.TEMP_FILE_PREFIX}{Guid.NewGuid()}.tmp");
|
||
File.WriteAllText(testFile, "test");
|
||
File.Delete(testFile);
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
throw new InvalidOperationException(ErrorMessages.EXPORT_PATH_ACCESS_DENIED_MSG);
|
||
}
|
||
catch (DirectoryNotFoundException)
|
||
{
|
||
throw new InvalidOperationException(ErrorMessages.EXPORT_PATH_INVALID_MSG);
|
||
}
|
||
catch (ArgumentException)
|
||
{
|
||
throw; // 重新抛出参数异常
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"{ErrorMessages.EXPORT_PATH_INVALID_MSG}: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置IFC导出选项
|
||
/// </summary>
|
||
/// <param name="request">导出请求</param>
|
||
/// <returns>IFC导出选项</returns>
|
||
private static IFCExportOptions ConfigureIfcExportOptions(ExportIfcRequest request)
|
||
{
|
||
var options = new IFCExportOptions();
|
||
|
||
// 设置IFC版本
|
||
switch (request.Version)
|
||
{
|
||
case IfcVersion.IFC2x3:
|
||
options.FileVersion = IFCVersion.IFC2x3CV2;
|
||
break;
|
||
case IfcVersion.IFC4:
|
||
options.FileVersion = IFCVersion.IFC4;
|
||
break;
|
||
case IfcVersion.IFC4RV:
|
||
options.FileVersion = IFCVersion.IFC4RV;
|
||
break;
|
||
case IfcVersion.IFC4DTV:
|
||
options.FileVersion = IFCVersion.IFC4DTV;
|
||
break;
|
||
default:
|
||
options.FileVersion = IFCVersion.IFC2x3CV2;
|
||
break;
|
||
}
|
||
|
||
// 设置导出范围
|
||
switch (request.Range)
|
||
{
|
||
case ExportRange.VisibleElements:
|
||
options.FilterViewId = ElementId.InvalidElementId;
|
||
break;
|
||
case ExportRange.CurrentView:
|
||
// 当前视图导出需要在调用时设置
|
||
break;
|
||
case ExportRange.EntireProject:
|
||
options.FilterViewId = ElementId.InvalidElementId;
|
||
break;
|
||
}
|
||
|
||
// 其他选项
|
||
options.SpaceBoundaryLevel = request.IncludeSpaceBoundaries ? 2 : 0;
|
||
// 注意:Revit 2017的IFCExportOptions可能不支持某些属性
|
||
// options.SplitWallsAndColumns = request.SplitWallsAndColumns; // Revit 2017可能不支持
|
||
// options.Export2DElements = request.Include2DElements; // Revit 2017可能不支持
|
||
|
||
return options;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行IFC导出
|
||
/// </summary>
|
||
/// <param name="doc">Revit文档</param>
|
||
/// <param name="outputPath">输出路径</param>
|
||
/// <param name="options">导出选项</param>
|
||
/// <returns>导出结果</returns>
|
||
private static bool ExecuteIfcExport(Document doc, string outputPath, IFCExportOptions options)
|
||
{
|
||
try
|
||
{
|
||
using (var transaction = new Transaction(doc, "IFC Export"))
|
||
{
|
||
transaction.Start();
|
||
|
||
// 执行IFC导出 - Revit 2017 API
|
||
var result = doc.Export(Path.GetDirectoryName(outputPath),
|
||
Path.GetFileNameWithoutExtension(outputPath),
|
||
options);
|
||
|
||
transaction.Commit();
|
||
return result;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new InvalidOperationException($"{ErrorMessages.IFC_EXPORT_FAILED_MSG}: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建导出响应
|
||
/// </summary>
|
||
/// <param name="outputPath">输出路径</param>
|
||
/// <param name="exportSuccess">导出是否成功</param>
|
||
/// <param name="startTime">开始时间</param>
|
||
/// <param name="request">原始请求</param>
|
||
/// <returns>导出响应</returns>
|
||
private static ExportIfcResponse CreateExportResponse(string outputPath, bool exportSuccess,
|
||
DateTime startTime, ExportIfcRequest request)
|
||
{
|
||
if (!exportSuccess || !File.Exists(outputPath))
|
||
{
|
||
throw new InvalidOperationException(ErrorMessages.IFC_EXPORT_FAILED_MSG);
|
||
}
|
||
|
||
var fileInfo = new FileInfo(outputPath);
|
||
var processingTime = (DateTime.Now - startTime).TotalSeconds;
|
||
|
||
var response = new ExportIfcResponse
|
||
{
|
||
FilePath = outputPath,
|
||
FileName = fileInfo.Name,
|
||
FileSizeBytes = fileInfo.Length,
|
||
FileSizeDisplay = FormatFileSize(fileInfo.Length),
|
||
ProcessingTimeSeconds = Math.Round(processingTime, 2),
|
||
IfcVersion = request.Version.ToString(),
|
||
ExportedAt = DateTime.Now,
|
||
Statistics = new ExportStatistics
|
||
{
|
||
HasGeometry = true,
|
||
HasProperties = true
|
||
}
|
||
};
|
||
|
||
// 尝试获取导出统计信息
|
||
try
|
||
{
|
||
response.ExportedElementsCount = GetExportedElementsCount(outputPath);
|
||
response.Statistics = GetExportStatistics(outputPath, request);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 统计信息获取失败不影响主要功能
|
||
System.Diagnostics.Debug.WriteLine($"Failed to get export statistics: {ex.Message}");
|
||
response.ExportedElementsCount = 0;
|
||
}
|
||
|
||
return response;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化文件大小显示
|
||
/// </summary>
|
||
/// <param name="bytes">字节数</param>
|
||
/// <returns>格式化的文件大小</returns>
|
||
private static string FormatFileSize(long bytes)
|
||
{
|
||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||
double len = bytes;
|
||
int order = 0;
|
||
while (len >= 1024 && order < sizes.Length - 1)
|
||
{
|
||
order++;
|
||
len = len / 1024;
|
||
}
|
||
return $"{len:0.##} {sizes[order]}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取导出的元素数量(简化实现)
|
||
/// </summary>
|
||
/// <param name="ifcFilePath">IFC文件路径</param>
|
||
/// <returns>元素数量</returns>
|
||
private static int GetExportedElementsCount(string ifcFilePath)
|
||
{
|
||
try
|
||
{
|
||
// 简单的行数统计作为元素数量的近似值
|
||
var lines = File.ReadAllLines(ifcFilePath);
|
||
return lines.Count(line => line.StartsWith("#") && line.Contains("="));
|
||
}
|
||
catch
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取导出统计信息(简化实现)
|
||
/// </summary>
|
||
/// <param name="ifcFilePath">IFC文件路径</param>
|
||
/// <param name="request">原始请求</param>
|
||
/// <returns>统计信息</returns>
|
||
private static ExportStatistics GetExportStatistics(string ifcFilePath, ExportIfcRequest request)
|
||
{
|
||
var statistics = new ExportStatistics
|
||
{
|
||
HasGeometry = true,
|
||
HasProperties = true,
|
||
ElementsByCategory = new Dictionary<string, int>(),
|
||
ElementsByLevel = new Dictionary<string, int>()
|
||
};
|
||
|
||
try
|
||
{
|
||
// 这里可以添加更详细的IFC文件分析
|
||
// 目前提供基本的统计信息
|
||
statistics.CategoriesCount = request.Categories?.Count ?? 0;
|
||
statistics.LevelsCount = request.LevelNames?.Count ?? 0;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"Failed to analyze IFC file: {ex.Message}");
|
||
}
|
||
|
||
return statistics;
|
||
}
|
||
}
|
||
}
|