NXOpen.Features.Feature and the Feature Collection

In the NXOpen API, the "part" object is among the most used and most useful to the programmer. The part object gives us access to dozens (hundreds?) of properties and collections of other objects that we can use to query and modify the part. Let's take a look at the NXOpen.Feature object and the {part}.Features collection.

The feature tree in the part navigator will be immediately familiar if you have been using NX to create models for any length of time. The order of the features in the navigator act as an algorithm for creating the current model that you see in the graphics window. Scroll through the part navigator, and you can quickly tell which feature is current, which features are suppressed, and which features have update errors (hopefully none!). Expanding the "dependencies" window will give quick access to the selected feature's parent feauture(s) and any dependent features. The NXOpen part object gives us similar access to the part's features through our program. The part navigator lists all of the features in the current model; the part's .Features collection is analogous to the part navigator, it is a container for the individual feature objects that make up the model.

The code below shows how to access the features collection and process each feature object that it contains.

Option Strict Off
Imports System
Imports NXOpen
 
Module Module1
 
    Sub Main()
 
        Dim theSession As Session = Session.GetSession()
        If IsNothing(theSession.Parts.BaseWork) Then
            'active part required
            Return
        End If
 
        Dim workPart As Part = theSession.Parts.Work
        Dim lw As ListingWindow = theSession.ListingWindow
        lw.Open()
 
        For Each partFeature As Features.Feature In workPart.Features
            lw.WriteLine("feature name: " & partFeature.GetFeatureName)
            lw.WriteLine("feature type: " & partFeature.GetType.ToString)
            lw.WriteLine("")
        Next
 
        lw.Close()
 
    End Sub
 
    Public Function GetUnloadOption(ByVal dummy As String) As Integer
 
        'Unloads the image immediately after execution within NX
        GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately
 
    End Function
 
End Module

Ok, so the code above isn't all that exciting. There are, however, a few interesting aspects that may not be immediately obvious. First, depending on the features used in your current work part, the journal output may list more features than what you see in the part navigator; furthermore, multiple features may report the same timestamp. If you did not experience this with your current part and would like to see it for yourself, try the following: create a block feature then start the hole command, select a large face of the block to sketch on and pick multiple points on the face for the hole locations. Now run the journal again. In the part navigator you will see the block and hole commands, but in the journal output, in addition to the block and hole, you will also see a sketch feature and instead of a single hole feature, there will be multiple hole features. So what's going on here? An error in the journal code? or worse, a bug in the API? Actually, neither. Here the curtain starts to part and we begin to see the inner workings of NX. Some features can be internal to other features. In this case, the sketch feature and the individual hole features are wrapped up and presented to the user as a single feature in the part navigator. This behavior helps the user by keeping the feature tree concise, but we now know that each of these items is a feature in its own right.

The second item of note about the journal code above is not about feature objects or even the NXOpen API, but about programming in general; specifically it is about object oriented programming (OOP). Take another look at the For loop variable "partFeature"; you will notice that it is declared as type "Features.Feature", yet none of the reported features were of the type "Features.Feature". In the journal output, each feature was reported as a specific type such as .Features.Block or Features.HolePackage. How did we process these specific feature types if we didn't declare our variable as the proper type? If you look up the Feature object type in the NXOpen API reference, you will see that it is described as an "abstract" class. Dozens of specific feature types are shown as deriving from the Feature class. If you are familiar with OOP, the Feature class is a "superclass" or "base class". The Feature class defines all of the properties and methods that will be common to all feature types. Specific feature objects, such as a hole feature, will automatically "inherit" all of the properties and methods defined in the "Feature" class. These specific feature types, also known as "subclasses" or "child classes", may use these properties and methods differently and the subclass will often add new properties and/or methods that are specific to the subclass. Any subclass object can be treated as if it were of the superclass type. What this means for us is that every specific feature type (subclass) can be treated as if it were of type Features.Feature (superclass); any calls to properties or methods that are defined in the superclass will work on the subclass objects. This gives us access to a variety of useful properties and methods without needing to know the specific feature type.

Iterating through the part features and querying each feature's properties is a good way to learn more about specific features. Once you know the .FeatureType, you can look it up in the API reference to see what specific properties and methods the feature type supports. This knowledge better equips you to write useful and robust code to handle the feature type. When learning about a new feature, the first thing that I do is create a file that uses the feature and run the journal code below:

Option Strict Off
Imports System
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Features
 
Module Module2
    Sub Main()
 
 
        Dim theSession As Session = Session.GetSession()
        Dim theUfSession As UFSession = UFSession.GetUFSession
        Dim lw As ListingWindow = theSession.ListingWindow
        lw.Open()
 
        lw.WriteLine("*** All Features ***")
        For Each myFeature As Feature In theSession.Parts.Work.Features
            lw.WriteLine(".GetFeatureName: " & myFeature.GetFeatureName)
            lw.WriteLine(".Name: " & myFeature.Name)
            lw.WriteLine(".IsInternal: " & myFeature.IsInternal.ToString)
            lw.WriteLine(".FeatureType: " & myFeature.FeatureType)
            lw.WriteLine(".GetType: " & myFeature.GetType.ToString)
            lw.WriteLine(".Timestamp: " & myFeature.Timestamp.ToString)
            lw.WriteLine(".Suppressed: " & myFeature.Suppressed.ToString)
 
            Try
                lw.WriteLine(".Location: " & myFeature.Location.ToString)
            Catch ex As NXException
            End Try
 
            lw.WriteLine("")
 
        Next
        lw.Close()
 
    End Sub
End Module

The call to the .Location property is wrapped in a Try block; this is because even though .Location is defined in the .Feature superclass, not all feature types make use of it. For those features that do not make use of it, the .Location property will be empty and trying to access its value will throw an error.

Conclusion
Have a look at the API reference for the Feature object type. There are many more interesting properties and methods that this article did not touch on. You should now have a better idea of how to use the feature collection and how to interrogate the individual features.

Comments

Is there a way to collect all solids in the assembly (at each level).
I want to create a journal that will mirror the whole assembly as (Mirrored Body Wavelink) with all solids included, regardless if they are inside a part or the assembly.

For solids collection (.Bodies) I need to reference the Part.
Component.OwningPart doesnt seem to work for me...

The code below demonstrates the easiest method that I have found so far to get all the bodies from a given assembly.

Option Strict Off
Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
 
Module Module1
 
    Dim theSession As Session = Session.GetSession()
    Dim theUfSession As UFSession = UFSession.GetUFSession()
    Dim lw As ListingWindow = theSession.ListingWindow
 
    Sub Main()
 
        If IsNothing(theSession.Parts.BaseWork) Then
            'active part required
            Return
        End If
 
        Dim workPart As Part = theSession.Parts.Work
        lw.Open()
 
        Const undoMarkName As String = "report assembly bodies"
        Dim markId1 As Session.UndoMarkId
        markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)
 
        Dim theBodies As New List(Of Body)
        theBodies = AskAllBodies(theSession.Parts.Display)
 
        For Each tempBody As Body In theBodies
            lw.WriteLine("isOccurrence: " & tempBody.IsOccurrence.ToString)
            If tempBody.IsOccurrence Then
                lw.WriteLine("OwningComponent: " & tempBody.OwningComponent.DisplayName)
 
            Else
                lw.WriteLine("OwningPart: " & tempBody.OwningPart.Leaf)
 
            End If
 
 
 
            lw.WriteLine("")
 
        Next
 
        lw.Close()
 
    End Sub
 
    Function AskAllBodies(ByVal thePart As Part) As List(Of Body)
 
        Dim theBodies As New List(Of Body)
 
        Dim aBodyTag As Tag = Tag.Null
        Do
            theUfSession.Obj.CycleObjsInPart(thePart.Tag, _
                UFConstants.UF_solid_type, aBodyTag)
            If aBodyTag = Tag.Null Then
                Exit Do
            End If
 
            Dim theType As Integer, theSubtype As Integer
            theUfSession.Obj.AskTypeAndSubtype(aBodyTag, theType, theSubtype)
            If theSubtype = UFConstants.UF_solid_body_subtype Then
                theBodies.Add(Utilities.NXObjectManager.Get(aBodyTag))
 
            End If
        Loop While True
 
        Return theBodies
 
    End Function
 
    Public Function GetUnloadOption(ByVal dummy As String) As Integer
 
        'Unloads the image immediately after execution within NX
        GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately
 
    End Function
 
End Module