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

24/01/2015

Скрыть все элементы кроме элементов заданной системы

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

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

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

Код - C#: [Выделить]
  1. Parameter Namesystem = elem.get_Parameter(BuiltInParameter.RBS_SYSTEM_NAME_PARAM);
  2. View view = doc.ActiveView;
  3.  
  4. IList<ElementId> categories = new List<ElementId>();
  5. categories.Add(new ElementId(BuiltInCategory.OST_PlaceHolderDucts));
  6. categories.Add(new ElementId(BuiltInCategory.OST_DuctLinings));
  7. categories.Add(new ElementId(BuiltInCategory.OST_DuctInsulations));
  8. categories.Add(new ElementId(BuiltInCategory.OST_DuctTerminal));
  9. categories.Add(new ElementId(BuiltInCategory.OST_MechanicalEquipment));
  10.  
  11. IList<FilterRule> rules = new List<FilterRule>();
  12. rules.Add(ParameterFilterRuleFactory.CreateNotContainsRule(Namesystem.Id, Namesystem.AsString(), true));
  13.  
  14. ParameterFilterElement filter = null;
  15. using (Transaction t = new Transaction(doc, "Create and Apply Filter"))
  16. {
  17.     t.Start();
  18.     filter = ParameterFilterElement.Create(doc, "2222", categories, rules);
  19.     view.AddFilter(filter.Id);
  20.     t.Commit();
  21. }
  22. using (Transaction t = new Transaction(doc, "Set Visibility Appearance"))
  23. {
  24.     t.Start();
  25.     view.SetFilterVisibility(filter.Id, false);
  26.     t.Commit();
  27. }
  28.  

у меня не все элементы исчезают ,которые принадлежат другим системам. я так понимаю нужно добавить строки categories.Add(new ElementId(BuiltInCategory.OST_PlaceHolderDucts)); все что начинается на OST_Duct ?

То есть решение задачи сводится к созданию правильного фильтра и применения этого вида к фильтру.

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

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

Но, в метод ParameterFilterElement.Create в любом случае надо передать список категорий. В этом случае, условие фильтра получается таким: Выбрать все элементы всех категорий, у которых параметр ‘Имя системы’ не равен названию системы выбранного элемента.

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

Код - C#: [Выделить]
  1. var allCategories =
  2.     doc.Settings.Categories.OfType<Category>().Select(c => c.Id).ToList();
  3.  
  4. var filter = ParameterFilterElement
  5.     .Create(doc,
  6.         string.Format("Все элементы, кроме системы {0}", system.Name),
  7.         allCategories,
  8.         rules);

Первая неудача. Получим исключение 'One of the given categories is not filterable' (Одна из заданных категорий не может быть использована для фильтрации).

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

Немного покопавшись в файле справки к Revit API находим полезный класс ParameterFilterUtilities и не менее полезный метод ParameterFilterUtilities.GetAllFilterableCategories(). Метод возвращает список категорий, которые можно использовать для фильтрации. Причем возвращаемое значение метода – ICollection<ElementId> - точно такое же, как и необходимо использовать в метода ParameterFilterElement.Create.

Попытка номер 2.

Код - C#: [Выделить]
  1. var filter = ParameterFilterElement
  2.     .Create(doc,
  3.         string.Format("Все элементы, кроме системы {0}", system.Name),
  4.         ParameterFilterUtilities.GetAllFilterableCategories(),
  5.         rules);

Снова неудача. One of the given rules refers to a parameter that doesn’t apply to this filter’s categories. (Один из фильтров параметра не может быть применен к заданному списку категорий)

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

Таким образом, необходимо создать уже два фильтра.

  • Выбрать все элементы, категории которых НЕ имеют параметра Имя системы
  • Выбрать все элементы, которые имею параметр Имя системы, но название системы не соответствует заданному.

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

Снова смотрим в методы класса ParameterFilterUtilities. Конкретно того, что нам нужно, нет. Нам нужно на вход параметр, а на выходе получить список категорий. Но, тем не менее, есть подходящие методы, из которых мы можем извлечь то что нужно. ParameterFilterUtilities.GetFilterableParametersInCommon – возвращает список параметров, который доступны для заданного списка категорий.

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

В результате родился вот такой метод:

Код - C#: [Выделить]
  1. /// <summary>
  2. /// Возвращает список категорий, которые можно использовать для фильтрации
  3. /// по заданному параметру
  4. /// </summary>
  5. /// <param name="doc">Доеумент, в котором необходимо выполнить фильтрацию</param>
  6. /// <param name="bip">Встроенный параметр</param>
  7. /// <param name="inverse">Если true - то список будет инвертирован,
  8. /// Т.е. получим список категорий, которые нельзя использовать для фильтрации
  9. /// с данным параметром</param>
  10. /// <returns></returns>
  11. ICollection<ElementId> GetCategoriesApplicableForParameter(Document doc,
  12.     BuiltInParameter bip,
  13.     bool inverse = false)
  14. {
  15.     // Берем все категории, доступные для фильтрации
  16.     var allCategories = ParameterFilterUtilities.GetAllFilterableCategories();
  17.  
  18.     ICollection<ElementId> retResult = new List<ElementId>();
  19.  
  20.     // для каждой категории
  21.     foreach (ElementId categoryId in allCategories)
  22.     {               
  23.         // получаем список параметров, доступных для фильтрации с этой категорией
  24.         var applicableParameters =
  25.             ParameterFilterUtilities.GetFilterableParametersInCommon(doc, new[] { categoryId });
  26.  
  27.         // если среди параметров есть интересующий нас параметр
  28.         // добавляем его в коллекцию
  29.         if (applicableParameters.Contains(new ElementId(bip)))
  30.         {
  31.             retResult.Add(categoryId);
  32.         }
  33.     }
  34.  
  35.     // Инвертируем список, если необходимо.
  36.     if (inverse)
  37.         retResult =
  38.             allCategories.Where(x => !retResult.Contains(x)).ToList();
  39.  
  40.     return retResult;
  41. }

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

Код - C#: [Выделить]
  1. var categoriesWithSystem =
  2.     GetCategoriesApplicableForParameter(doc, BuiltInParameter.RBS_SYSTEM_NAME_PARAM);

И список категорий, не содержащих параметр Имя системы:

Код - C#: [Выделить]
  1. var categoriesWithoutSystemNameParameter =
  2.     GetCategoriesApplicableForParameter(doc, BuiltInParameter.RBS_SYSTEM_NAME_PARAM, true);

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

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

Код - C#: [Выделить]
  1. IList<FilterRule> rules = new List<FilterRule>();
  2.  
  3. var systems = systemName.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
  4.  
  5. foreach (var system in systems)
  6. {
  7.         rules.Add(ParameterFilterRuleFactory
  8.     .CreateNotContainsRule(new ElementId(BuiltInParameter.RBS_SYSTEM_NAME_PARAM),
  9.     system.Trim(), true));
  10. }

Полный код команды приведен ниже:

Код - C#: [Выделить]
  1. public Result Execute(
  2.     ExternalCommandData commandData,
  3.     ref string message,
  4.     ElementSet elements)
  5. {
  6.     UIApplication uiapp = commandData.Application;
  7.     UIDocument uidoc = uiapp.ActiveUIDocument;
  8.     Application app = uiapp.Application;
  9.     Document doc = uidoc.Document;
  10.  
  11.     Reference r;
  12.     try
  13.     {
  14.         r = uidoc.Selection.PickObject(ObjectType.Element,
  15.             new SystemElementFilter(),
  16.             "Выберите элемент системы");
  17.     }
  18.     catch (OperationCanceledException)
  19.     {
  20.         return Result.Cancelled;
  21.                
  22.     }
  23.  
  24.     var elem = doc.GetElement(r.ElementId);
  25.  
  26.     var systemNameParam =
  27.         elem.get_Parameter(BuiltInParameter.RBS_SYSTEM_NAME_PARAM);
  28.     if (systemNameParam == null)
  29.     {
  30.         message = "Вы выбрали не элемент системы";
  31.         return Result.Failed;
  32.     }
  33.  
  34.     var view = doc.ActiveView;
  35.  
  36.     var categoriesWithSystem =
  37.         GetCategoriesApplicableForParameter(doc, BuiltInParameter.RBS_SYSTEM_NAME_PARAM);
  38.  
  39.     var categoriesWithoutSystemNameParameter =
  40.         GetCategoriesApplicableForParameter(doc, BuiltInParameter.RBS_SYSTEM_NAME_PARAM, true);
  41.  
  42.     var systemName = systemNameParam.AsString();
  43.  
  44.     // элемент может принадлежать нескольким ситсемам
  45.     // в этом случае в параметре Имя системы они разделены запятыми
  46.     // создаем фильтр по условию И
  47.     IList<FilterRule> rules = new List<FilterRule>();
  48.  
  49.     var systems = systemName.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
  50.  
  51.     foreach (var system in systems)
  52.     {
  53.             rules.Add(ParameterFilterRuleFactory
  54.         .CreateNotContainsRule(new ElementId(BuiltInParameter.RBS_SYSTEM_NAME_PARAM),
  55.         system.Trim(), true));
  56.     }
  57.  
  58.  
  59.     using (var t = new Transaction(doc, "Изолирование системы"))
  60.     {
  61.         t.Start();
  62.  
  63.         //Прячем все элементы, которые не содержат параметр Имя системы
  64.         // НЕ ПРИМЕНЯТЬ ФИЛЬТР, ЕСЛИ ТРЕБУЕТСЯ СПРЯТАТЬ ТОЛЬКО СИСТЕМЫ
  65.         ParameterFilterElement filter1 =
  66.             ParameterFilterElement.Create(doc,
  67.                 "Все элементы без параметра Имя системы",
  68.                 categoriesWithoutSystemNameParameter);
  69.  
  70.         view.AddFilter(filter1.Id); // применяем фильтр к виду
  71.         view.SetFilterVisibility(filter1.Id, false); // прячем элементы
  72.  
  73.  
  74.         // Прячем элементы, которые не принадлежат выбранной системе
  75.         ParameterFilterElement filter2 =
  76.             ParameterFilterElement.Create(doc,
  77.                 string.Format("Все элементы не находящиеся в системах {0}", systemName),
  78.                 categoriesWithSystem,
  79.                 rules);
  80.  
  81.         view.AddFilter(filter2.Id);
  82.         view.SetFilterVisibility(filter2.Id, false);
  83.  
  84.         t.Commit();
  85.     }
  86.  
  87.     return Result.Succeeded;
  88. }

Класс для реализации выбора элемента системы.

Код - C#: [Выделить]
  1. public class SystemElementFilter : ISelectionFilter
  2. {
  3.   public bool AllowElement(Element elem)
  4.   {
  5.      var systemNameParam =
  6.        elem.get_Parameter(BuiltInParameter.RBS_SYSTEM_NAME_PARAM);
  7.      return systemNameParam != null && systemNameParam.HasValue;
  8.   } 
  9.   public bool AllowReference(Reference reference, XYZ position)
  10.   {
  11.      throw new NotImplementedException();
  12.   }
  13. }

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

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

Результат выполнения команды.

До выполнения:

 

После:

 

И, в случае с элементом, принадлежащим нескольким системам.

До:

 

После:

 
Автор: Виктор Чекалин
Автор перевода: Виктор Чекалин

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

Опубликовано 24.01.2015
Отредактировано 24.01.2015 в 16:59:48