Report length of Tube Feature

The following journal will prompt the user to select a tube feature and will report the total length of the tube. Copy and paste the following code into a text file, then save it with a name such as “Tube_Length.vb”. Create or open a file that has some tube features in it and run the journal. The tube may be made of a single sketch, multiple sketches, dumb curves, edges, or any combination thereof. The journal demonstrates how to access the individual curves used to create the tube feature, and the use of the Ui.SelectWithSingleDialog function. The Ui.SelectWithSingleDialog function provides the use of callbacks which allow you to use your own custom functions to define the selection filter.

'NX 7.5
' journal to limit selection to single tube feature
' and return total length of the tube
' April 5, 2012
'NXJournaling.com
 
Option Strict Off
Imports System
Imports NXOpen
 
Module Tube_Length
 
    Dim ufs As UF.UFSession = UF.UFSession.GetUFSession()
 
    '”pointers” to custom filter functions
    Dim init_proc_ref As UF.UFUi.SelInitFnT = AddressOf init_proc_body
    Dim filter_proc_ref As UF.UFUi.SelFilterFnT = AddressOf tube_feature_filter
 
    Sub Main()
 
       Dim theSession As Session = Session.GetSession()
       Dim tubePrompt As String = "Select tube feature"
       Dim totalLength As Double
       Dim tubeParents() As NXObject
       Dim tubeCurves() As NXObject
 
       Dim myTubeFeature As Features.Feature = select_tube_feature(tubePrompt)
 
       If myTubeFeature IsNot Nothing Then
           tubeParents = myTubeFeature.GetSections
           For i As Integer = 0 To tubeParents.Length - 1
               For Each objSec As Section In tubeParents
                   objSec.GetOutputCurves(tubeCurves)
                   For Each objCurve As Curve In tubeCurves
                       totalLength += objCurve.GetLength()
                   Next
               Next
           Next
           'do something with the calculated total length
           MsgBox("Total Length: " & totalLength)
       End If
 
    End Sub
 
    Public Function select_tube_feature(ByRef prompt As String) As Features.Feature
 
       Dim response As Integer = 0
       Dim objs() As Tag = Nothing
       Dim user_data As System.IntPtr
       Dim selTube As Tag = Nothing
       Dim cursor(2) As Double
       Dim view As Tag = Nothing
 
       ufs.Ui.LockUgAccess(UF.UFConstants.UF_UI_FROM_CUSTOM)
 
       Try
           ufs.Ui.SelectWithSingleDialog("Select Tube: ", prompt, _
               UF.UFConstants.UF_UI_SEL_SCOPE_ANY_IN_ASSEMBLY, init_proc_ref, _
               user_data, response, selTube, cursor, view)
       Finally
           ufs.Ui.UnlockUgAccess(UF.UFConstants.UF_UI_FROM_CUSTOM)
       End Try
 
       If Not selTube.Equals(Tag.Null) Then
           'the SelectWithSingleDialog method returns a tag,
           'return the feature from the given tag
           Dim myTube As Features.Feature
           ufs.Disp.SetHighlight(selTube, 0)
           myTube = Utilities.NXObjectManager.Get(selTube)
           Return myTube
       Else
           Return Nothing
       End If
 
    End Function
 
    Public Function init_proc_body(ByVal select_ As IntPtr, _
                                  ByVal userdata As IntPtr) As Integer
       'this function must have the same signature as UFUi.SelInitFnT Delegate
 
       'setup mask to filter for features
       Dim num_triples As Integer = 1
       Dim mask_triples As UF.UFUi.Mask() = New UF.UFUi.Mask(0) {}
       mask_triples(0).object_type = UF.UFConstants.UF_feature_type
       mask_triples(0).object_subtype = 0
       mask_triples(0).solid_type = 0
 
       ufs.Ui.SetSelMask(select_, _
                         UF.UFUi.SelMaskAction.SelMaskClearAndEnableSpecific, _
                         num_triples, mask_triples)
 
       'filter_proc_ref is the pointer to tube_feature_filter, which filters the features for tube features
       ufs.Ui.SetSelProcs(select_, filter_proc_ref, Nothing, userdata)
 
       Return UF.UFConstants.UF_UI_SEL_SUCCESS
 
    End Function
 
   Public Function tube_feature_filter(ByVal _object As Tag, _
                                       ByVal type As Integer(), _
                                       ByVal user_data As IntPtr, _
                                       ByVal select_ As IntPtr) As Integer
       'type, user_data, and select_ are unused, but this function must have
       'the same signature as UFUi.SelFilterFnT Delegate
 
       Dim myFeature As Features.Feature = Nothing
       myFeature = Utilities.NXObjectManager.Get(_object)
       If myFeature.FeatureType = "SWP104" Then
           Return UF.UFConstants.UF_UI_SEL_ACCEPT
       Else
           Return UF.UFConstants.UF_UI_SEL_REJECT
       End If
 
    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


Now that you have had a chance to see what the journal does, let’s break it down to see how it works. We start by declaring some module level variables, these variables will be available to Sub Main as well as any functions or subroutines we add to the module. We could call these global variables since this particular journal only consists of the single module.

First the UFSession object is created, it will be used in the selection functions. Next we have two variables declared as type: UF.UFUi.SelInitFnT. Where SelInitFnT is programmer shorthand for Selection Initialization Function. These variables are defined using the AddressOf operator, which is the VB.Net way of creating a delegate.

“So what’s a delegate?”, you ask. An excellent question, to answer it we’ll have to take a step back and talk about pointers. For those unfamiliar with pointers; a pointer is a construct in a programming language that allows the programmer direct access to memory locations and content. This allows a greater degree of control and allows for some neat tricks - one of which is passing a function call as you would a variable. As we learned from Spiderman’s wise uncle: “With great power comes great responsibility”; along with the increased control comes the very real possibility of corrupting memory contents and crashing the program and potentially taking the OS with it (blue screen of death, anyone?). The .NET languages were designed to run in “managed mode”; in short, this means that pointers are not allowed and there are some memory management functions at work in the background (you can create and work with unmanaged code in .NET, but that is well beyond the scope of this article). Ok, back to delegates; a delegate is the .NET way of providing some of the advantages of pointers without the extra responsibility that usually comes with them. In its original form, one of the NXOpen functions used a pointer to pass control to a programmer defined function to do additional processing. As the NXOpen functions are being converted to work with .NET code, we use a delegate here instead of a pointer. The delegates created here will be used in the selection routine - more on that later.

Sub Main is where the calculation of the length of the tube feature takes place. A tube can be made from multiple sections; for instance, imagine a tube created from a helix and a sketch connected by an associative bridge curve. In this case the tube has three sections: helix, sketch, and bridge curve. The journal first collects each section then breaks each section down into an array of curves if necessary and finally adds each individual curve length to the total. This demonstration journal ends by displaying the total length in a message box, but you will probably want to modify this to do something more useful such as placing this information in a tabular note, writing the length to an external database, or adding it as an attribute to the tube itself.

The code in Sub Main is straightforward and honestly, not that remarkable. The interesting bits lie in the selection routines, which make up the bulk of code in this journal. Sub Main calls the select_tube_feature shown below for easy reference:

Public Function select_tube_feature(ByRef prompt As String) As Features.Feature
 
 Dim response As Integer = 0
 Dim objs() As Tag = Nothing
 Dim user_data As System.IntPtr
 Dim selTube As Tag = Nothing
 Dim cursor(2) As Double
 Dim view As Tag = Nothing
 
 ufs.Ui.LockUgAccess(UF.UFConstants.UF_UI_FROM_CUSTOM)
 
 Try
     ufs.Ui.SelectWithSingleDialog("Select Tube: ", prompt, _
               UF.UFConstants.UF_UI_SEL_SCOPE_ANY_IN_ASSEMBLY, init_proc_ref, _
               user_data, response, selTube, cursor, view)
 Finally
     ufs.Ui.UnlockUgAccess(UF.UFConstants.UF_UI_FROM_CUSTOM)
 End Try
 
 If Not selTube.Equals(Tag.Null) Then
 'the SelectWithSingleDialog method returns a tag,
 'return the feature from the given tag
     Dim myTube As Features.Feature
     ufs.Disp.SetHighlight(selTube, 0)
     myTube = Utilities.NXObjectManager.Get(selTube)
     Return myTube
 Else
     Return Nothing
 End If
 
End Function


After a few variable declarations, we call the function Ui.LockUgAccess(). This will disable most of the toolbars so that the user cannot create new geometry until the selection dialog box is responded to. We now call the Ui.SelectWithSingleDialog() function; you’ll notice that the 4th parameter passed to this function is our delegate, init_proc_ref (initialize process reference), which points to the function: init_proc_body (initialize process body). Control now passes to this function that we have written that will “initialize” the selection process by specifying what objects we wish to allow the user to select.

Public Function init_proc_body(ByVal select_ As IntPtr, _
                                  ByVal userdata As IntPtr) As Integer
       'this function must have the same signature as UFUi.SelInitFnT Delegate
 
       'setup mask to filter for features
       Dim num_triples As Integer = 1
       Dim mask_triples As UF.UFUi.Mask() = New UF.UFUi.Mask(0) {}
       mask_triples(0).object_type = UF.UFConstants.UF_feature_type
       mask_triples(0).object_subtype = 0
       mask_triples(0).solid_type = 0
 
       ufs.Ui.SetSelMask(select_, _
                         UF.UFUi.SelMaskAction.SelMaskClearAndEnableSpecific, _
                         num_triples, mask_triples)
 
       'filter_proc_ref is the pointer to tube_feature_filter, which filters the features for tube features
       ufs.Ui.SetSelProcs(select_, filter_proc_ref, Nothing, userdata)
 
       Return UF.UFConstants.UF_UI_SEL_SUCCESS
 
    End Function


This function creates a mask triple that will only allow the user to select a feature. The mask triple acts as a filter that only allows through what we specify; you can see the object_type is set to UF_feature_type. The object_subtype is set to 0, which means “allow all available subtypes of the given object_type”. For more information on mask triples, refer to the tutorial: Using Mask Triples from the SelectObject Routine.

One of the arguments to the function call Ui.SetSelProcs is our delegate, filter_proc_ref, which points to the function: tube_feature_filter. As we continue down the rabbit hole, control is now passed to this function that we have written which will further filter the objects allowed for selection. The function tube_feature_filter is shown below.

Public Function tube_feature_filter(ByVal _object As Tag, _
                                       ByVal type As Integer(), _
                                       ByVal user_data As IntPtr, _
                                       ByVal select_ As IntPtr) As Integer
       'type, user_data, and select_ are unused, but this function must have
       'the same signature as UFUi.SelFilterFnT Delegate
 
       Dim myFeature As Features.Feature = Nothing
       myFeature = Utilities.NXObjectManager.Get(_object)
       If myFeature.FeatureType = "SWP104" Then
           Return UF.UFConstants.UF_UI_SEL_ACCEPT
       Else
           Return UF.UFConstants.UF_UI_SEL_REJECT
       End If
 
    End Function


The first argument, _object, holds the tag of any selected object that has made it this far. We know _object must hold the tag of a feature since init_proc_body uses a mask triple that only allows features through the selection process. We use the NXObjectManager to return the feature object from the given tag, then check the feature type. Internally a tube feature is reported as type “SWP104”. If we have a match the function returns UF_UI_SEL_ACCEPT to the init_proc_body function, which then returns a value indicating success to select_tube_feature, which then returns the tube feature to Sub Main for processing.

The use of delegates allows us to write our own functions to customize the selection process to our needs. The process is a little more complicated than using the SelectObjects method, but occasionally you will need the added power. Please note that for the sake of clarity, this example journal does very little error handling. The function init_proc_body is particularly optimistic in that it does no error checking and always returns the value indicating success. The journal has performed well in my very limited testing, but before you put similar code into a production environment, it would be wise to test test test and add error handling as necessary.

Comments

Hi
That's a very nice piece of code you have there. I was wondering how difficult would be to develop a journal identifying a list of design features, for example, blocks, cylinders and holes? Will be based on the same principle? Perhaps will be easier to read the part-navigator tree-list?
Thank you in advance,
G.

If you are starting with a parameterized model, it would be fairly simple to write out a list of a certain feature. The same could be done more easily with a filter applied to the part navigator in interactive mode.
If you are starting with an unparameterized model, "feature recognition" gets much more difficult.

Very useful code here !
It'll answer a lot of my questions. I'll study it carefully.

Thanks for sharing !