Связь Revit

Автор Тема: Связь Revit  (Прочитано 5228 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Тема содержит сообщение с Решением. Нажмите здесь чтобы посмотреть его.

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Связь Revit
« : 05-04-2018, 12:15:21 »
Добрый день!

В Revit существует кнопка "Связь Revit", которая позволяет вставить в rvt файл ссылку на другой файл, скажем, тоже rvt. При нажатии кнопки появляется диалоговое окно выбора файла. Кстати, такое же диалоговое окно вызывается из диспетчера связей (кнопочка "Добавить...").
Так вот, вопрос в чем, можно ли перехватить появление окна выбора файла? В AutoCAD, знаю, есть такая возможность, а вот в Revit? Надо бы направить пользователя в систему документооборота, а не в файловую систему, файл для связи выбирать.

Заранее спасибо.

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Связь Revit
« Ответ #1 : 05-04-2018, 13:47:41 »
Думаю, что в Revit API возможности перехвата этого диалога нет. Так что только хуки. А потом вот эта тема для добавления связи: http://adndevblog.typepad.com/aec/2015/02/revitapi-how-to-insert-revit-link-file.html
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #2 : 05-04-2018, 14:15:01 »
Хуки на весь Revit? Имеешь ввиду перехватить все события Revit штатными средствами Windows API?

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Связь Revit
« Ответ #3 : 05-04-2018, 14:19:53 »
Хуки на весь Revit? Имеешь ввиду перехватить все события Revit штатными средствами Windows API?
Угу. Хотя можно попробовать подписаться на событие DialogBoxShowing, закрывать диалог, открывать свой  и т.д.
Пример как закрывать диалог: http://adndevblog.typepad.com/aec/2013/06/dismiss-the-dialog-when-opening-a-copied-central-model-file.html
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #4 : 06-04-2018, 10:47:54 »
Я верно понял, что событие DialogBoxShowing не перехватывается в приложении IExternalApplication?
Подписывался на него в событие OnStartup и ни разу оно не сработало.
Код - C# [Выбрать]
  1. application.DialogBoxShowing += new EventHandler<Autodesk.Revit.UI.Events.DialogBoxShowingEventArgs>(Application_DialogBoxShowing);
  2. // или так
  3. application.DialogBoxShowing += Application_DialogBoxShowing;
  4.  
« Последнее редактирование: 06-04-2018, 14:01:00 от Александр Ривилис »

Оффлайн Александр Ривилис

  • Administrator
  • *****
  • Сообщений: 13829
  • Карма: 1784
  • Рыцарь ObjectARX
  • Skype: rivilis
Re: Связь Revit
« Ответ #5 : 06-04-2018, 14:04:37 »
Подписывался на него в событие OnStartup и ни разу оно не сработало.
А вроде должно. Пример вот: https://knowledge.autodesk.com/search-result/caas/CloudHelp/cloudhelp/2016/ENU/Revit-API/files/GUID-CEF0F9C9-046E-46E2-9535-3B9620D8A170-htm.html
Не забывайте про правильное Форматирование кода на форуме
Создание и добавление Autodesk Screencast видео в сообщение на форуме
Если Вы задали вопрос и на форуме появился правильный ответ, то не забудьте про кнопку Решение

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 1152
  • Карма: 338
  • Skype: alexandr.ignatovich.itc
Re: Связь Revit
« Ответ #6 : 10-04-2018, 10:59:32 »
Добрый день, коллеги.

В Revit есть возможность переопределения некоторых стандартных комманд.

Для начала нужно получить RevitCommandId. Это можно сделать так:
Код - C# [Выбрать]
  1. var commandId = RevitCommandId.LookupPostableCommandId(PostableCommand.LinkRevit);
  2.  

или так:
Код - C# [Выбрать]
  1. var commandId = RevitCommandId.LookupCommandId("ID_RVTDOC_LINK");
  2.  

Во втором случае я подсмотрел ID команды в файле журнала Revit

Далее в общем случае проверяем значение свойства commandId.CanHaveBinding. Для этой команды оно True, можем продолжать.

После этого нам нужно получить AddInCommandBinding с помощью соотвествующего метода UIApplication:
Код - C# [Выбрать]
  1. var commandBinding = uiApp.CreateAddInCommandBinding(commandId);
  2.  

Теперь соответственно, мы можем переопределить поведение команды, подписавшись на событие Executed.

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

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #7 : 11-04-2018, 09:14:11 »
Очень странно ведет себя событие DialogBoxShowing (Revit 2018, 18.0.0.420).
Событие не перехватывает команду "Связь Revit". Начал тыкать во все кнопки подряд. Оказалось, что перехват события работает, но только для команд:
Управление изображениями
Разместить деколь (хз чего это)
Диспетчер связей
Применить свойства шаблона к текущему виду
Создать шаблон на основе текущего вида
Управление шаблонами видов
Видимость/графика
Фильтры
Легенда
Легенда ключевых пометок
Настройки систем ОВиВК
Настройки электротехнических систем
Настройки соединений несущих конструкций
Параметры несущих конструкций
О программе Autodesk revit 2018 (нахрена его то перехватывать?)

Возможно еще какие-то, но все они мне сейчас не нужны. :)
Переходим к плану "Б".

Отмечено как Решение Alxd 12-04-2018, 12:58:37

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #8 : 11-04-2018, 10:08:26 »
Александр Игнатович, пошел по предложенному вами пути.
Сделал следующее:
Код - C# [Выбрать]
  1. RevitCommandId _linkRevitCommandId = null;
  2.  
  3. public Result OnStartup(UIControlledApplication application)
  4. {
  5.                 ...
  6.                 _linkRevitCommandId = RevitCommandId.LookupPostableCommandId(PostableCommand.LinkRevit);                
  7.                 if (_linkRevitCommandId.CanHaveBinding)
  8.                 {
  9.                     AddInCommandBinding commandBinding = application.CreateAddInCommandBinding(_linkRevitCommandId);
  10.                     commandBinding.Executed += CommandBinding_Executed;
  11.                 }
  12.                 ...
  13. }
  14.  
  15. private void CommandBinding_Executed(object sender, Autodesk.Revit.UI.Events.ExecutedEventArgs e)
  16. {
  17.         //my code
  18. }
  19.  

Перехват команды произошел успешно. Здорово! Но теперь встал другой вопрос. При выполнении моего кода происходит анализ открытого файла и принимается решение, использовать штатное окно для вставки ссылки на файл rvt или окно системы документооборота. Как бы так из события вызвать штатную команду LinkRevit, если нужно?
« Последнее редактирование: 11-04-2018, 10:37:51 от Александр Ривилис »

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #9 : 11-04-2018, 10:46:56 »
Кстати, добавлю, описанные выше два способа перехвата, перехватывают команду "Связь Revit" и вызов окна "Диспетчер связей", но не перехватывают кнопку "Добавить..." из самого диспетчера связей. Печально.

P.S. Без царя в голове писали Revit.

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 1152
  • Карма: 338
  • Skype: alexandr.ignatovich.itc
Re: Связь Revit
« Ответ #10 : 11-04-2018, 11:09:03 »
Ну про Revit зря Вы это так, система очень большая и сложная с долгим и непростым путем развития.

Касательно Вашего вопроса. Есть очевидный ответ. Вы в любом случае программируете функцию, аналогичную родной Revit-овской вставке связанного файла, отличие, насколько понимаю только в одном окне, соответственно, вызвать диалог выбора файла на "стандартный" вариант не представляется слишком сложно.

Я сейчас экспериментирую в общем случае на тему вызова стандартного функционала из AddInCommandBinding, опубликую результаты чуть позже

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #11 : 11-04-2018, 12:28:08 »
Мне, как пользователю/программисту под Revit, абсолютно все равно, какой был сложный и непростой путь развития продукта. Поставить простой вызов одного события перед всеми вызовами всех диалоговых окон или окон выбора файла, не составляет никакого труда. А сейчас, заметив ... эээ ... странность в поведении перехватчиков, представляется какой-то "индусский код" в исходниках продукта.

Сдаётся мне, что перехватить нажатие "Добавить" в окне "Диспетчер  связей" не представится возможным. Ну, разве что хуком, что, по мне, "грязный хак".

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

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 1152
  • Карма: 338
  • Skype: alexandr.ignatovich.itc
Re: Связь Revit
« Ответ #12 : 11-04-2018, 12:32:12 »
В общем, получился работоспособный код, к сожалению, не слишком удобочитаемый. Суть решения в том, чтобы подписываться/отписываться на событие AddInCommandBinding.Executed, но проблема, как всегда, в деталях. Есть еще одно событие - AddInCommandBinding.BeforeExecuted, где я пытался реализовывать подпись/отписку события Executed, но результат проявлялся только при следующем запуске команды. Было бы здорово, если бы событие BeforeExecuted было Cancellable, тогда для случая собственной реализации можно было бы прописать алгоритм в этом событии (ну или вызове External Event, т.к. это событие read-only), но нет. Теоретически можно, конечно, запостить в Revit Ideas Station, но смысла мало, идея голосов не наберет достаточно.

Итак, собственно, нужно отслеживать состояние, для этого добавил enum:
Код - C# [Выбрать]
  1. public enum LinkCommandMode
  2. {
  3.         Unknown,
  4.  
  5.         Standard,
  6.  
  7.         ExecutingStandard,
  8.  
  9.         OwnRealization
  10. }
  11.  

Standard - требуется стандартная реализация команды, ExecutingStandard - событие вызывается во время обработки по стандартному алгоритму, OwnRealization - собственная реализация.

Добавляем поля класса приложения (реализующего интерфейс IExternalApplication)
Код - C# [Выбрать]
  1. private LinkCommandMode mode;
  2. private AddInCommandBinding binding;
  3.  

В методе OnStartup подписываемся на событие BeforeExecuted:
Код - C# [Выбрать]
  1.  
  2. public Result OnStartup(UIControlledApplication application)
  3. {
  4.         var command = RevitCommandId.LookupPostableCommandId(PostableCommand.LinkRevit);
  5.        
  6.         binding = application.CreateAddInCommandBinding(command);
  7.        
  8.         binding.BeforeExecuted += OnBeforeExecuted;
  9.  
  10.         binding.Executed += OnExecuted;
  11.  
  12.         return Result.Succeeded;
  13. }
  14.  
  15.  

Реализация OnBeforeExecuted. Здесь, во первых, всегда осуществляется подписка на событие Executed - оно сработает на следующем вызове. Это важно, когда мы отписались от события, чтобы заупустить стандартную реализацию, нам нужно чтобы событие сработало на следующий вызов. Если mode = Unknown, то запрашиваем нужную нам реализацию, если ExecutingStandard, то сбрасываем обратно на unknown
Код - C# [Выбрать]
  1. private void OnBeforeExecuted(object sender, BeforeExecutedEventArgs e)
  2. {
  3.         binding.Executed -= OnExecuted;
  4.         binding.Executed += OnExecuted;
  5.  
  6.         switch (mode)
  7.         {
  8.                 case LinkCommandMode.Unknown:
  9.                         var dialog = new TaskDialog("dev");
  10.  
  11.                         dialog.AddCommandLink(TaskDialogCommandLinkId.CommandLink1, "Стандартный");
  12.  
  13.                         dialog.AddCommandLink(TaskDialogCommandLinkId.CommandLink2, "Собственный");
  14.  
  15.                         mode = dialog.Show() == TaskDialogResult.CommandLink2 ? LinkCommandMode.OwnRealization : LinkCommandMode.Standard;
  16.  
  17.                         break;
  18.  
  19.                 case LinkCommandMode.ExecutingStandard:
  20.                         mode = LinkCommandMode.Unknown;
  21.                         break;
  22.         }
  23. }
  24.  

Ну и код метода OnExecuted. Если запрошена стандартная реализация, отписываемся от события Executed и ставим mode = ExecutingStandard:
Код - C# [Выбрать]
  1. private void OnExecuted(object sender, ExecutedEventArgs e)
  2. {
  3.         switch (mode)
  4.         {
  5.                 case LinkCommandMode.Standard:
  6.                         binding.Executed -= OnExecuted;
  7.  
  8.                         var uiapp = (UIApplication) sender;
  9.  
  10.                         uiapp.PostCommand(e.CommandId);
  11.  
  12.                         mode = LinkCommandMode.ExecutingStandard;
  13.  
  14.                         break;
  15.  
  16.                 case LinkCommandMode.OwnRealization:
  17.                         var doc = e.ActiveDocument;
  18.  
  19.                         // делаем что-то полезное
  20.  
  21.                         mode = LinkCommandMode.Unknown;
  22.  
  23.                         break;
  24.         }
  25. }
  26.  

Оффлайн Александр Игнатович

  • Administrator
  • *****
  • Сообщений: 1152
  • Карма: 338
  • Skype: alexandr.ignatovich.itc
Re: Связь Revit
« Ответ #13 : 11-04-2018, 12:41:32 »
Индусский код встречается в любых больших проектах, только тссс :-X Но в целом, Revit предлагает удобное и стабильное API

Насчет Диспетчера проектов... Ну есть варианты вообще говоря без таких безобразий. Посмотрите в сторону фреймворка DMU (Dynamic Model Updater) + Failure Messages API

Можно отлавливать добавление связи. Вы упоминали, что можете для основного проекта каким-то образом понимать, должны ли быть добавлены связанные файлы из некой Вашей системы или можно добавлять любые, соответственно, если добавленный связанный файл должен быть из Вашей системы, но по факту "неправильный", можно бросить document.PostFailure, т.е. не дадите по факту пользователю добавить "неправильный" связанный файл

Оффлайн AlxdАвтор темы

  • ADN Club
  • **
  • Сообщений: 78
  • Карма: 2
Re: Связь Revit
« Ответ #14 : 12-04-2018, 11:58:18 »
Александр, большое вам спасибо за помощь! Я принял следующее решение. Не стал мудрить с перехватом и возвратом, сделал проще. Если модуль загружен и при загрузке найден TDMS (система документооборота), то он перехватывает команду "Связь Revit" полностью. Если же надо создать связь с файлом не находящимся в TDMS, то пользователь будет использовать "Диспетчер связей" и кнопку "Добавить...".
Код получился такой:
Код - C# [Выбрать]
  1.         RevitCommandId _linkRevitCommandId = null;
  2.  
  3.         public Result OnStartup(UIControlledApplication application)
  4.         {
  5.                 if (existTDMS())
  6.                 {
  7.                     _linkRevitCommandId = RevitCommandId.LookupPostableCommandId(PostableCommand.LinkRevit);
  8.                     if (_linkRevitCommandId.CanHaveBinding)
  9.                     {
  10.                         AddInCommandBinding commandBinding = application.CreateAddInCommandBinding(_linkRevitCommandId);
  11.                         commandBinding.Executed += CommandBinding_Executed;
  12.                     }
  13.                 }
  14.         }
  15.  
  16.         private void CommandBinding_Executed(object sender, Autodesk.Revit.UI.Events.ExecutedEventArgs e)
  17.         {
  18.             TDMS.TDMSApplication tdmsApplication = null;
  19.             initTDMS(ref tdmsApplication);
  20.  
  21.             if (tdmsApplication != null)
  22.             {
  23.                 TDMS.TDMSSelectObjectDlg dialog = tdmsApplication.Dialogs.SelectObjectDlg;
  24.                 dialog.ParentWindow = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle.ToInt32();
  25.                 if (dialog.Show())
  26.                 {
  27.                     Document document = (sender as UIApplication).ActiveUIDocument.Document;
  28.                     string fileFullName = document.PathName;
  29.                     Regex regex = new Regex(_alxdOptions.PathPattern, RegexOptions.IgnoreCase);
  30.                     MatchCollection matches = regex.Matches(fileFullName);
  31.  
  32.                     if (matches.Count < 1)
  33.                         return;
  34.  
  35.                     if (matches[0].Groups.Count < 4)
  36.                         return;
  37.  
  38.                     string documentGUID = matches[0].Groups[2].Value;
  39.  
  40.                     TDMS.TDMSObject selectedObject = dialog.Objects[0];
  41.                     if (selectedObject.GUID == documentGUID)
  42.                     {
  43.                         TaskDialog.Show("Предупреждение", string.Format("Выбран объект текущего файла. Файл не может ссылаться сам на себя!\nОбъект: {0}", selectedObject.Description));
  44.                     }
  45.                     else
  46.                     {
  47.                         Transaction tr = null;
  48.                         try
  49.                         {
  50.                             tdmsApplication.Commands["CMD_REVIT_OPEN_HOOK"].Execute(selectedObject);
  51.                             string selectedFile = tdmsApplication.Dictionary.Item("ALXDINTERCEPT");
  52.  
  53.                             if (string.IsNullOrEmpty(selectedFile))
  54.                                 TaskDialog.Show("Предупреждение", string.Format("Невозможно подготовить файлы для открытия у объекта!\nОбъект: {0}", selectedObject.Description));
  55.                             else
  56.                             {
  57.                                 if (HasRevitLinkByFilename(document, selectedFile))
  58.                                     TaskDialog.Show("Предупреждение", string.Format("Файл выбранного объекта уже вставлен в модель!\nФайл: {0}", selectedFile));
  59.                                 else
  60.                                 {
  61.                                     tr = new Transaction((sender as UIApplication).ActiveUIDocument.Document);
  62.                                     tr.Start("Add revit link from TDMS");
  63.                                     ModelPath mp = ModelPathUtils.ConvertUserVisiblePathToModelPath(selectedFile);
  64.                                     RevitLinkOptions rlo = new RevitLinkOptions(false);
  65.                                     var linkType = RevitLinkType.Create(document, mp, rlo);
  66.                                     var instance = RevitLinkInstance.Create(document, linkType.ElementId);
  67.                                     tr.Commit();                                    
  68.                                 }
  69.                             }
  70.                         }
  71.                         catch (Exception ex)
  72.                         {
  73.                             if (tr != null)
  74.                                 tr.RollBack();
  75.                             TaskDialog.Show("Ошибка", ex.Message);
  76.                         }                                            
  77.                     }
  78.                 }
  79.             }
  80.         }
  81.  
  82.         private bool existTDMS()
  83.         {
  84.             return Type.GetTypeFromProgID(_tdmsProgId) != null;
  85.         }
  86.         private void initTDMS(ref TDMS.TDMSApplication tdmsApplication)
  87.         {
  88.             Type tdmsType = Type.GetTypeFromProgID(_tdmsProgId);
  89.             if (tdmsType == null)
  90.             {
  91.                 TaskDialog.Show("Предупреждение", "Внимание! Невозможно корректно сохранить файл в TDMS, т.к. система TDMS не обнаружена на этом компьютере!", TaskDialogCommonButtons.Ok);
  92.                 return;
  93.             }
  94.  
  95.             try
  96.             {
  97.                 tdmsApplication = (TDMS.TDMSApplication)Microsoft.VisualBasic.Interaction.GetObject("", _tdmsProgId);
  98.                 tdmsApplication.Visible = true;
  99.             }
  100.             catch
  101.             {
  102.                 if (TaskDialog.Show("Предупреждение", "Внимание! Невозможно корректно сохранить файл в TDMS, т.к. система TDMS не обнаружена на этом компьютере!", TaskDialogCommonButtons.Yes & TaskDialogCommonButtons.No) == TaskDialogResult.Yes)
  103.                 {
  104.                     try
  105.                     {
  106.                         tdmsApplication = (TDMS.TDMSApplication)System.Activator.CreateInstance(tdmsType, true);
  107.                         tdmsApplication.Visible = true;
  108.                     }
  109.                     catch
  110.                     {
  111.                         tdmsApplication = null;
  112.                         TaskDialog.Show("Предупреждение", "Внимание! Сдача файлов в TDMS прервана, т.к. систему TDMS не удалось открыть!", TaskDialogCommonButtons.Ok);
  113.                         return;
  114.                     }
  115.                 }
  116.                 else
  117.                 {
  118.                     tdmsApplication = null;
  119.                     return;
  120.                 }
  121.             }
  122.         }
  123.  
  124.         public bool HasRevitLinkByFilename(Document doc, string linkFileName)
  125.         {
  126.             FilteredElementCollector collector = new FilteredElementCollector(doc);
  127.             collector.OfClass(typeof(RevitLinkInstance));
  128.             foreach (Element elem in collector)
  129.                 if ((elem as RevitLinkInstance).GetLinkDocument().PathName.ToLower().CompareTo(linkFileName.ToLower()) == 0)
  130.                 {
  131.                     collector.Dispose();
  132.                     return true;
  133.                 }
  134.             collector.Dispose();
  135.  
  136.             return false;
  137.         }
  138.