How to retrieve a Dimension's (segment) geometry ?

How to retrieve a Dimension's (segment) geometry ?

maisoui
Advocate Advocate
6,588 Views
29 Replies
Message 1 of 30

How to retrieve a Dimension's (segment) geometry ?

maisoui
Advocate
Advocate

Hello,

 

I'd like to retrieve the geometry of a Dimension : for each Dimension's segment, I'd like to have start, end points and text position and direction. I'm able to obtain the text position and the text string by looping on segments array :

 

foreach(DimensionSegment segment in dimension.Segments)
{
   //segment.TextPosition
   //segment.ValueString
}

But, I don't understand how to obtain the segment's points and direction. I tried get_geometry and looping on references array, but no success.

 

foreach(Reference reference in dimension.References)
{
    reference.GlobalPoint; // ---> always null
}

I don't find any samples.

Any suggestion is welcomed.

Best regards,

--
Jonathan
Accepted solutions (2)
6,589 Views
29 Replies
Replies (29)
Message 2 of 30

jeremytammik
Autodesk
Autodesk

Can you add an image to clarify exactly what you are after, and what dimension segment means? Thank you!



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 3 of 30

maisoui
Advocate
Advocate

Hi Jeremy,

 

Here is an image to clarify what I tried to explain :

 

revit_dimension_geometry.png

I have a dimension with 3 parts. My goal is to obtain this :

  • Segment A : from point x to point y, text = "30" and text position = t1
  • Segment B : from point y to point z, text = "2.695" and text position = t2
  • Segment C : from point z to point w, text = "305" and text position = t3

I wrote "segments" beacause API, I found the method "Dimension.Segments" :

 

foreach(DimensionSegment segment in dimension.Segments)
{
   //segment.Origin : not the point I'm looking for
   //segment.LeaderEndPosition : not the point I'm looking for
   //segment.TextPosition : OK
   //segment.ValueString: OK
}

I hope my question is now clearer.

Best regards

--
Jonathan
0 Likes
Message 4 of 30

jeremytammik
Autodesk
Autodesk

Dear Jonathan,

 

Thank you for your explanation.

 

Yes, that helps a lot.

 

Have you looked at the Revit API help documentation on the Dimension class?

 

http://www.revitapidocs.com/2017/210f88be-e3c5-26a4-7dd8-3296f6725cce.htm

 

Doesn't the sample code presented there provide exactly what you are asking for via

 

dimension.Curve

 

?

 

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 5 of 30

maisoui
Advocate
Advocate

Thank you for your reply. Before sending my post, I searched this forum and I looked in the API samples and your blog, but without success.

In my example, the Dimension's is unbound : dimension.Curve.IsBound = false; So I have, an infinite line defined by a point and a direction. Maybe I need to project anything on it ? Maybe I habe to look references ?

I tried a lot of variants, but without success.

--
Jonathan
Message 6 of 30

jeremytammik
Autodesk
Autodesk
Accepted solution

Dear Jonathan,

 

Thank you for testing and reporting this. How weird. How useless.

 

Funnily enough, a colleague of mine apparently ran into this very same issue yesterday and raised a development team issue for it:

 

We have an existing change request number REVIT-115341 [API Wish: Get end points of a linear dimension] for this issue, which will require exploration and possibly a modification to our software. Please make a note of this number for future reference. I have added a note of your request to this item in order to make the development team aware of its importance.

 

You are welcome to request an update on the status of this issue or to provide additional information on it at any time quoting this change request number.

 

This issue is important to me. What can I do to help?

 

This issue needs to be assessed by our engineering team, and prioritised against all other outstanding change requests. Any information that you can provide to influence this assessment will help. Please provide the following where possible:

 

  • Impact on your application and/or your development.
  • The number of users affected.
  • The potential revenue impact to you.
  • The potential revenue impact to Autodesk.
  • Realistic timescale over which a fix would help you.
  • In the case of a request for a new feature or a feature enhancement, please also provide detailed Use cases for the workflows that this change would address.

 

This information is extremely important. Our engineering team have limited resources, and so must focus their efforts on the highest impact items. We do understand that this will cause you delays and affect your development planning, and we appreciate your cooperation and patience.

 

You workaround ideas sounds eminently doable to me, e.g., use the ReferenceIntersector class to determine the appropriate element intersections and project them onto the unbounded dimension line. However, I do not see why this is required.

Cheers,

 

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 7 of 30

maisoui
Advocate
Advocate

Ok thank you for your answer. I will wait for a future API changes.

Best regards

--
Jonathan
0 Likes
Message 8 of 30

FAIR59
Advisor
Advisor

while I agree that the API should provide the points of a DimensionLine, you can calculate them.

 

StringBuilder sb = new StringBuilder();				
Dimension dim = doc.GetElement(sel.GetElementIds().FirstOrDefault()) as Dimension; Line dimLine = dim.Curve as Line; if (dimLine==null) return; dimLine.MakeBound(0,1); XYZ pt1 = dimLine.GetEndPoint(0); XYZ pt2 = dimLine.GetEndPoint(1); XYZ direction = pt2.Subtract(pt1).Normalize(); sb.AppendLine(string.Format("point1 {0}",pt1)); if (dim.Segments.Size == 0) { pt2 = pt1.Add(direction.Multiply((double) dim.Value)); sb.AppendLine(string.Format("point2 {0}",pt2)) ; } else { XYZ segmentPt0 = pt1; foreach( DimensionSegment seg in dim.Segments) { XYZ segmentPt1 = segmentPt0.Add(direction.Multiply((double) seg.Value)); sb.AppendLine(string.Format("pt {0}, value {1}",segmentPt1,(double) seg.Value)); segmentPt0 =segmentPt1; } } TaskDialog.Show("debug",sb.ToString());
0 Likes
Message 9 of 30

FAIR59
Advisor
Advisor

sorry, I was too hasty. my solution won't work, pt1 is not the starting Point of the DimensionLine;

0 Likes
Message 10 of 30

maisoui
Advocate
Advocate

I am trying your solution and I also noticed that there is a position issue.

Using the direction vector with the segment value is still a good idea.

 

A few comments:

 

Line line = dim.Curve as Line;

dimLine.MakeBound(0,1);
XYZ pt1 = dimLine.GetEndPoint(0); // ---> = line.Origin
XYZ pt2 = dimLine.GetEndPoint(1); // not needed
XYZ direction = pt2.Subtract(pt1).Normalize(); // ---> = line.Direction

Last thing:

segment.Origin

gives another point, but still not the correct position (start point).

 

Dimensions in Revit API, is really not clear to me.

--
Jonathan
0 Likes
Message 11 of 30

FAIR59
Advisor
Advisor
Accepted solution

I think that in most cases this will get the startpoint of the dimensrion

 

				XYZ dimStartPoint=null;
				XYZ pt1 = dimLine.GetEndPoint(0);
				XYZ pt2 = null;
				foreach (Reference ref1 in dim.References)
				{
					XYZ refPoint = null;;
					Element el = doc.GetElement(	ref1.ElementId);
					GeometryObject obj = el.GetGeometryObjectFromReference(ref1);
					if (obj==null) 
					{
						// element is Grid or ReferencePlane or ??
						ReferencePlane refPl = el as ReferencePlane;
						if (refPl!= null) refPoint = refPl.GetPlane().Origin;
						Grid _grid = el as Grid;
						if (_grid!= null) refPoint = _grid.Curve.GetEndPoint(0);
					}
					else
					{
						// reference to Line or Plane 
						// or Point?
						Line l = obj as Line;
						if (l!=null) refPoint = l.GetEndPoint(0);
						PlanarFace f = obj as PlanarFace;
						if (f!=null) refPoint = f.Origin;
					}
					if (refPoint!=null) 
					{
						Plane WorkPlane = doc.ActiveView.SketchPlane.GetPlane();
						XYZ normal = WorkPlane.Normal.Normalize();
						// project the "globalpoint" of the reference onto the sketchplane
						XYZ refPtonPlane = refPoint.Subtract(normal.Multiply(normal.DotProduct(refPoint-WorkPlane.Origin)));
						XYZ LineNormal = normal.CrossProduct(direction).Normalize();
						// project the result onto the dimensionLine
						dimStartPoint = refPtonPlane.Subtract(LineNormal.Multiply(LineNormal.DotProduct( refPtonPlane-pt1)));
					}
					break;
				}
Message 12 of 30

maisoui
Advocate
Advocate

Perfect, thank you. You are faster than me Smiley Wink

Last challenge is to obtain the two "pointing points" (in AutoCAD : xLinePoint1, xLinePoint2 and dimLinePoint).

 

revit_dimension_dimline.png

 

--
Jonathan
Message 13 of 30

FAIR59
Advisor
Advisor

I don't see a solution to your last question.

 

PS, I think the Origin of a segment is the MidPoint of that segment on the dimensionLine, so you can use the Origin of the first segment to find the startPoint of the dimension  [ Origin.Subtract (direction.Multiply(segment.Value /2) ]

0 Likes
Message 14 of 30

maisoui
Advocate
Advocate

I'm looking to do something with property LeaderEndPosition.

 

I confirm that the segment's origin is the middle of the segment. So we don't need to iterate through references to fix point1.

Once again, thank you for your help.

--
Jonathan
0 Likes
Message 15 of 30

jeremytammik
Autodesk
Autodesk

Dear Jonathan,

Thank you for your confirmation and appreciation.

Can you please provide a minimal sample model containing a macro to demonstrate how it all works?

 

Alternatively, an external add-in would also be useful.

 

For documentation purposes and to enable others to make use of this also.

 

Thank you!

 

Cheers,

Jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 16 of 30

jeremytammik
Autodesk
Autodesk

Wouldn't it be safer to use the dim.View instead of doc.ActiveView?



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 17 of 30

jeremytammik
Autodesk
Autodesk

@FAIR59

 

i tested your code with a dimension spanning a sequence of three parallel walls.

 

running it, i see the following:

 

  • `el` is a wall.
  • `obj` is a valid geometry object, but neither a line nor a face, so the start point ends up remaining undefined.

 

how would you suggest handling this?

 

should i make use of `e'`? how?

 

the code is checked in to The Building Coder samples in 

 

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/C...

 

because the start point is undefined, the command currently prints out the following:

 

pt  (6.285955083, -4.935853072, 0.000000000),  value  3.28083989501312
pt  (9.566794978, -4.935853072, 0.000000000),  value  3.28083989501312
Start at , points (3.01,-4.94,0),(6.29,-4.94,0),(9.57,-4.94,0).

the start point before the comma is missing...

 

any idea?

 

thank you!

 

cheers,

 

jeremy



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 18 of 30

FAIR59
Advisor
Advisor

@jeremytammik

 

The obj is probably an Edge, so you need to adapt the code to get a Point from that Edge. In my testing I only considered Lines and PlanarFaces.

 

However, as I said in a previous post, you can find the startPoint of the dimensionline using the Dimension.Origin or DimensionSegment.Origin.

 

cheers

0 Likes
Message 19 of 30

jeremytammik
Autodesk
Autodesk

@FAIR59 thank you!

 

i implemented it in 

 

https://github.com/jeremytammik/the_building_coder_samples/blob/master/BuildingCoder/BuildingCoder/C...

 

The entire code is:

 

    List<XYZ> GetDimensionPoints( Dimension dim )
    {
      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();
      pts.Add( pt1 );
      if( dim.Segments.Size == 0 )
      {
        pt2 = pt1.Add( direction.Multiply( (double) dim.Value ) );
        pts.Add( pt2 );
      }
      else
      {
        XYZ segmentPt0 = pt1;
        foreach( DimensionSegment seg in dim.Segments )
        {
          XYZ segmentPt1 = segmentPt0.Add( direction.Multiply( (double) seg.Value ) );
          Debug.Print( "pt  {0},  value  {1}", segmentPt1, (double) seg.Value );
          pts.Add( segmentPt1 );
          segmentPt0 = segmentPt1;
        }
      }
      return pts;
    }

    XYZ GetDimensionStartPointFirstAttempt(
      Dimension dim )
    {
      Document doc = dim.Document;

      Line dimLine = dim.Curve as Line;
      if( dimLine == null ) return null;
      dimLine.MakeBound( 0, 1 );

      XYZ dimStartPoint = null;
      XYZ pt1 = dimLine.GetEndPoint( 0 );

      // dim.Origin throws "Cannot access this method
      // if this dimension has more than one segment."
      //Debug.Assert( Util.IsEqual( pt1, dim.Origin ),
      //  "expected equal points" );

      foreach( Reference ref1 in dim.References )
      {
        XYZ refPoint = null;
        Element el = doc.GetElement( ref1.ElementId );
        GeometryObject obj = el.GetGeometryObjectFromReference(
          ref1 );

        if( obj == null )
        {
          // element is Grid or ReferencePlane or ??
          ReferencePlane refPl = el as ReferencePlane;
          if( refPl != null ) refPoint = refPl.GetPlane().Origin;
          Grid grid = el as Grid;
          if( grid != null ) refPoint = grid.Curve.GetEndPoint( 0 );
        }
        else
        {
          // reference to Line, Plane or Point?
          Line l = obj as Line;
          if( l != null ) refPoint = l.GetEndPoint( 0 );
          PlanarFace f = obj as PlanarFace;
          if( f != null ) refPoint = f.Origin;
        }

        if( refPoint != null )
        {
          //View v = doc.ActiveView;
          View v = dim.View;
          Plane WorkPlane = v.SketchPlane.GetPlane();
          XYZ normal = WorkPlane.Normal.Normalize();

          // Project the "globalpoint" of the reference onto the sketchplane

          XYZ refPtonPlane = refPoint.Subtract(
            normal.Multiply( normal.DotProduct(
              refPoint - WorkPlane.Origin ) ) );

          XYZ lineNormal = normal.CrossProduct(
            dimLine.Direction ).Normalize();

          // Project the result onto the dimensionLine

          dimStartPoint = refPtonPlane.Subtract(
            lineNormal.Multiply( lineNormal.DotProduct(
              refPtonPlane - pt1 ) ) );
        }
        break;
      }
      return dimStartPoint;
    }

    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." ) );

        foreach( DimensionSegment seg in dim.Segments )
        {
          p = seg.Origin;
          break;
        }
      }
      return p;
    }

    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 );

      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 );
      foreach( XYZ q in pts )
      {
        d.Add( q.X - p.X );
        p = q;
      }

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

      return Result.Succeeded;
    }

However, when running it in this scenario:

 

Screen Shot 2017-06-15 at 13.39.10.png

In that scenario, the current code prints:

 

Dimension origin at (0.04,-4.94,0) followed by 3 further points: 
  (3.01,-4.94,0), (6.29,-4.94,0), (9.57,-4.94,0)

Horizontal distances in metres: 0.9, 1, 1

I guess that means that the '3 further points' really are the dimension points of interest.

 

The question is, what is the 'start point'?

 

Is it arbitrary?

 

In this case, it seems best to simply ignore it...

 

Thank you!

 

Cheers,

 

Jeremy

 

 

 

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

Message 20 of 30

FAIR59
Advisor
Advisor

the arbitrary point is the point pt1 in GetDimensionPoints

 

I suggest

List<XYZ> GetDimensionPoints( Dimension dim )
    {
      Line dimLine = dim.Curve as Line;
      if( dimLine == null ) return null;
      List<XYZ> pts = new List<XYZ>();

      dimLine.MakeBound( 0, 1 );
      XYZ pt1 =  GetDimensionStartPoint( dim );
      XYZ pt2 = null;
      XYZ direction =  dimLine.Direction.Normalize();
      pts.Add( pt1 );

etc..