Auto-Dimension Filled Region Boundary

I am back from my break in the French Jura and looking at all the interesting Revit API forum discussions again.

One that stands out and that I'll pick up to get back into the blogging rhythm again is Jorge Villarroel's question about creating dimensions for a filled region boundary, answered by Alexander @aignatovich @CADBIMDeveloper Ignatovich, aka Александр Игнатович:

Filled regions auto-dimensioned

Programmatically Creating Dimensions for a Filled Region

I am working with dimensions for multiple objects. The dimension creation method needs a ReferenceArray to work. Now, I need to create dimensions for a filled region:

Filled region

Filled region

I can create dimensions manually in the user interface using native commands, no API, just clicking, using "Align Dimension":

Dimensions for the filled region

Dimensions for the filled region

However, I can't retrieve the reference for the boundary curves to create them programmatically.

I used RevitLookup to search for some reference in the Filled Region sub-elements with no results.

Also tried to get the references from the CurveLoop curves, but again, with no results.

Any tip of advice will be very well received.

Coding Suggestion

Hi!

The trick is to retrieve the filled region geometry using the appropriate view and setting ComputeReferences to true.

Try this code:

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

    var view = uidoc.ActiveGraphicalView;

    var filledRegions = FindFilledRegions( doc, view.Id );

    usingvar transaction = new Transaction( doc, 
      "filled regions dimensions" ) )
    {
      transaction.Start();

      foreachvar filledRegion in filledRegions )
      {
        CreateDimensions( filledRegion, 
          -1 * view.RightDirection );

        CreateDimensions( filledRegion, view.UpDirection );
      }

      transaction.Commit();
    }
    return Result.Succeeded;
  }

  private static void CreateDimensions( 
    FilledRegion filledRegion, 
    XYZ dimensionDirection )
  {
    var document = filledRegion.Document;

    var view = (View) document.GetElement( 
      filledRegion.OwnerViewId );

    var edgesDirection = dimensionDirection.CrossProduct( 
      view.ViewDirection );

    var edges = FindRegionEdges( filledRegion )
      .Where( x => IsEdgeDirectionSatisfied( x, edgesDirection ) )
      .ToList();

    if( edges.Count < 2 )
      return;

    var shift = UnitUtils.ConvertToInternalUnits( 
      -10 * view.Scale, DisplayUnitType.DUT_MILLIMETERS ) 
      * edgesDirection;

    var dimensionLine = Line.CreateUnbound( 
      filledRegion.get_BoundingBox( view ).Min 
      + shift, dimensionDirection );

    var references = new ReferenceArray();

    foreachvar edge in edges )
      references.Append( edge.Reference );

    document.Create.NewDimension( view, dimensionLine, 
      references );
  }

  private static bool IsEdgeDirectionSatisfied( 
    Edge edge, 
    XYZ edgeDirection )
  {
    var edgeCurve = edge.AsCurve() as Line;

    if( edgeCurve == null )
      return false;

    return edgeCurve.Direction.CrossProduct( 
      edgeDirection ).IsAlmostEqualTo( XYZ.Zero );
  }

  private static IEnumerable<Edge> FindRegionEdges( 
    FilledRegion filledRegion )
  {
    var view = (View) filledRegion.Document.GetElement( 
      filledRegion.OwnerViewId );

    var options = new Options
    {
      View = view,
      ComputeReferences = true
    };

    return filledRegion
      .get_Geometry( options )
      .OfType<Solid>()
      .SelectMany( x => x.Edges.Cast<Edge>() );
  }

  private static IEnumerable<FilledRegion> 
    FindFilledRegions( 
      Document document, 
      ElementId viewId )
  {
    var collector = new FilteredElementCollector( 
      document, viewId );

    return collector
      .OfClass( typeofFilledRegion ) )
      .Cast<FilledRegion>();
  }
}

It produces something like this:

Filled regions dimensioned by Alexander's code

Dimensioning in Revit is one of my favorite topics   :-)

Final Solution

Thanks, @aignatovich. I really appreciate it.

Your suggestion was the solution to my problem!

I extended the approach, so the method asks for the type name (string) of the dimension you want to assign:

private void CreateDimensions(
  FilledRegion filledRegion,
  XYZ dimensionDirection,
  string typeName )
{
  var document = filledRegion.Document;

  var view = (View) document.GetElement( 
    filledRegion.OwnerViewId );

  var edgesDirection = dimensionDirection.CrossProduct( 
    view.ViewDirection );

  var edges = FindRegionEdges( filledRegion )
    .Where( x => IsEdgeDirectionSatisfied( x, edgesDirection ) )
    .ToList();

  if( edges.Count < 2 )
    return;

  // Se hace este ajuste para que la distancia no 
  // depende de la escala. <<<<<< evaluar para 
  // información de acotado y etiquetado!!!

  var shift = UnitUtils.ConvertToInternalUnits( 
    5 * view.Scale, DisplayUnitType.DUT_MILLIMETERS ) 
    * edgesDirection;

  var dimensionLine = Line.CreateUnbound(
    filledRegion.get_BoundingBox( view ).Min + shift,
    dimensionDirection );

  var references = new ReferenceArray();

  foreachvar edge in edges )
    references.Append( edge.Reference );

  Dimension dim = document.Create.NewDimension( 
    view, dimensionLine, references );

  ElementId dr_id = DimensionTypeId(
    document, typeName );

  if( dr_id != null )
  {
    dim.ChangeTypeId( dr_id );
  }
}

private static bool IsEdgeDirectionSatisfied( 
  Edge edge, 
  XYZ edgeDirection )
{
  var edgeCurve = edge.AsCurve() as Line;

  if( edgeCurve == null )
    return false;

  return edgeCurve.Direction.CrossProduct( 
    edgeDirection ).IsAlmostEqualTo( XYZ.Zero );
}

private static IEnumerable<FilledRegion> 
  FindFilledRegions( 
    Document document, 
    ElementId viewId )
{
  var collector = new FilteredElementCollector( 
    document, viewId );

  return collector
    .OfClass( typeofFilledRegion ) )
    .Cast<FilledRegion>();
}

private static IEnumerable<Edge> 
  FindRegionEdges( 
    FilledRegion filledRegion )
{
  var view = (View) filledRegion.Document.GetElement( 
    filledRegion.OwnerViewId );

  var options = new Options
  {
    View = view,
    ComputeReferences = true
  };

  return filledRegion
    .get_Geometry( options )
    .OfType<Solid>()
    .SelectMany( x => x.Edges.Cast<Edge>() );
}

private static ElementId DimensionTypeId(
  Document doc,
  string typeName )
{
  FilteredElementCollector mt_coll 
    = new FilteredElementCollector( doc )
      .OfClass( typeofDimensionType ) )
      .WhereElementIsElementType();

  DimensionType dimType = null;

  foreachElement type in mt_coll )
  {
    if( type is DimensionType )
    {
      if( type.Name == typeName )
      {
        dimType = type as DimensionType;
        break;
      }
    }
  }
  return dimType.Id;
}

This code produces dimensioning as shown in the top-most screen snapshot.

Hope this is helpful for others also!

Many thanks to Jorge and Alexander for this nice solution!