Reference Intersector and Deleting Reference Planes

We already looked at deleting unnamed non-hosting reference planes back in 2012 and 2014. Some things have changed since then, and the old code requires fixing and updating once again.

Other interesting topics also want to be mentioned:

Embodyment Workshop

I attended a very nourishing dance workshop with Alain Allard of Moves into Consciousness, UK.

Alain is a dancer, fully licensed and practising psychotherapist, and meditates.

On that basis, he led us through two very fulfilling days of attending to the rhythms of the body and listening more clearly the rhythms of life, the voice of the heart and gut intelligence, practicing giving up the everyday habits of dulling our creative response to Life’s unfolding by numbing down and overthinking.

It was very enjoyable and enlivening indeed!

Thank you, Alain, and thank you, Bea, for inviting and organising!

Bös Fulen Mountain Hike

I followed up on the dance workshop with a mountain hike with Alex to the Bös Fulen:

Bös Fulen

In spite of its looks, it in in fact a pretty easy hike across the entire ridge a couple of hundred meters to the summit.

Unfortunately, the weather we had was not as good as in the picture. We had some sight, though much of it was of dramatic clouds forming all around us, and we got pretty wet walking out again.

Using ReferenceIntersector to Place Lighting Fixture on Ceiling Face

Returning to the Revit API, Joshua Lumley shared a nice little example of using the ReferenceIntersector class to determine a point on and a reference to the ceiling face in a linked file to place a lighting fixture above a given point, in his comment on the old discussion of the FindReferencesByDirection method, the precursor of ReferenceIntersector.

I generalised and simplified his code snippet to this GetCeilingReferenceAbove method, which I added to The Building Coder Samples in the module CmdDimensionWallsFindRefs.cs:

  /// <summary>
  /// Return reference to ceiling face to place 
  /// lighting fixture above a given point.
  /// </summary>
  Reference GetCeilingReferenceAbove( 
    View3D view, 
    XYZ p )
  {
    ElementClassFilter filter = new ElementClassFilter( 
      typeofCeiling ) );

    ReferenceIntersector refIntersector 
      = new ReferenceIntersector( filter, 
        FindReferenceTarget.Face, view );

    refIntersector.FindReferencesInRevitLinks = true;

    ReferenceWithContext rwc = refIntersector.FindNearest( 
      p, XYZ.BasisZ );

    Reference r = (null == rwc) 
      ? null 
      : rwc.GetReference();

    ifnull == r )
    {
      System.Windows.MessageBox.Show( "no intersecting geometry" );
    }
    return r;
  }

Here is a sample snippet showing how the method expects to be used:

  void TestGetCeilingReferenceAbove( Document doc )
  {
    View3D view = doc.GetElement( new ElementId( 147335 ) ) as View3D;
    Space space = doc.GetElement( new ElementId( 151759 ) ) as Space;
    XYZ center = ( (LocationPoint) space.Location ).Point;

    Reference r = GetCeilingReferenceAbove( view, center );

    // Populate these as needed:

    XYZ startPoint = null;
    FamilySymbol sym = null;

    doc.Create.NewFamilyInstance( r, startPoint, XYZ.BasisY, sym );
  }

Reformat Stable Representation String for Dimensioning

By the way, Joshua also added an important note on how to reformat the stable representation string for dimensioning in the discussion on the reference stable representation magic voodoo.

Many thanks for your helpful contributions, Joshua!

Deleting Unnamed Non-Hosting Reference Planes Updated

Another important update was initiated by Austin Sudtelgte, who pointed out that the approach described in 2014 for deleting unnamed non-hosting reference planes no longer works. Austin adds:

As of 2017 this method no longer works, because Revit doesn't throw an error when deleting a plane with something hosted to it. You will have to get all family instances in the document, check their host ID parameter, get all reference planes excluding the ones whose IDs we just collected, then delete. Dimensions that measure to a reference plane will be removed with the reference plane. The methods used above will still work to determine if one of those will be deleted or not. Alternatively, as of 2018.1, there is an Element.GetDependentElements method that will return the same things as what is returned when deleting the element, just without having to delete it and roll back a transaction.

He also points out in his comment on What's New in the Revit 23019 API:

Point 2.3 on finding element dependencies with Element.GetDependentElements seems to require an element filter rather than having it be optional as noted above. It's possible that in 2019 the filter is optional, but in 2018.1 it was not.

This is relevant in The Building Coder sample CmdDeleteUnusedRefPlanes.cs to delete unnamed non-hosting reference planes, originally discussed and implemented at the Melbourne DevLab in 2012 to delete all reference planes that:

The original approach stopped working and was updated and added to The Building Coder samples in 2014, when the Delete method overload taking an Element argument was deprecated in Revit 2014 and the deletion of a reference plane hosting elements started throwing an exception.

Austin shared his version of the broken command, plus his new working solution.

I added them both to The Building Coder Samples module CmdDeleteUnusedRefPlanes.cs.

Yet again, I discovered a couple of optimisation possibilities that I very frequently keep pointing out and also added to Austin's code:

You can see exactly what I modified in the commit highlighting those changes.

Here Austin's solution in its current form:

[TransactionAttributeTransactionMode.Manual )]
public class CmdDeleteUnusedRefPlanes : IExternalCommand
{
  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Document doc = uidoc.Document;

    ICollection<ElementId> refplaneids
      = new FilteredElementCollector( doc )
        .OfClass( typeofReferencePlane ) )
        .ToElementIds();

    usingTransactionGroup tg = new TransactionGroup( doc ) )
    {
      tg.Start( "Remove unused reference planes" );

      FilteredElementCollector instances
        = new FilteredElementCollector( doc )
          .OfClass( typeofFamilyInstance ) );

      Dictionary<ElementIdint> toKeep 
        = new Dictionary<ElementIdint>();

      foreachFamilyInstance i in instances )
      {
        // Ensure the element is hosted

        ifnull != i.Host )
        {
          ElementId hostId = i.Host.Id;

          // Check list to see if we've already added this plane

          if( !toKeep.ContainsKey( hostId ) )
          {
            toKeep.Add( hostId, 0 );
          }
          ++toKeep[hostId];
        }
      }

      // Loop through reference planes and 
      // delete the ones not in the list toKeep.

      foreachElementId refid in refplaneids )
      {
        if( !toKeep.ContainsKey( refid ) )
        {
          usingTransaction t = new Transaction( doc ) )
          {
            t.Start( "Removing plane "
              + doc.GetElement( refid ).Name );

            // Ensure there are no dimensions measuring to the plane

            if( doc.Delete( refid ).Count > 1 )
            {
              t.Dispose();
            }
            else
            {
              t.Commit();
            }
          }
        }
      }
      tg.Assimilate();
    }
    return Result.Succeeded;
  }
}

Many thanks to Austin for picking up and fixing this!