using System;
using System.IO;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using RevitHttpControl.Models;
namespace RevitHttpControl.Services
{
///
/// 文档操作服务
///
public static class DocumentService
{
private const string ParkingFileName = "parking.rvt";
private const string AutoParkingFileName = "parking.auto.rvt";
private const string AutoParkingDirectoryName = "RevitHttpControl";
///
/// 打开文档文件
///
/// 打开文件请求
/// 打开文件响应
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);
}
}
///
/// 关闭当前活动文档
///
/// 关闭文件响应
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 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;
}
}
///
/// 异步打开文档文件
///
/// 打开文件请求
/// 任务ID
/// 任务管理器
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}");
}
}
///
/// 验证文件路径
///
/// 文件路径
/// 是否有效
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;
}
}
///
/// 获取文件信息
///
/// 文件路径
/// 文件信息
public static FileInfo GetFileInfo(string filePath)
{
if (!ValidateFilePath(filePath))
throw new ArgumentException($"无效的文件路径: {filePath}");
return new FileInfo(filePath);
}
}
}