Processing all part files in a directory
Occasionally you will need to perform a certain action on each part file in a given directory. This journal will prompt the user to specify a directory, then it will open each file found in the directory and do something with the file. In the case of this example it simply reports how many solid and sheet bodies the part file contains, but in a real usage scenario it may export all the drawing sheets to a pdf, export the solids to a .STP file, perform file cleanup, or well, whatever else you can code. Below is the code.
'NXJournaling 'October 26, 2012 'tested on NX 8 '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 then close the file 'August 5, 2015 'update: fix for the "part scan error: file already exists" error ' Previous version was not closing assembly components correctly, ' this left the component file in NX memory. When the journal later ' attempted to open the part file to process it, ' the "file already exists" error would be thrown. Option Strict Off Imports System Imports System.IO Imports System.Collections Imports System.Windows.Forms 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) 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 Try part1 = theSession.Parts.OpenDisplay(fileName, Nothing) Catch ex As NXException LW.WriteLine("open error: " & ex.Message) Catch ex2 As Exception LW.WriteLine("exception: " & ex2.Message) End Try 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 LW.WriteLine(" this part contains a total of " & CountBodies() & " solid and sheet bodies") 'close file unless this file was initially open If (IsNothing(initialPart)) OrElse (initialPart.FullPath <> fileName) Then part1.Close(BasePart.CloseWholeTree.True, 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 End Sub '*************************************************************************** Function CountBodies() As Integer Dim total As Integer total = workPart.Bodies.ToArray.Length Return total End Function '*********************************************************************** 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
There are a few variables declared at the module level of the journal. The only one really worth mentioning here is initialPart, which is set to the initial display part. Since the journal will be opening and closing files, we want a way to restore the initial display when the journal ends. If there is no open part when the journal is started, initialPart will equal "Nothing" (a visual basic keyword that means the variable has no value); otherwise it will equal the current display part.
At this point Sub Main starts executing. The bulk of Sub Main is devoted to retrieving the user's desired directory by way of a folder picker dialog. If the user presses OK, the selected folder's path is returned; if the user presses Cancel, we exit the journal by using the Exit Sub statement. The Exit Sub|Function statement will immediately end the currently executing Sub|Function and return control to the calling Sub|Function. When we exit Sub Main, the journal stops and the interactive NX session resumes. Assuming a folder is chosen and OK is pressed, the journal will now write out some info to the listing window and call the subroutine processParts. The processParts subroutine requires two parameters be passed to it: a string value that holds the path to a folder, and a boolean value that indicates if subfolders should be searched.
The .NET IO.Directory.GetFiles method does all the hard work for us finding the files in the given directory. The GetFiles method is overloaded (it accepts different input parameters depending on what you need from it) for flexibility; for more information vist the MSDN page. The version we are using needs three input parameters: the directory path, a matching pattern, and a search option (include subdirectories or not). The pattern string can consist of any alphanumeric characters allowed in a filename along with the two special wildcard characters "*" and "?". The wildcard * (asterisk or star) means zero or more characters (any character) and the wildcard ? (question mark) means zero or one character. The pattern "g?t" would match "get", "got", "gut", but not "goat" or "gust". The pattern "g*t" would match all the preceding examples and many others such as "goats have disgusting snot". You can mix and match the wildcards and alphanumeric characters to make more complicated patterns, but they don't provide the flexibility and power of regular expressions. The search string we use is "*.prt" which will return all the files with the .prt extension. You can change this to "a*.prt" to get all the files that start with the letter a; or use "*_dwg.prt" to get all the parts that end with _dwg.prt (a common way to distinguish drawing files).
Continuing on, we use a For loop to iterate over all the files that were found. The first If block checks to see if the initial part equals nothing (there was no part open when the journal was started) or if the returned file path does not equal the initial file path; in this case it is safe to open the given file as the display part. If the file path is the same as the initial part, we already have the file open and will get an error if we try to open it again; in this case we simply need to update the part1 variable. One item of note here is the use of the OrElse operator; this is the "short circuit" version of the Or operator. If we have the expression C = A or B, C will be true if either A or B is true. With the normal Or operator both A and B are evaluated before assigning a value to C; but if A is true, we know C will be assigned the value of true no matter if B is true or false. The shortcircuit OrElse operator takes advantage of this fact and if A is true, it will assign a true value to C and completely skip checking the value of B. We take advantage of this behavior to avoid an error condition. If initial part equals nothing, we immediately know it is safe to open the given part as the display part; if we had used the Or operator, the journal would go on to evaluate condition B, does initialPart.FullPath equal the given file name? But if initialPart is Nothing, trying to access one of its properties (.FullPath) will trigger an error. So if initialPart is Nothing, the OrElse operator saves us here by immediately executing the code in the "true" portion of the If block.
The actual file processing happens right after the 'do something comment. I suggest you write subroutines and/or functions to perform the processing you want to do and call them from here. In this journal, we have a simple function that returns the number of solids/sheets in the part. This value is written to the listing window and we move on to the next block of code. The next If block will close the file; if your processing alters the file and you want to save the changes, comment out the part1.close... line and uncomment the part1.save... line. The final If block makes the initial part the display part again.
Stepping through all the part files in a given directory turned out to be easier than I thought it would be. The folder browser dialog provides a GUI for the user to specify a directory and .NET's GetFiles method does all the hard work of searching and gathering the file names into an array. All that is really left for us is to manage these two functions and make sure we don't try to open a file that is already open or display a file that is already the display part. I hope you found this technique helpful.
Happy file processing!