Counting Total Number of Unique Components

Hello. First off, thank you so much for this great resource of a website. I have learned a lot as I am recently new to VB.net and NXOpen.

The goal of this journal is to import component name to an excel document, but right now I am stuck on this current issue. Right now this reports to the info window.

In an assembly with multiple sub assemblies with components. The count gets reset to the last component of the sub assembly.

Sample Assembly Tree

Main
SubAsm1 (Reports Count 1)
Comp1 (Reports Count 2)
Comp2 (Reports Count 3)
Comp3 (Reports Count 4)
SubAsm2 (Reports Count 2)
Comp4 (Reports Count 3)
Comp5 (Reports Count 4)
SubAsm3 (Reports Count 3)

I want to have it Count 1, 2, 3, 4, 5... down the list

Thanks

'Journal to recursively walk through the assembly structure
' will run on assemblies or piece parts
' will step through all components of the displayed part
'NX 7.5, native
'NXJournaling.com February 24, 2012

Option Strict Off

Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Utilities
Imports NXOpen.Assemblies
Imports System.Drawing
Imports System.Windows.Forms
Imports Microsoft.Office.Interop

Module NXJournal

Public theSession As Session = Session.GetSession()
Public ufs As UFSession = UFSession.GetUFSession()
Public lw As ListingWindow = theSession.ListingWindow

Sub Main()
Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim theUISession As UI = UI.GetUI

Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display

Try
Dim c As ComponentAssembly = dispPart.ComponentAssembly

lw.Open

Dim startRow As Integer = 1

If Not IsNothing(c.RootComponent) Then
'*** insert code to process 'root component' (assembly file)
lw.WriteLine("Assembly: " & c.RootComponent.DisplayName)

'*** end of code to process root component
ReportComponentChildren(c.RootComponent, startRow)
Else
'*** insert code to process piece part
lw.WriteLine("Part has no components")
End If

Catch e As Exception
theSession.ListingWindow.WriteLine("Failed: " & e.ToString)
End Try
lw.Close

ResetToMainAssembly()

End Sub

'**********************************************************
Sub ReportComponentChildren(ByVal comp As Component, ByVal lastRow As Integer)
' List to keep track of components along with their suppressed status

Dim componentsWithStatus As New List(Of Tuple(Of Component, Boolean))

' List to keep track of processed component display names for duplicate check
Dim NameList As New List(Of String)

' Collect components and their suppressed status
For Each child As Component In comp.GetChildren()
' Add component and its suppressed status to the list
componentsWithStatus.Add(New Tuple(Of Component, Boolean)(child, child.IsSuppressed))
Next

' Sort the list so that suppressed components come first
componentsWithStatus.Sort(Function(x, y) y.Item2.CompareTo(x.Item2))

' Process sorted components
For Each tuple As Tuple(Of Component, Boolean) In componentsWithStatus
Dim child As Component = tuple.Item1
Dim isSuppressed As Boolean = tuple.Item2

' Dim wb = Excel.Application.Workbook

' Check for duplicate part
If NameList.Contains(child.DisplayName()) Then
' Logic for handling duplicate parts
' lw.WriteLine("Duplicate part skipped: " & child.DisplayName())
Continue For
Else
NameList.Add(child.DisplayName()) ' Add new component display name to the list
End If

If isSuppressed Then
'lw.WriteLine("Suppressed component skipped: " & child.DisplayName())
Else
lastRow = lastRow + 1
lw.WriteLine("-" & lastRow & "-")
End If

' Retrieve and process the part associated with the child component
Dim childPart As Part = LoadComponentAndGetPart(child)

lw.WriteLine(child.Name)

If child.GetChildren.Length <> 0 Then
' Continue processing subassemblies
ReportComponentChildren(child, lastRow)
End If
Next
End Sub

Function LoadComponentAndGetPart(ByVal component As Component) As Part
Dim partLoadStatus As PartLoadStatus = Nothing
Try
' Set the work component to load the component
theSession.Parts.SetWorkComponent(component, PartCollection.RefsetOption.Current, PartCollection.WorkComponentOption.Visible, partLoadStatus)

' Get the part associated with the component
If TypeOf component.Prototype Is Part Then
Return CType(component.Prototype, Part)
End If
Catch ex As Exception
lw.WriteLine("Exception during loading component: " & ex.Message)
Return Nothing
Finally
' Dispose of the part load status
If partLoadStatus IsNot Nothing Then
partLoadStatus.Dispose()
End If
End Try
Return Nothing
End Function

Sub ResetToMainAssembly()
Dim partLoadStatus2 As PartLoadStatus = Nothing

Try
' Reset to main assembly
theSession.Parts.SetWorkComponent(Nothing, PartCollection.RefsetOption.Current, PartCollection.WorkComponentOption.Visible, partLoadStatus2)
'lw.WriteLine(" ")
'lw.WriteLine("Reset to main assembly")
'lw.WriteLine(" ")
Catch ex As Exception
lw.WriteLine("Failed to reset to main assembly: " & ex.Message)
Finally
' Dispose the PartLoadStatus object if it's not null
If partLoadStatus2 IsNot Nothing Then
partLoadStatus2.Dispose()
End If
End Try
End Sub
'**********************************************************
Public Function GetUnloadOption(ByVal dummy As String) As Integer
Return Session.LibraryUnloadOption.Immediately
End Function
'**********************************************************

End Module

The value of the "lastRow" variable gets written to the information window. Do you want this to always increment by 1 each time?

The ReportComponentChildren sub is called recursively and "lastRow" is passed in ByVal. This means that each time ReportComponentChildren is called, a new, local "lastRow" variable is created. If you want this to be a static count, you could try passing it in "ByRef" instead. This will essentially use the current variable value and not create a new, local variable. Does this get what you want?

Yes, this is what I was looking for and my initial tests have worked a little more what i was going for. I removed some of my code to present what was driving the issue, but now I am having a couple of other issues. I'll post my full once I have enough time to troubleshoot on my own.

Thank you for your response.

I once wrote a program in Python that counts the number of occurrences in an assembly of parts. Maybe it will work?

import NXOpen
import NXOpen.UF

theSession = NXOpen.Session.GetSession()
theLw = theSession.ListingWindow

buf={}

def main():

workPart = theSession.Parts.Work
displayPart = theSession.Parts.Display
dp = displayPart.ComponentAssembly
if dp.RootComponent is None :
return

theLw.Open()
comps = theSession.Parts.Display.ComponentAssembly.RootComponent.GetChildren()

Tree (comps)

ks = list(buf.keys())
ks.sort()

for key in ks:
theLw.WriteLine(key +" x "+str(buf[key]))

theLw.Close()

def Tree(comps):

for x in comps:
if ( not x.IsSuppressed) :

if x.GetChildren() :
Tree (x.GetChildren())

if buf.get(x.DisplayName)== None:
buf[x.DisplayName] = 1
else:
buf[x.DisplayName] = buf[x.DisplayName] + 1

if __name__ == '__main__':
main()

I haven't tested this, but I am trying to further my knowledge of Python, so maybe in the future if I try to convert this journal to python.

Result of the program

225-U_Horizontalspanner_Destaco_stp x 8
23.22.03.0410.0000.200 x 1
23.22.03.0410.0000.300 x 1
23.22.03.0410.0001.000 x 4
23.22.03.0410.0001.100 x 4
23.22.03.0412.1000.000 x 1
23.22.03.0412.1000.100 x 1
23.22.03.0412.1000.200_aviacam x 1
Bolt M10x50 GOST 7798 x 6
Bolt_M12X30_GOST7798_70 x 4
RUD_VLBG-1,5t-M16 x 4
Shtift 3x8 GOST 3128-70 x 1
Shtift 8m6x40 GOST3128-70 x 2
Shtir 7030-0416 GOST 12213-66 x 2
Vint M6x12_GOST11738 x 32

Shows in sorted form

Runs the same way as vb, just specify the extension py

Here is full program that I am trying to get to report correctly. I am still having issues where it will double count a component when reading a child.

I know its probably how I have line 130 set up, but I am not really sure how else to construct it.

Open to any and all feedback, not just on my current issue

'Journal to recursively walk through the assembly structure
' will run on assemblies or piece parts
' will step through all components of the displayed part
'NX 7.5, native
'NXJournaling.com February 24, 2012

Option Strict Off

Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Utilities
Imports NXOpen.Assemblies
Imports System.Drawing
Imports System.Windows.Forms
Imports Microsoft.Office.Interop.Excel
Imports Microsoft.Office.Interop

Module NXJournal
Dim theSession As Session = Session.GetSession()
Dim lw As ListingWindow = theSession.ListingWindow
Dim theUFSession As UFSession = UFSession.GetUFSession()
Dim workPart As Part = theSession.Parts.Work

Sub Main()
Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim theUISession As UI = UI.GetUI

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "Excel Bom")
lw.Open()

Try

Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display
Dim ca As ComponentAssembly = dispPart.ComponentAssembly

' Excel File Path
Const excelFileName As String = "C:\Users\neil\Desktop\NX\BOM.xlsx"

Dim objExcel = CreateObject("Excel.Application")
If objExcel Is Nothing Then
theUISession.NXMessageBox.Show("Error", NXMessageBox.DialogType.Error, "Could not start Excel, journal exiting")
theSession.UndoToMark(markId1, "journal")
Exit Sub
End If

Dim objWorkbook = objExcel.Workbooks.Open(excelFileName)
If objWorkbook Is Nothing Then
theUISession.NXMessageBox.Show("Error", NXMessageBox.DialogType.Error, "Could not open Excel file: " & excelFileName & ControlChars.NewLine & "journal exiting.")
theSession.UndoToMark(markId1, "journal")
Exit Sub
End If

objExcel.visible = True

' Starting Row for Excel
Dim startRow As Integer = 3

If Not IsNothing(dispPart) Then
lw.WriteLine("Main Assembly: " & dispPart.Name)
ReportComponentChildren(ca.RootComponent, objExcel, startRow)
Else
lw.WriteLine("Part has no components")
End If
Catch e As Exception
theSession.ListingWindow.WriteLine("Failed: " & e.ToString)
Finally
' Reset to main assembly
lw.WriteLine(" ")
lw.WriteLine("Returning home to the main assembly.")
lw.WriteLine("-------------------------------------------------------- ")
ResetToMainAssembly()
End Try
End Sub

'**********************************************************
Sub ReportComponentChildren(ByVal comp As Component,
ByVal wb As Object,
ByRef currentRow As Integer)
' List to keep track of components along with their suppressed status
Dim componentsWithStatus As New List(Of Tuple(Of Component, Boolean))

' List to keep track of processed component display names for duplicate check
Dim NameList As New List(Of String)

' Collect components and their suppressed status
For Each child As Component In comp.GetChildren()
' Add component and its suppressed status to the list
componentsWithStatus.Add(New Tuple(Of Component, Boolean)(child, child.IsSuppressed))
Next

' Sort the list so that suppressed components come first
componentsWithStatus.Sort(Function(x, y) y.Item2.CompareTo(x.Item2))

' Process sorted components
For Each tuple As Tuple(Of Component, Boolean) In componentsWithStatus
Dim child As Component = tuple.Item1
Dim isSuppressed As Boolean = tuple.Item2

' Check for duplicate part
If NameList.Contains(child.DisplayName()) Then
' Logic for handling duplicate parts
lw.WriteLine("Duplicate part skipped: " & child.DisplayName())
Continue For
Else
NameList.Add(child.DisplayName()) ' Add new component display name to the list
End If

If isSuppressed Then
'lw.WriteLine("Suppressed component skipped: " & child.DisplayName())
Else
lw.WriteLine("-----------------" & currentRow & "-----------------")

' Retrieve and process the part associated with the child component
Dim childPart As Part = LoadComponentAndGetPart(child)

If childPart IsNot Nothing AndAlso childPart.IsFullyLoaded Then
' Display the part name instead of the component name
lw.WriteLine(childPart.Name)

ReportToExcel(childPart, wb, currentRow)

ReportToInfo(childPart)

currentRow = currentRow + 1

If child.GetChildren.Length <> 0 Then
' Continue processing subassemblies
ReportComponentChildren(child, wb, currentRow + 1)
End If
End If
End If
Next
End Sub

Function LoadComponentAndGetPart(ByVal component As Component) As Part
Dim partLoadStatus As PartLoadStatus = Nothing
Try
' Set the work component to load the component
theSession.Parts.SetWorkComponent(component, PartCollection.RefsetOption.Current, PartCollection.WorkComponentOption.Visible, partLoadStatus)

' Get the part associated with the component
If TypeOf component.Prototype Is Part Then
Return CType(component.Prototype, Part)
End If
Catch ex As Exception
lw.WriteLine("Exception during loading component: " & ex.Message)
Return Nothing
Finally
' Dispose of the part load status
If partLoadStatus IsNot Nothing Then
partLoadStatus.Dispose()
End If
End Try
Return Nothing
End Function

Sub ReportToInfo(ByVal childPart As Part)

Dim attributeInfo As NXObject.AttributeInformation

If childPart.HasUserAttribute("TYPE", NXObject.AttributeType.String, -1) Then
attributeInfo = childPart.GetUserAttribute("TYPE", NXObject.AttributeType.String, -1)

Dim attrinfotype = attributeInfo.StringValue

If attrinfotype = "STL" Then

If childPart.HasUserAttribute("DETAIL NUMBER", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" DETAIL NUMBER: " & childPart.GetStringAttribute("DETAIL NUMBER"))
End If

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
lw.WriteLine(" QUANTITY: " & childPart.GetStringAttribute("QUANTITY"))
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" ITEM DESCRIPTION: " & childPart.GetStringAttribute("ITEM DESCRIPTION"))
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" MFG / MAT'L DESCRIPTION: " & childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION"))
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" PART NUMBER / SIZE: " & childPart.GetStringAttribute("PART NUMBER / SIZE"))
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" HEAT TREAT: " & childPart.GetStringAttribute("HEAT TREAT"))
End If

ElseIf attrinfotype = "STD" Then

If childPart.HasUserAttribute("DETAIL NUMBER", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" DETAIL NUMBER: " & childPart.GetStringAttribute("DETAIL NUMBER"))
End If

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
lw.WriteLine(" QUANTITY: " & childPart.GetStringAttribute("QUANTITY"))
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" ITEM DESCRIPTION: " & childPart.GetStringAttribute("ITEM DESCRIPTION"))
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" MFG / MAT'L DESCRIPTION: " & childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION"))
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" PART NUMBER / SIZE: " & childPart.GetStringAttribute("PART NUMBER / SIZE"))
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" HEAT TREAT: " & childPart.GetStringAttribute("HEAT TREAT"))
End If

ElseIf attrinfotype = "FNR" Then

If childPart.HasUserAttribute("DETAIL NUMBER", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" DETAIL NUMBER: " & childPart.GetStringAttribute("DETAIL NUMBER"))
End If

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
lw.WriteLine(" QUANTITY: " & childPart.GetStringAttribute("QUANTITY"))
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" ITEM DESCRIPTION: " & childPart.GetStringAttribute("ITEM DESCRIPTION"))
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" MFG / MAT'L DESCRIPTION: " & childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION"))
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" PART NUMBER / SIZE: " & childPart.GetStringAttribute("PART NUMBER / SIZE"))
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" HEAT TREAT: " & childPart.GetStringAttribute("HEAT TREAT"))
End If
End If
End If
End Sub

Sub ReportToExcel(ByVal childPart As Part, ByVal wb As Object, ByRef currentRow As Integer)

Dim attributeInfo As NXObject.AttributeInformation

If childPart.HasUserAttribute("TYPE", NXObject.AttributeType.String, -1) Then
attributeInfo = childPart.GetUserAttribute("TYPE", NXObject.AttributeType.String, -1)

Dim attrinfotype = attributeInfo.StringValue

wb.visible = True

If attrinfotype = "STL" Then

wb.Cells(currentRow, 1) = (childPart.Name)

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
wb.Cells(currentRow, 2) = childPart.GetStringAttribute("QUANTITY")
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 3) = childPart.GetStringAttribute("ITEM DESCRIPTION")
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 4) = childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION")
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 5) = childPart.GetStringAttribute("PART NUMBER / SIZE")
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 6) = childPart.GetStringAttribute("HEAT TREAT")
End If

ElseIf attrinfotype = "STD" Then

wb.Cells(currentRow, 1) = (childPart.Name)

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
wb.Cells(currentRow, 2) = childPart.GetStringAttribute("QUANTITY")
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 3) = childPart.GetStringAttribute("ITEM DESCRIPTION")
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 4) = childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION")
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 5) = childPart.GetStringAttribute("PART NUMBER / SIZE")
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 6) = childPart.GetStringAttribute("HEAT TREAT")
End If

ElseIf attrinfotype = "FNR" Then

wb.Cells(currentRow, 1) = (childPart.Name)

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
wb.Cells(currentRow, 2) = childPart.GetStringAttribute("QUANTITY")
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 3) = childPart.GetStringAttribute("ITEM DESCRIPTION")
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 4) = childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION")
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 5) = childPart.GetStringAttribute("PART NUMBER / SIZE")
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 6) = childPart.GetStringAttribute("HEAT TREAT")
End If
End If
End If
End Sub

Sub ResetToMainAssembly()
Dim partLoadStatus2 As PartLoadStatus = Nothing

Try
' Reset to main assembly
theSession.Parts.SetWorkComponent(Nothing, PartCollection.RefsetOption.Current, PartCollection.WorkComponentOption.Visible, partLoadStatus2)
'lw.WriteLine(" ")
'lw.WriteLine("Reset to main assembly")
'lw.WriteLine(" ")
Catch ex As Exception
lw.WriteLine("Failed to reset to main assembly: " & ex.Message)
Finally
' Dispose the PartLoadStatus object if it's not null
If partLoadStatus2 IsNot Nothing Then
partLoadStatus2.Dispose()
End If
End Try
End Sub
'**********************************************************
Public Function GetUnloadOption(ByVal dummy As String) As Integer
Return Session.LibraryUnloadOption.Immediately
End Function
'**********************************************************

End Module

I'd suggest removing the +1 from the current row variable when doing the recursive call.

If child.GetChildren.Length <> 0 Then
' Continue processing subassemblies
ReportComponentChildren(child, wb, currentRow)
End If

The code does a good job of skipping identical components from each subassembly. However, a component will be reported multiple times if it is used at multiple levels in the assembly. For instance, imagine we have a car assembly; it contains 2 axle assemblies that each contain 2 tires. We also want to have a spare tire, but this is not attached to an axle assembly. The assembly might look like this:

Car
Axle
Tire
Tire
Axle
Tire
Tire
Tire

In this case, the code will report the car, axle, tire, and tire. When the code processes the children of "car", it finds Axle, Axle, and Tire. It skips the 2nd axle because it is the same as the first one. When it processes the axle component it finds Tire and Tire. It processes the first one and skips the 2nd. The code sees the spare tire as different from the axle tire because they are at different levels in the assembly.

This happens because in the recursive function "ReportComponentChildren" (I'll call it RCC for short), a list called "NameList" is created to keep track of duplicate component names. This variable is local to the function call. Each time RCC is called a new set of local variables is created for that function execution. The 2nd call to RCC knows nothing of the NameList variable created in the first call. In the example above, one NameList is created when processing the car assembly and another is made when processing the axle assembly. The car's name list will contain "axle" and "tire". When the axle assembly is processed, it will create a new, blank name list and add "tire". In this way, you get "tire" output twice.

If you want to eliminate all duplicate components no matter the assembly level, I'd suggest creating NameList in Sub Main and passing it in by ref to the RCC function. This way you have an overall list of component names for the entire assembly and not just the current assembly level.

One suggestion for code simplification:
In the ReportToInfo and ReportToExcel functions, if the "type" attribute is "STL", "STD", or "FNR", the code looks for and reports certain attributes. It appears to search for the exact same attributes no matter the type returned (the code is duplicated for each type). In this case, the code can be simplified to:

If attrinfotype = "STL" OrElse attrinfotype = "STD" OrElse attrinfotype = "FNR" Then

wb.Cells(currentRow, 1) = (childPart.Name)

If childPart.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
wb.Cells(currentRow, 2) = childPart.GetStringAttribute("QUANTITY")
End If

If childPart.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 3) = childPart.GetStringAttribute("ITEM DESCRIPTION")
End If

If childPart.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 4) = childPart.GetStringAttribute("MFG / MAT'L DESCRIPTION")
End If

If childPart.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 5) = childPart.GetStringAttribute("PART NUMBER / SIZE")
End If

If childPart.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
wb.Cells(currentRow, 6) = childPart.GetStringAttribute("HEAT TREAT")
End If

End If

Through lots of test and trial and error. I finally got my desired result.

Moving the duplicate list to the main and checking it for each nested child makes a lot more sense to me now. Thank for explaining everything in detail.

I also set subs controlled by the 'chained' OrElse statement. Along with some other changes

But here is my final journal. I hope this can help anyone else who is trying to achieve a similar journal

'Journal to recursively walk through the assembly structure
' will run on assemblies or piece parts
' will step through all components of the displayed part
'NX 7.5, native
'NXJournaling.com February 24, 2012

Option Strict Off

Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Utilities
Imports NXOpen.Assemblies
Imports System.Drawing
Imports System.Windows.Forms
Imports Microsoft.Office.Interop.Excel
Imports Microsoft.Office.Interop
Imports Excel = Microsoft.Office.Interop.Excel

Module NXJournal

Dim theSession As Session = Session.GetSession()
Dim lw As ListingWindow = theSession.ListingWindow

Sub Main()

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim theUISession As UI = UI.GetUI
Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display
Dim lw As ListingWindow = theSession.ListingWindow

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "Excel Bom")
lw.Open()

Try
Dim c As ComponentAssembly = dispPart.ComponentAssembly
'to process the work part rather than the display part,
' comment the previous line and uncomment the following line

' Excel File Path
Const excelFileName As String = "C:\Users\neil\Desktop\NX\BOM.xlsm"

Dim objExcel = CreateObject("Excel.Application")
If objExcel Is Nothing Then
theUISession.NXMessageBox.Show("Error", NXMessageBox.DialogType.Error, "Could not start Excel, journal exiting")
theSession.UndoToMark(markId1, "journal")
Exit Sub
End If

Dim objWorkbook = objExcel.Workbooks.Open(excelFileName)
If objWorkbook Is Nothing Then
theUISession.NXMessageBox.Show("Error", NXMessageBox.DialogType.Error, "Could not open Excel file: " & excelFileName & ControlChars.NewLine & "journal exiting.")
theSession.UndoToMark(markId1, "journal")
Exit Sub
End If

objExcel.visible = True

' Starting Row for Excel
Dim startRow As Integer = 3

'Dim c As ComponentAssembly = workPart.ComponentAssembly
If Not IsNothing(c.RootComponent) Then
'*** insert code to process 'root component' (assembly file)
lw.WriteLine("Assembly: " & c.RootComponent.DisplayName)

Dim MasterList As New List(Of String)

reportComponentChildren(c.RootComponent, MasterList, objExcel, startRow)
Else
lw.WriteLine("Part has no components")
End If

Catch e As Exception
theSession.ListingWindow.WriteLine("Failed: " & e.ToString)
End Try
lw.Close

End Sub

'**********************************************************
Sub reportComponentChildren(ByVal comp As Component, ByRef MasterList As List(Of String), ByVal wb As Object, ByRef Line As Integer)

Dim componentsWithStatus As New List(Of Tuple(Of Component, Boolean))

' Collect components and their suppressed status
For Each child As Component In comp.GetChildren()
' Add component and its suppressed status to the list
componentsWithStatus.Add(New Tuple(Of Component, Boolean)(child, child.IsSuppressed))
Next

' Sort the list so that suppressed components come first
componentsWithStatus.Sort(Function(x, y) y.Item2.CompareTo(x.Item2))

' Process sorted components
For Each tuple As Tuple(Of Component, Boolean) In componentsWithStatus
Dim child As Component = tuple.Item1
Dim isSuppressed As Boolean = tuple.Item2

If MasterList.Contains(child.DisplayName()) Then
' Logic for handling duplicate parts
'lw.WriteLine("Duplicate part skipped: " & child.DisplayName())
Continue For
Else
MasterList.Add(child.DisplayName()) ' Add new component display name to the list
End If

If isSuppressed Then
'lw.WriteLine("Suppressed component skipped: " & child.DisplayName())
Else
Dim attributeInfo As NXObject.AttributeInformation
If child.HasUserAttribute("TYPE", NXObject.AttributeType.String, -1) Then
attributeInfo = child.GetUserAttribute("TYPE", NXObject.AttributeType.String, -1)

Dim attrinfotype = attributeInfo.StringValue

If attrinfotype = "STL" OrElse attrinfotype = "STD" OrElse attrinfotype = "FNR" Then

'ReportToInfo(child)

ReportToExcel(child, wb, Line)

Line = Line + 1

End If

End If
End If
reportComponentChildren(child, MasterList, wb, Line)

Next
End Sub
'**********************************************************

Sub ReportToInfo(ByVal child As Component)

lw.WriteLine(child.DisplayName)

If child.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
lw.WriteLine(" QUANTITY: " & child.GetStringAttribute("QUANTITY"))
End If

If child.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" ITEM DESCRIPTION: " & child.GetStringAttribute("ITEM DESCRIPTION"))
End If

If child.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" MFG / MAT'L DESCRIPTION: " & child.GetStringAttribute("MFG / MAT'L DESCRIPTION"))
End If

If child.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" PART NUMBER / SIZE: " & child.GetStringAttribute("PART NUMBER / SIZE"))
End If

If child.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" HEAT TREAT: " & child.GetStringAttribute("HEAT TREAT"))
End If

If child.HasUserAttribute("NOTE", NXObject.AttributeType.String, -1) Then
lw.WriteLine(" NOTE: " & child.GetStringAttribute("NOTE"))
End If

End Sub

'**********************************************************

Sub ReportToExcel(ByVal child As Component, ByVal wb As Object, ByRef Line As Integer)

wb.visible = True

wb.Cells(Line, 1) = (child.DisplayName)

If child.HasUserAttribute("QUANTITY", NXObject.AttributeType.Integer, -1) Then
wb.Cells(Line, 2) = child.GetStringAttribute("QUANTITY")
End If

If child.HasUserAttribute("ITEM DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(Line, 3) = child.GetStringAttribute("ITEM DESCRIPTION")
End If

If child.HasUserAttribute("MFG / MAT'L DESCRIPTION", NXObject.AttributeType.String, -1) Then
wb.Cells(Line, 4) = child.GetStringAttribute("MFG / MAT'L DESCRIPTION")
End If

If child.HasUserAttribute("PART NUMBER / SIZE", NXObject.AttributeType.String, -1) Then
wb.Cells(Line, 5) = child.GetStringAttribute("PART NUMBER / SIZE")
End If

If child.HasUserAttribute("HEAT TREAT", NXObject.AttributeType.String, -1) Then
wb.Cells(Line, 6) = child.GetStringAttribute("HEAT TREAT")
End If

If child.HasUserAttribute("NOTE", NXObject.AttributeType.String, -1) Then
wb.Cells(Line, 8) = child.GetStringAttribute("NOTE")
End If

End Sub

'**********************************************************
Public Function GetUnloadOption(ByVal dummy As String) As Integer
Return Session.LibraryUnloadOption.Immediately
End Function
'**********************************************************

End Module