- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
I'd like to know what I'm missing in my code while trying to get angle value between two adjacent faces
Dim body = ThisDoc.Document.ComponentDefinition.SurfaceBodies(1)
Dim edge = body.Edges(1)
Dim face1 = edge.Faces(1)
Dim face2 = edge.Faces(2)
MsgBox(ThisApplication.MeasureTools.GetAngle(face1, face2)*180/PI & " deg",, "Angle")
The Dodecahedron (see model attached) has all angles equal to ~"117 deg" but the code returns "63 deg" (angle adjacent to the measured)
Different manual and API output on same input.
I know that In this particular case I can get the desired by subtracting the value from 180 deg, but this is not applicable for concave angles
Isn't it an API defect?
Dear @MjDeck, could you, please, clarify this?
Please vote for Inventor-Idea Text Search within Option Names
Solved! Go to Solution.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi @Maxim-CADman77. I can't explain the behavior of the manual measure tool, but when doing it by code, I can understand it better. It goes by the 'Normal' (direction) of the face, and by the order they are selected. Picture an arrow pointing perpendicularly outward from the center of each face, then when you select a face to measure its angle, it is really selecting that arrow, then when you select the second face, it measures between the angle from the direction of the first face's arrow, to the direction of the second face's arrow. So the following properties were likely used behind the scenes to figure it out.
Face.Evaluator (as SurfaceEvaluator)
Edit: Here is alternate code example, just for additional reference.
Dim body As SurfaceBody = ThisDoc.Document.ComponentDefinition.SurfaceBodies(1)
Dim edge As Edge = body.Edges(1)
Dim face1 As Face = edge.Faces(1)
Dim face2 As Face = edge.Faces(2)
Dim oF1points() As Double = {}
Dim oF1normals() As Double = {}
face1.PointOnFace.GetPointData(oF1points)
face1.Evaluator.GetNormalAtPoint(oF1points, oF1normals)
Dim oF2points() As Double = {}
Dim oF2normals() As Double = {}
face2.PointOnFace.GetPointData(oF2points)
face2.Evaluator.GetNormalAtPoint(oF2points, oF2normals)
Dim oF1Normal, oF2Normal As UnitVector
oF1Normal = ThisApplication.TransientGeometry.CreateUnitVector(oF1normals(0), oF1normals(1), oF1normals(2))
oF2Normal = ThisApplication.TransientGeometry.CreateUnitVector(oF2normals(0), oF2normals(1), oF2normals(2))
Dim dAngle As Double = (oF1Normal.AngleTo(oF2Normal) * (180 / Math.PI))
MsgBox(dAngle.ToString & " deg", vbInformation, "Angle")
Wesley Crihfield
(Not an Autodesk Employee)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi @Maxim-CADman77 - as Wesley says, the angle measurement uses the surface normal. We should mention that in the documentation for MeasureTools.GetAngle .
The angle isn't affected by the order of arguments you pass to the function. The result will be the same when you provide them in either order (face1, face2) or (face2, face1).
The return value should be the smallest angle between the normals. It will always be in the range 0 to 180 degrees. Here's a short test rule:
Dim face1 = ThisApplication.CommandManager.Pick(SelectionFilterEnum.kPartFacePlanarFilter, "Select Face 1")
Dim face2 = ThisApplication.CommandManager.Pick(SelectionFilterEnum.kPartFacePlanarFilter, "Select Face 2")
If face1 IsNot Nothing And face2 IsNot Nothing Then
Logger.Info("angle = {0} deg", ThisApplication.MeasureTools.GetAngle(face1, face2)*180/PI )
End If
Edit: the function is not working the way I would expect it to in all cases. I'll try to find out more.
About convex and concave:
The face normals always point outside the body. (Here's some documentation. Faces on the boundary of a solid body are single-sided faces.)
If two planar faces are adjacent (share an edge), I know of one way to find out if they are convex or concave on the body. But before I post it, I'll try to find out if there's a simpler way.

Mike Deck
Software Developer
Autodesk, Inc.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
@Maxim-CADman77 , here is what I had on hand. It is a version of what @Cadkunde.nl posted a while back.
It's been a bit since I looked at this, but I think I reworked it with the goal of returning the same result as if we used the Measure tool manually. I don't know how reliable the results are, but it seemed to work well for me when I was working with this in the past. MjDeck might have more straightforward approach though.
the original is here:
Sub main Dim oFace1 = ThisApplication.CommandManager.Pick _ (SelectionFilterEnum.kPartFacePlanarFilter, "Select first face") Dim oFace2 = ThisApplication.CommandManager.Pick _ (SelectionFilterEnum.kPartFacePlanarFilter, "Select second face") Dim oAngle As Double = ThisApplication.MeasureTools.GetAngle(oFace1, oFace2) * 180 / Math.PI Dim oComparisonPts1 As Object = GetComparisionPoints(oFace1) Dim oComparisonPts2 As Object = GetComparisionPoints(oFace2) Dim oDistance1 As Double = ThisApplication.MeasureTools.GetMinimumDistance _ (oComparisonPts1(0), oComparisonPts2(0)) Dim oDistance2 As Double = ThisApplication.MeasureTools.GetMinimumDistance _ (oComparisonPts1(1), oComparisonPts2(1)) Dim oLine As LineSegment = ThisApplication.TransientGeometry.CreateLineSegment _ (oComparisonPts1(0), oComparisonPts1(2)) Dim oIntersectpoint As ObjectsEnumerator = oLine.IntersectWithSurface(oFace2.Geometry) If oDistance1 > oDistance2 And Not oIntersectpoint Is Nothing Then oQ = "Quadrant 1" oAngle = oAngle ElseIf oDistance1 > oDistance2 And oIntersectpoint Is Nothing Then oQ = "Quadrant 2" oAngle = 180 - oAngle ElseIf oDistance1 < oDistance2 And Not oIntersectpoint Is Nothing Then oQ = "Quadrant 3" oAngle = 180 - oAngle ElseIf oDistance1 < oDistance2 And oIntersectpoint Is Nothing Then oQ = "Quadrant 4" oAngle = oAngle End If MsgBox(oAngle, , oQ) End Sub Function GetComparisionPoints(oFace As Face) Dim FacePoint1 As Point = oFace.PointOnFace Dim Params(1) As Double Params(0) = 0 Params(1) = 0 Dim Normals(2) As Double oFace.Evaluator.GetNormal(Params, Normals) Dim FacePoint2 As Point = ThisApplication.TransientGeometry.CreatePoint _ (FacePoint1.X + Normals(0) * 0.001, FacePoint1.Y + Normals(1) * 0.001, FacePoint1.Z + Normals(2) * 0.001) Dim FacePoint3 As Point = ThisApplication.TransientGeometry.CreatePoint _ (FacePoint1.X + Normals(0) * 9999, FacePoint1.Y + Normals(1) * 9999, FacePoint1.Z + Normals(2) * 9999) GetComparisionPoints = New Point() {FacePoint1, FacePoint2, FacePoint3 } End Function
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
I am not sure if this is what @MjDeck had in mind about dealing with concave vs convex, but I created a custom Function, paired with a custom Enum, for determining if two faces are concave or convex. Then I used the result of that to determine if the angle between the two faces should be 'as computed' (inside angle), or inverted (outside angle). It's likely not as error proof as it could be, but it seemed to work OK in my initial testing.
It first essentially determines if the two 'input' faces have a common edge. If they do not, then these faces can not really be labeled either concave or convex, as far as I understand anyways. Then, it uses the tools already available to us for finding concave or convex edges of a body. Based on if the 'common edge' is considered concave or convex, that determines if the faces are concave or convex to each other. Then, as stated before, if they are convex, I invert the measured angle between them to essentially be 360 degrees minus the measured angle, to get its inverse. You can use that association however you want though.
Sub Main
Dim oFace1 As Face = ThisApplication.CommandManager.Pick(SelectionFilterEnum.kPartFacePlanarFilter, "Select Face 1")
If oFace1 Is Nothing Then Return
Dim oFace2 As Face = ThisApplication.CommandManager.Pick(SelectionFilterEnum.kPartFacePlanarFilter, "Select Face 2")
If oFace2 Is Nothing Then Return
Dim eResult As ConcaveConvexOrNeitherEnum = CheckFacesForConcaveOrConvex(oFace1, oFace2)
Dim dAngle As Double = (ThisApplication.MeasureTools.GetAngle(oFace1, oFace2) * (180/Math.PI))
If eResult = ConcaveConvexOrNeitherEnum.Concave Then 'inside angle
'do nothing
ElseIf eResult = ConcaveConvexOrNeitherEnum.Convex Then 'outside angle
dAngle = (360 - dAngle) 'invert angle
End If
MsgBox("Faces are " & eResult.ToString & " and," & vbCrLf & _
"Angle = " & dAngle.ToString & " deg.", vbInformation, "iLogic")
End Sub
Public Enum ConcaveConvexOrNeitherEnum
Concave
Convex
Neither
End Enum
Function CheckFacesForConcaveOrConvex(oFace1 As Face, oFace2 As Face) As ConcaveConvexOrNeitherEnum
'find the common edge between those two faces
Dim commonEdge As Edge
For Each oEdge As Edge In oFace1.Edges
If oEdge.Faces.Count > 1 Then
For Each oOtherFace In oEdge.Faces
If oOtherFace Is oFace2 Then
commonEdge = oEdge
Exit For
End If
Next oOtherFace
End If
If commonEdge IsNot Nothing Then Exit For
Next oEdge
If commonEdge Is Nothing Then
'Logger.Debug("No common edge found!", vbExclamation, "iLogic")
Return ConcaveConvexOrNeitherEnum.Neither
End If
Dim oCCEs As EdgeCollection = oFace1.Parent.ConcaveEdges
'Logger.Info("ConcaveEdges = " & oCCEs.Count)
Dim oCVEs As EdgeCollection = oFace1.Parent.ConvexEdges
'Logger.Info("ConvexEdges = " & oCVEs.Count)
If oCCEs IsNot Nothing AndAlso oCCEs.Count > 0 Then
For Each oConcaveEdge As Edge In oCCEs
If oConcaveEdge Is commonEdge Then
Return ConcaveConvexOrNeitherEnum.Concave
End If
Next oConcaveEdge
End If
If oCVEs IsNot Nothing AndAlso oCVEs.Count > 0 Then
For Each oConvexEdge As Edge In oCVEs
If oConvexEdge Is commonEdge Then
Return ConcaveConvexOrNeitherEnum.Convex
End If
Next oConvexEdge
End If
Return ConcaveConvexOrNeitherEnum.Neither
End Function
If this solved your problem, or answered your question, please click ACCEPT SOLUTION .
Or, if this helped you, please click (LIKE or KUDOS)
.
Wesley Crihfield
(Not an Autodesk Employee)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
@WCrihfield attached is my test file where I was measuring the color coded matched face pairs. Your example returns 45 degrees for all pairs.
If you have a few minutes can you take a look at this file?
this is the result of selecting the blue faces:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Here's a version that doesn't use the MeasureTools.GetAngle function. Instead, it uses vector cross product calculations to get directions of lines on the faces. So far, this has given me the same results as the Measure command. Please try it out.
@WCrihfield , I added code similar to what you have to check if the edge is convex. But the way I'm doing it, this isn't needed for the angle calculation. This code could be improved to be more like what you have, and explicitly show convex and concave. (I initially had another idea to check for convex/concave, but I'm glad to find out that it's available directly from the API!)

Mike Deck
Software Developer
Autodesk, Inc.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Interesting topic. Just replying to find it again after vacation
Had to do a lot of work with this and bothers me I havent found simple solution.
Convex and concave seems a promising method. I can understand how to use it now. Thanks
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
@MjDeck wrote:If two planar faces are adjacent (share an edge), I know of one way to find out if they are convex or concave on the body. But before I post it, I'll try to find out if there's a simpler way.
How about this:
@gerrardhickson wrote:Well, this feels silly - I just discovered the "SurfaceBodies.ConcaveEdges" edge collection. In my case, this is exactly what I want.
Found in this thread: Re: How to determine if an edge belongs to faces that are inward facing or outward facing?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi @_dscholtes_ - yes, it's good that those functions (or properties) are available. I didn't know about them when I made my initial reply, but I found them later. I included ConvexEdges in my reply in message 7 above. @WCrihfield posted a more complete example with tests for both convex and concave edges in message 5.

Mike Deck
Software Developer
Autodesk, Inc.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Well this has been a very interesting and fun topic.
@Curtis_Waguespack Thanks for the sample part. I never considered that a part could have a single Edge connecting what would normally have been 2 different bodies, and could have 4 different Faces associated with it. Due to how odd that one central edge is, it is neither concave, nor convex. You were right about my process (using convex vs concave to determine if I should simply subtract the measured angle from 360 degrees) not being error proof yet.
@MjDeck Thanks. I don't know why I didn't just go the CrossProduct route right away, since I am already pretty familiar with that process, due to the ' view / camera' related topics I was recently involved with. That was definitely the shortest code path, and seems to require the least processing, to get to the result we were looking for. Each 'cross product' leaves us with a 'direction' that is perpendicular to both of the 'input' directions, which is super handy sometimes. Figuring out the correct order to process them in, to control the resulting direction's polarity is the tricky part to picture in my mind.
Since this appears to be a 'busier' overall process than many of us had in mind, I decided that a 'helper' Class may be in order, to 'bundle' all that helper code into one overall block of code that can be referenced and called in to help, which should keep the 'Main' code routine simpler & easier to look at. I created two different versions of the helper Class. One version uses the simpler CrossProduct first step shown in Mike's example to get the 'edge direction', and has fewer public properties in the Class. The second version uses more 'traditional' routes to get the 'edge direction', and offers a few more public properties in the Class. Both versions use the same final two CrossProduct steps, to get the 2 virtual directions pointing away from the virtual edge, for measuring the angle between. This longer example also includes a couple new relatively simple, custom Functions (not part of the helper Class) that can be used to determine if an edge is concave or convex.
I will show those 2 here directly, for quicker reference:
Private Function EdgeIsConcave(oEdgeToCheck As Inventor.Edge) As Boolean
For Each oEdge As Inventor.Edge In oEdgeToCheck.Parent.ConcaveEdges
If oEdge Is oEdgeToCheck Then Return True
Next
Return False
End Function
and
Private Function EdgeIsConvex(oEdgeToCheck As Inventor.Edge) As Boolean
For Each oEdge As Inventor.Edge In oEdgeToCheck.Parent.ConvexEdges
If oEdge Is oEdgeToCheck Then Return True
Next
Return False
End Function
Those 2 complete examples using the 2 different versions of the helper Class are attached as text files, since they are rather long.
Wesley Crihfield
(Not an Autodesk Employee)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi Wesley - those rules look good, but they're not getting the same result as the Measure command in the attached part. They report some angles as 45 degrees instead of 135.

Mike Deck
Software Developer
Autodesk, Inc.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
@MjDeck You are right.
The difference comes from the first half of my 'GetFaceNormal' Function, where I first attempt to get the 'Normal' from the Plane.Normal property, where Face.Geometry is the Plane object. Apparently that Normal can point in the opposite direction from the 'Normal' we get when using the SurvaceEvaluator.GetNormalAtPoint method. Not sure why. But I did do some follow-up testing on the part, due to a suspicion, and found what I was expecting. For some of the faces of that part, its Face.IsParamReversed property returned a value of True, meaning the face's Normal is pointing inwards (towards interior of material), instead of outwards (away from material). I guess maybe that can be used as a lesson for how to avoid that unpredictable situation. Always use the 'Evaluator' tools, instead of the properties of the underlying 'transient geometry' objects (when possible). When I commented out that whole If...Else...End If statement, and the code within the 'If' half, it works the same as the manual measure tool, as planned.
So, this:
Private Function GetFaceNormal(oFace As Inventor.Face) As Inventor.UnitVector
If TypeOf oFace.Geometry Is Inventor.Plane Then
Dim oPlane As Inventor.Plane = oFace.Geometry
Return oPlane.Normal
Else
Dim oPt() As Double = {} : Dim oNorm() As Double = {}
oFace.PointOnFace.GetPointData(oPt)
oFace.Evaluator.GetNormalAtPoint(oPt, oNorm)
Return _InvApp.TransientGeometry.CreateUnitVector(oNorm(0), oNorm(1), oNorm(2))
End If
End Function
...had to be simplified back to this:
Private Function GetFaceNormal(oFace As Inventor.Face) As Inventor.UnitVector
Dim oPt() As Double = {} : Dim oNorm() As Double = {}
oFace.PointOnFace.GetPointData(oPt)
oFace.Evaluator.GetNormalAtPoint(oPt, oNorm)
Return _InvApp.TransientGeometry.CreateUnitVector(oNorm(0), oNorm(1), oNorm(2))
End Function
I knew that it was possible for the Normal's of faces to be pointing inwards, but still do not understand when or how it happens to be able to predict it. It seems to me like I usually saw that in bodies / parts that have been created through Mirror type process.
Edit: Here is another version, where it checks that Face.IsParamReversed, and if True, it reverses the polarity of the Normal before returning it. I only included the Plane.Normal part in the first place, because I thought that it might require less processing or run faster, when compared with the evaluator tools, but have not done specific testing to see if that is true.
Private Function GetFaceNormal(oFace As Inventor.Face) As Inventor.UnitVector
If TypeOf oFace.Geometry Is Inventor.Plane Then
Dim oPlane As Inventor.Plane = oFace.Geometry
If Not oFace.IsParamReversed Then
Return oPlane.Normal
Else
Dim oDir As UnitVector = oPlane.Normal
oDir.PutUnitVectorData({-oDir.X, -oDir.Y, -oDir.Z}) 'reverse polarity
Return oDir
End If
Else
Dim oPt() As Double = {} : Dim oNorm() As Double = {}
oFace.PointOnFace.GetPointData(oPt)
oFace.Evaluator.GetNormalAtPoint(oPt, oNorm)
Return _InvApp.TransientGeometry.CreateUnitVector(oNorm(0), oNorm(1), oNorm(2))
End If
End Function
Wesley Crihfield
(Not an Autodesk Employee)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
Hi Wesley - the normal of the Face will always point outside of the body. But the underlying surface normal could point either way. This gives the modeler more flexibility in constructing a solid. It can stitch together different surfaces without having to modify their geometry. I'll try to find a simple case to demonstrate.

Mike Deck
Software Developer
Autodesk, Inc.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
About face normals:
Consider a rectangular box. Say the surface normals have the same direction as the face normals. They're all pointing outwards from the box.
Now you use that box to do a Boolean and cut a pocket in another shape. Any face normal that was pointing outward should now be changed to point inward (inward for the original box is now outward for the result solid). The way it's implemented, I'm pretty sure that the solid modeling software only has to set a boolean value on the face to say that the normal is flipped. It doesn't have to modify the surface.

Mike Deck
Software Developer
Autodesk, Inc.