Доступ к Revit из внешнего приложения
Как известно, официального API для доступа к Revit из внешнего приложения не существует.
Тем не менее, можно реализовать его самостоятельно, хоть и с некоторыми ограничениями.
Обращаться к методам Revit API можно только в определенных контекстах:
- открытие/закрытие приложения (IExternalApplication.OnStartup и IExternalApplication.OnShutdown)
- Выполнение внешней команды (IExternalCommand.Execute)
- Событие Idling (UIApplication.Idling)
- Внешние события (ExternalEvents)
Ни один из этих способов недоступен вне приложения Revit.
Моя идея заключается в следующем:
Внутри Revit я создаю сервис, который будет получать запросы из внешнего приложения
Сервис получает команды из внешнего приложения и помешает ее в очередь команд
Обработчик события Idling просматривает очередь команд на наличие таковых и, в случае наличия команды, выполняет ее.
Таким образом, выполнение осуществляется в контексте события Idling, т.е. является абсолютно безопасным.
Схема работы представлена на диаграмме:
В своей реализации я использую WCF Service. Однако ничего не мешает использовать любую другую технологию взаимодействия между приложениями.
Базовою информацию по созданию WCF Service можно прочитать на сайте Microsoft.
Для демонстрации своей идеи я создал небольшой проект. В нем реализовано пару функций для доступа к Revit API из внешнего приложения. Один из методов получает некую информацию из модели, второй, добавляет стены в модель.
Интерфейс сервиса:
- [ServiceContract]
- public interface IRevitExternalService
- {
- [OperationContract]
- string GetCurrentDocumentPath();
- [OperationContract]
- bool CreateWall(XYZ startPoint, XYZ endPoint);
- }
Так же нам понадобятся:
- Интерфейс IExternalApplication для управления подпиской на событие Idling
- Реализация сервиса, объявленного выше
- Два вспомогательных класса:
- Первый, для управления очередью команд
- Второй – переопределение класса XYZ
Видео с демонстрацией работы:
Полный код демонстрационного проекта доступен на GitHub, либо можно скачать архивом.
Помните, что Revit должен находится в состоянии бездействия (Idilng), чтобы была возможность выполнить команду из внешнего приложения.
В связи с этим, вызов метода SetRaiseWithoutDelay крайне важен. Без него, будет задержка при выполнении команды или вообще команда может не выполнится. Однако с ним, будет постоянная загрузка процессора на 25% (в случае 4-х ядерного процессора), даже если в Revit не происходит никаких действий. Arnošt Löbel предложил решение: нужно подписываться на событие Idling только в тот момент, когда оно действительно нужно, и отписаться от него сразу же, как только отпадет необходимость в нем.
Очевидно, что в данном случае, нам необходима обработка события Idling только тогда, когда мы получили сообщение из внешнего приложения. Идея состоит в том, чтобы подписаться на событие в тот момент, когда мы получили запрос на выполнение команды из внешнего приложения, и отписаться от него, как только очередь команд станет пустой.
К сожалению, в данной версии это не реализовано.
Немного кода из текущей версии.
Сначала реализация интерфейса IExternalApplication:
- OnStartUp. Создаем WCF Service и подписываемся на событие Idling
- OnIdling. Обработка события Idling. В нем берется команда из очереди и выполняется.
- OnShutDown. Отписываемся от события и освобождаем ресурсы.
- class App : IExternalApplication
- {
- private const string serviceUrl =
- "net.pipe://localhost/";
- private ServiceHost serviceHost;
- public Result OnStartup(UIControlledApplication a)
- {
- a.Idling += OnIdling;
- Uri uri = new Uri(serviceUrl);
- serviceHost =
- new ServiceHost(typeof(RevitExternalService), uri);
- try
- {
- serviceHost.AddServiceEndpoint(typeof (IRevitExternalService),
- new NetNamedPipeBinding(),
- "RevitExternalService");
- //ServiceMetadataBehavior smb =
- // new ServiceMetadataBehavior();
- //smb.HttpGetEnabled = true;
- //serviceHost.Description.Behaviors.Add(smb);
- serviceHost.Open();
- }
- catch (Exception ex)
- {
- a.ControlledApplication
- .WriteJournalComment(string.Format("{0}.\r\n{1}",
- Resources.CouldNotStartWCFService,
- ex.ToString()),
- true);
- }
- return Result.Succeeded;
- }
- private void OnIdling(object sender, IdlingEventArgs e)
- {
- var uiApp = sender as UIApplication;
- Debug.Print("OnIdling: {0}", DateTime.Now.ToString("HH:mm:ss.fff"));
- // Осторожно. Загружает процессор
- e.SetRaiseWithoutDelay();
- if (!TaskContainer.Instance.HasTaskToPerform)
- return;
- try
- {
- Debug.Print("{0}: {1}", Resources.StartExecuteTask, DateTime.Now.ToString("HH:mm:ss.fff"));
- var task = TaskContainer.Instance.DequeueTask();
- task(uiApp);
- Debug.Print("{0}: {1}", Resources.EndExecuteTask, DateTime.Now.ToString("HH:mm:ss.fff"));
- }
- catch (Exception ex)
- {
- uiApp.Application.WriteJournalComment(
- string.Format("RevitExternalService. {0}:\r\n{2}",
- Resources.AnErrorOccuredWhileExecutingTheOnIdlingEvent,
- ex.ToString()), true);
- Debug.WriteLine(ex);
- }
- //e.SetRaiseWithoutDelay();
- }
- public Result OnShutdown(UIControlledApplication a)
- {
- a.Idling -= OnIdling;
- if (serviceHost != null)
- {
- serviceHost.Close();
- }
- return Result.Succeeded;
- }
- }
И код реализации сервиса:
- class RevitExternalService : IRevitExternalService
- {
- private string currentDocumentPath;
- private static readonly object _locker =
- new object();
- private const int WAIT_TIMEOUT = 10000; // 10 seconds timeout
- #region Implementation of IRevitExternalService
- public string GetCurrentDocumentPath()
- {
- Debug.Print("{0}: {1}", Resources.PushTaskToTheContainer, DateTime.Now.ToString("HH:mm:ss.fff"));
- lock (_locker)
- {
- TaskContainer.Instance.EnqueueTask(GetDocumentPath);
- // Ждем завершение задачи
- Monitor.Wait(_locker, WAIT_TIMEOUT);
- }
- Debug.Print("{0}: {1}", Resources.FinishTask, DateTime.Now.ToString("HH:mm:ss.fff"));
- return currentDocumentPath;
- }
- private void GetDocumentPath(UIApplication uiapp)
- {
- try
- {
- currentDocumentPath = uiapp.ActiveUIDocument.Document.PathName;
- }
- // Всегда освобождаем объект блокировки
- finally
- {
- lock (_locker)
- {
- Monitor.Pulse(_locker);
- }
- }
- }
- public bool CreateWall(XYZ startPoint, XYZ endPoint)
- {
- Wall wall = null;
- lock (_locker)
- {
- TaskContainer.Instance.EnqueueTask(uiapp =>
- {
- try
- {
- var doc = uiapp.ActiveUIDocument.Document;
- using (Transaction t = new Transaction(doc, Resources.CreateWall))
- {
- t.Start();
- Curve curve = Line.get_Bound(
- new Autodesk.Revit.DB.XYZ(startPoint.X, startPoint.Y, startPoint.Z),
- new Autodesk.Revit.DB.XYZ(endPoint.X, endPoint.Y, endPoint.Z));
- FilteredElementCollector collector = new FilteredElementCollector(doc);
- var level =
- collector
- .OfClass(typeof(Level))
- .ToElements()
- .OfType<Level>()
- .FirstOrDefault();
- #if REVIT2012
- wall = doc.Create.NewWall(curve, level, false);
- #else
- wall = Wall.Create(doc, curve, level.Id, true);
- #endif
- t.Commit();
- }
- }
- finally
- {
- lock (_locker)
- {
- Monitor.Pulse(_locker);
- }
- }
- });
- Monitor.Wait(_locker);
- }
- return wall != null;
- }
- #endregion
- }
Эту же статью на английском можно прочитать на блоге Jeremy Tammik.
Автор перевода: Виктор Чекалин
Обсуждение: http://adn-cis.org/forum/index.php?topic=745
Опубликовано 21.05.2014Отредактировано 21.05.2014 в 17:44:22