Creating a Subroutine to Process all Components in an Assembly

Occasionally you will want to process all the parts in a given assembly. The journal in this tutorial will recursively step through your assembly structure allowing you to process each part. If the code is run as provided, it will open a listing window and print the name of each component and the current active arrangement if it is a subassembly. This journal was developed on NX 7.5 native; therefore if you are running Teamcenter (or other PLM) some changes will need to be made.

 

'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 NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies

Module NXJournal

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

Sub Main()
Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display

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
'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)
lw.WriteLine(" + Active Arrangement: " & c.ActiveArrangement.Name)
'*** end of code to process root component
ReportComponentChildren(c.RootComponent, 0)
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

End Sub

'**********************************************************
Sub reportComponentChildren( ByVal comp As Component, _
ByVal indent As Integer)

For Each child As Component In comp.GetChildren()
'*** insert code to process component or subassembly
lw.WriteLine(New String(" ", indent * 2) & child.DisplayName())
'*** end of code to process component or subassembly
if child.GetChildren.Length <> 0 then
'*** this is a subassembly, add code specific to subassemblies
lw.WriteLine(New String(" ", indent * 2) & _
"* subassembly with " & _
child.GetChildren.Length & " components")
lw.WriteLine(New String(" ", indent * 2) & _
" + Active Arrangement: " & _
child.OwningPart.ComponentAssembly.ActiveArrangement.Name)
'*** end of code to process subassembly
else
'this component has no children (it is a leaf node)
'add any code specific to bottom level components
end if
reportComponentChildren(child, indent + 1)
Next
End Sub
'**********************************************************
Public Function GetUnloadOption(ByVal dummy As String) As Integer
Return Session.LibraryUnloadOption.Immediately
End Function
'**********************************************************

End Module

 

So how does it work?

First, we grab the ComponentAssembly of the displayed part. The ComponentAssembly object gives us access to the components and allows us to do things such as add components, move components, query components, set attributes, etc. If the RootComponent property is Nothing, then we are dealing with a piece part (no components). If the RootComponent property indicates this part is an assembly, we call the ReportComponentChildren subroutine; this is where the heavy lifting takes place. Near the end of the ReportComponentChildren subroutine a call is made to the subroutine: ReportComponentChildren.

 

To understand recursion, you must first understand recursion

When a subroutine or function calls itself, it is called recursion. Recursion is very useful when dealing with tree type structures such as a directory listing or NX assembly. If you wanted a hand written list of all the folders on your C drive, you could do the following:

  1. Open the C drive, write down the name of all the first folder
  2. Open folder: if subfolders exist, write down the name of the first folder
  3. No subfolders? move up a level and open the next folder
  4. Repeat until you run out of folders

The beauty of this approach is it works if you only have a handful of folders or if you have hundreds of folders nested dozens of levels deep - you don't need to know anything about the structure, other than the basic layout, before you start. It may look strange for a subroutine to call itself, but as far as the computer is concerned it is a copy. So subroutine A calls itself, now subroutine A' is running which kicks off A''. A'' finishes returning control to A' which eventually finishes returning control to A. Each copy has its own copy of local variables to use so they won't overwrite each other's information. If my explanation left you scratching your head, you may want to try Wikipedia's definition.

 

Conclusion

A journal was presented that recursively steps through the assembly structure. Feel free to copy this code and modify it for your purposes. Some examples would be:

  • assigning an attribute to every component in the assembly
  • writing out the assembly structure to a text file or spreadsheet
  • report the quantity of each component and/or the number of unique components

I hope you find this journal useful. If you experience any problems or could do some testing for a Teamcenter version, please let me know!

Comments

Hi,
I want to walk /process recursive from buttom up to the top level of a deeper fragmented assembly structure . -> I want to fix all compoments of a imported assembly.
thanks ind ad

If you want to process all the files in the assembly, recursively walking through an assembly works best from the top down. If you are starting somewhere down the tree, loop through the component's .RootComponent property until it is equal "nothing" - when that happens, you are at the top node.
 
If you are starting somewhere down the assembly tree and only want to process the files in its particular branch, you can get the .RootComponent each time to cycle up a level in the assembly. You will miss other "branches" of the assembly tree with this strategy.

I'd like to know if is possible create this same subroutine to run at Teamcenter?
I need to perform a task over files inside an assembly. 
My journal works individually, but i need a way to run this determined task (just some clicks) over whole assembly components.

Christopher Prazeres
Brazil - Designer
551297174168
christopherdomingues@hotmail.com
 

Hello Christopher,
Did you find a solution to running this in Teamcenter?

Carlo Tony Daristotile

I currently do not have a Teamcenter install to test with. When you run the code, what errors do you get?

How would you go about writing this to a text file instead of sending it to the listing window ?

Both .NET and NXOpen provide ways of writing to text files, here are 2 links for using .NET methods:
http://www.dotnetperls.com/streamwriter-vbnet
http://www.homeandlearn.co.uk/net/nets8p4.html

In the NXOpen API, the listing window itself provides for a method of writing to a text file. Have a look at the SelectDevice method of the ListingWindow object in the NXOpen API help file.

It is a question that I've seen come up multiple times, I'll add it to my queue of "future article ideas".

Hello I have a component returned from a function.
But then I cannot apply any component functions to the component.
Can anyone help with this.
Thanks

dim device_component(ConnectorDevice_s1.length - 1) as Component '''works
dim ConnectorDevice_s1() as nxopen.routing.electrical.ConnectorDevice '''works
for i as integer = 0 to ConnectorDevice_s1.length - 1 '''works
device_component(i) = ConnectorDevice_s1(i).OwningComponent '''works component returned from function
device_component(i).DisplayName() '''fails!!!!!!!!!!!!!!!!!!!!
next

Carlo Tony Daristotile

There are two problems with the code snippet above:

  • device_component(i).DisplayName() cannot stand on its own; you need to use the value in some way (ie assign the value to a variable or use it as part of a larger expression)
  • the loop variable is never incremented, probably the result of posting a small portion of your code (which I appreciate), but I thought I'd mention it just in case.

Is there an error message shown when you run your code? If so, what exactly does it say?

HELP
I updated the code.
I still get an error "OBJECT REFERENCE NOT SET TO AN INSTANCE OF AN OBJECT"

dim ConnectorDevice_s1() as nxopen.routing.electrical.ConnectorDevice = ConnectorDevice_Collection1.toarray()
dim device_component(ConnectorDevice_s1.length - 1) as Component
dim lineouts(ConnectorDevice_s1.length - 1) as string
dim lineOut as string = ""
for i as integer = 0 to ( ConnectorDevice_s1.length - 1 )
device_component(i) = ConnectorDevice_s1(i).OwningComponent '''works component returned from function
lineOut = device_component(i).DisplayName '''fails!!!!!!!!!!!!!!!!!!!!
lineouts(i)= lineOut
next

Carlo Tony Daristotile

Please HELP

'''fails!! UNABLE TO CAST OBJECT OF TYPE 'NXOpen.Routing.PartDefinitionShadow' to type 'NXOpen.Routing.Electrical.ElectricalPartDefinitionShadow'

dim ConnectorDevice_Collection1 as nxopen.routing.electrical.ConnectorDeviceCollection
ConnectorDevice_Collection1 = workpart.routemanager.ConnectorDevices
dim ConnectorDevice_s1() as nxopen.routing.electrical.ConnectorDevice = ConnectorDevice_Collection1.toarray()
dim ElectricalPartDefinitionShadow1(ConnectorDevice_s1.length - 1) as NXOpen.Routing.Electrical.ElectricalPartDefinitionShadow
dim lineouts(ConnectorDevice_s1.length - 1) as string
dim lineOut as string = ""
for i as integer = 0 to ( ConnectorDevice_s1.length - 1 )
ElectricalPartDefinitionShadow1(i) = ConnectorDevice_s1(i).GetPartDefinition() '''fails!! UNABLE TO CAST OBJECT OF TYPE
lineouts(i)= lineOut
next

Carlo Tony Daristotile

I don't see an obvious reason why it shouldn't work. Perhaps this is a good one to take up with the GTAC help desk...

Hello,
This is the analysis I got from GTAC. I still dont see the error in my Journal.

The error states ‘UNABLE TO CAST OBJECT OF TYPE’ PartDefinitionShadow to type ElectricalPartDefinitionShadow.
In the documentation we can see the hierarchy.
PartDefinitionShadow Inheritance Hierarchy
Object
MarshalByRefObject
NXRemotableObject
TaggedObject
NXObject
RootObject
RouteObject
ItemDefinition
PartDefinitionShadow
ElectricalPartDefinitionShadow Inheritance Hierarchy
Object
MarshalByRefObject
NXRemotableObject
TaggedObject
NXObject
RootObject
RouteObject
ItemDefinition
PartDefinitionShadow
ElectricalPartDefinitionShadow
Note that ElectricalPartDefinitionShadow inherits from PartDefinitionShadow. What the journal is trying to do is cast an object down the inherintance tree. I.E. make an onject inherit from itself. One can usually cast to the same level or up. But not down.

Art Schumacher
Sr. Application Engineer
art.schumacher@siemens.com

---------------------------------------------------------------
'Brief Summary of My Journal
dim ConnectorDevice_s1() as nxopen.routing.electrical.ConnectorDevice = ConnectorDevice_Collection1.toarray()
dim EleclPartDefShad1(ConnectorDevice_s1.length - 1) as NXOpen.Routing.Electrical.ElectricalPartDefinitionShadow
for i as integer = 0 to ( ConnectorDevice_s1.length - 1 )
'''fails!! UNABLE TO CAST OBJECT OF TYPE 'NXOpen.Routing.PartDefinitionShadow' to type 'NXOpen.Routing.Electrical.ElectricalPartDefinitionShadow'
EleclPartDefShad1(i) = ConnectorDevice_s1(i).GetPartDefinition()
Next

Carlo Tony Daristotile

I understand what the GTAC representative is saying, however the question becomes "where is this NXOpen.Routing.PartDefinitionShadow object coming from?".

If your actual code matches what you have posted here, I don't see where this PartDefinitionShadow is coming from. According to the .net API reference docs (7.5, 8, & 8.5), a "ConnectorDevice" object is part of the Routing.Electrical namespace and the ".GetPartDefinition" method of this object should return an "ElectricalPartDefinitionShadow" object.

Is it possible to
Cycle through each component as Make Displayed part ?
or
Cycle through each component as Work Displayed part ?

Carlo Tony Daristotile

The short answer is: yes that is possible.

I'd suggest opening an assembly, start the journal recorder and make one of the components the displayed/work part as desired. Stop the recording and examine the code, incorporate the code as needed into the journal above.

It works

'Journal to walk through the assembly structure
' will run on assemblies or piece parts
' will step through all "components of 1st level" of the displayed part and make them displayed part

Option Strict Off

Imports System
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies
Imports System.IO
Imports NXOpenUI
Imports System.Windows.Forms

Module NXJournal

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

Sub Main()
Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display
'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
'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)
'lw.WriteLine(" + Active Arrangement: " & c.ActiveArrangement.Name)
'*** end of code to process root component
'lw.WriteLine("")
'lw.WriteLine("comps")
'lw.WriteLine("")
dim comps() as component = c.RootComponent.getchildren()
Dim part2 As Part = CType(theSession.Parts.FindObject(c.RootComponent.DisplayName), Part)
for i as integer = 0 to ( comps.length - 1 )
'lw.writeline( comps(i).name )
'-----------------------------------------------------------
Dim component1 As Assemblies.Component = comps(i)
Dim components1(0) As Assemblies.Component
components1(0) = component1
Dim errorList1 As ErrorList
errorList1 = component1.DisplayComponentsExact(components1)
errorList1.Clear()
Dim part1 As Part = CType(theSession.Parts.FindObject(comps(i).displayname), Part)
part1.Preferences.Modeling.CutViewUpdateDelayed = True
Dim partLoadStatus1 As PartLoadStatus
Dim status1 As PartCollection.SdpsStatus
status1 = theSession.Parts.SetDisplay(part1, True, False, partLoadStatus1)
workPart = theSession.Parts.Work
dispPart = theSession.Parts.Display
partLoadStatus1.Dispose()
MessageBox.Show(comps(i).displayname)
'------------------------------------------------------------
'Dim part2 As Part = CType(theSession.Parts.FindObject(c.RootComponent.DisplayName), Part)
workPart.Preferences.Modeling.CutViewUpdateDelayed = True
Dim partLoadStatus2 As PartLoadStatus
Dim status2 As PartCollection.SdpsStatus
status2 = theSession.Parts.SetDisplay(part2, True, False, partLoadStatus2)
workPart = theSession.Parts.Work
dispPart = theSession.Parts.Display
partLoadStatus2.Dispose()
'------------------------------------------------------------
next
workPart.Preferences.Modeling.CutViewUpdateDelayed = True
Dim partLoadStatus6 As PartLoadStatus
Dim status6 As PartCollection.SdpsStatus
status6 = theSession.Parts.SetDisplay(part2, True, False, partLoadStatus6)
workPart = theSession.Parts.Work
dispPart = theSession.Parts.Display
Dim nullAssemblies_Component As Assemblies.Component = Nothing
dispPart.Preferences.Modeling.CutViewUpdateDelayed = True
Dim partLoadStatus7 As PartLoadStatus
theSession.Parts.SetWorkComponent(nullAssemblies_Component, PartCollection.RefsetOption.Current, PartCollection.WorkComponentOption.Visible, partLoadStatus7)
workPart = theSession.Parts.Work
partLoadStatus7.Dispose()
MessageBox.Show(c.RootComponent.DisplayName)
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

End Sub

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

End Module

Carlo Tony Daristotile

How do you verify each component is loaded/opened?
Thanks

Carlo Tony Daristotile

I've been doing some work on a class that gathers information about the open assembly. It isn't finished yet, but I think it is far enough along to help answer your question. The LoadComponent function in the code listing below (last function in the listing), checks to see if the given component is fully or partially loaded; if it is not, it will attempt to load the component and check the "load status" variable to see if it was successful.



Option Strict Off
Imports System
Imports System.Collections.Generic
Imports System.windows.forms
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies

Module Module1

Sub Main()

Dim theSession As Session = Session.GetSession()

If IsNothing(theSession.Parts.Display) Then
MessageBox.Show("Active Part Required", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If

Dim lw As ListingWindow = theSession.ListingWindow
lw.Open()

Dim myAsmInfo As New NXJ_Assembly_info
myAsmInfo.Part = theSession.Parts.Work

lw.WriteLine("All Components")
lw.WriteLine("--------------")
For Each tempComp As Assemblies.Component In myAsmInfo.AllComponents
lw.WriteLine(tempComp.DisplayName)

Next

lw.WriteLine("")
lw.WriteLine("All Unique Parts")
lw.WriteLine("----------------")
For Each tempPart As Part In myAsmInfo.AllUniqueParts
lw.WriteLine(tempPart.FullPath)
Next

lw.WriteLine("")
lw.WriteLine("Parts that could not be loaded")
lw.WriteLine("------------------------------")
For Each temp As String In myAsmInfo.NotLoaded
lw.WriteLine(temp)
Next

End Sub

End Module

Public Class NXJ_Assembly_info

#Region "Private Variables"

Private Const Version As String = "0.1.1"

Private _theSession As Session = Session.GetSession()
Private _theUfSession As UFSession = UFSession.GetUFSession

Private _components As New List(Of Assemblies.Component)
Private _uniqueParts As New List(Of Part)
Private _allComponents As New List(Of Assemblies.Component)
Private _allUniqueParts As New List(Of Part)
Private _notLoaded As New List(Of String)

Private lg As LogFile = _theSession.LogFile

#End Region

#Region "Properties"

Private _isTCRunning As Boolean
Public ReadOnly Property IsTCRunning() As Boolean
Get
Return _isTCRunning
End Get
End Property

Private _thePart As Part = Nothing
Public Property Part() As Part
Get
Return _thePart
End Get
Set(ByVal value As Part)
_thePart = value
'Me.GetInfo()
Me.GetAllInfo()
End Set
End Property

Public ReadOnly Property AllComponents() As List(Of Component)
Get
Return _allComponents
End Get
End Property

Public ReadOnly Property AllUniqueParts() As List(Of Part)
Get
Return _allUniqueParts
End Get
End Property

Public ReadOnly Property Components As List(Of Component)
Get
Return _components
End Get
End Property

Public ReadOnly Property UniqueParts As List(Of Part)
Get
Return _uniqueParts
End Get
End Property

Public ReadOnly Property NotLoaded As List(Of String)
Get
Return _notLoaded
End Get
End Property

#End Region

Public Sub New()

lg.WriteLine("")
lg.WriteLine("~ NXJournaling.com: NXJ_Assembly_info object created ~")
lg.WriteLine(" ~~ Version: " & Version & " ~~")
lg.WriteLine(" ~~ Timestamp of run: " & DateTime.Now.ToString & " ~~")
lg.WriteLine("NXJ_Assembly_info Sub New()")

'determine if we are running under TC or native
_theUfSession.UF.IsUgmanagerActive(_isTCRunning)
lg.WriteLine("IsTcRunning: " & _isTCRunning.ToString)

lg.WriteLine("exiting Sub New")
lg.WriteLine("")

End Sub

Private Sub GetAllInfo()

'get all component info from assembly (all levels)
lg.WriteLine("Sub GetAllInfo()")

Try
Dim c As ComponentAssembly = Part.ComponentAssembly
If Not IsNothing(c.RootComponent) Then
'*** insert code to process 'root component' (assembly file)
lg.WriteLine(" part has components")
'*** end of code to process root component
lg.WriteLine(" calling GetAllComponentChildren")
GetAllComponentChildren(c.RootComponent)
Else
'*** insert code to process piece part, part has no components
lg.WriteLine(" part has no components")
End If
Catch ex As NXException
lg.WriteLine("Sub GetAllInfo error: " & ex.ErrorCode)
lg.WriteLine(" " & ex.Message)
End Try

lg.WriteLine("exiting Sub GetAllInfo()")

End Sub

Private Sub GetAllComponentChildren(ByVal comp As Component)

For Each child As Component In comp.GetChildren()
lg.WriteLine(child.DisplayName)
'*** insert code to process component or subassembly

If Me.LoadComponent(child) Then

_allComponents.Add(child)

Dim tempPart As Part = child.Prototype.OwningPart
If Not _allUniqueParts.Contains(tempPart) Then
_allUniqueParts.Add(tempPart)
End If

Else
'component could not be loaded
End If
'*** end of code to process component or subassembly
If child.GetChildren.Length <> 0 Then
'*** this is a subassembly, add code specific to subassemblies

'*** end of code to process subassembly
Else
'this component has no children (it is a leaf node)
'add any code specific to bottom level components

End If
Me.GetAllComponentChildren(child)
Next
End Sub

Private Sub GetInfo()

'get top level component info from assembly (no recursion)
lg.WriteLine("Sub GetInfo()")

Try
Dim c As ComponentAssembly = Part.ComponentAssembly
If Not IsNothing(c.RootComponent) Then
'*** insert code to process 'root component' (assembly file)
lg.WriteLine(" part has components")
'*** end of code to process root component
lg.WriteLine(" calling GetComponentChildren")
Me.GetComponentChildren(c.RootComponent)
Else
'*** insert code to process piece part, part has no components
lg.WriteLine(" part has no components")
End If
Catch ex As NXException
lg.WriteLine("Sub GetInfo error: " & ex.ErrorCode)
lg.WriteLine(" " & ex.Message)
End Try

lg.WriteLine("exiting GetInfo()")

End Sub

Private Sub GetComponentChildren(ByVal comp As Component)

For Each child As Component In comp.GetChildren()
'*** insert code to process component or subassembly
_components.Add(child)
Dim tempPart As Part = child.Prototype.OwningPart
If Not _uniqueParts.Contains(tempPart) Then
_uniqueParts.Add(tempPart)
End If
'*** end of code to process component or subassembly
If child.GetChildren.Length <> 0 Then
'*** this is a subassembly, add code specific to subassemblies

'*** end of code to process subassembly
Else
'this component has no children (it is a leaf node)
'add any code specific to bottom level components

End If
Next
End Sub

Private Function LoadComponent(ByVal theComponent As Component) As Boolean

lg.WriteLine("Sub LoadComponent()")

Dim thePart As Part = theComponent.Prototype.OwningPart

Dim partName As String = ""
Dim refsetName As String = ""
Dim instanceName As String = ""
Dim origin(2) As Double
Dim csysMatrix(8) As Double
Dim transform(3, 3) As Double

Try
If thePart.IsFullyLoaded Then
'component is fully loaded
Else
'component is partially loaded
End If
lg.WriteLine(" component: " & theComponent.DisplayName & " is already partially or fully loaded")
lg.WriteLine(" return: True")
lg.WriteLine("exiting Sub LoadComponent()")
lg.WriteLine("")
Return True
Catch ex As NullReferenceException
'component is not loaded
Try
lg.WriteLine(" component not loaded, retrieving part information")
_theUfSession.Assem.AskComponentData(theComponent.Tag, partName, refsetName, instanceName, origin, csysMatrix, transform)
lg.WriteLine(" component part file: " & partName)

Dim theLoadStatus As PartLoadStatus
_theSession.Parts.Open(partName, theLoadStatus)

If theLoadStatus.NumberUnloadedParts > 0 Then
If theLoadStatus.NumberUnloadedParts > 1 Then
lg.WriteLine(" problem loading " & theLoadStatus.NumberUnloadedParts.ToString & " components")
Else
lg.WriteLine(" problem loading 1 component")
End If

Dim allReadOnly As Boolean = True
For i As Integer = 0 To theLoadStatus.NumberUnloadedParts - 1
lg.WriteLine("part name: " & theLoadStatus.GetPartName(i))
lg.WriteLine("part status: " & theLoadStatus.GetStatus(i))
If theLoadStatus.GetStatus(i) = 641058 Then
'read-only warning, file loaded ok
Else
'641044: file not found
allReadOnly = False
If Not _notLoaded.Contains(partName) Then
_notLoaded.Add(partName)
End If
End If
lg.WriteLine("status description: " & theLoadStatus.GetStatusDescription(i))
lg.WriteLine("")
Next
If allReadOnly Then
lg.WriteLine(" 'read-only' warnings only")
lg.WriteLine(" return: True")
Return True
Else
'warnings other than read-only...
lg.WriteLine(" return: False")
lg.WriteLine("exiting Sub LoadComponent()")
lg.WriteLine("")
Return False
End If
Else
lg.WriteLine(" component(s) loaded successfully")
lg.WriteLine(" return: True")
lg.WriteLine("exiting Sub LoadComponent()")
lg.WriteLine("")
Return True
End If

Catch ex2 As NXException
lg.WriteLine(" Load error: " & ex2.Message)
lg.WriteLine(" error code: " & ex2.ErrorCode)
lg.WriteLine(" return: False")
lg.WriteLine("exiting Sub LoadComponent()")
lg.WriteLine("")
If ex2.Message.ToLower = "file not found" Then
If Not _notLoaded.Contains(partName) Then
_notLoaded.Add(partName)
End If
End If
Return False
End Try
Catch ex As NXException
'unexpected error
lg.WriteLine(" Error in Sub LoadComponent: " & ex.Message)
lg.WriteLine(" return: False")
lg.WriteLine("exiting Sub LoadComponent()")
lg.WriteLine("")
Return False
End Try

End Function

End Class

I received an internal error when attempting to un-hide everything in a Nx 7.5 assembly after running this code in Nx 7.5, I later used the same assembly in Nx 8.5 and attempted the same process with success. Any ideas?

DHuskic
Nx 9 VB

I was using NX 8.5 when I wrote the code, but I can't see anything obvious that would cause errors in 7.5. The code writes updates to the log file, check the log after you run the code, it may give you clues as to what is causing the error.

Hi
Do you know what the command to replace a child component after you identify it as not loaded?
Thanks

There are a number of reasons why a component may not be loaded. Perhaps it would be better to see if the component in question could be loaded, rather than replacing it?

If you need a code sample of replacing a component (or just about anything else), I suggest recording a journal while performing the operation. The returned code will show you what commands are necessary; with a little tweaking, the code can be made more general (usually it only works on objects involved at the time of recording).

At the end of this tutorial, you left us with the 'homework' to output the BoM to excel. I have done that and posted the example here. It also generates a screenshot and reports the quantity of each part.

This is the vb that tested out. Please comment with suggestions

'NXJournaling
'April 2, 2014

'Journal to Cycle All Part Files in a specified folder (with option to include subfolders)
'based on 'cyclePartFiles' code example from GTAC
'
'This journal will prompt for a folder (selection dialog)
'then open each file and perform some action on the file (export drawing as pdf) then close the file

Option Strict Off
Imports System
Imports System.IO
Imports System.Collections.Generic
Imports System.Windows.Forms
Imports System.Windows.Forms.MessageBox

Imports NXOpen
Imports NXOpenUI
Imports NXOpen.UF

Module Cycle_Files_and_Folders_b
Dim theSession As Session = Session.GetSession
Dim LW As ListingWindow = theSession.ListingWindow

Dim workPart As Part = theSession.Parts.Work
Dim displayPart As Part = theSession.Parts.Display
Dim initialPart As Part = theSession.Parts.Display

Dim nTotalPartFiles As Integer = 0

Sub Main()

Dim strOutputFolder As String
LW.Open()
Try

Dim FolderBrowserDialog1 As New FolderBrowserDialog
' Change the .SelectedPath property to the default location
With FolderBrowserDialog1
' Desktop is the root folder in the dialog.
.RootFolder = Environment.SpecialFolder.Desktop
' Change the following line to default to a given path
.SelectedPath = "C:\"
' Prompt the user with a custom message.
.Description = "Select the directory to scan"
If .ShowDialog = DialogResult.OK Then
' Display the selected folder if the user clicked on the OK button.
'msgbox(.SelectedPath)
strOutputFolder = .SelectedPath
Else
'user pressed "cancel", exit the journal
Exit Sub
End If
End With

LW.WriteLine("Cycle All Parts in a Folder Tree")
LW.WriteLine("Start Time: " & CType(TimeOfDay(), String))
LW.WriteLine("")

processParts(strOutputFolder, False)

LW.WriteLine("")
LW.WriteLine("Total Part Files Scanned: " & nTotalPartFiles)
LW.WriteLine("Stop Time: " & CType(TimeOfDay(), String))

Catch ex As NXException
LW.WriteLine("Cycle Files and Folders Error: " & ex.Message)
Exit Sub
End Try
End Sub

'***************************************************************************
'Process All Parts in a Directory

Sub processParts(ByVal directoryPath As String, ByVal includeSubDirs As Boolean)

Try
Dim nPartFiles As Integer = 0
Dim part1 As Part
Dim files() As String

If includeSubDirs Then
files = Directory.GetFiles(directoryPath, "*.prt", SearchOption.AllDirectories)
Else
files = Directory.GetFiles(directoryPath, "*.prt", SearchOption.TopDirectoryOnly)
End If
For Each fileName As String In files
nPartFiles += 1
nTotalPartFiles += 1
LW.WriteLine(" " & nPartFiles & " " & Path.GetFileName(fileName))

If (IsNothing(initialPart)) OrElse (initialPart.FullPath <> fileName) Then
part1 = theSession.Parts.OpenDisplay(fileName, Nothing)
Else
'LW.WriteLine("initial part equals display part: " & initialPart.Equals(displayPart).ToString)
part1 = displayPart
End If

displayPart = theSession.Parts.Display
workPart = theSession.Parts.Display

'do something
'write your own subroutines and/or functions to process the part and call them from here
ExportPDF(part1)

'close file unless this file was initially open
If (IsNothing(initialPart)) OrElse (initialPart.FullPath <> fileName) Then
'part1.Save(BasePart.SaveComponents.True, BasePart.CloseAfterSave.True)
part1.Close(BasePart.CloseWholeTree.False, BasePart.CloseModified.UseResponses, Nothing)
part1 = Nothing
workPart = Nothing
displayPart = Nothing
End If

If Not IsNothing(initialPart) Then
Dim partLoadStatus1 As PartLoadStatus
Dim status1 As PartCollection.SdpsStatus
status1 = theSession.Parts.SetDisplay(initialPart, False, False, partLoadStatus1)

displayPart = theSession.Parts.Display
partLoadStatus1.Dispose()
theSession.Parts.SetWork(displayPart)
workPart = theSession.Parts.Work
End If

Next fileName
Catch ex As Exception
LW.WriteLine("Part Scan Error: " & ex.Message)
End Try

End Sub

'***************************************************************************
Function CountBodies() As Integer

Dim total As Integer
total = workPart.Bodies.ToArray.Length
Return total

End Function

'***********************************************************************
Sub ExportPDF(ByVal thePart As Part)

Dim myPdfExporter As New NXJ_PdfExporter
myPdfExporter.Part = thePart

Try
myPdfExporter.Commit()
Catch ex As Exception
MessageBox.Show("Error:" & ControlChars.CrLf & ex.GetType.ToString & " : " & ex.Message, "PDF export error", MessageBoxButtons.OK, MessageBoxIcon.Error)

Finally
myPdfExporter = Nothing
End Try

End Sub

'***********************************************************************
Public Function GetUnloadOption(ByVal dummy As String) As Integer

'Unloads the image when the NX session terminates
GetUnloadOption = NXOpen.Session.LibraryUnloadOption.AtTermination

End Function

End Module

#Region "imports"

'** uncomment the imports statments if using this as a standalone class file
'Imports System
'Imports System.IO
'Imports System.Collections
'Imports System.Windows.Forms
'Imports System.Windows.Forms.MessageBox
'Imports NXOpen
'Imports NXOpen.UF

#End Region

'Information for adding this code to your project:
' if you are going to compile your code:
' add this class file to your project, create and use an instance of this class in your code
'
' If you intend to use this in a journal (no compilation required),
' make sure all of the imports statements above are present at the top of your journal file.
' Copy and paste all of the code below here to your journal file (paste it after your "End Module" statement).
' Create and use an instance of this class in your module.
' One of the limitations of a journal is that all the code must be in a single file.

Class NXJ_PdfExporter

#Region "information"

'NXJournaling.com
'Jeff Gesiakowski
'December 9, 2013
'
'NX 8.5
'class to export drawing sheets to pdf file
'
'Please send any bug reports and/or feature requests to: info@nxjournaling.com
'
'version 0.4 {beta}, initial public release
' special thanks to Mike H. and Ryan G.
'
'Public Properties:
' ExportToTc [Boolean] {read/write} - flag indicating that the pdf should be output to the TC dataset, False value = output to filesystem
' default value: False
' IsTcRunning [Boolean] {read only} - True if NX is running under TC, false if native NX
' OpenPdf [Boolean] {read/write} - flag to indicate whether the journal should attempt to open the pdf after creation
' default value: False
' OutputFolder [String] {read/write} - path to the output folder
' default value (native): folder of current part
' default value (TC): user's Documents folder
' OutputPdfFileName [String] {read/write} - full file name of outut pdf file (if exporting to filesystem)
' default value (native): \_{_preliminary}.pdf
' default value (TC): \_{_preliminary}.pdf
' OverwritePdf [Boolean] {read/write} - flag indicating that the pdf file should be overwritten if it already exists
' currently only applies when exporting to the filesystem
' default value: True
' Part [NXOpen.Part] {read/write} - part that contains the drawing sheets of interest
' default value: none, must be set by user
' PartFilePath [String] {read only} - for native NX part files, the path to the part file
' PartNumber [String] {read only} - for native NX part files: part file name
' for TC files: value of DB_PART_NO attribute
' PartRevision [String] {read only} - for native NX part files: value of part "Revision" attribute, if present
' for TC files: value of DB_PART_REV
' PreliminaryPrint [Boolean] {read/write} - flag indicating that the pdf should be marked as an "preliminary"
' when set to True, the output file will be named _preliminary.pdf
' default value: False
' SheetCount [Integer] {read only} - integer indicating the total number of drawing sheets found in the file
' ShowConfirmationDialog [Boolean] {read/write} - flag indicating whether to show the user a confirmation dialog after pdf is created
' if set to True and ExportToTc = False, user will be asked if they want to open the pdf file
' if user chooses "Yes", the code will attempt to open the pdf with the default viewer
' default value: False
' SortSheetsByName [Boolean] {read/write} - flag indicating that the sheets should be sorted by name before output to pdf
' default value: True
' TextAsPolylines [Boolean] {read/write} - flag indicating that text objects should be output as polylines instead of text objects
' default value: False set this to True if you are using an NX font and the output changes the 'look' of the text
' UseWatermark [Boolean] {read/write} - flag indicating that watermark text should be applied to the face of the drawing
' default value: False
' WatermarkAddDatestamp [Boolean] {read/write} - flag indicating that today's date should be added to the end of the
' watermark text
' default value: True
' WatermarkText [String] {read/write} - watermark text to use
' default value: "PRELIMINARY PRINT NOT TO BE USED FOR PRODUCTION"
'
'Public Methods:
' New() - initializes a new instance of the class
' PickExportFolder() - displays a FolderPicker dialog box, the user's choice will be set as the output folder
' PromptPreliminaryPrint() - displays a yes/no dialog box asking the user if the print should be marked as preliminary
' if user chooses "Yes", PreliminaryPrint and UseWatermark are set to True
' PromptWatermarkText() - displays an input box prompting the user to enter text to use for the watermark
' if cancel is pressed, the default value is used
' if Me.UseWatermark = True, an input box will appear prompting the user for the desired watermark text. Initial text = Me.WatermarkText
' if Me.UseWatermark = False, calling this method will have no effect
' Commit() - using the specified options, export the given part's sheets to pdf

#End Region

#Region "properties and private variables"

Private Const Version As String = "0.5.0"

Private _theSession As Session = Session.GetSession
Private _theUfSession As UFSession = UFSession.GetUFSession
Private lg As LogFile = _theSession.LogFile

Private _drawingSheets As New List(Of Drawings.DrawingSheet)
Private _exportFile As String = ""
Private _partUnits As Integer
Private _watermarkTextFinal As String = ""

Private _exportToTC As Boolean = False
Public Property ExportToTc() As Boolean
Get
Return _exportToTC
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property ExportToTc")
_exportToTC = value
lg.WriteLine(" exportToTc: " & _exportToTC.ToString)
Me.GetOutputName()
lg.WriteLine("exiting Set Property ExportToTc")
lg.WriteLine("")
End Set
End Property

Private _isTCRunning As Boolean
Public ReadOnly Property IsTCRunning() As Boolean
Get
Return _isTCRunning
End Get
End Property

Private _openPdf As Boolean = False
Public Property OpenPdf() As Boolean
Get
Return _openPdf
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property OpenPdf")
_openPdf = value
lg.WriteLine(" openPdf: " & _openPdf.ToString)
lg.WriteLine("exiting Set Property OpenPdf")
lg.WriteLine("")
End Set
End Property

Private _outputFolder As String = ""
Public Property OutputFolder() As String
Get
Return _outputFolder
End Get
Set(ByVal value As String)
lg.WriteLine("Set Property OutputFolder")
If Not Directory.Exists(value) Then
Try
lg.WriteLine(" specified directory does not exist, trying to create it...")
Directory.CreateDirectory(value)
lg.WriteLine(" directory created: " & value)
Catch ex As Exception
lg.WriteLine(" ** error while creating directory: " & value)
lg.WriteLine(" " & ex.GetType.ToString & " : " & ex.Message)
lg.WriteLine(" defaulting to: " & My.Computer.FileSystem.SpecialDirectories.MyDocuments)
value = My.Computer.FileSystem.SpecialDirectories.MyDocuments
End Try
End If
_outputFolder = value
_outputPdfFile = IO.Path.Combine(_outputFolder, _exportFile & ".pdf")
lg.WriteLine(" outputFolder: " & _outputFolder)
lg.WriteLine(" outputPdfFile: " & _outputPdfFile)
lg.WriteLine("exiting Set Property OutputFolder")
lg.WriteLine("")
End Set
End Property

Private _outputPdfFile As String = ""
Public Property OutputPdfFileName() As String
Get
Return _outputPdfFile
End Get
Set(ByVal value As String)
lg.WriteLine("Set Property OutputPdfFileName")
_outputPdfFile = value
lg.WriteLine(" outputPdfFile: " & value)
'update _outputFolder
Me.OutputFolder = Me.GetParentPath(_outputPdfFile)
lg.WriteLine("exiting Set Property OutputPdfFileName")
lg.WriteLine("")
End Set
End Property

Private _overwritePdf As Boolean = True
Public Property OverwritePdf() As Boolean
Get
Return _overwritePdf
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property OverwritePdf")
_overwritePdf = value
lg.WriteLine(" overwritePdf: " & _overwritePdf.ToString)
lg.WriteLine("exiting Set Property OverwritePdf")
lg.WriteLine("")
End Set
End Property

Private _thePart As Part = Nothing
Public Property Part() As Part
Get
Return _thePart
End Get
Set(ByVal value As Part)
lg.WriteLine("Set Property Part")
_thePart = value
_partUnits = _thePart.PartUnits
Me.GetPartInfo()
If Me.SortSheetsByName Then
Me.SortDrawingSheets()
End If
lg.WriteLine("exiting Set Property Part")
lg.WriteLine("")
End Set
End Property

Private _partFilePath As String
Public ReadOnly Property PartFilePath() As String
Get
Return _partFilePath
End Get
End Property

Private _partNumber As String
Public ReadOnly Property PartNumber() As String
Get
Return _partNumber
End Get
End Property

Private _partRevision As String = ""
Public ReadOnly Property PartRevision() As String
Get
Return _partRevision
End Get
End Property

Private _preliminaryPrint As Boolean = False
Public Property PreliminaryPrint() As Boolean
Get
Return _preliminaryPrint
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property PreliminaryPrint")
_preliminaryPrint = value
If String.IsNullOrEmpty(_exportFile) Then
'do nothing
Else
Me.GetOutputName()
End If
lg.WriteLine(" preliminaryPrint: " & _preliminaryPrint.ToString)
lg.WriteLine("exiting Set Property PreliminaryPrint")
lg.WriteLine("")
End Set
End Property

Public ReadOnly Property SheetCount() As Integer
Get
Return _drawingSheets.Count
End Get
End Property

Private _showConfirmationDialog As Boolean = False
Public Property ShowConfirmationDialog() As Boolean
Get
Return _showConfirmationDialog
End Get
Set(ByVal value As Boolean)
_showConfirmationDialog = value
End Set
End Property

Private _sortSheetsByName As Boolean = True
Public Property SortSheetsByName() As Boolean
Get
Return _sortSheetsByName
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property SortSheetsByName")
_sortSheetsByName = value
If _sortSheetsByName = True Then
Me.SortDrawingSheets()
End If
lg.WriteLine(" sortSheetsByName: " & _sortSheetsByName.ToString)
lg.WriteLine("exiting Set Property SortSheetsByName")
lg.WriteLine("")
End Set
End Property

Private _textAsPolylines As Boolean = False
Public Property TextAsPolylines() As Boolean
Get
Return _textAsPolylines
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property TextAsPolylines")
_textAsPolylines = value
lg.WriteLine(" textAsPolylines: " & _textAsPolylines.ToString)
lg.WriteLine("exiting Set Property TextAsPolylines")
lg.WriteLine("")
End Set
End Property

Private _useWatermark As Boolean = False
Public Property UseWatermark() As Boolean
Get
Return _useWatermark
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property UseWatermark")
_useWatermark = value
lg.WriteLine(" useWatermark: " & _useWatermark.ToString)
lg.WriteLine("exiting Set Property UseWatermark")
lg.WriteLine("")
End Set
End Property

Private _watermarkAddDatestamp As Boolean = True
Public Property WatermarkAddDatestamp() As Boolean
Get
Return _watermarkAddDatestamp
End Get
Set(ByVal value As Boolean)
lg.WriteLine("Set Property WatermarkAddDatestamp")
_watermarkAddDatestamp = value
lg.WriteLine(" watermarkAddDatestamp: " & _watermarkAddDatestamp.ToString)
If _watermarkAddDatestamp Then
'to do: internationalization for dates
_watermarkTextFinal = _watermarkText & " " & Today
Else
_watermarkTextFinal = _watermarkText
End If
lg.WriteLine(" watermarkTextFinal: " & _watermarkTextFinal)
lg.WriteLine("exiting Set Property WatermarkAddDatestamp")
lg.WriteLine("")
End Set
End Property

Private _watermarkText As String = "PRELIMINARY PRINT NOT TO BE USED FOR PRODUCTION"
Public Property WatermarkText() As String
Get
Return _watermarkText
End Get
Set(ByVal value As String)
lg.WriteLine("Set Property WatermarkText")
_watermarkText = value
lg.WriteLine(" watermarkText: " & _watermarkText)
lg.WriteLine("exiting Set Property WatermarkText")
lg.WriteLine("")
End Set
End Property

#End Region

#Region "public methods"

Public Sub New()

lg.WriteLine("")
lg.WriteLine("~ NXJournaling.com: Start of drawing to PDF journal ~")
lg.WriteLine(" ~~ Version: " & Version & " ~~")
lg.WriteLine(" ~~ Timestamp of run: " & DateTime.Now.ToString & " ~~")
lg.WriteLine("PdfExporter Sub New()")

'determine if we are running under TC or native
_theUfSession.UF.IsUgmanagerActive(_isTCRunning)
lg.WriteLine("IsTcRunning: " & _isTCRunning.ToString)

lg.WriteLine("exiting Sub New")
lg.WriteLine("")

End Sub

Public Sub PickExportFolder()

'Requires:
' Imports System.IO
' Imports System.Windows.Forms
'if the user presses OK on the dialog box, the chosen path is returned
'if the user presses cancel on the dialog box, 0 is returned
lg.WriteLine("Sub PickExportFolder")

If Me.ExportToTc Then
lg.writeline(" N/A when ExportToTc = True")
lg.writeline(" exiting Sub PickExportFolder")
lg.writeline("")
Return
End If

Dim strLastPath As String

'Key will show up in HKEY_CURRENT_USER\Software\VB and VBA Program Settings
Try
'Get the last path used from the registry
lg.WriteLine(" attempting to retrieve last export path from registry...")
strLastPath = GetSetting("NX journal", "Export pdf", "ExportPath")
'msgbox("Last Path: " & strLastPath)
Catch e As ArgumentException
lg.WriteLine(" ** Argument Exception: " & e.Message)
Catch e As Exception
lg.WriteLine(" ** Exception type: " & e.GetType.ToString)
lg.WriteLine(" ** Exception message: " & e.Message)
'MsgBox(e.GetType.ToString)
Finally
End Try

Dim FolderBrowserDialog1 As New FolderBrowserDialog

' Then use the following code to create the Dialog window
' Change the .SelectedPath property to the default location
With FolderBrowserDialog1
' Desktop is the root folder in the dialog.
.RootFolder = Environment.SpecialFolder.Desktop
' Select the D:\home directory on entry.
If Directory.Exists(strLastPath) Then
.SelectedPath = strLastPath
Else
.SelectedPath = My.Computer.FileSystem.SpecialDirectories.MyDocuments
End If
' Prompt the user with a custom message.
.Description = "Select the directory to export .pdf file"
If .ShowDialog = DialogResult.OK Then
' Display the selected folder if the user clicked on the OK button.
Me.OutputFolder = .SelectedPath
lg.WriteLine(" selected output path: " & .SelectedPath)
' save the output folder path in the registry for use on next run
SaveSetting("NX journal", "Export pdf", "ExportPath", .SelectedPath)
Else
'user pressed 'cancel', keep original value of output folder
lg.WriteLine(" folder browser dialog cancel button pressed")
lg.WriteLine(" current output path: " & Me.OutputFolder)
End If
End With

lg.WriteLine("exiting Sub PickExportFolder")
lg.WriteLine("")

End Sub

Public Sub PromptPreliminaryPrint()

lg.WriteLine("Sub PromptPreliminaryPrint")

Dim rspPreliminaryPrint As DialogResult
rspPreliminaryPrint = MessageBox.Show("Add preliminary print watermark?", "Add Watermark?", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If rspPreliminaryPrint = DialogResult.Yes Then
Me.PreliminaryPrint = True
Me.UseWatermark = True
lg.WriteLine(" this is a preliminary print")
Else
Me.PreliminaryPrint = False
lg.WriteLine(" this is not a preliminary print")
End If

lg.WriteLine("exiting Sub PromptPreliminaryPrint")
lg.WriteLine("")

End Sub

Public Sub PromptWatermarkText()

lg.WriteLine("Sub PromptWatermarkText")
lg.WriteLine(" useWatermark: " & Me.UseWatermark.ToString)

Dim theWatermarkText As String = Me.WatermarkText

If Me.UseWatermark Then
theWatermarkText = InputBox("Enter watermark text", "Watermark", theWatermarkText)
Me.WatermarkText = theWatermarkText
Else
lg.WriteLine(" suppressing watermark prompt")
End If

lg.WriteLine("exiting Sub PromptWatermarkText")
lg.WriteLine("")

End Sub

Public Sub Commit()

lg.WriteLine("Sub ExportSheetsToPdf")
lg.WriteLine(" number of drawing sheets in part file: " & _drawingSheets.Count.ToString)

Dim sheetCount As Integer = 0
Dim sheetsExported As Integer = 0

Dim numPlists As Integer = 0
Dim myPlists() As Tag

'_theUfSession.Plist.AskTags(myPlists, numPlists)
For i As Integer = 0 To numPlists - 1
_theUfSession.Plist.Update(myPlists(i))
Next

For Each tempSheet As Drawings.DrawingSheet In _drawingSheets

sheetCount += 1

lg.WriteLine(" working on sheet: " & tempSheet.Name)
lg.WriteLine(" sheetCount: " & sheetCount.ToString)

'the pdf export uses 'append file', if we are on sheet 1 make sure the user wants to overwrite
'if the drawing is multisheet, don't ask on subsequent sheets
If sheetCount = 1 Then
'export to file system
If File.Exists(_outputPdfFile) Then
lg.WriteLine(" specified output file already exists")
If Me.OverwritePdf Then
Try
lg.WriteLine(" user chose to overwrite existing pdf file")
File.Delete(_outputPdfFile)
Catch ex As Exception
'rethrow error?
lg.WriteLine(" ** error while attempting to delete existing pdf file")
lg.WriteLine(" " & ex.GetType.ToString & " : " & ex.Message)
End Try
Else
'file exists, overwrite option is set to false - do nothing
lg.WriteLine(" specified pdf file exists, user chose not to overwrite")
lg.WriteLine(" exiting Sub ExportSheetsToPdf")
lg.WriteLine("")
Exit Sub
End If
End If

End If

'update any views that are out of date
lg.WriteLine(" updating OutOfDate views on sheet: " & tempSheet.Name)
Me.Part.DraftingViews.UpdateViews(Drawings.DraftingViewCollection.ViewUpdateOption.OutOfDate, tempSheet)

Next

If Me._drawingSheets.Count > 0 Then

lg.WriteLine(" done updating views on all sheets")

Try
lg.WriteLine(" calling Sub ExportPdf")
lg.WriteLine("")
Me.ExportPdf()
Catch ex As Exception
lg.WriteLine(" ** error exporting PDF")
lg.WriteLine(" " & ex.GetType.ToString & " : " & ex.Message)
'MessageBox.Show("Error occurred in PDF export" & vbCrLf & ex.Message, "Error exporting PDF", MessageBoxButtons.OK, MessageBoxIcon.Error)
Throw ex
End Try

Else
'no sheets in file
lg.WriteLine(" ** no drawing sheets in file: " & Me._partNumber)

End If

If Me.ShowConfirmationDialog Then
Me.DisplayConfirmationDialog()
End If

If (Not Me.ExportToTc) AndAlso (Me.OpenPdf) AndAlso (Me._drawingSheets.Count > 0) Then
'open new pdf print
lg.WriteLine(" trying to open newly created pdf file")
Try
System.Diagnostics.Process.Start(Me.OutputPdfFileName)
lg.WriteLine(" pdf open process successful")
Catch ex As Exception
lg.WriteLine(" ** error opening pdf **")
lg.WriteLine(" " & ex.GetType.ToString & " : " & ex.Message)
End Try
End If

lg.WriteLine(" exiting Sub ExportSheetsToPdf")
lg.WriteLine("")

End Sub

#End Region

#Region "private methods"

Private Sub GetPartInfo()

lg.WriteLine("Sub GetPartInfo")

If Me.IsTCRunning Then
_partNumber = _thePart.GetStringAttribute("DB_PART_NO")
_partRevision = _thePart.GetStringAttribute("DB_PART_REV")

lg.WriteLine(" TC running")
lg.WriteLine(" partNumber: " & _partNumber)
lg.WriteLine(" partRevision: " & _partRevision)

Else 'running in native mode

_partNumber = IO.Path.GetFileNameWithoutExtension(_thePart.FullPath)
_partFilePath = IO.Directory.GetParent(_thePart.FullPath).ToString

lg.WriteLine(" Native NX")
lg.WriteLine(" partNumber: " & _partNumber)
lg.WriteLine(" partFilePath: " & _partFilePath)

Try
_partRevision = _thePart.GetStringAttribute("REVISION")
_partRevision = _partRevision.Trim
Catch ex As Exception
_partRevision = ""
End Try

lg.WriteLine(" partRevision: " & _partRevision)

End If

If String.IsNullOrEmpty(_partRevision) Then
_exportFile = _partNumber
Else
_exportFile = _partNumber & "_" & _partRevision
End If

lg.WriteLine("")
Me.GetOutputName()

lg.WriteLine(" exportFile: " & _exportFile)
lg.WriteLine(" outputPdfFile: " & _outputPdfFile)
lg.WriteLine(" exiting Sub GetPartInfo")
lg.WriteLine("")

End Sub

Private Sub GetOutputName()

lg.WriteLine("Sub GetOutputName")

_exportFile.Replace("_preliminary", "")
_exportFile.Replace("_PDF_1", "")

If IsNothing(Me.Part) Then
lg.WriteLine(" Me.Part is Nothing")
Else

If Not IsTCRunning And _preliminaryPrint Then
_exportFile &= "_preliminary"
End If

If Me.ExportToTc Then 'export to Teamcenter dataset
lg.WriteLine(" export to TC option chosen")
If Me.IsTCRunning Then
lg.WriteLine(" TC is running")
_exportFile &= "_PDF_1"
Else
'error, cannot export to a dataset if TC is not running
lg.WriteLine(" ** error: export to TC option chosen, but TC is not running")
'todo: throw error
End If
Else 'export to file system
lg.WriteLine(" export to filesystem option chosen")
If Me.IsTCRunning Then
lg.WriteLine(" TC is running")
'exporting from TC to filesystem, no part folder to default to
'default to "MyDocuments" folder
_outputPdfFile = IO.Path.Combine(My.Computer.FileSystem.SpecialDirectories.MyDocuments, _exportFile & ".pdf")
Else
lg.WriteLine(" native NX")
'exporting from native to file system
'use part folder as default output folder
If _outputFolder = "" Then
_outputFolder = _partFilePath
End If
_outputPdfFile = IO.Path.Combine(_outputFolder, _exportFile & ".pdf")
End If

End If

End If

lg.WriteLine(" exiting Sub GetOutputName")
lg.WriteLine("")

End Sub

Private Sub GetDrawingSheets()

_drawingSheets.Clear()

For Each tempSheet As Drawings.DrawingSheet In _thePart.DrawingSheets
_drawingSheets.Add(tempSheet)
Next

End Sub

Private Sub SortDrawingSheets()

If _sortSheetsByName Then
Me.GetDrawingSheets()
_drawingSheets.Sort(AddressOf CompareSheetNames)
End If

End Sub

Private Function CompareSheetNames(ByVal x As Drawings.DrawingSheet, ByVal y As Drawings.DrawingSheet) As Integer

'case-insensitive sort
Dim myStringComp As StringComparer = StringComparer.CurrentCultureIgnoreCase

'for a case-sensitive sort (A-Z then a-z), change the above option to:
'Dim myStringComp As StringComparer = StringComparer.CurrentCulture

Return myStringComp.Compare(x.Name, y.Name)

End Function

Private Function GetParentPath(ByVal thePath As String) As String

lg.WriteLine("Function GetParentPath(" & thePath & ")")

Try
Dim directoryInfo As System.IO.DirectoryInfo
directoryInfo = System.IO.Directory.GetParent(thePath)
lg.WriteLine(" returning: " & directoryInfo.FullName)
lg.WriteLine("exiting Function GetParentPath")
lg.WriteLine("")

Return directoryInfo.FullName
Catch ex As ArgumentNullException
lg.WriteLine(" Path is a null reference.")
Throw ex
Catch ex As ArgumentException
lg.WriteLine(" Path is an empty string, contains only white space, or contains invalid characters")
Throw ex
End Try

lg.WriteLine("exiting Function GetParentPath")
lg.WriteLine("")

End Function

Private Sub ExportPdf()

lg.WriteLine("Sub ExportPdf")

Dim printPDFBuilder1 As PrintPDFBuilder

printPDFBuilder1 = _thePart.PlotManager.CreatePrintPdfbuilder()
printPDFBuilder1.Scale = 1.0
printPDFBuilder1.Colors = PrintPDFBuilder.Color.BlackOnWhite
printPDFBuilder1.Size = PrintPDFBuilder.SizeOption.ScaleFactor
printPDFBuilder1.RasterImages = True
printPDFBuilder1.ImageResolution = PrintPDFBuilder.ImageResolutionOption.Medium

If _thePart.PartUnits = BasePart.Units.Inches Then
lg.WriteLine(" part units: English")
printPDFBuilder1.Units = PrintPDFBuilder.UnitsOption.English
Else
lg.WriteLine(" part units: Metric")
printPDFBuilder1.Units = PrintPDFBuilder.UnitsOption.Metric
End If

If _textAsPolylines Then
lg.WriteLine(" output text as polylines")
printPDFBuilder1.OutputText = PrintPDFBuilder.OutputTextOption.Polylines
Else
lg.WriteLine(" output text as text")
printPDFBuilder1.OutputText = PrintPDFBuilder.OutputTextOption.Text
End If

lg.WriteLine(" useWatermark: " & _useWatermark.ToString)
If _useWatermark Then
printPDFBuilder1.AddWatermark = True
printPDFBuilder1.Watermark = _watermarkTextFinal
Else
printPDFBuilder1.AddWatermark = False
printPDFBuilder1.Watermark = ""
End If

lg.WriteLine(" export to TC? " & _exportToTC.ToString)
If _exportToTC Then
'output to dataset
'printPDFBuilder1.Relation = PrintPDFBuilder.RelationOption.Manifestation
'printPDFBuilder1.DatasetType = "PDF"
'printPDFBuilder1.NamedReferenceType = "PDF_Reference"
'printPDFBuilder1.Action = PrintPDFBuilder.ActionOption.Overwrite
printPDFBuilder1.Action = PrintPDFBuilder.ActionOption.New
printPDFBuilder1.DatasetName = _exportFile & "_PDF_1"
lg.WriteLine(" dataset name: " & _exportFile)

Try
lg.WriteLine(" printPDFBuilder1.Assign")
printPDFBuilder1.Assign()
Catch ex As NXException
lg.WriteLine(" ** error with printPDFBuilder1.Assign")
lg.WriteLine(" " & ex.ErrorCode & " : " & ex.Message)
End Try

Else
'output to filesystem
lg.WriteLine(" pdf file: " & _outputPdfFile)
printPDFBuilder1.Action = PrintPDFBuilder.ActionOption.Native
printPDFBuilder1.Append = False
printPDFBuilder1.Filename = _outputPdfFile

End If

printPDFBuilder1.SourceBuilder.SetSheets(_drawingSheets.ToArray)

Dim nXObject1 As NXObject
Try
lg.WriteLine(" printPDFBuilder1.Commit")
nXObject1 = printPDFBuilder1.Commit()

Catch ex As NXException
lg.WriteLine(" ** error with printPDFBuilder1.Commit")
lg.WriteLine(" " & ex.ErrorCode & " : " & ex.Message)

Finally
printPDFBuilder1.Destroy()
End Try

lg.WriteLine(" exiting Sub ExportPdf")
lg.WriteLine("")

End Sub

Private Sub DisplayConfirmationDialog()

Dim sb As New Text.StringBuilder

If Me._drawingSheets.Count = 0 Then
MessageBox.Show("No drawing sheets found in file.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
Return
End If

sb.Append("The following sheets were output to PDF:")
sb.AppendLine()
For Each tempSheet As Drawings.DrawingSheet In _drawingSheets
sb.AppendLine(" " & tempSheet.Name)
Next
sb.AppendLine()

If Not Me.ExportToTc Then
sb.AppendLine("Open pdf file now?")
End If

Dim prompt As String = sb.ToString

Dim response As DialogResult
If Me.ExportToTc Then
response = MessageBox.Show(prompt, Me.OutputPdfFileName, MessageBoxButtons.OK, MessageBoxIcon.Information)
Else
response = MessageBox.Show(prompt, Me.OutputPdfFileName, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1)
End If

If response = DialogResult.Yes Then
Me.OpenPdf = True
Else
Me.OpenPdf = False
End If

End Sub

#End Region

End Class

Paul Fillion

So what would I put in to make this script measure bodies for each piece part in the assembly, while saving a text file to the hard drive for each part? I tired different things but I am not getting the desired result.

I would like to use this journal as a base for a customer requirement that I have.

The requirement is that all purchase parts be on a specific range of layers and all manufactured parts be on another set of layers. The parts can be identified by the first character of the part number.

Any recommendations on how to approach this? I was thinking about adding a conditional statement to determine the part file name and then move to the appropriate layer, and then index the layer number by one.

Sounds like a good plan. If you are working in native NX, you can get the component part file name with something like:

{component variable}.Prototype.OwningPart.Leaf

So if you were working on the file: "C:\CAD_data\rest\of\the\path\12345.prt", the above command would return "12345". Then you can use the .net string functions to read the first character.

Hello! First off thank you for all the information about journaling with NX! I'm currently working on a program that analyses assemblies and parts to predict the cost of a product. The program is able to process parts already. Now I need to cycle trough all parts of my assembly. I use a Public Class that contains Public functions and subs. But I get errors if I change the program from a module to a Class. The Error Message: System.Reflection.TargetException: The non-static method requires target. Or is there a way to integrate a Module in a Class and call Functions from the Class?

Greetings
Tim Schröder

Imports Microsoft.VisualBasic
Imports System
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies

Public Class NXJournal

Public theSession As Session = Session.GetSession()
Public ufs As UFSession = UFSession.GetUFSession()
Public lw As ListingWindow = theSession.ListingWindow
' class members
'Public theSession As Session
'Public theUFSession As UFSession
Public theUI As UI
'Public lw As ListingWindow
'Public workPart As Part

'Zählervariable
'Dim z As Integer

Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display
Dim z As Integer = 0

Public Sub Main(ByVal args() As String)

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
'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)
lw.WriteLine(" + Active Arrangement: " & c.ActiveArrangement.Name)
'*** end of code to process root component
reportComponentChildren(c.RootComponent, 0)

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.writeLine("Test" & z)
lw.Close

End Sub

'**********************************************************
Public Sub reportComponentChildren(ByVal comp As Component,
ByVal indent As Integer)

For Each child As Component In comp.GetChildren()
'*** insert code to process component or subassembly
lw.WriteLine(New String(" ", indent * 2) & child.DisplayName())
If child.DisplayName Like "03-*" Then
'test2.testsub(z)
End If
'*** end of code to process component or subassembly
If child.GetChildren.Length <> 0 Then
'*** this is a subassembly, add code specific to subassemblies
lw.WriteLine(New String(" ", indent * 2) &
"* subassembly with " &
child.GetChildren.Length & " components")
lw.WriteLine(New String(" ", indent * 2) &
" + Active Arrangement: " &
child.OwningPart.ComponentAssembly.ActiveArrangement.Name)
'*** end of code to process subassembly
Else
'this component has no children (it is a leaf node)
'add any code specific to bottom level components
End If
reportComponentChildren(child, indent + 1)
Next
End Sub
'**********************************************************
'Sub GetUnloadOption(ByVal dummy As String) 'As Integer
'Return Session.LibraryUnloadOption.Immediately
'End Sub
'**********************************************************

End Class

Why do you want to convert your code from a module to a class? What benefit do you hope to gain by doing so?

Hi,
I just want to integrate the code in my program that uses a Public Class. I figured out, that it isn't possible to call a Function from a Module that is contained in a Public Class. Is there a way to change this code, to run in a Class instead of a Module? This would save me a lot of work! I'm currently working on my thesis and I’ve never been working with Visual Basic before. I don’t really know exactly the difference between a Module and a Class. The program I’m working on is based on a Class that contains Functions and Subs. Or is there a way to integrate this Module in my program?

"I figured out, that it isn't possible to call a Function from a Module that is contained in a Public Class."

You will need to create an instance of the class in your module, then you can call its public methods and properties. If you are going to be using VB, I suggest reading up on modules and classes. Each have their use and trying to force one to work like the other may be more trouble than it is worth.

Below is a quick and dirty (i.e. probably not the best) way to get your journal to run as a class.

Imports Microsoft.VisualBasic
Imports System
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Assemblies

Public Class NXJournal

public shared theSession As Session
public shared lw As ListingWindow

'Zählervariable
'Dim z As Integer

'Dim workPart As Part = theSession.Parts.Work
'Dim dispPart As Part = theSession.Parts.Display
Dim z As Integer = 0

public sub New()
theSession = Session.GetSession()
lw = theSession.ListingWindow
end sub

Public shared Sub Main(ByVal args() As String)
dim theProgram as new NXJournal

NXJournal.lw.Open

Try
Dim c As ComponentAssembly = NXJournal.theSession.Parts.Display.ComponentAssembly
'to process the work part rather than the display part,
' comment the previous line and uncomment the following line
'Dim c As ComponentAssembly = workPart.ComponentAssembly
If Not IsNothing(c.RootComponent) Then
'*** insert code to process 'root component' (assembly file)
NXJournal.lw.WriteLine("Assembly: " & c.RootComponent.DisplayName)
NXJournal.lw.WriteLine(" + Active Arrangement: " & c.ActiveArrangement.Name)
'*** end of code to process root component
reportComponentChildren(c.RootComponent, 0)

Else
'*** insert code to process piece part
NXJournal.lw.WriteLine("Part has no components")

End If
Catch e As Exception
NXJournal.lw.WriteLine("Failed: " & e.ToString)
End Try
NXJournal.lw.writeLine("Test" & theProgram.z.ToString)
NXJournal.lw.Close

End Sub

'**********************************************************
Public shared Sub reportComponentChildren(ByVal comp As Component,
ByVal indent As Integer)

For Each child As Component In comp.GetChildren()
'*** insert code to process component or subassembly
NXJournal.lw.WriteLine(New String(" ", indent * 2) & child.DisplayName())
If child.DisplayName Like "03-*" Then
'test2.testsub(z)
End If
'*** end of code to process component or subassembly
If child.GetChildren.Length <> 0 Then
'*** this is a subassembly, add code specific to subassemblies
NXJournal.lw.WriteLine(New String(" ", indent * 2) &
"* subassembly with " &
child.GetChildren.Length & " components")
NXJournal.lw.WriteLine(New String(" ", indent * 2) &
" + Active Arrangement: " &
child.OwningPart.ComponentAssembly.ActiveArrangement.Name)
'*** end of code to process subassembly
Else
'this component has no children (it is a leaf node)
'add any code specific to bottom level components
End If
reportComponentChildren(child, indent + 1)
Next
End Sub
'**********************************************************
'Sub GetUnloadOption(ByVal dummy As String) 'As Integer
'Return Session.LibraryUnloadOption.Immediately
'End Sub
'**********************************************************

End Class

Hi! It's working now! Thank you so much for your help. It's not easy to get useful information about programing with NX exapt on this site!

Hey!

I´m trying to achieve the following:

If I have an assembly containing Model1, Model2, Model3, 2x Model4 I´m adding these components into a list. So there are 5 components in my list (Model 4 twice).
My goal is to remove the component duplicates, but one "Model4" has a different tag than the other one (as I discovered). I tried to create an extra list with the display name of the child because then there are 4 different names (tried the same with the prototype part). With "For Each" and "List. Remove" and ".Add" I tried to remove the duplicates and add the parts the list does not contain.
But all of this didn´t work so I´m asking for help because in bigger assemblies it is time saving when I´m not querying every single component.

Thanks in advance.

Dim childCompList As New List(Of Assemblies.Component)
Dim childCompProtoList As New List(Of Part)

For Each children As NXOpen.Assemblies.Component In comp.GetChildren()
If childCompProtoList.Contains(children.Prototype) Then 'Remove the double part / component
childCompList.Remove(children)
childCompProtoList.Remove(children.Prototype)
Else 'Add the part / component
childCompList.Add(children)
childCompProtoList.Add(children.Prototype)
End If
Next children

For Each child As NXOpen.Assemblies.Component In childCompList

I wrote the code below a while back while looking for unique parts in the assembly. The end result is a list of parts; if you want a list of components, you will need to modify the code slightly (but the concept is the same).

Option Strict Off
Imports System
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.Assemblies
Imports NXOpen.UF

Module Module1

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession()
Dim lw As ListingWindow = theSession.ListingWindow

Dim uniqueParts As New List(Of Part)
Dim uniqueSubAssemblies As New List(Of Part)

Sub Main()

If IsNothing(theSession.Parts.BaseWork) Then
'active part required
Return
End If

Dim workPart As Part = theSession.Parts.Work
Dim dispPart As Part = theSession.Parts.Display
lw.Open()

Try
Dim c As ComponentAssembly = dispPart.ComponentAssembly
If Not IsNothing(c.RootComponent) Then
reportComponentChildren(c.RootComponent)
Else
lw.WriteLine("Part has no components")
End If
Catch e As Exception
theSession.ListingWindow.WriteLine("Failed: " & e.ToString)
End Try

lw.WriteLine(dispPart.FullPath)
lw.WriteLine("count of unique sub-assemblies: " & uniqueSubAssemblies.Count.ToString)
For Each temp As Part In uniqueSubAssemblies
Try
'may throw an error if the file is not loaded
lw.WriteLine(" " & temp.FullPath)
Catch ex As Exception
End Try
Next
lw.WriteLine("")

lw.WriteLine("count of unique parts: " & uniqueParts.Count.ToString)
For Each temp As Part In uniqueParts
Try
'may throw an error if the file is not loaded
lw.WriteLine(" " & temp.FullPath)
Catch ex As Exception
End Try
Next

lw.Close()

End Sub

Sub reportComponentChildren(ByVal comp As Component)

For Each child As Component In comp.GetChildren()
Dim thePart As Part = child.Prototype.OwningPart
If child.GetChildren.Length <> 0 Then
'*** this is a subassembly, add code specific to subassemblies
If Not uniqueSubAssemblies.Contains(thePart) Then
uniqueSubAssemblies.Add(thePart)
End If
'*** end of code to process subassembly
Else
'this component has no children (it is a leaf node)
If Not uniqueParts.Contains(thePart) Then
uniqueParts.Add(thePart)
End If
End If
reportComponentChildren(child)
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

Thanks a lot!!
I modified my journal to work with Part instead of Component.

Hello and thank you for the journal.

I should type the assembly name into the vb code?
When I indicate the assembly the walk through means that my screen is changing to every part in the assembly that I can change something?
With the original code I just obtain a list with the assemblies and its arrangements.

Thank you in advance,

MCE - Designer

The journal works on the current display part. If you open your assembly and run the code, it will report some information on your assembly to the listing window. The information is just to show that the code processes each component; it does not change anything in your assembly, but you could certainly add code that would.

"When I indicate the assembly the walk through means that my screen is changing to every part in the assembly that I can change something?"

The code doesn't change the work or displayed parts, you probably won't see any change on the screen.

OK, so that means I should add extra code to implement the desired action?
So that,the journal will walk through all part and apply it?

Thank you!

MCE - Designer