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

24/10/2013

Программный запуск пользовательской внешней команды

Программный запуск пользовательской внешней команды

Одним из ключевых нововведений в Revit API 2014 является появление метода PostCommand, позволяющего программно выполнить стандартные встроенные команды Revit.

В примерах из Revit SDK есть проект PostCommandWorkflow, демонстрирующий работу нового метода.

Однако, с помощью этого метода можно выполнить только встроенную команду Revit. Запустить внешнюю команду созданную в сторонней надстройке, выполнить с помощью этого метода не получится.

К счастью, эта проблема была исправлена в обновлении Update Release 1 и среди улучшений в Revit API Update Release 1 можно увидеть следующее:

  • В методе UIApplication.PostCommand() разрешена работа с командами, созданными во внешних надстройках.

Так как же мы можем использовать эту функциональность?

Ниже представлен вопрос и мое пояснение от нового Revit разработчика.

Программный запуск внешней команды

Вопрос: В метод PostCommand из Revit API требуется передать RevitCommandId в качестве параметра.

Код - VB.NET: [Выделить]
  1. commandData.Application.PostCommand(revitCmdID)

Я попытался получить идентификатор команды с помощью метода LookupCommandId, передав ему название класса моей внешней команды, но безуспешно. Метод не возвратил ничего.

Код - VB.NET: [Выделить]
  1.  Dim revitCmdID As Autodesk.Revit.UI.RevitCommandId _
  2.     = RevitCommandId.LookupCommandId("DoorCommandLogger")
  3.  
  4.   Dim revitCmdID As Autodesk.Revit.UI.RevitCommandId _
  5.     = RevitCommandId.LookupCommandId("SwingTool.DoorCommandLogger")

Я также посмотрел файл журнала, после того, как я запустил команду из пользовательского интерфейса и попробовал передать различные варианты имени команды, которые я увидел в журнале.

  • "Execute external command:CustomCtrl_%CustomCtrl_%NTItools%Parameters%Door Tool:SwingTool.DoorCommandLogger"
  • "CustomCtrl_%CustomCtrl_%NTItools%Parameters%Door Tool:SwingTool.DoorCommandLogger"

По-прежнему метод не возвратил мне идентификатор моей команды.

Так как же можно выполнить пользовательскую команду?

Ответ: Давайте начнем с того, что более детально посмотрим на пример PostCommandWorkflow из SDK и на классы, которые там используются.

Вот код, который используется для запуска команды:

Код - C#: [Выделить]
  1.  /// <summary>
  2.   /// Prompts to edit the revision and resave.
  3.   /// </summary>
  4.   /// <param name="application"></param>
  5.   private void PromptToEditRevisionsAndResave(
  6.     UIApplication application )
  7.   {
  8.     // Setup external event to be notified
  9.     // when activity is done
  10.  
  11.     externalEvent = ExternalEvent.Create(
  12.       new PostCommandRevisionMonitorEvent(
  13.         this ) );
  14.  
  15.     // Setup event to be notified when revisions
  16.     // command starts (this is a good place to
  17.     // raise this external event)
  18.  
  19.     RevitCommandId id
  20.       = RevitCommandId.LookupPostableCommandId(
  21.         PostableCommand.SheetIssuesOrRevisions );
  22.  
  23.     if( binding == null )
  24.     {
  25.       binding = application
  26.         .CreateAddInCommandBinding( id );
  27.     }
  28.  
  29.     binding.BeforeExecuted
  30.       += ReactToRevisionsAndSchedulesCommand;
  31.  
  32.     // Post the revision editing command
  33.  
  34.     application.PostCommand( id );
  35.   }

В этом коде много строк, которые нам не важны в данный момент.

На самом деле, нам интересны всего лишь две строчки – вызов методов LookupPostableCommandId и PostCommand.

Метод LookupPostableCommandId принимает перечисление типа PostableCommand и возвращает объект типа RevitCommandId. Этот объект необходимо передать в метод PostCommand.

Класс  RevitCommandId хранит в себе идентификатор команды и ее наименование.

В случае с командой SheetIssuesOrRevisions, которая используется в примере, идентификатор и наименование выглядят вот так:

  • Id = 3153
  • Name = "ID_SETTINGS_REVISIONS"

Вы с легкостью можете увидеть эти значения в отладчике Visual Studio, реализовав внешнюю команду с вот таким простым выражением:

Код - C#: [Выделить]
  1.  RevitCommandId id_built_in
  2.     = RevitCommandId.LookupPostableCommandId(
  3.       PostableCommand.SheetIssuesOrRevisions );

Как мы видим, встроенные команды Revit мы можем выполнить и получить их список в перечислении PostableCommand. Также мы можем воспользоваться методом LookupPostableCommandId, чтобы получить подходящий объект класса RevitCommandId, необходимый для программного выполнения команды.

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

Я изучил документацию по Revit API для этих классов и нашел следующее утверждение:

Класс RevitCommandId представляет собой идентификатор команды в Autodesk Revit. Каждая команда в Revit имеет числовой идентификатор и нелокализованное наименование. Этот класс позволяет найти команду по ее имени и представляет собой команду, которую необходимо использовать в методе AddInCommandBinding.

Это все хорошо, но как мы можем это использовать?

LookupPostableCommandId метод принимает перечисление в качестве параметра и для пользовательских команд подходящего перечисления нет.

Но, класс RevitCommandId имеет аж два статических метода: LookupPostableCommandId и LookupCommandId.

Первому методу необходимо передать одно из значений перечисления PostableCommand, тогда как второму методу достаточно указать лишь название команды в виде строки. Документация по последнему методу говорит нам следующее:

Вы можете использовать метод RevitCommandId.LookupCommandId для того чтобы получить соответствующий идентификатор команды Revit по заданной строке. Чтобы узнать название команды, можно посмотреть в файл журнала и найти там строку, соответствующую названию выполняемой команды.

Таким образом, нужно проделать то, что вы уже пробовали и описали в своем вопросе:

  • Создать простую ничего не делающую команду. Назовем ее CmdDummy к примеру.
  • Запустить команду из интерфейса Revit
  • Найти строки, в файле журнала, содержащие текст «Dummy»

Вот такие строки я нашел в файле журнала после выполнения команды:

C:\Users\tammikj\AppData\Local\Autodesk\Revit\Autodesk Revit 2014\Journals > grep -i dummy journal.0242.txt

' 0:< Added new API pushbutton 35024, name PostAddinCommand Dummy Command, class PostAddinCommand.CmdDummy, assembly PostAddinCommand.dll, vendorId TBC_, vendor description The Building Coder, http://thebuildingcoder.typepad.com.

' 4:< MasterLocks 0x0000000014BB1250 DummyStorage stole m_oDataStorage 0x0000000014C52950 but left m_pDataStorage 0x0000000014C52950

Jrn.RibbonEvent "Execute external command:64b3d907-37cf-4cab-8bbc-3de9b66a3efa:PostAddinCommand.CmdDummy"

' 0:< DummyStorage destroying DataStorageInterface 0x0000000014C52950

Исключив все строки со словом Dummy, которые возможно не были созданы после выполнения команды, остается только одна подходящая строка, где содержится наименование команды:

Jrn.RibbonEvent "Execute external command:64b3d907-37cf-4cab-8bbc-3de9b66a3efa:PostAddinCommand.CmdDummy"

GUID, который стотит перед названием команды 0 это GUID, который я указал в файле манифеста моей надстройки.

Код - XML: [Выделить]
  1.  <AddIn Type="Command">
  2.     <Text>PostAddinCommand Dummy Command</Text>
  3.     <Description>Test command for PostAddinCommand.</Description>
  4.     <Assembly>PostAddinCommand.dll</Assembly>
  5.     <FullClassName>PostAddinCommand.CmdDummy</FullClassName>
  6.     <ClientId>64b3d907-37cf-4cab-8bbc-3de9b66a3efa</ClientId>
  7.     <VendorId>TBC_</VendorId>
  8.     <VendorDescription>The Building Coder, http://thebuildingcoder.typepad.com</VendorDescription>
  9.   </AddIn>

В первой попытке я попробовал передать в метод LookupCommandId и название и идентификатор как они написаны в файле журнала:

"64b3d907-37cf-4cab-8bbc-3de9b66a3efa:PostAddinCommand.CmdDummy"

Попытка оказалась неудачной и метод вернул null.

В следующей попытке я передал только GUID. На этот раз меня настигла удача.

Ниже представлен код моей команды, в которой я успешно программно запускаю выполнение другой своей команды.

Код - C#: [Выделить]
  1.  UIApplication uiapp = commandData.Application;
  2.  
  3.   // Built-in Revit commands are listed in the
  4.   // PostableCommand enumeration
  5.  
  6.   RevitCommandId id_built_in
  7.     = RevitCommandId.LookupPostableCommandId(
  8.       PostableCommand.SheetIssuesOrRevisions );
  9.  
  10.   // External commands defined by add-ins are
  11.   // identified by the client id specified in
  12.   // the add-in manifest
  13.  
  14.   string name
  15.     = "64b3d907-37cf-4cab-8bbc-3de9b66a3efa";
  16.  
  17.   RevitCommandId id_addin
  18.     = RevitCommandId.LookupCommandId(
  19.       name );
  20.  
  21.   uiapp.PostCommand( id_addin );

Числовой идентификатор моей команды равен 35024, а название – это просто GUID моей команды.

Числовой идентификатор так же виден в файле журнала, когда создалась кнопка для запуска команды.

Полный исходный код примера для Visual Studio можно скачать из репозитория на GitHub.

 

Программный запуск команды, вызывающейся по кнопке на ленте

Мы уже знаем, как программно запустить команду, которая описана в файле манифеста. Нам нужно использовать GUID этой команды.

Однако более менее серьезные приложения не описывают команды явно в файле манифеста, так как это не очень удобно для пользователя запускать команды на вкладке Надстройки -> Внешние инструменты.

Фактически существуют два совершенно различных тип внешних команд, которые реализуют интерфейс  IExternalCommand:

  • Внешняя команда описана в файле манифеста, которую можно выполнить из вкладки Надстройки -> Внешние инструменты
  • Команда выполняется с помощью кнопки, созданной во внешнем приложении.

Последний вариант не имеет GUID.

Тем не менее, чтобы получить наименование такой команды, нужно проделать те же самые шаги и найти в файле журнала соответствующее наименование.

Я реализовал новую внешнюю команду CmdDummy2 чтобы протестировать случай, когда выполнение команды осуществляется с помощью кнопки. Кнопку я создаю в методе OnStartup класса приложения.

Повторяя шаги, описанные выше, где я вручную запускаю команду в интерфейсе, а затем ищу подходящие строки в файле журнала, я получил следующее:

c:\Users\tammikj\AppD

' 0:< Added new API pushbutton 6417 name  text Dummy2 class PostAddinCommand.CmdDummy2 assembly C:\Users\tammikj\AppData\Roaming\Autodesk\Revit\Addins\2014\PostAddinCommand.dll

 Jrn.RibbonEvent "Execute external command:CustomCtrl_%CustomCtrl_%Add-Ins%Post Add-in Command%Dummy2:PostAddinCommand.CmdDummy2"

' 1:< TaskDialog "Hello from CmdDummy2!"

Наименование команды в этом случае выглядит более длинным:

"CustomCtrl_%CustomCtrl_%Add-Ins%Post Add-in Command%Dummy2"

Как можно заметить строка представляет собой иерархический список разделенный знаком %

Первые два пункта списка фиксированы и являются константой CustomCtrl_

Последние три – это последовательность элементов на ленте, по которым пользователь должен пройтись, чтобы запустить команду. В нашем случае это стандартная вкладка Надстрйоки, панель на ленте "Post Add-in Command", которую я создал, и текст кнопки на этой панели – Dummy2.

Несмотря на то что название команды выглядит громоздким, в этом есть некоторое преимущество. Зная алгоритм формирования названия, мы можем получить название команды не заглядывая в файл журнала.

Следующий код без каких-либо проблем возвращает мне RevitCommandId для моей команды и программно  ее запускает.

Код - C#: [Выделить]
  1.  // External tool commands defined by add-ins are
  2.   // identified by the string listed in the
  3.   // journal file when the command is launched
  4.   // manually.
  5.  
  6.   string name_addin_button_cmd
  7.     = "CustomCtrl_%CustomCtrl_%"
  8.       + "Add-Ins%Post Add-in Command%Dummy2";
  9.  
  10.   RevitCommandId id_addin_button_cmd
  11.     = RevitCommandId.LookupCommandId(
  12.       name_addin_button_cmd );
  13.  
  14.   uiapp.PostCommand( id_addin_button_cmd );

Пошагово выполняя этот код в отладчике и просматривая свойства объекта класса RevitCommandId , я вижу, что числовой идентификатор команды равен 6417.

Тот же самый номер я видел и в файле журнала при создании кнопки.

Если же вы посмотрите на числовые идентификаторы каждой встроенной команды, полученной с помощью LookupPostableCommandId, то вы можете увидеть, что этот идентификатор совпадает с числовым значением перечисления PostableCommand.

Исходя из этого, я подумал, что может быть возможнопривести числовой идентификатор к типу перечисления PostableCommand и использовать метод  LookupPostableCommandId вместо поиска наименований команд.

Однако, это не сработало. Я получал исключения: «Неверное значение PostableCommand».

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

Источник: http://thebuildingcoder.typepad.com/blog/2013/10/programmatic-custom-add-in-external-command-launch.html

 

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

Опубликовано 24.10.2013