Report Missing Parts List Balloons

When working with a parts list on an assembly drawing, it can be difficult to determine if each item in the list has an associated callout balloon on the drawing. NX offers no command to report those items that have missing callouts; the usual course of action is to print a copy of the drawing and manually working through the parts list marking off those items for which you find a balloon. Frank Berger (of GTAC) has recognized this shortcoming and has written a journal that when run will report which items in the list have no corresponding balloon callout. You can find this journal fairly easily by searching the GTAC solution center with the phrase "report missing balloons" (it will be document: nxapi_5333). This journal takes advantage of some new functionality added to the API in NX 9; consequently, it will only run in NX 9 or higher. For those of us working with NX 8.5 (or lower), hope is not lost; the journal does provide a good "blueprint", we'll just have to find a way to duplicate the functionality while avoiding the NX 9 specific function calls.

The code below is my attempt to duplicate the functionality of Frank's journal for users of NX 8.5. My testing has been fairly limited, but the results are promising. If you use the journal and experience any issues, please let me know at info@nxjournaling.com or in the comments below.

Known Issues

The code below will probably not work if the callout symbol type used with the parts list is set to "none" or "underline". This is due to a bug/limitation of the API.

'NXJournaling.com
'January 20, 2015
'
'NX 8.5
'
'Journal to report which items in a parts list do not have corresponding balloon callouts.
'Inspired by and based on the GTAC sample "report_missing_balloons_of_parts_lists" (nxapi_5333)
'by Frank Berger.
'The GTAC sample will only work with NX 9 or higher; this journal will work with NX 8.5,
'and possibly with older versions back to NX 5.
 
'Similar to _v2, but this version uses a simple list to hold the autoballoon callout numbers
'instead of a dictionary. I thought there might be a performance improvement by using a list
'rather than a dictionary. Testing on a small assembly (~120 unique parts) shows very little,
'if any difference.
 
'February 22, 2016
'  Update to ignore potential "phantom" parts list returned by Plist.AskTags
 
Option Strict Off
Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
 
Module report_missing_balloons_v3
 
    Dim theSession As Session = Session.GetSession()
    Dim theUfSession As UFSession = UFSession.GetUFSession()
 
    Dim lw As ListingWindow = theSession.ListingWindow
 
    Sub Main()
 
        If IsNothing(theSession.Parts.Work) Then
            'active part required
            Return
        End If
 
        Dim workPart As Part = theSession.Parts.Work
        lw.Open()
 
        Dim partListTags() As Tag = Nothing
        Dim numPartLists As Integer = 0
        theUfSession.Plist.AskTags(partListTags, numPartLists)
 
        'test for a dummy parts list
        '.AskTags sometimes returns a tag even on a blank part that contains no parts list
        'PR 7643749
        'lw.WriteLine("number of parts lists: " & numPartLists.ToString)
        If numPartLists > 0 Then
            For i As Integer = 0 To partListTags.Length - 1
                Dim prtList As DisplayableObject = Utilities.NXObjectManager.Get(partListTags(i))
                'lw.WriteLine("parts list layer: " & prtList.Layer.ToString)
                If prtList.Layer = 0 Then
                    numPartLists -= 1
                End If
            Next
        End If
 
        If numPartLists <= 0 Then
            lw.WriteLine("No parts list found in the work part")
            Return
        End If
 
        If numPartLists > 1 Then
            lw.WriteLine("ERROR: Cannot evaluate AutoBalloons with multiple parts lists.")
            lw.WriteLine("Check environment variable UGII_UPDATE_ALL_ID_SYMBOLS_WITH_PLIST")
            Return
        End If
 
        theUfSession.Plist.UpdateAllPlists()
 
        Dim numRows As Integer
        theUfSession.Tabnot.AskNmRows(partListTags(0), numRows)
        'lw.WriteLine("number of rows: " & numRows.ToString)
 
        Dim plistPrefs As UFPlist.Prefs = Nothing
        theUfSession.Plist.AskPrefs(partListTags(0), plistPrefs)
        'lw.WriteLine("symbol type: " & plistPrefs.symbol_type.ToString)
        'lw.WriteLine("main symbol text: " & plistPrefs.main_symbol_text)
 
        Dim colCallout As Tag = PartListCalloutColumn(partListTags(0))
        'lw.WriteLine("callout column tag: " & colCallout.ToString)
        If colCallout = Tag.Null Then
            lw.WriteLine("parts list callout column not found")
            Return
        End If
 
        Dim plBalloonList As New List(Of String)
        CollectPartsListBalloons(plBalloonList, plistPrefs.symbol_type)
 
        Dim missingBalloons As New List(Of String)
 
        Dim startTime As DateTime = Now
 
        'loop through rows of the parts list, look for corresponding autoballoon callout
        For i As Integer = 0 To numRows - 1
 
            Dim rowTag As Tag
            theUfSession.Tabnot.AskNthRow(partListTags(0), i, rowTag)
 
            Dim cellTag As Tag
            theUfSession.Tabnot.AskCellAtRowCol(rowTag, colCallout, cellTag)
 
            Dim calloutEvText As String = ""
            theUfSession.Tabnot.AskEvaluatedCellText(cellTag, calloutEvText)
 
            If plBalloonList.Contains(calloutEvText) Then
                'balloon found, remove it from the list so future searches are potentially faster
                plBalloonList.Remove(calloutEvText)
            Else
                'balloon not found, add callout to the "missing" list
                missingBalloons.Add(calloutEvText)
            End If
 
        Next
 
        Dim endTime As DateTime = Now
 
        If missingBalloons.Count = 0 Then
            lw.WriteLine("Great news! All the parts in the list have a corresponding balloon callout.")
        Else
            lw.WriteLine("Hmmm... It seems that " & missingBalloons.Count.ToString & " parts in the list do NOT have corresponding balloon callouts.")
            For Each missing As String In missingBalloons
                lw.WriteLine(missing)
            Next
        End If
 
        Dim elapsedTime As TimeSpan = endTime.Subtract(startTime)
        lw.WriteFullline("elapsed time: " & elapsedTime.ToString)
 
        lw.Close()
 
    End Sub
 
    Function PartListCalloutColumn(ByVal partListTag As Tag) As Tag
 
        Dim numColumns As Integer
        theUfSession.Tabnot.AskNmColumns(partListTag, numColumns)
 
        'Dim numRows As Integer
        'theUfSession.Tabnot.AskNmRows(partListTag, numRows)
 
        'Dim plistPrefs As UFPlist.Prefs = Nothing
        'theUfSession.Plist.AskPrefs(partListTag, plistPrefs)
 
        Dim rowTag As Tag
        theUfSession.Tabnot.AskNthRow(partListTag, 0, rowTag)
 
        For j As Integer = 0 To numColumns - 1
            Dim colTag As Tag
            theUfSession.Tabnot.AskNthColumn(partListTag, j, colTag)
 
            Dim cellTag As Tag
            theUfSession.Tabnot.AskCellAtRowCol(rowTag, colTag, cellTag)
 
            'get the current cell text
            Dim cellText As String = ""
            theUfSession.Tabnot.AskCellText(cellTag, cellText)
            If cellText = "$~C" Then
                'callout column
                Return colTag
 
            End If
 
        Next
 
        Return Nothing
 
    End Function
 
    Sub CollectPartsListBalloons(ByRef theBalloonList As List(Of String), ByVal plSymbolType As Integer)
 
        For Each tempId As Annotations.IdSymbol In theSession.Parts.Work.Annotations.IdSymbols
 
            Dim theIdSymbolBuilder As Annotations.IdSymbolBuilder = theSession.Parts.Work.Annotations.IdSymbols.CreateIdSymbolBuilder(tempId)
 
            If plSymbolType = theIdSymbolBuilder.Type + 1 Then
                'symbol matches type used in parts list
            Else
                'symbol not the type used in parts list, skip it
                Continue For
            End If
 
            'add unique callouts to the list
 
            'assumes the callout is in the ".UpperText" property
            'change this to .LowerText if needed
 
            If Not theBalloonList.Contains(theIdSymbolBuilder.UpperText) Then
                theBalloonList.Add(theIdSymbolBuilder.UpperText)
            End If
 
            theIdSymbolBuilder.Destroy()
 
        Next
 
    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

Comments

This is cool but it does not seem to check an items quantity of balloons against the quantity on the parts list; say item number 2 has a quantity of 5, as long the as there is one balloon callout it will not report item 2 as missing any balloons.

The code above was only intended to report if any callouts were completely missing from the drawing. To borrow a phrase from GTAC, it is "working as designed".

However, you bring up a good point. Each user of NX has different needs and that is where customization pays off. I believe with a little work that you could extend this code to check that the balloon quantity matches the parts list quantity. If you undertake this project, please post your results as I'm sure we could all learn something from it!

I am brand new to Journaling... I didn't know it existed until I found this post in a google search. I am interested to know if the code above can be modified to work with a user defined attribute named "CALLOUT" in NX8.5? I ran this code as is and got an error message "parts list callout column not found" which appears to be due to us using an attribute rather than the NX callout.

If the title of your callout attribute is "CALLOUT", try changing the following line:

If cellText = "$~C" Then

to:

If cellText = "CALLOUT" Then

I'm fairly sure that this edit will do the job; if it does not, please post back with any errors you encounter.

Hello,

This script does not work if it runs for the first time without a parts list on the drawing. It seems that Plist.AskTags does not give an appropriate response (1 instead of zero). There is no parts list, why does it says 1?

The funny thing: If I start the journal a second time, everything works fine and it behaves like it should do.

Any ideas why this happens? I can´t find anything wrong

This occurs in NX8.0.3 and NX9.0.3.4

There seems to be some unexpected behavior from the .AskTags function. Sometimes it reports a parts list on layer zero; I'm not sure if this is a placeholder entity in the file or what. I've edited the code above to handle this "phantom" parts list. Post back if you are still having issues with it.