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
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.
re: callout quantity
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!
Modify code to work with CALLOUT attribute?
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.
re: CALLOUT attribute
If the title of your callout attribute is "CALLOUT", try changing the following line:
to:
I'm fairly sure that this edit will do the job; if it does not, please post back with any errors you encounter.
Script is not working the first time
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
re: AskTags
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.