TellmeRevitPluging/Services/ExportService.cs
2025-12-09 17:43:30 +08:00

469 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}