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

08/02/2014

Удаление неиспользуемых опорных плоскостей

Как-то во время проведения тренинга по Revit API, участники тренинга придумали вопрос, решение которого можно использовать для демонстрации использования FilteredElementCollector. В результате получилось вполне полезная команда. Необходимо было удалить все опорные плоскости, которые не содержат ни одного элемента. Так я создал команду DeleteUnnamedNonHostingReferencePlanes.

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

Вот тема на форуме(англ.), где обсуждалось как отфильтровать опорные плоскости и описано как использовать эту команду.

Перед удалением необходимо проверить два условия. Первое условие – опорная плоскость имеет наименование. Для этого нужно использовать FilteredElementCollector с фильтром ElementParameterFilter. Нам необходимо выбрать элементы, у которых значение встроенного параметра DATUM_TEXT, в котором задается наименование опорной плоскости, равно пустой строке.

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

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

Вопрос: Я пользуюсь кодом, который удаляет все опорные плоскости без наименования и на которой нет элементов.

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

Можете его проверить и исправить?

Ответ: Спасибо за обращение.

Тем не менее, короткий ответ – нет.

Команда, на которую вы ссылаетесь находится в примерах The Building Coder. Весь код в примерах является лишь обучающим и основная его цель – показать вам как решить вашу конкретную проблему, либо дать направление с чего начать ее решать.

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

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

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

Надеюсь вы поняли причину.

По данному вопросу, увы, но я не смогу помочь.

С другой стороны, мне самому стало интересно, что же такого поменялось, и я все же решил проверить. Так что в качестве исключения, мое «нет» в данном случае означает «да». Можно сказать, что вы счастливчик.

Я создал новую команду CmdDeleteUnusedRefPlanes в примерах The Building Coder и быстренько протестировал работоспособность.

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

Код - C#: [Выделить]
  1.   // Создаем фильтр по параметру
  2.   // для выбора опорных плоскостей без названия, т.е. для плоскостей
  3.   // чьи названия равны пустой строке
  4.   BuiltInParameter bip
  5.     = BuiltInParameter.DATUM_TEXT;
  6.   ParameterValueProvider provider
  7.     = new ParameterValueProvider(
  8.       new ElementId( bip ) );
  9.  
  10.   FilterStringRuleEvaluator evaluator
  11.     = new FilterStringEquals();
  12.  
  13.   FilterStringRule rule = new FilterStringRule(
  14.     provider, evaluator, "", false );
  15.  
  16.   ElementParameterFilter filter
  17.     = new ElementParameterFilter( rule );
  18.  
  19.   FilteredElementCollector col
  20.     = new FilteredElementCollector( doc )
  21.       .OfClass( typeof( ReferencePlane ) )
  22.       .WherePasses( filter );
  23.  
  24.   int n = 0;
  25.   int nDeleted = 0;
  26.  
  27.   // Дополнительного приведения не требуется
  28.  // FilteredElementCollector гарантирует нм
  29.  // что будут возвращены элементы типа ReferencePlane
  30.    foreach( ReferencePlane rp in col )
  31.   {
  32.     ++n;
  33.     nDeleted += DeleteIfNotHosting( rp ) ? 1 : 0;
  34.   }

Эта часть работает прекрасно и остается без изменений.

Как вы видите в коде, в цикле перебираются все опорные плохсоксти без названия, затем для каждой такой плоскости вызывается метод DeleteIfNotHosting, в котором опорная плоскость удаляется с помощью метода doc.Delete:

Код - C#: [Выделить]
  1.   /// <summary>
  2.   /// Удаляет заданную опорную плоскость
  3.   /// Если на ней ней содержатся элементы
  4.   /// </summary>
  5.   /// <returns>True если опорная плоскость была удалена, в противном случае false.</returns>
  6.   bool DeleteIfNotHosting( ReferencePlane rp )
  7.   {
  8.     bool rc = false;
  9.     Document doc = rp.Document;
  10.     Transaction tx = new Transaction( doc );
  11.     tx.Start( "Delete ReferencePlane "
  12.       + ( ++_i ).ToString() );
  13.  
  14.     // Удаление просто не выполняется если плоскость
  15.     // содержит элементы. В этом случае метод возвращает null
  16.     ICollection<ElementId> ids = doc.Delete( rp );
  17.     if( null == ids || 1 < ids.Count )
  18.     {
  19.       tx.RollBack();
  20.     }
  21.     else
  22.     {
  23.       tx.Commit();
  24.       rc = true;
  25.     }
  26.     return rc;
  27.   }

Метод Delete, в который необходимо передавать элемент, уже устарел в Revit 2014, поэтому я заменил его и передаю только идентификатор:

Код - C#: [Выделить]
  1. ICollection<ElementId> ids = doc.Delete( rp.Id );

Но когда я запускаю команду, то получаю исключение:

Autodesk.Revit.Exceptions.ArgumentException:  HResult=-2146233088  Message=ElementId cannot be deleted.Parameter name: elementId  Source=RevitAPI  ParamName=elementIdStack Trace:  at Autodesk.Revit.DB.Document.Delete(ElementId)  at DeleteIfNotHosting(ReferencePlane rp)  at CmdDeleteUnusedRefPlanes.Execute(...) ...

В комментарии к коду указано: «Удаление просто не выполняется если плоскость содержит элементы. В этом случае метод возвращает null».

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

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

Еще одна особенность, которая появилась в Revit 2014 – это запрет удаления элементов в тот момент, когда элемент перебирается из коллекции элементов.

При попытке удалить элемент, который находится в коллекции мы получим вот такое исключение:

Autodesk.Revit.Exceptions.InvalidOperationException:  HResult=-2146233088  Message=The iterator cannot proceed due to changes made  to the Element table in Revit's database (typically,  This can be the result of an Element deletion).  Source=RevitAPIStack Trace:  at Autodesk.Revit.DB.FilteredElementIterator.MoveNext()  at BuildingCoder.CmdDeleteUnusedRefPlanes.Execute() ...

Единственное правильное решение описано и показано ниже.

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

Также, получим все идентификаторы опорных плоскостей, которые вернул нам FilteredElementColector.

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

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

Код - C#: [Выделить]
  1.             UIApplication uiapp = commandData.Application;
  2.             UIDocument uidoc = uiapp.ActiveUIDocument;
  3.             Application app = uiapp.Application;
  4.             Document doc = uidoc.Document;
  5.  
  6.             // Создаем фильтр по параметру
  7.             // для выбора опорных плоскостей без названия, т.е. для плоскостей
  8.             // чьи названия равны пустой строке
  9.  
  10.             BuiltInParameter bip
  11.               = BuiltInParameter.DATUM_TEXT;
  12.  
  13.             ParameterValueProvider provider
  14.               = new ParameterValueProvider(
  15.                 new ElementId(bip));
  16.  
  17.             FilterStringRuleEvaluator evaluator
  18.               = new FilterStringEquals();
  19.  
  20.             FilterStringRule rule = new FilterStringRule(
  21.               provider, evaluator, "", false);
  22.  
  23.             ElementParameterFilter filter
  24.               = new ElementParameterFilter(rule);
  25.  
  26.             FilteredElementCollector col
  27.               = new FilteredElementCollector(doc)
  28.                 .OfClass(typeof(ReferencePlane))
  29.                 .WherePasses(filter);
  30.  
  31.             int n = 0;
  32.             int nDeleted = 0;
  33.  
  34.             ICollection<ElementId> ids = col.ToElementIds();
  35.  
  36.             n = ids.Count;
  37.  
  38.             if (0 < n)
  39.             {
  40.                 using (Transaction tx = new Transaction(doc))
  41.                 {
  42.                     tx.Start(string.Format(
  43.                       "Удаление опорных плоскостей"
  44.                       ));
  45.  
  46.                     List<ElementId> ids2 = new List<ElementId>(
  47.                       ids);
  48.  
  49.                     foreach (ElementId id in ids2)
  50.                     {
  51.                         try
  52.                         {
  53.                             ICollection<ElementId> ids3 = doc.Delete(
  54.                               id);
  55.  
  56.                             nDeleted += ids3.Count;
  57.                         }
  58.                         catch (Autodesk.Revit.Exceptions.ArgumentException)
  59.                         {
  60.                         }
  61.                     }
  62.  
  63.                     tx.Commit();
  64.                 }
  65.             }
  66.  
  67.             TaskDialog.Show("The Building Coder", string.Format(
  68.               "{0} опорных плоскостей без названия, "
  69.               + "{1} элементов в общей сложности было удалено.",
  70.               n,
  71.               nDeleted));
  72.  
  73.             return Result.Succeeded;

Выполнив эту команду на примере модели, поставляемой с Revit rac_basic_sample_project.rvt, получим вот такой результат:

 

Новая команда включена в примеры The Building Coder. Версия из статьи -  release 2014.0.107.0.

Источник: http://thebuildingcoder.typepad.com/blog/2014/02/deleting-unnamed-non-hosting-reference-planes.html

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

Опубликовано 08.02.2014
Отредактировано 09.02.2014 в 10:14:10