TellmeRevitPluging/Services/DocumentService.cs

615 lines
21 KiB
C#
Raw 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.IO;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using RevitHttpControl.Models;
namespace RevitHttpControl.Services
{
/// <summary>
/// 文档操作服务
/// </summary>
public static class DocumentService
{
private const string ParkingFileName = "parking.rvt";
private const string AutoParkingFileName = "parking.auto.rvt";
private const string AutoParkingDirectoryName = "RevitHttpControl";
/// <summary>
/// 打开文档文件
/// </summary>
/// <param name="request">打开文件请求</param>
/// <returns>打开文件响应</returns>
public static OpenFileResponse OpenDocument(OpenFileRequest request)
{
if (string.IsNullOrWhiteSpace(request?.FilePath))
throw new ArgumentException("文件路径不能为空");
if (!File.Exists(request.FilePath))
throw new FileNotFoundException($"文件不存在: {request.FilePath}");
try
{
var fileName = Path.GetFileName(request.FilePath);
var completed = false;
Exception capturedException = null;
App.Instance.EnqueueCommand(uiApp =>
{
try
{
// 将字符串路径转换为ModelPath
var modelPath = ModelPathUtils.ConvertUserVisiblePathToModelPath(request.FilePath);
// 配置打开选项
var openOptions = new OpenOptions
{
DetachFromCentralOption = request.Detached
? DetachFromCentralOption.DetachAndPreserveWorksets
: DetachFromCentralOption.DetachAndDiscardWorksets
};
// 打开并激活文档
uiApp.OpenAndActivateDocument(modelPath, openOptions, false);
}
catch (Exception ex)
{
capturedException = ex;
}
finally
{
completed = true;
}
});
// 等待命令执行完成最多等待10秒
var timeout = DateTime.Now.AddSeconds(10);
while (!completed && DateTime.Now < timeout)
{
System.Threading.Thread.Sleep(100);
}
if (!completed)
throw new TimeoutException("打开文件操作超时");
if (capturedException != null)
throw capturedException;
return new OpenFileResponse
{
Result = "文件打开成功",
FileName = fileName,
FilePath = request.FilePath
};
}
catch (Exception ex)
{
throw new InvalidOperationException($"打开文件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 关闭当前活动文档
/// </summary>
/// <returns>关闭文件响应</returns>
public static CloseFileResponse CloseCurrentDocument()
{
try
{
string targetTitle = null;
string targetPath = null;
string fileName = null;
string filePath = null;
const int maxAttempts = 3;
Exception lastException = null;
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
ExecuteRevitCommand(uiApp =>
{
var activeDoc = uiApp.ActiveUIDocument?.Document;
if (activeDoc == null)
{
throw new InvalidOperationException("没有打开的文档");
}
if (string.IsNullOrWhiteSpace(targetTitle) && string.IsNullOrWhiteSpace(targetPath))
{
targetTitle = activeDoc.Title;
targetPath = activeDoc.PathName;
fileName = string.IsNullOrEmpty(activeDoc.PathName)
? activeDoc.Title
: Path.GetFileName(activeDoc.PathName);
filePath = string.IsNullOrEmpty(activeDoc.PathName) ? null : activeDoc.PathName;
}
var target = ResolveDocumentForClose(uiApp, targetTitle, targetPath);
if (target == null)
{
throw new InvalidOperationException("目标文档未找到或已关闭");
}
if (!TrySwitchAwayFromDocument(uiApp, target))
{
throw new InvalidOperationException("无法切换到可关闭目标(请先打开第二个文档,或确保默认模板可用以自动创建 parking 文档)");
}
}, $"关闭文件操作超时(切换文档阶段,第{attempt}次)");
ExecuteRevitCommand(uiApp =>
{
var target = ResolveDocumentForClose(uiApp, targetTitle, targetPath);
if (target == null)
{
throw new InvalidOperationException("目标文档未找到或已关闭");
}
var activeDoc = uiApp.ActiveUIDocument?.Document;
if (IsSameDocument(activeDoc, targetTitle, targetPath))
{
throw new InvalidOperationException("文档切换未生效,无法关闭当前激活文档");
}
target.Close(false);
TryFinalizeUiAfterClose(uiApp);
}, $"关闭文件操作超时(关闭文档阶段,第{attempt}次)");
lastException = null;
break;
}
catch (Exception ex)
{
lastException = ex;
if (attempt >= maxAttempts)
{
throw;
}
}
}
if (lastException != null)
{
throw lastException;
}
return new CloseFileResponse
{
Result = "文件关闭成功",
FileName = fileName,
FilePath = filePath
};
}
catch (TimeoutException)
{
throw;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception ex)
{
throw new InvalidOperationException($"关闭文件失败: {ex.Message}", ex);
}
}
private static bool TrySwitchAwayFromDocument(UIApplication uiApp, Document sourceDoc)
{
var documents = uiApp.Application.Documents;
var sourceTitle = sourceDoc?.Title;
var sourcePath = sourceDoc?.PathName;
foreach (Document doc in documents)
{
if (string.IsNullOrWhiteSpace(doc.PathName) || IsSameDocument(doc, sourceTitle, sourcePath))
{
continue;
}
try
{
uiApp.OpenAndActivateDocument(doc.PathName);
var activeAfterSwitch = uiApp.ActiveUIDocument?.Document;
if (!IsSameDocument(activeAfterSwitch, sourceTitle, sourcePath))
{
return true;
}
}
catch
{
// 尝试下一个文档
}
}
var parkingPath = ResolveOrCreateParkingPath(uiApp, sourcePath);
if (string.IsNullOrWhiteSpace(parkingPath))
{
return false;
}
try
{
uiApp.OpenAndActivateDocument(parkingPath);
var activeAfterSwitch = uiApp.ActiveUIDocument?.Document;
return !IsSameDocument(activeAfterSwitch, sourceTitle, sourcePath);
}
catch
{
return false;
}
}
private static string ResolveOrCreateParkingPath(UIApplication uiApp, string sourcePath)
{
var packagedParkingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ParkingFileName);
if (IsUsableParkingPath(packagedParkingPath, sourcePath))
{
return packagedParkingPath;
}
var autoParkingPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
AutoParkingDirectoryName,
AutoParkingFileName);
if (IsSamePath(autoParkingPath, sourcePath))
{
return null;
}
if (File.Exists(autoParkingPath))
{
return autoParkingPath;
}
return TryCreateAutoParkingDocument(uiApp, autoParkingPath) ? autoParkingPath : null;
}
private static bool IsUsableParkingPath(string parkingPath, string sourcePath)
{
if (string.IsNullOrWhiteSpace(parkingPath) || !File.Exists(parkingPath))
{
return false;
}
return !IsSamePath(parkingPath, sourcePath);
}
private static bool IsSamePath(string pathA, string pathB)
{
if (string.IsNullOrWhiteSpace(pathA) || string.IsNullOrWhiteSpace(pathB))
{
return false;
}
try
{
return string.Equals(
Path.GetFullPath(pathA),
Path.GetFullPath(pathB),
StringComparison.OrdinalIgnoreCase);
}
catch
{
return string.Equals(pathA, pathB, StringComparison.OrdinalIgnoreCase);
}
}
private static bool TryCreateAutoParkingDocument(UIApplication uiApp, string parkingPath)
{
Document parkingDoc = null;
try
{
var revitApp = uiApp.Application;
var defaultTemplate = ResolveProjectTemplatePath(revitApp);
if (!string.IsNullOrWhiteSpace(defaultTemplate) && File.Exists(defaultTemplate))
{
parkingDoc = revitApp.NewProjectDocument(defaultTemplate);
}
else
{
return false;
}
if (parkingDoc == null)
{
return false;
}
var directory = Path.GetDirectoryName(parkingPath);
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var saveAsOptions = new SaveAsOptions
{
OverwriteExistingFile = true
};
parkingDoc.SaveAs(parkingPath, saveAsOptions);
return File.Exists(parkingPath);
}
catch
{
return false;
}
finally
{
if (parkingDoc != null)
{
try
{
parkingDoc.Close(false);
}
catch
{
// parking 文档关闭失败不阻塞主流程
}
}
}
}
private static string ResolveProjectTemplatePath(Autodesk.Revit.ApplicationServices.Application revitApp)
{
var defaultTemplate = revitApp.DefaultProjectTemplate;
if (!string.IsNullOrWhiteSpace(defaultTemplate) && File.Exists(defaultTemplate))
{
return defaultTemplate;
}
var version = revitApp.VersionNumber;
var programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
var candidates = new[]
{
Path.Combine(programData, "Autodesk", $"RVT {version}", "Templates"),
Path.Combine(programData, "Autodesk", "RVT", version, "Templates")
};
foreach (var dir in candidates)
{
if (!Directory.Exists(dir))
{
continue;
}
try
{
var template = Directory.GetFiles(dir, "*.rte", SearchOption.AllDirectories);
if (template.Length > 0)
{
return template[0];
}
}
catch
{
// ignore and try next candidate
}
}
return null;
}
private static void TryFinalizeUiAfterClose(UIApplication uiApp)
{
try
{
var activeDoc = uiApp.ActiveUIDocument?.Document;
var documents = uiApp.Application.Documents;
if (activeDoc == null || documents == null || documents.Size != 1)
{
return;
}
if (!IsParkingDocument(activeDoc.PathName))
{
return;
}
var closeCmd = RevitCommandId.LookupPostableCommandId(PostableCommand.Close);
if (closeCmd != null && uiApp.CanPostCommand(closeCmd))
{
uiApp.PostCommand(closeCmd);
}
}
catch
{
// UI收尾失败不影响主流程
}
}
private static bool IsParkingDocument(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
var packagedParkingPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ParkingFileName);
var autoParkingPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
AutoParkingDirectoryName,
AutoParkingFileName);
return IsSamePath(path, packagedParkingPath) || IsSamePath(path, autoParkingPath);
}
private static Document ResolveDocumentForClose(UIApplication uiApp, string targetTitle, string targetPath)
{
foreach (Document doc in uiApp.Application.Documents)
{
if (IsSameDocument(doc, targetTitle, targetPath))
{
return doc;
}
}
return null;
}
private static bool IsSameDocument(Document doc, string targetTitle, string targetPath)
{
if (doc == null)
{
return false;
}
if (!string.IsNullOrWhiteSpace(targetPath) && !string.IsNullOrWhiteSpace(doc.PathName))
{
try
{
return string.Equals(
Path.GetFullPath(doc.PathName),
Path.GetFullPath(targetPath),
StringComparison.OrdinalIgnoreCase);
}
catch
{
return string.Equals(doc.PathName, targetPath, StringComparison.OrdinalIgnoreCase);
}
}
return string.Equals(doc.Title, targetTitle, StringComparison.OrdinalIgnoreCase);
}
private static void ExecuteRevitCommand(Action<UIApplication> command, string timeoutMessage)
{
var completed = false;
Exception capturedException = null;
App.Instance.EnqueueCommand(uiApp =>
{
try
{
command(uiApp);
}
catch (Exception ex)
{
capturedException = ex;
}
finally
{
completed = true;
}
});
var timeout = DateTime.Now.AddSeconds(10);
while (!completed && DateTime.Now < timeout)
{
System.Threading.Thread.Sleep(100);
}
if (!completed)
{
throw new TimeoutException(timeoutMessage);
}
if (capturedException != null)
{
throw capturedException;
}
}
/// <summary>
/// 异步打开文档文件
/// </summary>
/// <param name="request">打开文件请求</param>
/// <param name="taskId">任务ID</param>
/// <param name="taskManager">任务管理器</param>
public static void OpenDocumentAsync(OpenFileRequest request, Guid taskId, TaskManager taskManager)
{
try
{
if (string.IsNullOrWhiteSpace(request?.FilePath))
{
taskManager.FailTask(taskId, "文件路径不能为空");
return;
}
if (!File.Exists(request.FilePath))
{
taskManager.FailTask(taskId, $"文件不存在: {request.FilePath}");
return;
}
var fileName = Path.GetFileName(request.FilePath);
App.Instance.EnqueueCommand(uiApp =>
{
try
{
// 将字符串路径转换为ModelPath
var modelPath = ModelPathUtils.ConvertUserVisiblePathToModelPath(request.FilePath);
// 配置打开选项
var openOptions = new OpenOptions
{
DetachFromCentralOption = request.Detached
? DetachFromCentralOption.DetachAndPreserveWorksets
: DetachFromCentralOption.DetachAndDiscardWorksets
};
// 打开并激活文档
uiApp.OpenAndActivateDocument(modelPath, openOptions, false);
// 成功完成任务
var response = new OpenFileResponse
{
Result = "文件打开成功",
FileName = fileName,
FilePath = request.FilePath
};
taskManager.CompleteTask(taskId, response);
}
catch (Exception ex)
{
taskManager.FailTask(taskId, $"打开文件失败: {ex.Message}");
}
});
}
catch (Exception ex)
{
taskManager.FailTask(taskId, $"打开文件失败: {ex.Message}");
}
}
/// <summary>
/// 验证文件路径
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>是否有效</returns>
public static bool ValidateFilePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
return false;
try
{
var extension = Path.GetExtension(filePath).ToLowerInvariant();
return extension == ".rvt" && File.Exists(filePath);
}
catch
{
return false;
}
}
/// <summary>
/// 获取文件信息
/// </summary>
/// <param name="filePath">文件路径</param>
/// <returns>文件信息</returns>
public static FileInfo GetFileInfo(string filePath)
{
if (!ValidateFilePath(filePath))
throw new ArgumentException($"无效的文件路径: {filePath}");
return new FileInfo(filePath);
}
}
}