Удаление неиспользуемых опорных плоскостей
Как-то во время проведения тренинга по Revit API, участники тренинга придумали вопрос, решение которого можно использовать для демонстрации использования FilteredElementCollector. В результате получилось вполне полезная команда. Необходимо было удалить все опорные плоскости, которые не содержат ни одного элемента. Так я создал команду DeleteUnnamedNonHostingReferencePlanes.
Команда удаляет из проекта все опорные плоскости, которые не имеют названия и не содержат ни одного элемента.
Вот тема на форуме(англ.), где обсуждалось как отфильтровать опорные плоскости и описано как использовать эту команду.
Перед удалением необходимо проверить два условия. Первое условие – опорная плоскость имеет наименование. Для этого нужно использовать FilteredElementCollector с фильтром ElementParameterFilter. Нам необходимо выбрать элементы, у которых значение встроенного параметра DATUM_TEXT, в котором задается наименование опорной плоскости, равно пустой строке.
Вторым условием является отсутствие элементов на опорной плоскости. Тут как таковую проверку мы не делаем и просто вызываем метод Delete. И если опорная плоскость содержит элементы, то просто ничего не удалится.
Но один из разработчиков недавно обратился с проблемой, связанной с использованием этой команды.
Вопрос: Я пользуюсь кодом, который удаляет все опорные плоскости без наименования и на которой нет элементов.
Довольно продолжительное время все работало прекрасно, но сейчас стало выбрасываться исключения и Revit закрывается.
Можете его проверить и исправить?
Ответ: Спасибо за обращение.
Тем не менее, короткий ответ – нет.
Команда, на которую вы ссылаетесь находится в примерах The Building Coder. Весь код в примерах является лишь обучающим и основная его цель – показать вам как решить вашу конкретную проблему, либо дать направление с чего начать ее решать.
Этот код не поддерживается, и используя его как есть, вы возлагается на себя все риски по его использованию.
Мы рады предоставить вам техническую поддержку по разработке ваших собственных приложений и надстроек, но мы не можем еще и «допиливать» примеры кода, а также мы не можем гарантировать, что все примеры из этого блога будут всегда работать и что их можно использовать в реальных проектах. Все примеры предназначены лишь для обучения.
Перефразируйте пожалуйста свой запрос, указав конкретные проблемы, с которыми вы столкнулись при использовании Revit API при разработке вашего приложения, и мы с радостью вам поможем.
Надеюсь вы поняли причину.
По данному вопросу, увы, но я не смогу помочь.
С другой стороны, мне самому стало интересно, что же такого поменялось, и я все же решил проверить. Так что в качестве исключения, мое «нет» в данном случае означает «да». Можно сказать, что вы счастливчик.
Я создал новую команду CmdDeleteUnusedRefPlanes в примерах The Building Coder и быстренько протестировал работоспособность.
В исходной команде используется FilteredElementCollector для поиска опорных плоскостей без названия:
- // Создаем фильтр по параметру
- // для выбора опорных плоскостей без названия, т.е. для плоскостей
- // чьи названия равны пустой строке
- BuiltInParameter bip
- = BuiltInParameter.DATUM_TEXT;
- ParameterValueProvider provider
- = new ParameterValueProvider(
- new ElementId( bip ) );
- FilterStringRuleEvaluator evaluator
- = new FilterStringEquals();
- FilterStringRule rule = new FilterStringRule(
- provider, evaluator, "", false );
- ElementParameterFilter filter
- = new ElementParameterFilter( rule );
- FilteredElementCollector col
- = new FilteredElementCollector( doc )
- .OfClass( typeof( ReferencePlane ) )
- .WherePasses( filter );
- int n = 0;
- int nDeleted = 0;
- // Дополнительного приведения не требуется
- // FilteredElementCollector гарантирует нм
- // что будут возвращены элементы типа ReferencePlane
- foreach( ReferencePlane rp in col )
- {
- ++n;
- nDeleted += DeleteIfNotHosting( rp ) ? 1 : 0;
- }
Эта часть работает прекрасно и остается без изменений.
Как вы видите в коде, в цикле перебираются все опорные плохсоксти без названия, затем для каждой такой плоскости вызывается метод DeleteIfNotHosting, в котором опорная плоскость удаляется с помощью метода doc.Delete:
- /// <summary>
- /// Удаляет заданную опорную плоскость
- /// Если на ней ней содержатся элементы
- /// </summary>
- /// <returns>True если опорная плоскость была удалена, в противном случае false.</returns>
- bool DeleteIfNotHosting( ReferencePlane rp )
- {
- bool rc = false;
- Document doc = rp.Document;
- Transaction tx = new Transaction( doc );
- tx.Start( "Delete ReferencePlane "
- + ( ++_i ).ToString() );
- // Удаление просто не выполняется если плоскость
- // содержит элементы. В этом случае метод возвращает null
- ICollection<ElementId> ids = doc.Delete( rp );
- if( null == ids || 1 < ids.Count )
- {
- tx.RollBack();
- }
- else
- {
- tx.Commit();
- rc = true;
- }
- return rc;
- }
Метод Delete, в который необходимо передавать элемент, уже устарел в Revit 2014, поэтому я заменил его и передаю только идентификатор:
- ICollection<ElementId> ids = doc.Delete( rp.Id );
Но когда я запускаю команду, то получаю исключение:
Autodesk.Revit.Exceptions.ArgumentException:
HResult=-2146233088
Message=ElementId cannot be deleted.
Parameter name: elementId
Source=RevitAPI
ParamName=elementId
Stack 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=RevitAPI
Stack Trace:
at Autodesk.Revit.DB.FilteredElementIterator.MoveNext()
at BuildingCoder.CmdDeleteUnusedRefPlanes.Execute() ...
Единственное правильное решение описано и показано ниже.
Вместо того чтобы использовать отдельную транзакцию для каждой попытки удалить опорную плоскость, поместим все в одну транзакцию.
Также, получим все идентификаторы опорных плоскостей, которые вернул нам FilteredElementColector.
И, наконец, сделаем обработку исключения при попытке удалить опорную плоскость, так как если опорная плоскость содержит элементы, то будет выброшено исключение.
Полный код исправленной команды:
- UIApplication uiapp = commandData.Application;
- UIDocument uidoc = uiapp.ActiveUIDocument;
- Application app = uiapp.Application;
- Document doc = uidoc.Document;
- // Создаем фильтр по параметру
- // для выбора опорных плоскостей без названия, т.е. для плоскостей
- // чьи названия равны пустой строке
- BuiltInParameter bip
- = BuiltInParameter.DATUM_TEXT;
- ParameterValueProvider provider
- = new ParameterValueProvider(
- new ElementId(bip));
- FilterStringRuleEvaluator evaluator
- = new FilterStringEquals();
- FilterStringRule rule = new FilterStringRule(
- provider, evaluator, "", false);
- ElementParameterFilter filter
- = new ElementParameterFilter(rule);
- FilteredElementCollector col
- = new FilteredElementCollector(doc)
- .OfClass(typeof(ReferencePlane))
- .WherePasses(filter);
- int n = 0;
- int nDeleted = 0;
- ICollection<ElementId> ids = col.ToElementIds();
- n = ids.Count;
- if (0 < n)
- {
- using (Transaction tx = new Transaction(doc))
- {
- tx.Start(string.Format(
- "Удаление опорных плоскостей"
- ));
- List<ElementId> ids2 = new List<ElementId>(
- ids);
- foreach (ElementId id in ids2)
- {
- try
- {
- ICollection<ElementId> ids3 = doc.Delete(
- id);
- nDeleted += ids3.Count;
- }
- catch (Autodesk.Revit.Exceptions.ArgumentException)
- {
- }
- }
- tx.Commit();
- }
- }
- TaskDialog.Show("The Building Coder", string.Format(
- "{0} опорных плоскостей без названия, "
- + "{1} элементов в общей сложности было удалено.",
- n,
- nDeleted));
- 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