Deleting Unnamed Non-Hosting Reference Planes

Once upon a time, while teaching a Revit API class, the training participants identified a problem that was simple enough to be used as a filtered element collector learning example and did something useful at the same time:

deleting reference planes not hosting any elements
,
implemented as the stand-alone external command DeleteUnnamedNonHostingReferencePlanes.

It deletes all reference planes that have not been named and are not hosting any elements.

Here is another thread discussing

filtering reference planes
and
explaining the use of this.

The first part of the check, for a valid element name, is achieved by using a filtered element collector with an ElementParameterFilter that returns only those elements whose built-in parameter DATUM_TEXT that returns the reference plane element name has an empty string value.

The second part of the check, whether the reference plane is currently hosting any elements, is automatically executed by the Delete method itself, which fails if that is the case.

A developer now raised an issue with that command:

Question: I have been using the code to delete all unnamed reference planes that do not host any elements.

It worked fine for a long time, but now it is throwing an error and shutting down Revit.

Will you please check this.

Answer: Thank you for your query.

Basically, the short answer to this question is: no.

The utility command that you refer to was provided on The Building Coder as a learning example to show you how to solve a specific problem yourself.

It is completely unsupported, and any use you make of it is completely at your own risk.

We are more than happy to provide you with developer support for your own applications, but we cannot maintain sample code, nor can we provide any guarantees that the examples presented on the blog will work or be of any productive use at all, except for learning purposes.

Please rephrase the question to request support for solving a specific API related issue that you are encountering in your own application development, and we will be more than happy to help.

I hope you understand the difference.

So, alas, no help for that specific query.

On the other hand, I do wonder for myself what might have changed, and decided to explore the issue anyway.

Meaning that, as an exception, I can change that ‘no’ to a ‘yes’ for the nonce.
Lucky you.

To do so, I implemented a new external command named CmdDeleteUnusedRefPlanes in

The Building Coder samples
,
migrated the code to the current version and ran a quick test.

Unfortunately, it ended up being not so quick after all, as usual.

The original command uses a filtered element collector to retrieve all unnamed reference planes like this:


  // Construct a parameter filter to get only 
  // unnamed reference planes, i.e. reference 
  // planes whose name equals the empty string:
 
  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;
 
  // No need to cast ... this is pretty nifty,
  // I find ... grab the elements as ReferencePlane
  // instances, since the filter guarantees that 
  // only ReferencePlane instances are selected.
 
  foreach( ReferencePlane rp in col )
  {
    ++n;
    nDeleted += DeleteIfNotHosting( rp ) ? 1 : 0;
  }

That part still works fine and remains unchanged.

As you can see, the original command loops through the resulting reference planes and calls the method DeleteIfNotHosting that performs the deletion using the doc.Delete method like this:


  /// <summary>
  /// Delete the given reference plane 
  /// if it is not hosting anything.
  /// </summary>
  /// <returns>True if the given reference plane
  /// was in fact deleted, else false.</returns>
  bool DeleteIfNotHosting( ReferencePlane rp )
  {
    bool rc = false;
 
    Document doc = rp.Document;
 
    Transaction tx = new Transaction( doc );
 
    tx.Start( "Delete ReferencePlane "
      + ( ++_i ).ToString() );
 
    // Deletion simply fails if the reference plane 
    // hosts anything. If so, the return value ids 
    // is null:
 
    ICollection<ElementId> ids = doc.Delete( rp );
 
    if( null == ids || 1 < ids.Count )
    {
      tx.RollBack();
    }
    else
    {
      tx.Commit();
      rc = true;
    }
    return rc;
  }

The Delete method overload taking an Element argument is deprecated in Revit 2014, so I replaced it by a call to the overload taking an element id instead:


    ICollection<ElementId> ids = doc.Delete( rp.Id );

When running the resulting command, the following exception is thrown:


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(...) ...

The comment in the original code states that “Deletion simply fails if the reference plane hosts anything. If so, the return value ids collection is null”.

This behaviour has been modified in Revit 2014 to throw the exception listed above instead of simply failing.

In other words, this exception is thrown if the reference plane is in fact hosting any elements and therefore should not be removed.

Furthermore, the original command was implemented before I started pointing out that all

transactions should best be encapsulated within a ‘using’ statement
block.

Such a block and an exception handler should be added to the call.

Another check that was added in Revit 2014 prevents element deletion while iterating over a collection.

Attempting to delete an element as shown above within the loop iterating over the filtered element collector throws the following exception:


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() ...

One solution to all this is the following simplification.

Instead of using a separate transaction for each individual attempt to delete a reference plane, do them all in one single go.

Furthermore, extract the element ids from the filtered element collector and process them one by one in a separate follow-up loop.

Finally, add an exception handler around each call to the Delete method, since each call to it with a plane hosting elements will cause it to throw an exception.

Here is the complete resulting external command mainline Execute method implementation:


  UIApplication uiapp = commandData.Application;
  UIDocument uidoc = uiapp.ActiveUIDocument;
  Application app = uiapp.Application;
  Document doc = uidoc.Document;
 
  // Construct a parameter filter to get only 
  // unnamed reference planes, i.e. reference 
  // planes whose name equals the empty string:
 
  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(
        "Delete {0} ReferencePlane{1}",
        n, Util.PluralSuffix( n ) ) );
 
      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();
    }
  }
 
  Util.InfoMsg( string.Format(
    "{0} unnamed reference plane{1} examined, "
    + "{2} element{3} in total were deleted.",
    n, Util.PluralSuffix( n ),
    nDeleted, Util.PluralSuffix( nDeleted ) ) );
 
  return Result.Succeeded;

Running this in the rac_basic_sample_project.rvt model produces the following result:

Delete unused reference planes

The new command CmdDeleteUnusedRefPlanes is included in

The Building Coder samples
,
and the version discussed above is

release 2014.0.107.0
.


Comments

5 responses to “Deleting Unnamed Non-Hosting Reference Planes”

  1. Thanks Jeremy,
    How to retain reference planes where other elements are aligned & locked (not hosted)?

  2. Dear Jolish,
    I have not tested that. Please let us know whether such elements automatically prevent deletion of the reference plane or not. Otherwise, I would suggest giving such reference planes a name to prevent deletion, unless you can come up with a better suggestion.
    Cheers, Jeremy.

  3. Thanks
    Locked elements does not prevent deleting reference planes. Best way as you suggested give name for all. Still wonder where the locked elements ids are saved? And Who stores that (reference plane or locked wall)? Tried snoop, no luck.

  4. I’ve done a further test and unnamed refplanes that cut elements are also deleted. Therefore these must be named also. It’s a shame the element being affected by refplanes does not list the ids of refplanes it is affected by on some property of its associated class. If it did you could go through a list of common model categories and build up a list of refplane ids to leave alone. Or alternatively the refplane itself could have a property stating if it is influencing other items.

  5. If you lock an item to a refplane then it seems to add a dimension object (which gets listed in the deleted ids list when the refplane is deleted). Therefore in the case of finding unnamed refplanes that lock elements to planes you can use the old trick of creating a sub-transaction to delete the refplane and if the resulting element ID count is greater than 1 then you can roll back the sub transaction to look through the element ids (finding 1 dimension object for each lock). Obviously the dimensions in the list could also be user created ones rather than those automatically created by locking planes. However I’ve noticed their categories are different i.e. “Dimensions” (-2000260) for user created linear dimensions and “Constraints” (-2000262) for linear dims formed by locking elements to a plane. You could take the view that you don’t want to delete a refplane if it has a dimension associated with it anyway. The other difference is the ‘Value’ property of a system created linear dimension is ‘nothing’ not 0 or greater.
    None of this addresses unnamed ref planes used for cuts.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading