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.

[vbnet]'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[/vbnet]

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:

[vbnet]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[/vbnet]

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.

[vbnet]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[/vbnet]

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.

[vbnet]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[/vbnet]

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 !

I'm new to coding. This code helped me a lot to find the tube length. Thanks a lot. I need to find the tube bend radius and bend angle. can anyone help me with this?
Thanks in advance... :-)