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

27/12/2013

API для работы с семействами. Создание типоразмеров. Выделение и изменение экземпляров семейства

Добро пожаловать во втроую часть подробного обсуждения API для работы с семействами.

Основная функциональность работы с семействами была представлена Стивеном Кэмпбелом (Steven Campbell) в его докладе Ключевые концепции при работе с семействами на Revit DevCamp в Москве. Первую часть статей по работе с семействами можете прочитать здесь.

Будет еще и третяя заключительная часть, которую мы рассмотрим чуть позднее.

Итак, начнем.

Часть 2 - Выбор и редактирование экземпляров семейства

Рассмотрим редактирование заданного экземпляра семейства на примере создания нового типоразмера и применения созданного типоразмера на экземпляре.

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

Создание нового типоразмера

Создать новый типоразмер для уже загруженного семейства на самом деле очень легко, так как достаточно просто вызвать метод Duplicate для копирования уже существующего типоразмера.

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

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

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

Код - C#: [Выделить]
  1.   /// <summary>
  2.  /// Устанавливает значение параметра по его наименованию
  3.  /// заданного элемента в миллиметрах
  4.  /// </summary>
  5.  void SetElementParameterInMm(
  6.    Element e,
  7.    string parameter_name,
  8.    double lengthInMm )
  9.  {
  10.    e.get_Parameter( parameter_name )
  11.      .Set( Util.MmToFoot( lengthInMm ) );
  12.  }  

Кроме этого, мы изменим материал нового типоразмера нашего стола на Стекло. Материал определяется двумя параметрами: материал столешницы и материал ножек. Значение параметров задающих материал являются идентификаторами материала в соответствующей внутренней базе данных Revit.

Реализация метода CreateNewType, принимающего в качества параметра исходный типоразмер, который нужно скопировать, представлена ниже:

Код - C#: [Выделить]
  1.   /// <summary>
  2.  /// Создание нового типоразмера
  3.  /// Параметры для изменения
  4.  /// Ширина = 1200
  5.  /// Глубина = 750
  6.  /// Высота = 380
  7.  /// Материал столешницы  = Стекло
  8.  /// Материал ножек = Стекло
  9.  /// </summary>
  10.  FamilySymbol CreateNewType( FamilySymbol oldType )
  11.  {
  12.    FamilySymbol sym = oldType.Duplicate(
  13.      _type_name ) as FamilySymbol;
  14.  
  15.    SetElementParameterInMm( sym, "Ширина", 1200 );
  16.    SetElementParameterInMm( sym, "Глубина", 750 );
  17.    SetElementParameterInMm( sym, "Высота", 380 );
  18.  
  19.    Element material_glass = Util.FindElementByName(
  20.      sym.Document, typeof( Material ), "Стекло" );
  21.  
  22.    ElementId id = material_glass.Id;
  23.  
  24.    sym.get_Parameter( "Материал столешницы" ).Set( id );
  25.    sym.get_Parameter( "Материал столешницы" ).Set( id );
  26.    return sym;
  27.  }

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

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

В данном случае нам нужно найти типоразмер (класс FamilySymbol) с соответствующим именем.

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

Поиск элемента по его наименованию можно также ускорить, если применить фильтр по значению параметра, вместо того, чтобы использовать фильтрацию средствами .NET, когда Revit уже выдал результат поиска.

Вызов вспомогательного метода и поиск существующего типоразмера можно выполнить одной строкой кода, использовав оператор ‘??’. Оператор проверяет значение правой части выражения. Если оно равно null, то выполняется левая часть, если же не равно, то берется значение правой части:

Код - C#: [Выделить]
  1.  
  2.   // Найти существующий типоразмер или создать новый
  3.   FamilySymbol symbol
  4.     = Util.FindElementByName( doc,
  5.       typeof( FamilySymbol ), _type_name )
  6.         as FamilySymbol
  7.     ?? CreateNewType( tables[0].Symbol );
  8.  


Работа с выбранными экземплярами семейств.

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

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

Первый, до выполнения команды. Необходимо выделить нужные элементы и затем получить к ним доступ с помощью свойства UIDocument.Selection.

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

Я реализовал два этих способа в отдельном классе TableSelector. Работать с этим классом достаточно легко:

Код - C#: [Выделить]
  1.   TableSelector selector
  2.     = new TableSelector( uidoc );
  3.  
  4.   IList<FamilyInstance> tables
  5.     = selector.SelectedTables;
  6.  
  7.   if( null == tables )
  8.   {
  9.     return selector.Return();
  10.   }

Как можно заметить класс TableSelector содержит три публичных метода:

  • Конструктор, в котором и выполняются все необходимые действия
  • Свойство SelectedTables, которое возвращает результат выделения
  • Метод Return, который определяет результат: успешно, с ошибкой или отмена операции

В нем так же содержится метод IsTable, в котором определяется является ли объект столом или нет.

В его реализации я проверяю, что категорией элемента является Мебель и что элемент является экземпляром семейства, а также что наименование типоразмера совпадает с нужным нам наименованием, которое мы определили заранее:

Код - C#: [Выделить]
  1. static public bool IsTable( Element e )
  2. {
  3.   bool rc = false;
  4.  
  5.   Category cat = e.Category;
  6.  
  7.   if( null != cat )
  8.   {
  9.     if( cat.Id.IntegerValue.Equals(
  10.       (int) BuiltInCategory.OST_Furniture ) )
  11.     {
  12.       FamilyInstance fi = e as FamilyInstance;
  13.  
  14.       if( null != fi )
  15.       {
  16.         string fname = fi.Symbol.Family.Name;
  17.  
  18.         rc = fname.Equals(
  19.           CmdTableLoadPlace.FamilyName );
  20.       }
  21.     }
  22.   }
  23.   return rc;
  24. }

Используя этот вспомогательный метод, реализация фильтра для выбора стола становится довольно простой:

Код - C#: [Выделить]
  1. class TableSelectionFilter : ISelectionFilter
  2. {
  3.   public bool AllowElement( Element e )
  4.   {
  5.     return IsTable( e );
  6.   }
  7.  
  8.   public bool AllowReference( Reference r, XYZ p )
  9.   {
  10.     return true;
  11.   }
  12. }

Целиком класс TableSelector выглядит следующим образом:

Код - C#: [Выделить]
  1. class TableSelector
  2. {
  3.   const string _usage_error = "Пожалуйста, выберите предварительно "
  4.     + "экземпляр семейства Стол "
  5.     + "перед тем как запускать команду.";
  6.  
  7.   List<FamilyInstance> _tables;
  8.   string _msg;
  9.   Result _result;
  10.  
  11.   /// <summary>
  12.   /// Возвращает выделенные столы или null
  13.   /// </summary>
  14.   public IList<FamilyInstance> SelectedTables
  15.   {
  16.     get
  17.     {
  18.       return _tables;
  19.     }
  20.   }
  21.  
  22.   /// <summary>
  23.   /// Инициализация TableSelector
  24.   /// </summary>
  25.   public TableSelector( UIDocument uidoc )
  26.   {
  27.     _tables = null;
  28.     _msg = null;
  29.  
  30.     Document doc = uidoc.Document;
  31.  
  32.     if( null == doc )
  33.     {
  34.       _msg = "Пожалуйста запустите команду из файла проекта";
  35.       _result = Result.Failed;
  36.     }
  37.  
  38.     // Проверка на выбор стола перед запуском команды.
  39.  
  40.     Selection sel = uidoc.Selection;
  41.  
  42.     int n = sel.Elements.Size;
  43.  
  44.     if( 0 < n )
  45.     {
  46.       if( 1 != n )
  47.       {
  48.         _msg = _usage_error;
  49.         _result = Result.Failed;
  50.       }
  51.  
  52.       foreach( Element e in sel.Elements )
  53.       {
  54.         if( !IsTable( e ) )
  55.         {
  56.           _msg = _usage_error;
  57.           _result = Result.Failed;
  58.         }
  59.  
  60.         if( null == _tables )
  61.         {
  62.           _tables = new List<FamilyInstance>( n );
  63.         }
  64.  
  65.         _tables.Add( e as FamilyInstance );
  66.       }
  67.     }
  68.  
  69.     // Если Столы не выбраны перед выполнением команды,
  70.     // просим пользователя их выбрать.
  71.  
  72.     if( null == _tables
  73.       || 0 == _tables.Count )
  74.     {
  75.       IList<Reference> refs = null;
  76.  
  77.       try
  78.       {
  79.         refs = sel.PickObjects( ObjectType.Element,
  80.           new TableSelectionFilter(),
  81.           "Пожалуйста выберите столы для изменения" );
  82.       }
  83.       catch( Autodesk.Revit.Exceptions
  84.         .OperationCanceledException )
  85.       {
  86.         _result = Result.Cancelled;
  87.       }
  88.  
  89.       if( null != refs && 0 < refs.Count )
  90.       {
  91.         _tables = new List<FamilyInstance>(
  92.           refs.Select<Reference, FamilyInstance>(
  93.             r => doc.GetElement( r.ElementId )
  94.               as FamilyInstance ) );
  95.       }
  96.     }
  97.  
  98.     Debug.Assert(
  99.       null == _tables || 0 < _tables.Count,
  100.       "Удостоверьтесь, что столы выбраны" );
  101.  
  102.     _result = (null == _tables) // || 0 == _tables.Count
  103.       ? Result.Cancelled
  104.       : Result.Succeeded;
  105.   }
  106.   /// <summary>
  107.   /// Возвращает отмену или код возврата
  108.   /// или сообщение об ошибке
  109.   /// </summary>
  110.   public Result Return()
  111.   {
  112.     if( Result.Failed == _result )
  113.     {
  114.       Debug.Assert( 0 < _msg.Length,
  115.         "Ожидаем сообщение об ошибки" );
  116.  
  117.       Util.ErrorMsg( _msg );
  118.     }
  119.     return _result;
  120.   }
  121. }

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

Изменение типоразмера для выбранного экземпляра семейства

Мы уже получили типоразмер, выбрав его из существующих или создав новый, выбрали экземпляры семейства, типоразмер которого мы хотим поменять. Осталось самое простое – изменить типоразмер. Сделать это довольно легко. Нужно перебрать все выбранные экземпляры и изменить свойство Symbol.

Полный код команды реализующий изменение типоразмера:

Код - C#: [Выделить]
  1. /// <summary>
  2. /// Создание нового типоразмера для семейства Стол
  3. /// и применение типоразмера для выбранных элементов
  4. /// </summary>
  5. [Transaction( TransactionMode.Manual )]
  6. public class CmdTableNewTypeModify : IExternalCommand
  7. {
  8.   const string _usage_error = "Пожалуйста, выберите предварительно "
  9.     + "экземпляр семейства Стол "
  10.     + "перед тем как запускать команду.";
  11.  
  12.   /// <summary>
  13.   /// Наименование нового типоразмера стола
  14.   /// </summary>
  15.   const string _type_name = "1200x750x380мм";
  16.  
  17.   public Result Execute(
  18.     ExternalCommandData commandData,
  19.     ref string message,
  20.     ElementSet elements )
  21.   {
  22.     UIApplication uiapp = commandData.Application;
  23.     UIDocument uidoc = uiapp.ActiveUIDocument;
  24.     Application app = uiapp.Application;
  25.     Document doc = uidoc.Document;
  26.  
  27.     TableSelector selector
  28.       = new TableSelector( uidoc );
  29.  
  30.     IList<FamilyInstance> tables
  31.       = selector.SelectedTables;
  32.  
  33.     if( null == tables )
  34.     {
  35.       return selector.Return();
  36.     }
  37.  
  38.     using( Transaction tx = new Transaction( doc ) )
  39.     {
  40.       tx.Start( "Создание типоразмера и применение его к экземпляру" );
  41.  
  42.       // Поиск существующего типоразмера или создание нового
  43.  
  44.       FamilySymbol symbol
  45.         = Util.FindElementByName( doc,
  46.           typeof( FamilySymbol ), _type_name )
  47.             as FamilySymbol
  48.         ?? CreateNewType( tables[0].Symbol );
  49.  
  50.       foreach( FamilyInstance table in tables )
  51.       {
  52.         Debug.Print( Util.ElementDescription(
  53.           table ) );
  54.  
  55.         table.Symbol = symbol;
  56.       }
  57.  
  58.       tx.Commit();
  59.     }
  60.     return Result.Succeeded;
  61.   }
  62. }

Для скачивания полного готового проекта для VisualStudio посетите страничку с обсуждением API для работы с семействами.

Источник: http://thebuildingcoder.typepad.com/blog/2013/07/family-api-create-type-select-and-modify-instances.html

 

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

Опубликовано 27.12.2013
Отредактировано 29.12.2013 в 13:55:07