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