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

07/04/2014

Выбор категорий объектов, видимых на нескольких видах

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

Это вторая часть реализации возможности редактирования помещения для упрощенного 2D BIM редактора.

В этой части мы рассмотрим пункты 4 и 5 моего плана:

  1. Запустить команду RoomAppEditor для экспорта планов этажей
  2. Отобразить список всех планов этажей на форме
  3. Выбрать виды, которые нужно экспортировать и нажать ОК.
  4. Отобразить категории на форме
  5. Выбрать категории и нажать ОК.
  6. Сохранить соответствующую графическую и другую информацию в облачной базе данных.
  7. Отобразить модель на мобильном устройстве.
  8. Отредактировать информацию, как графическую, так и нет.
  9. Обновить модель в Revit в реальном времени.

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

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

Для каждого вида мы определим элементы отображаемы на нем элементы, которые нам интересны. Из этих элементов мы выберем различные категории. В данном случае, под выражением «Различные категории» мы понимаем категории, у которых различные идентификаторы, т.е. ElementId.

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

В словаре сами категории используются в качестве ключа. Это позволяет нам с лёгкостью использовать этот словарь в качестве объектов для выбора на форме для 5ого шага.

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

  • CategoryEqualityComparer – Реализует сравнение категорий по их идентификатору.
  • CategoryCollector – Выбирает категории из элементов, отображаемых на заданных видах.
  • FrmSelectCategories – Форма для интерактивного выбора категорий пользователем
  • CmdUploadViews – Выбор видов и категорий.

CategoryEqualityComparer

Этот класс реализует интерфейс IEqualityComparer, который содержит два метода: Equals и GetHashCode.

Реализация основывается на сравнении идентификаторов категорий.

Код - C#: [Выделить]
  1.     /// <summary>
  2.     /// Категории с одинаковыми ИД являются одинаковыми.
  3.     /// Без этого сравнения получим кучу дубикатов   
  4.     /// </summary>
  5.     class CategoryEqualityComparer
  6.       : IEqualityComparer<Category>
  7.     {
  8.       public bool Equals( Category x, Category y )
  9.       {
  10.         return x.Id.IntegerValue.Equals(
  11.           y.Id.IntegerValue );
  12.       }
  13.  
  14.       public int GetHashCode( Category obj )
  15.       {
  16.         return obj.Id.IntegerValue.GetHashCode();
  17.       }
  18.     }

CategoryCollector

Класс CategoryCollector собирает различные категории из всех элементов, отображаемых на заданных видах.

Нам интересны только те элементы, который мы желаем видеть в моем облачном BIM редакторе и просмотрщике.

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

Ранее мы уже несколько раз обсуждали как выбрать все элементы из модели, или видимые 3-D элементы в проекте несколькими разными спообами:

Последние 3 способа особенно эффективны.

В этом же случае, я выбрал комбинацию двух из способов, используя FilteredElementCollector передав ему идентификатор вида, а также дополнительно проверяю свойство Category.HasMaterialQuantities.

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

Все действия происходят в конструкторе класса:

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Выбирает все категории всех видимых элементов
  3.   /// на заданном наборе видов
  4.   /// </summary>
  5.   class CategoryCollector : Dictionary<Category, int>
  6.   {
  7.     /// <summary>
  8.     /// Количество видов
  9.     /// </summary>
  10.     int _nViews;
  11.  
  12.     /// <summary>
  13.     /// Общее количество элементов на выбранных видах
  14.     /// включая дублирующиеся элементы.
  15.     /// </summary>
  16.     int _nElements;
  17.  
  18.     /// <summary>
  19.     /// Количество элементов, категория которых
  20.     /// имеест свойство HasMaterialQuantities на всех видах
  21.     /// включая дублирующиеся элементы.
  22.     /// </summary>
  23.     int _nElementsWithCategorMaterialQuantities;
  24.  
  25.     public CategoryCollector( IList<ViewPlan> views )
  26.       : base( new CategoryEqualityComparer() )
  27.     {
  28.       _nViews = views.Count;
  29.       _nElements = 0;
  30.       _nElementsWithCategorMaterialQuantities = 0;
  31.  
  32.       if( 0 < _nViews )
  33.       {
  34.         Document doc = views[0].Document;
  35.  
  36.         FilteredElementCollector a;
  37.        
  38.         foreach( View v in views )
  39.         {
  40.           a = new FilteredElementCollector( doc, v.Id )
  41.             .WhereElementIsViewIndependent();
  42.  
  43.           foreach( Element e in a )
  44.           {
  45.             ++_nElements;
  46.  
  47.             Category cat = e.Category;
  48.  
  49.             if( null != cat
  50.               && cat.HasMaterialQuantities )
  51.             {
  52.               ++_nElementsWithCategorMaterialQuantities;
  53.  
  54.               if( !ContainsKey( cat ) )
  55.               {
  56.                 Add( cat, 0 );
  57.               }
  58.               ++this[cat];
  59.             }
  60.           }
  61.         }
  62.       }
  63.       Debug.Print( "Выбрано {0} категорий из "
  64.         + "{1} видов с {2} элементами, "
  65.         + "{3} элементов с HasMaterialQuantities=true",
  66.         Count,
  67.         _nViews,
  68.         _nElements,
  69.         _nElementsWithCategorMaterialQuantities );
  70.     }
  71.   }

Я выполнил этот код на очень простой модели, содержащей всего три вида: Уровень 1, Уровень 2 и Площадка. В результате осталось всего 5 категорий для выбора.

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

  • 14 категорий – все элементы, выбранные на заданном наборе видов
  • 13 категорий – категории, которые не зависят от вида
  • 12 категорий – элементы, которые имеют границы
  • 5 категорий - элементы, у которых свойство HasMaterialQuantities = true;

FrmSelectCategories

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

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

Код - C#: [Выделить]
  1.   public partial class FrmSelectCategories : Form
  2.   {
  3.     IList<Category> _categories;
  4.  
  5.     /// <summary>
  6.     /// Инициализируем класс, передав
  7.     /// список категорий для выбора
  8.     /// </summary>
  9.     /// <param name="categories"></param>
  10.     public FrmSelectCategories(
  11.       IList<Category> categories )
  12.     {
  13.       InitializeComponent();
  14.  
  15.       _categories = categories;
  16.     }
  17.  
  18.     /// <summary>
  19.     /// При загрузке формы
  20.     /// выделяем по умолчанию все категории   
  21.     /// </summary>
  22.     private void FrmSelectCategories_Load(
  23.       object sender,
  24.       EventArgs e )
  25.     {
  26.       checkedListBox1.DataSource = _categories;
  27.       checkedListBox1.DisplayMember = "Name";
  28.  
  29.       // Set all entries to be initially checked.
  30.  
  31.       int n = checkedListBox1.Items.Count;
  32.  
  33.       for( int i = 0; i < n; ++i )
  34.       {
  35.         checkedListBox1.SetItemChecked( i, true );
  36.       }
  37.     }
  38.  
  39.     /// <summary>
  40.     /// Получаем доступ к выбранным категориям   
  41.     /// </summary>
  42.     public List<Category> GetSelectedCategories()
  43.     {
  44.       return checkedListBox1.CheckedItems
  45.         .Cast<Category>().ToList<Category>();
  46.     }
  47.   }

CmdUploadViews

Категории, полученные с помощью CategoryCollector были отсортированы по алфавиту, перед тем как добавить их на форму со списком для выбора пользователем.

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

Ниже представлен код реализации команды:

Код - C#: [Выделить]
  1.   [Transaction( TransactionMode.ReadOnly )]
  2.   public class CmdUploadViews : IExternalCommand
  3.   {
  4.     public Result Execute(
  5.       ExternalCommandData commandData,
  6.       ref string message,
  7.       ElementSet elements )
  8.     {
  9.       IWin32Window revit_window
  10.         = new JtWindowHandle(
  11.           ComponentManager.ApplicationWindow );
  12.  
  13.       UIApplication uiapp = commandData.Application;
  14.       UIDocument uidoc = uiapp.ActiveUIDocument;
  15.       Application app = uiapp.Application;
  16.       Document doc = uidoc.Document;
  17.  
  18.       if( null == doc )
  19.       {
  20.         Util.ErrorMsg( "ВЫполните команду"
  21.           + " при открытом проекте." );
  22.         return Result.Failed;
  23.       }
  24.  
  25.       FrmSelectViews form = new FrmSelectViews( doc );
  26.  
  27.       if( DialogResult.OK == form.ShowDialog(
  28.         revit_window ) )
  29.       {
  30.         List<ViewPlan> views = form.GetSelectedViews();
  31.  
  32.         int n = views.Count;
  33.  
  34.         string caption = string.Format(
  35.           "{0} Планов этажей выбрано",
  36.           n);
  37.  
  38.         string list = string.Join( ", ",
  39.           views.Select<Element, string>(
  40.             e => e.Name ) );
  41.  
  42.         Util.InfoMsg2( caption, list );
  43.  
  44.         List<Category> categories
  45.           = new List<Category>(
  46.             new CategoryCollector( views ).Keys );
  47.  
  48.         // Sort categories alphabetically by name
  49.         // to display them in selection form.
  50.  
  51.         categories.Sort(
  52.           delegate( Category c1, Category c2 )
  53.           {
  54.             return string.Compare( c1.Name, c2.Name );
  55.           } );
  56.  
  57.         FrmSelectCategories form2
  58.           = new FrmSelectCategories( categories );
  59.  
  60.         if( DialogResult.OK == form2.ShowDialog(
  61.           revit_window ) )
  62.         {
  63.           categories = form2.GetSelectedCategories();
  64.  
  65.           n = categories.Count;
  66.  
  67.           caption = string.Format(
  68.             "{0} Категорий выбрано",
  69.             n);
  70.  
  71.           list = string.Join( ", ",
  72.             categories.Select<Category, string>(
  73.               e => e.Name ) );
  74.  
  75.           Util.InfoMsg2( caption, list );
  76.         }
  77.       }
  78.       return Result.Succeeded;
  79.     }
  80.   }

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

Форма выбора видов:

 

После выбора видов:

 

Выбор категорий:

 

Отчет по выбору категорий:

 

Загрузить проект можно на GitHub.

Источник: http://thebuildingcoder.typepad.com/blog/2014/03/selecting-visible-categories-from-a-set-of-views.html

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

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