If you need to write a program that accesses threads in a model, the first thought it to get the thread information from the feature that created the thread. Threads can be created by adding a tapped hole or using the thread feature. This approach works but requires you to write two paths of code to handle the two different types of features. However, there are also cases where this approach doesn’t work.
Problems Getting Thread Information
There are several construction techniques where the original features that created the thread are not accessible.
Derived Parts – Whenever you derive a part, the derivation is just the body and you lose all of the feature information.
iParts – When you use iParts and create members, each member is a derived part and doesn’t have any of the original feature information.
iFeatures – After placing an iFeature that contains one or more tapped holes or threads, all you have is an iFeature and not the features that created the threads.
Apprentice – Another case is when you use Apprentice to access the part, whether it’s the original part that contains the features or a derived part. Apprentice doesn’t support access to any feature information, even when there is feature information in the part.
There’s some functionality that has existed in the API for a long time and is documented in the help, but I don’t think has ever been pointed out and explained in much detail. This functionality provides access to thread information in all the above cases and it also useful in the case where you do have access to the feature information because your code can be simpler by not having to support querying two different types of features.
Alternate Approach to Getting Thread Information
The alternative approach for getting thread information utilizes a capability exposed through the Face object, which supports the ThreadInfos property. In most cases this property will return Nothing, indicating that there isn’t any associated thread information. However, for cylindrical or conical faces that have threads, this property will return an ObjectCollection that contains one or more ThreadInfo objects. The ThreadInfo object provides all of the information needed to fully describe the thread.
The picture above is an example model that illustrates various types of threads and ways to create threads. These are all created either by creating a tapped hole or a thread feature. Both of these methods support creating standard and tapered threads. One capability when creating tapped holes is that you can use the “From Sketch” option to create multiple holes (and threads) within a single hole feature; one at each sketch point. The example above illustrates a couple of other things where there are two thread features on a single face, and another has a tapped hole and a thread feature adding threads to the other end of the hole. These are easily handled because the collection returned by the ThreadInfos property will contain two ThreadInfo objects.
The VBA code below iterates over the faces in the model, looking for any cylindrical or conical faces that have threads associated with them. It then dumps out information about the threads. With one exception (described in more detail below), this code works the same if you run it in the original part that contains all of the features, or run it in a derivation of that part. Although this is quite a bit of code, 90% of it writing out the thread information, and the code to get access to the thread information is all in the first For loop.
' Example that illustrates getting thread information from faces.
Public Sub GetThreads()
Dim partDoc As PartDocument
Set partDoc = ThisApplication.ActiveDocument
' Write the results to a file.
Dim threadFile As Integer
threadFile = FreeFile
Open "C:\Temp\ThreadInfo.txt" For Output As #threadFile
' Iterate through all of the faces in the first body of the part.
Dim fc As Face
For Each fc In partDoc.ComponentDefinition.SurfaceBodies.Item(1).Faces
' Look for cylindrical or conical faces.
If fc.SurfaceType = kCylinderSurface Or fc.SurfaceType = kConeSurface Then
If Not fc.ThreadInfos Is Nothing Then
' Check to see if there are any threads on this face.
If fc.ThreadInfos.Count > 0 Then
' Get the information for this thread.
Dim thread As ThreadInfo
For Each thread In fc.ThreadInfos
Print #threadFile, "Thread"
Call WriteFaceInfo(threadFile, fc)
Call WriteThreadInfo(threadFile, fc, thread)
Next
End If
End If
End If
Next
MsgBox "Thread result written to ""C:\Temp\ThreadInfo.txt"""
End Sub
' Write out geometric information about the cylindrical or conical face.
Private Sub WriteFaceInfo(threadFile As Integer, threadFace As Face)
If threadFace.SurfaceType = kCylinderSurface Then
Dim cyl As Cylinder
Set cyl = threadFace.Geometry
Print #threadFile, " Cylinder Geometry"
Print #threadFile, " Cylinder Diameter: " & cyl.Radius * 2
Print #threadFile, " Cylinder Position: " & GetPointOrVector(cyl.basePoint)
Print #threadFile, " Cylinder Direction: " & GetPointOrVector(cyl.AxisVector)
ElseIf threadFace.SurfaceType = kConeSurface Then
Dim cn As Cone
Set cn = threadFace.Geometry
Print #threadFile, " Cone Geometry"
Print #threadFile, " Cone Diameter: " & cn.Radius * 2
Print #threadFile, " Cone Position: " & GetPointOrVector(cn.basePoint)
Print #threadFile, " Cone Direction: " & GetPointOrVector(cn.AxisVector)
Print #threadFile, " Cone Angle: " & Format(cn.HalfAngle * (180 / 3.14159265358979), "0.000")
End If
End Sub
' Write out all of the information provided by a ThreadInfo object.
Private Sub WriteThreadInfo(fc As Face, thread As ThreadInfo)
' Write out the common, generic thread information.
Print #threadFile, " General Thread Info"
Print #threadFile, " Designation: " & thread.ThreadDesignation
Print #threadFile, " Custom Designation: " & thread.CustomThreadDesignation
Print #threadFile, " Full thread depth: " & thread.FullThreadDepth
Print #threadFile, " Internal: " & thread.Internal
Print #threadFile, " Metric: " & thread.Metric
Print #threadFile, " RightHanded: " & thread.RightHanded
Print #threadFile, " Thread Type: " & thread.ThreadType
Print #threadFile, " Thread Type Identifier: " & thread.ThreadTypeIdentifier
Print #threadFile, " Direction: " & GetPointOrVector(thread.ThreadDirection.AsUnitVector)
If Not thread.FullThreadDepth Then
Print #threadFile, " Depth: " & Format(thread.ThreadDirection.Length, "0.00000")
End If
Print #threadFile, " Locations:"
Dim basePoint As Point
For Each basePoint In thread.ThreadBasePoints
Print #threadFile, " Position: " & GetPointOrVector(basePoint)
Next
' If a tapped hole is created using a sketch so that multiples holes are placed as
' a single hole feature, the ThreadBasePoints property will return the location of
' all of the holes. However, there's a problem because each of the physical holes
' created by the feature will return this set of points. This code is used to find
' the coordinate that is used for this specific physical hole.
If thread.ThreadBasePoints.Count > 1 Then
' Create a transient line along the axis of the cylinder or cone.
Dim fcLine As Line
Set fcLine = ThisApplication.TransientGeometry.CreateLine(fc.Geometry.basePoint, fc.Geometry.AxisVector.AsVector)
' Check to see which hole base point lies on the axis.
Dim foundBasePoint As Point
Set foundBasePoint = Nothing
For Each basePoint In thread.ThreadBasePoints
Dim dist As Double
dist = ThisApplication.MeasureTools.GetMinimumDistance(basePoint, fcLine)
If dist < 0.000001 Then
Set foundBasePoint = basePoint
Exit For
End If
Next
Print #threadFile, " Position Used: " & GetPointOrVector(foundBasePoint)
End If
If TypeOf thread Is StandardThreadInfo Then
' Print out information for standard threads.
Print #threadFile, " Standard Thread Info"
Dim standard As StandardThreadInfo
Set standard = thread
Print #threadFile, " Class: " & standard.Class
Print #threadFile, " Max Major Dia.: " & Format(standard.MajorDiameterMax, "0.00000")
Print #threadFile, " Min Major Dia.: " & Format(standard.MajorDiameterMin, "0.00000")
Print #threadFile, " Max Minor Dia.: " & Format(standard.MinorDiameterMax, "0.00000")
Print #threadFile, " Min Minor Dia.: " & Format(standard.MinorDiameterMin, "0.00000")
Print #threadFile, " Nominal Size: " & Format(standard.NominalSize, "0.00000")
Print #threadFile, " Pitch: " & Format(standard.Pitch, "0.00000")
Print #threadFile, " Max Pitch Dia.: " & Format(standard.PitchDiameterMax, "0.00000")
Print #threadFile, " Min Pitch Dia.: " & Format(standard.PitchDiameterMin, "0.00000")
Print #threadFile, " Tap Drill Dia.: " & Format(standard.TapDrillDiameter, "0.00000")
ElseIf TypeOf thread Is TaperedThreadInfo Then
' Print out information for tapered threads.
Print #threadFile, " Tapered Thread Info"
Dim tapered As TaperedThreadInfo
Set tapered = thread
Print #threadFile, " Basic Minor Dia.: " & Format(tapered.BasicMinorDiameter, "0.00000")
Print #threadFile, " Effective Dia.: " & Format(tapered.EffectiveDiameter, "0.00000")
Print #threadFile, " Effective Length: " & Format(tapered.EffectiveLength, "0.00000")
Print #threadFile, " Engagement Dia.: " & Format(tapered.EngagementDiameter, "0.00000")
Print #threadFile, " Engagement Length: " & Format(tapered.EngagementLength, "0.00000")
Print #threadFile, " External Pitch Dia.: " & Format(tapered.ExternalPitchDiameter, "0.00000")
Print #threadFile, " External Nominal Dia.: " & Format(tapered.NominalExternalDiameter, "0.00000")
Print #threadFile, " External Nomminal Length: " & Format(tapered.NominalExternalLength, "0.00000")
Print #threadFile, " Outside Pipe Dia.: " & Format(tapered.OutsidePipeDiameter, "0.00000")
Print #threadFile, " Overall External Length: " & Format(tapered.OverallExternalLength, "0.00000")
Print #threadFile, " Tap Drill Dia.: " & Format(tapered.TapDrillDiameter, "0.00000")
Print #threadFile, " Thread Height: " & Format(tapered.ThreadHeight, "0.00000")
Print #threadFile, " Threads Per Inch: " & Format(tapered.ThreadsPerInch, "0.00000")
Print #threadFile, " Vanish Thread: " & Format(tapered.VanishThread, "0.00000")
Print #threadFile, " Wrench Makeup Dia.: " & Format(tapered.WrenchMakeupDiameter, "0.00000")
Print #threadFile, " Wrench Makeup Length: " & Format(tapered.WrenchMakeupLength, "0.00000")
End If
End Sub
' Function used to create a printable string from a point or vector.
Private Function GetPointOrVector(pointOrVector As Object) As String
GetPointOrVector = Format(pointOrVector.X, "0.00000") & ", " & _
Format(pointOrVector.Y, "0.00000") & ", " & _
Format(pointOrVector.Z, "0.00000")
End Function
Below is an example of the output of the program for one thread.
Thread
Cylinder Geometry
Cylinder Diameter: 4.46786
Cylinder Position: 4.92743, -33.67887, -11.22845
Cylinder Direction: 0.00000, 1.00000, 0.00000
General Thread Info
Designation: 1 13/16-12 UN
Custom Designation: 1 13/16-12 UN
Full thread depth: False
Internal: True
Metric: False
RightHanded: True
Thread Type: ANSI Unified Screw Threads
Thread Type Identifier: ANSI Unified Screw Threads
Direction: 0.00000, 1.00000, 0.00000
Depth: 2.54000
Locations:
Position: 4.92743, 0.00000, -11.22845
Standard Thread Info
Class: 2B
Max Major Dia.:
Min Major Dia.: 1.81250
Max Minor Dia.: 1.74000
Min Minor Dia.: 1.72200
Nominal Size: 1.81250
Pitch: 0.08333
Max Pitch Dia.: 1.76620
Min Pitch Dia.: 1.75840
Tap Drill Dia.: 1.73440
The exception between running this with the part that contains the feature or a derived version of the part is that the location of the holes is handled slightly different. The exception only comes into play when you have a tapped hole feature that was created using a sketch and creates more than one physical hole. (There must have been a specific reason the API was implemented the way it is but I don’t remember what it was.) The API to get thread information returns a collection of locations for the hole. In most cases, this will be a collection of one and represents the thread information associated with the cylindrical face. However, in the case where the feature is available, this collection will return the locations for all of the threads created by the hole feature. This isn’t a problem by itself, but if you’re iterating over all cylindrical faces, each of the cylinders representing one of the holes of the feature will return this same set of points. Because of this if you process each point you will get duplicate threads. The code above handles this by finding the hole within the list that corresponds to the current cylindrical face being processed. This isn’t a problem if the model has been derived, is an iFeature, or you’re using Apprentice. In that case, every thread will always have a single position point.
Using Apprentice
Below is the dialog from a Visual Basic sample that demonstrates getting hole information from a specified part using Apprentice. The full project can be downloaded here. It was written referencing the Inventor 2018 interop library but it can easily be changed to work with older versions of Inventor because the capabilities it uses have been supported for a long time.

Hole Position and Depth
Most of the information in the report is describing details about the thread. However, the position and depth of the thread are also described. The ThreadInfo object supports the ThreadDirection, FullThreadDepth, and ThreadBasePoints properties. The ThreadBasePoints property returns the collection of points, as described above, which define the starting point of the thread in model space. The FullThreadDepth property is a Boolean property that indicates if the thread is the full length of the cylinder or not. The ThreadDirection is a Vector object that defines both the direction and the length of the thread. For example, if you have a hole and the thread is running in the negative Z direction for 1.5 inches the vector for FullThreadDepth will be (0.0, 0.0, -3.81). Remember that all distances in the API are in centimeters so 3.81 cm is equal to 1.5 inches.
I clean up the information in the report by returning the direction as a unit vector (length of 1) and display the depth by getting the length of the vector.