ADN Open CIS
Сообщество программистов Autodesk в СНГ

21/05/2014

Доступ к 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 из внешнего приложения. Один из методов получает некую информацию из модели, второй, добавляет стены в модель.

Интерфейс сервиса:

Код - C#: [Выделить]
  1.     [ServiceContract]
  2.     public interface IRevitExternalService
  3.     {
  4.         [OperationContract]
  5.         string GetCurrentDocumentPath();
  6.  
  7.         [OperationContract]
  8.         bool CreateWall(XYZ startPoint, XYZ endPoint);
  9.     }

Так же нам понадобятся:

  • Интерфейс IExternalApplication для управления подпиской на событие Idling
  • Реализация сервиса, объявленного выше
  • Два вспомогательных класса:
    • Первый, для управления очередью команд
    • Второй – переопределение класса XYZ

Видео с демонстрацией работы:

Полный код демонстрационного проекта доступен на GitHub, либо можно скачать архивом.

Помните, что Revit должен находится в состоянии бездействия (Idilng), чтобы была возможность выполнить команду из внешнего приложения.

В связи с этим, вызов метода SetRaiseWithoutDelay крайне важен. Без него, будет задержка при выполнении команды или вообще команда может не выполнится. Однако с ним, будет постоянная загрузка процессора на 25% (в случае 4-х ядерного процессора), даже если в  Revit не происходит никаких действий. Arnošt Löbel предложил решение: нужно подписываться на событие Idling только в тот момент, когда оно действительно нужно, и отписаться от него сразу же, как только отпадет необходимость в нем.

Очевидно, что в данном случае, нам необходима обработка события Idling только тогда, когда мы получили сообщение из внешнего приложения. Идея состоит в том, чтобы подписаться на событие в тот момент, когда мы получили запрос на выполнение команды из внешнего приложения, и отписаться от него, как только очередь команд станет пустой.

К сожалению, в данной версии это не реализовано.

Немного кода из текущей версии.

Сначала реализация интерфейса IExternalApplication:

  • OnStartUp. Создаем WCF Service и подписываемся на событие Idling
  • OnIdling. Обработка события Idling. В нем берется команда из очереди и выполняется.
  • OnShutDown. Отписываемся от события и освобождаем ресурсы.

Код - C#: [Выделить]
  1.     class App : IExternalApplication
  2.     {
  3.         private const string serviceUrl =
  4.             "net.pipe://localhost/";
  5.  
  6.  
  7.         private ServiceHost serviceHost;
  8.  
  9.         public Result OnStartup(UIControlledApplication a)
  10.         {
  11.             a.Idling += OnIdling;
  12.  
  13.             Uri uri = new Uri(serviceUrl);
  14.  
  15.             serviceHost =
  16.                 new ServiceHost(typeof(RevitExternalService), uri);
  17.  
  18.  
  19.             try
  20.             {
  21.                 serviceHost.AddServiceEndpoint(typeof (IRevitExternalService),
  22.                                                new NetNamedPipeBinding(),
  23.                                                "RevitExternalService");
  24.  
  25.                 //ServiceMetadataBehavior smb =
  26.                 //    new ServiceMetadataBehavior();
  27.                 //smb.HttpGetEnabled = true;
  28.                 //serviceHost.Description.Behaviors.Add(smb);
  29.  
  30.                 serviceHost.Open();
  31.             }
  32.             catch (Exception ex)
  33.             {               
  34.                 a.ControlledApplication
  35.                     .WriteJournalComment(string.Format("{0}.\r\n{1}"
  36.                         Resources.CouldNotStartWCFService,
  37.                         ex.ToString()),
  38.                     true);
  39.                
  40.             }
  41.            
  42.             return Result.Succeeded;
  43.         }
  44.  
  45.         private void OnIdling(object sender, IdlingEventArgs e)
  46.         {
  47.             var uiApp = sender as UIApplication;
  48.  
  49.             Debug.Print("OnIdling: {0}", DateTime.Now.ToString("HH:mm:ss.fff"));
  50.  
  51.             // Осторожно. Загружает процессор
  52.             e.SetRaiseWithoutDelay();
  53.  
  54.             if (!TaskContainer.Instance.HasTaskToPerform)
  55.                 return;
  56.  
  57.             try
  58.             {
  59.                 Debug.Print("{0}: {1}", Resources.StartExecuteTask, DateTime.Now.ToString("HH:mm:ss.fff"));
  60.  
  61.                 var task = TaskContainer.Instance.DequeueTask();
  62.                 task(uiApp);
  63.  
  64.                 Debug.Print("{0}: {1}", Resources.EndExecuteTask, DateTime.Now.ToString("HH:mm:ss.fff"));
  65.             }
  66.             catch (Exception ex)
  67.             {
  68.                 uiApp.Application.WriteJournalComment(
  69.                     string.Format("RevitExternalService. {0}:\r\n{2}",
  70.                     Resources.AnErrorOccuredWhileExecutingTheOnIdlingEvent,
  71.                     ex.ToString()), true);
  72.  
  73.                 Debug.WriteLine(ex);
  74.             }
  75.  
  76.             //e.SetRaiseWithoutDelay();
  77.         }
  78.  
  79.         public Result OnShutdown(UIControlledApplication a)
  80.         {
  81.             a.Idling -= OnIdling;
  82.  
  83.             if (serviceHost != null)
  84.             {
  85.                 serviceHost.Close();
  86.             }
  87.  
  88.             return Result.Succeeded;
  89.         }
  90.     }

И код реализации сервиса:

Код - C#: [Выделить]
  1.     class RevitExternalService : IRevitExternalService
  2.     {
  3.         private string currentDocumentPath;
  4.  
  5.         private static readonly object _locker =
  6.             new object();
  7.  
  8.         private const int WAIT_TIMEOUT = 10000; // 10 seconds timeout
  9.  
  10.         #region Implementation of IRevitExternalService
  11.  
  12.         public string GetCurrentDocumentPath()
  13.         {
  14.             Debug.Print("{0}: {1}", Resources.PushTaskToTheContainer, DateTime.Now.ToString("HH:mm:ss.fff"));
  15.  
  16.             lock (_locker)
  17.             {
  18.                 TaskContainer.Instance.EnqueueTask(GetDocumentPath);
  19.  
  20.                 // Ждем завершение задачи
  21.                 Monitor.Wait(_locker, WAIT_TIMEOUT);
  22.             }
  23.  
  24.             Debug.Print("{0}: {1}", Resources.FinishTask, DateTime.Now.ToString("HH:mm:ss.fff"));
  25.             return currentDocumentPath;
  26.         }
  27.  
  28.         private void GetDocumentPath(UIApplication uiapp)
  29.         {
  30.             try
  31.             {
  32.                 currentDocumentPath = uiapp.ActiveUIDocument.Document.PathName;
  33.             }
  34.             // Всегда освобождаем объект блокировки
  35.             finally
  36.             {
  37.                 lock (_locker)
  38.                 {
  39.                     Monitor.Pulse(_locker);
  40.                 }
  41.             }
  42.         }
  43.  
  44.         public bool CreateWall(XYZ startPoint, XYZ endPoint)
  45.         {
  46.  
  47.             Wall wall = null;
  48.  
  49.             lock (_locker)
  50.             {
  51.  
  52.                 TaskContainer.Instance.EnqueueTask(uiapp =>
  53.                     {
  54.                         try
  55.                         {
  56.                             var doc = uiapp.ActiveUIDocument.Document;
  57.  
  58.                             using (Transaction t = new Transaction(doc, Resources.CreateWall))
  59.                             {
  60.                                 t.Start();
  61.  
  62.                                 Curve curve = Line.get_Bound(
  63.                                     new Autodesk.Revit.DB.XYZ(startPoint.X, startPoint.Y, startPoint.Z),
  64.                                     new Autodesk.Revit.DB.XYZ(endPoint.X, endPoint.Y, endPoint.Z));
  65.                                 FilteredElementCollector collector = new FilteredElementCollector(doc);
  66.                                 var level =
  67.                                     collector
  68.                                         .OfClass(typeof(Level))
  69.                                         .ToElements()
  70.                                         .OfType<Level>()
  71.                                         .FirstOrDefault();
  72.  
  73.                                 #if REVIT2012
  74.                                 wall = doc.Create.NewWall(curve, level, false);
  75.                                 #else
  76.                                 wall = Wall.Create(doc, curve, level.Id, true);
  77.                                 #endif
  78.  
  79.                                 t.Commit();
  80.                             }
  81.                         }
  82.                         finally
  83.                         {
  84.  
  85.                             lock (_locker)
  86.                             {
  87.                                 Monitor.Pulse(_locker);
  88.                             }
  89.                         }
  90.  
  91.                     });
  92.  
  93.                 Monitor.Wait(_locker);
  94.             }
  95.             return wall != null;
  96.         }
  97.  
  98.         #endregion
  99.     }

Эту же статью на английском можно прочитать на блоге Jeremy Tammik.

Автор: Виктор Чекалин
Автор перевода: Виктор Чекалин

Обсуждение: http://adn-cis.org/forum/index.php?topic=745

Опубликовано 21.05.2014
Отредактировано 21.05.2014 в 17:44:22