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

25/12/2013

Работа с семействами. Загрузка семейства и вставка экземпляра семейства

Начнем небольшую серию статей о работе с семействами и с редактором семейств с помощью Revit API.

Рассмотрим два главных аспекта работы с семействами с точки зрения программиста:

  1. Создание семейства. Т.е. работа с редактором семейства.
  2. Использование семейства. В большинстве случаев используются в файле проекта.

Однако вставить семейство можно не только в проект, но и также в другое семейство. Поэтому второй аспект может рассматриваться, как и в контексте файла проекта так и в контексте редактора семейств. В него входят такие задачи как загрузка семейства, вставка экземпляра семейства, работа с типоразмерами и с существующими экземплярами семейств.

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

До Revit 2010 только второй пункт можно было реализовать программно. Программное создание семейства и в целом работа в редакторе семейств была невозможна до появления API для работы с семействами (Family API).

Создание нового семейства с нуля с помощью API подробно описано и продемонстрирован в материалах курса ADN Revit API (на англ.).

Также недавно мы обсуждали как создать и вставить выдавливание в редакторе семейства (на англ.).  Тот пример является более легким и простым примером как создать новое семейство и затем вставить его в проект не углубляясь в такие сложные детали как установка выравнивание и создание типоразмеров.

Статьи по API для работы с семействами завершаются списком прошлых тем которые Стив и я решили снова упомянуть:

  1. Загрузка семейства и размещение экземпляров семейства.
  2. Выбор экземпляра семейства в интерфейсе и редактирование экземпляров
  3. Извлечение типоразмеров и экземпляров семейств в проекте, изменение типоразмеров.

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

  1. CmdTableLoadPlace
  2. CmdTableNewTypeModify
  3. CmdKitchenUpdate

Рассмотрим более подробно реализацию этих команд.

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

Загрузка семейства и размещение экземпляров семейства

Внешняя команда CmdTableLoadPlace демонстрирует загрузку семейства и размещение экземпляра семейства в проекте.

Перед загрузкой семейства предварительно происходит проверка, не загружено ли уже семейство в проект.

Размещение экземпляра семейства происходит автоматически или получив от пользователя требуемое месторасположение.

В первом случае используется один из множества перегруженных методов NewFamilyInstance для автоматической вставки экземпляра семейства. Подробно мы его обсуждать в рамках данной статьи не будем.

Во втором – метод PromptForFamilyInstancePlacement, с помощью которого пользователь самостоятельно в интерфейсе выбирает месторасположение экземпляра.

Проверяем загружено ли семейство в проект

Перед тем как вставлять экземпляр любого семейства в проект, необходимо загрузить само семейство.

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

Всякое извлечение объектов из внутренней базы данных Revit происходит с помощью класса FilteredElementColector. Для наиболее эффективного результата выбора элементов по фильтру, как с точки зрения производительности, так и с точки зрения точности полученных результатов, необходимо задать как можно больше фильтров.

Лучше всего использовать «быстрые» фильтры, так как они позволяют отсеять ненужные элементы без предварительной загрузки элементов в память. «Медленные» фильтры также довольно эффективны с точки зрения производительности, так как проверка свойств и фильтрация происходит в рамках выделенной Revit’у памяти. И самый медленный способ – обработка элементов с помощью кода .NET после того как элементы были извлечены из базы данных Revit.

Предполагая, что в проекте у нас содержится не очень большое количество семейств, мы можем их все получить с помощью фильтра OfClass(typeof(Family)) и затем произвести дополнительную фильтрацию напрямую в .NET с помощью LINQ и стандартного метода FirstOrDefault:

Код - C#: [Выделить]
  1.   FilteredElementCollector a
  2.     = new FilteredElementCollector( doc )
  3.       .OfClass( typeof( Family ) );
  4.  
  5.   Family family = a.FirstOrDefault<Element>(
  6.     e => e.Name.Equals( FamilyName ) )
  7.       as Family;

Поиск элемента по типу и наименованию

Мы уже проверили находится ли семейство в проекте или нет, извлекая все элементы типа Family и получив конкретное семейство по его наименованию.

Такой подход может использоваться с различных видах поиска элементов в Revit, однако всегда необходимо помнить об оптимизации.

В этой надстройке, я сделал фильтрацию как можно проще без какой-либо оптимизации.

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Поиск элемента
  3.   /// по его типу и наименованию
  4.   /// </summary>
  5.   public static Element FindElementByName(
  6.     Document doc,
  7.     Type targetType,
  8.     string targetName )
  9.   {
  10.     return new FilteredElementCollector( doc )
  11.       .OfClass( targetType )
  12.       .FirstOrDefault<Element>(
  13.         e => e.Name.Equals( targetName ) );
  14.   }

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

  • Поиск конкретного семейства для проверки загружено ли семейство или нет
  • Поиск типоразмера семейства для проверки есть ли такой типоразмер в проекте или нет
  • Поиск определённого материала, который необходимо задать типоразмеру

Загрузка семейства

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

Код - C#: [Выделить]
  1.   if( null == family )
  2.   {
  3.     // Проверим наличие файла семейства
  4.     // перед загрузкой его в проект
  5.     if( !File.Exists( FamilyPath ) )
  6.     {
  7.       Util.ErrorMsg( string.Format(
  8.         "Проверьте наличие "
  9.         + "файла семейства '{0}' в директории '{1}'.",
  10.         FamilyName, _family_folder ) );
  11.       return Result.Failed;
  12.     }
  13.  
  14.     // Загрузка семейства из файла.
  15.     using( Transaction tx = new Transaction( doc ) )
  16.     {
  17.       tx.Start( "Загрузка семейства" );
  18.       doc.LoadFamily( FamilyPath, out family );
  19.       tx.Commit();
  20.     }
  21.   }

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

Загрузка семейства изменяет базу данных. Поэтому необходимо наличие транзакции.

Если же семейство содержит большое количество типоразмеров, а вам нужны лишь некоторые из них, то для более эффективного расходования ресурсов и увеличения быстродействия можно загрузить лишь требуемые типоразмеры по очереди с помощью метода LoadFamilySymbol вместо того чтобы загружать семейство целиком с помощью метода LoadFamily.

Размещение экземпляра семейства

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

Единственным параметром этого метода является типоразмер (FamilySymbol), экземпляр которого мы хотим поместить в проект.

Код - C#: [Выделить]
  1.   // Определим типоразмер
  2.   FamilySymbol symbol = null;
  3.   foreach( FamilySymbol s in family.Symbols )
  4.   {
  5.     symbol = s;
  6.  
  7.     // Так как наше тестовое семейство содержит только один типоразмер
  8.     // то возьмем первое и выйдем из цикла.
  9.     break;
  10.   }
  11.  
  12.   // Разместим экземпляр выбранного типоразмера:
  13.   // !Метод PromptForFamilyInstancePlacement нельзя  
  14.   // использовать внутри транзакции
  15.   uidoc.PromptForFamilyInstancePlacement( symbol );
  16.  

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

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

В теории, метод PromptForFamilyInstancePlacement мог бы возвратить список идентификаторов только что созданных объектов. Но так как метод ничего не возвращает, то придётся немного извертеться, чтобы добраться до созданных объектов. Можно перед тем как вызывать метод вставки экземпляра семейства, временно подписаться на событие DocumentChanged, которое возникает при изменении документа. И, после выполнения метода, сразу же отписаться от этого события. В обработчике события мы можем получить идентификаторы объектов, которые мы создали и нужно где-то их запомнить.

Я написал вот такой код:

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Коллекция добавленных элементов
  3.   /// </summary>
  4.   List<ElementId> _added_element_ids
  5.     = new List<ElementId>();
  6.  
  7.   void OnDocumentChanged(
  8.     object sender,
  9.     DocumentChangedEventArgs e )
  10.   {
  11.     _added_element_ids.AddRange(
  12.       e.GetAddedElementIds() );
  13.   }  

Ниже представлен полный исходный код команды, где видна подписка на событие.

Исходный код команды CmdTableLoadPlace

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

Код - C#: [Выделить]
  1.   /// <summary>
  2. /// Загрузка семейства в проект, если оно еще не загружено
  3. /// и размещение экземпляра этого семейства в проекте.
  4. /// </summary>
  5. [Transaction( TransactionMode.Manual )]
  6. public class CmdTableLoadPlace : IExternalCommand
  7. {
  8.   /// <summary>
  9.   ///Наименование семейства.
  10.   /// </summary>
  11.   public const string FamilyName = "family_api_table";
  12.  
  13.   /// <summary>
  14.   /// Путь к папке с семействами.
  15.   /// Вы можете воспользоваться методом
  16.   /// Application.GetLibraryPaths
  17.   /// для получения пути где размещаются файлы семейств.
  18.   /// В данном случае для простоты возьмём файл семейства оттуда же где и исходники
  19.   /// </summary>
  20.   //const string _family_folder = "Z:/a/rvt";
  21.   static string _family_folder
  22.     = Path.GetDirectoryName(
  23.       typeof( CmdTableLoadPlace )
  24.         .Assembly.Location );
  25.  
  26.   /// <summary>
  27.   /// Расширение файла семейств - RFA.
  28.   /// </summary>
  29.   const string _family_ext = "rfa";
  30.  
  31.   /// <summary>
  32.   /// Путь к файлу семейства.
  33.   /// </summary>
  34.   static string _family_path = null;
  35.  
  36.   /// <summary>
  37.   /// Возвращает полный путь к файлу семейства
  38.   /// </summary>
  39.   static string FamilyPath
  40.   {
  41.     get
  42.     {
  43.       if( null == _family_path )
  44.       {
  45.         _family_path = Path.Combine(
  46.           _family_folder, FamilyName );
  47.  
  48.         _family_path = Path.ChangeExtension(
  49.           _family_path, _family_ext );
  50.       }
  51.       return _family_path;
  52.     }
  53.   }
  54.  
  55.   /// <summary>
  56.   /// Коллекция созданных объектов
  57.   /// </summary>
  58.   List<ElementId> _added_element_ids
  59.     = new List<ElementId>();
  60.  
  61.   /// <summary>
  62.   /// Главный метод внешней команды
  63.   /// </summary>
  64.   public Result Execute(
  65.     ExternalCommandData commandData,
  66.     ref string message,
  67.     ElementSet elements )
  68.   {
  69.     UIApplication uiapp = commandData.Application;
  70.     UIDocument uidoc = uiapp.ActiveUIDocument;
  71.     Application app = uiapp.Application;
  72.     Document doc = uidoc.Document;
  73.  
  74.     // Получаем семейство если оно уже загружено в проект
  75.     Family family = Util.FindElementByName(
  76.       doc, typeof( Family ), FamilyName ) as Family;
  77.     if( null == family )
  78.     {
  79.       // если его еще нет в проекте
  80.       // проверяем существует ли файл
  81.  
  82.       if( !File.Exists( FamilyPath ) )
  83.       {
  84.         Util.ErrorMsg( string.Format(
  85.         "Проверьте наличие "
  86.         + "файла семейства '{0}' в директории '{1}'.",
  87.         FamilyName, _family_folder ) );
  88.  
  89.         return Result.Failed;
  90.       }
  91.  
  92.       // Загрузка семейства из файла:
  93.       using( Transaction tx = new Transaction( doc ) )
  94.       {
  95.         tx.Start( "Загрузка семейства" );
  96.         doc.LoadFamily( FamilyPath, out family );
  97.         tx.Commit();
  98.       }
  99.     }
  100.  
  101.     // Определяем типоразмер
  102.     FamilySymbol symbol = null;
  103.  
  104.     foreach( FamilySymbol s in family.Symbols )
  105.     {
  106.       symbol = s;
  107.  
  108.     // Так как наше тестовое семейство содержит только один типоразмер
  109.     // то возьмем первое и выйдем из цикла.
  110.       break;
  111.     }
  112.  
  113.     // Размещаем типоразмер:
  114.     //Подписываемся на событие DocumentChanged
  115.     // Для того чтобы получить идентификаторы элементов
  116.     //  Созданных при помощи метода PromptForFamilyInstancePlacement :
  117.     app.DocumentChanged
  118.       += new EventHandler<DocumentChangedEventArgs>(
  119.         OnDocumentChanged );
  120.  
  121.     _added_element_ids.Clear();
  122.  
  123.     // !Метод PromptForFamilyInstancePlacement нельзя  
  124.   // использовать внутри транзакции
  125.  
  126.     uidoc.PromptForFamilyInstancePlacement( symbol );
  127.  
  128.     app.DocumentChanged
  129.       -= new EventHandler<DocumentChangedEventArgs>(
  130.         OnDocumentChanged );
  131.  
  132.     // Получаем доступ к созданным элементам:
  133.  
  134.     int n = _added_element_ids.Count();
  135.  
  136.     string msg = string.Format(
  137.       "Размещено {0} экземпляров семейства {1} {2}{3}",
  138.       n, family.Name,
  139.       Util.PluralSuffix( n ),
  140.       Util.DotOrColon( n ) );
  141.  
  142.     string ids = string.Join( ", ",
  143.       _added_element_ids.Select<ElementId, string>(
  144.         id => id.IntegerValue.ToString() ) );
  145.  
  146.     Util.InfoMsg2( msg, ids );
  147.  
  148.     return Result.Succeeded;
  149.   }
  150.  
  151.   void OnDocumentChanged(
  152.     object sender,
  153.     DocumentChangedEventArgs e )
  154.   {
  155.     _added_element_ids.AddRange(
  156.       e.GetAddedElementIds() );
  157.   }
  158. }
  159.  

 

Как я сказал, подробное описание двух других команд будет в ближайшее время. Проект Visual Studio доступен в обзоре статей по API семейств.

 

Источник: http://thebuildingcoder.typepad.com/blog/2013/06/family-api-add-in-load-family-and-place-instances.html

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

Опубликовано 25.12.2013
Отредактировано 25.12.2013 в 11:55:09