Determining Dimension Segment Endoints

A rather hard struggle led to a rather simple solution for determining the start and end points of dimension segments.

In summary, the solution looks like this:

  direction = line.direction.normal;
  start = origin - half * length * direction;
  end = origin + half * length * direction;

I implemented and added a new external command CmdGetDimensionPoints to The Building Coder samples I added it to The Building Coder samples release 2018.0.134.0 that calculates the segment endpoints accordingly.

Here is a dimension between three parallel walls:

Dimensioning of three walls

CmdGetDimensionPoints adds the green model line markers denoting the dimension origin in the middle of the first segment and at the three start and end points of the two segments calculated along the dimension line from that starting point:

Dimension origin and segment points

For the full research and discussion leading up to this, please refer to the Revit API discussion forum thread on how to retrieve a dimension's segment geometry.

Many thanks to Fair59 for his important contributions and to Jonathan 'Maisoui' for raising the issue.

Here is the full final implementation based on this:

  /// <summary>
  /// Return dimension origin, i.e., the midpoint
  /// of the dimension or of its first segment.
  /// </summary>
  XYZ GetDimensionStartPoint(
    Dimension dim )
  {
    XYZ p = null;

    try
    {
      p = dim.Origin;
    }
    catch( Autodesk.Revit.Exceptions.ApplicationException ex )
    {
      Debug.Assert( ex.Message.Equals( "Cannot access this method if this dimension has more than one segment." ) );

      foreachDimensionSegment seg in dim.Segments )
      {
        p = seg.Origin;
        break;
      }
    }
    return p;
  }

  /// <summary>
  /// Retrieve the start and end points of
  /// each dimension segment, based on the 
  /// dimension origin determined above.
  /// </summary>
  List<XYZ> GetDimensionPoints( 
    Dimension dim, 
    XYZ pStart )
  {
    Line dimLine = dim.Curve as Line;
    if( dimLine == null ) return null;
    List<XYZ> pts = new List<XYZ>();

    dimLine.MakeBound( 0, 1 );
    XYZ pt1 = dimLine.GetEndPoint( 0 );
    XYZ pt2 = dimLine.GetEndPoint( 1 );
    XYZ direction = pt2.Subtract( pt1 ).Normalize();

    if( 0 == dim.Segments.Size )
    {
      XYZ v = 0.5 * (double)dim.Value * direction;
      pts.Add( pStart - v );
      pts.Add( pStart + v );
    }
    else
    {
      XYZ p = pStart;
      foreachDimensionSegment seg in dim.Segments )
      {
        XYZ v = (double) seg.Value * direction;
        if( 0 == pts.Count)
        {
          pts.Add( p = ( pStart - 0.5 * v ) );
        }
        pts.Add( p = p.Add( v ) );
      }
    }
    return pts;
  }

  /// <summary>
  /// Graphical debugging helper using model lines
  /// to draw an X at the given position.
  /// </summary>
  void DrawMarker( 
    XYZ p, 
    double size, 
    SketchPlane sketchPlane )
  {
    size *= 0.5;
    XYZ v = new XYZ( size, size, 0 );
    Document doc = sketchPlane.Document;
    doc.Create.NewModelCurve( Line.CreateBound( 
      p - v, p + v ), sketchPlane );
    v = new XYZ( size, -size, 0 );
    doc.Create.NewModelCurve( Line.CreateBound(
      p - v, p + v ), sketchPlane );
  }

  public Result Execute(
    ExternalCommandData commandData,
    ref string message,
    ElementSet elements )
  {
    UIApplication uiapp = commandData.Application;
    UIDocument uidoc = uiapp.ActiveUIDocument;
    Document doc = uidoc.Document;
    Selection sel = uidoc.Selection;

    ISelectionFilter f
      = new JtElementsOfClassSelectionFilter<Dimension>();

    Reference elemRef = sel.PickObject(
      ObjectType.Element, f, "Pick a dimension" );

    Dimension dim = doc.GetElement( elemRef ) as Dimension;

    XYZ p = GetDimensionStartPoint( dim );
    List<XYZ> pts = GetDimensionPoints( dim, p );

    int n = pts.Count;

    Debug.Print( "Dimension origin at {0} followed "
      + "by {1} further point{2}{3} {4}",
      Util.PointString( p ), n,
      Util.PluralSuffix( n ), Util.DotOrColon( n ),
      string.Join( ", ", pts.Select(
        q => Util.PointString( q ) ) ) );

    List<double> d = new List<double>( n );
    XYZ q0 = p;
    foreachXYZ q in pts )
    {
      d.Add( q.X - q0.X );
      q0 = q;
    }

    Debug.Print(
      "Horizontal distances in metres: "
      + string.Join( ", ", d.Select( x =>
        Util.RealString( Util.FootToMetre( x ) ) ) ) );

    usingTransaction tx = new Transaction( doc ) )
    {
      tx.Start( "Draw Point Markers" );

      SketchPlane sketchPlane = dim.View.SketchPlane;

      double size = 0.3;
      DrawMarker( p, size, sketchPlane );
      pts.ForEach( q => DrawMarker( q, size, sketchPlane ) );

      tx.Commit();
    }

    return Result.Succeeded;
  }