Find Duplicate Sheet Bodies

Find Duplicate Sheet Bodies

It can be easy to duplicate geometry in a CAD file, especially when importing from 'neutral' formats such as IGES. The journal below helps to identify duplicate sheet bodies. It will scan through the sheet bodies in the work part looking for potential duplicates. It is intended to be used on sheet bodies comprised of a single face each (such as imported IGES geometry, when the 'automatic sew' option is not used). By default, the journal moves any potential duplicates to layer 255 (change this to your preference in the code before running the journal). I suggest inspecting them more closely before doing something more drastic such as deleting them out of the file. The method used to find duplicates is not 100% guaranteed; but most of the misidentified matches in my testing have been due to suspect input geometry (Garbage In - Garbage Out, as always).

Point of Interest

This journal makes use of a DataTable object, which is essentially an in-memory-database. The DataTable object helps to hold, sort, search, and optionally write out the sheet body bounding box information. This code example really only scratches the surface of this useful object. A related object, the DataSet, can hold multiple DataTables; data can be read from or written to external databases and SQL statements can be executed on them. Do some web searches to get more information on them, as I'm sure you will eventually run into a programming problem that they can help solve.

The Code

'NXJournaling
'Journal to compare sheet bodies, move duplicates to a specified layer.
'April 19, 2013
'written and tested on NX 8
 
Option Strict Off
Imports System
Imports System.Data
Imports System.Collections.Generic
Imports NXOpen
Imports NXOpen.Utilities
Imports NXOpen.UF
 
Module compare_sheets
 
	Dim theSession As Session = Session.GetSession()
	Dim workPart As Part = theSession.Parts.Work
	Dim theUfSession As UFSession = UFSession.GetUFSession()
	Dim lw As ListingWindow = theSession.ListingWindow
 
	Dim myModelingTolerance As Double
 
	Sub Main()
 
		lw.Open()
 
        If IsNothing(theSession.Parts.Display) Then
            lw.WriteLine("Active part required, journal exiting")
            lw.Close()
            Exit Sub
        End If
 
	Dim sheetCount As Integer = 0
	For Each obj As Body In workPart.Bodies
		If obj.IsSheetBody Then
			sheetCount += 1
			'must have at least 2 sheet bodies to run a meaningful comparison
			If sheetCount >= 2 Then
				Exit For
			End If
		End If
	Next
 
	If sheetCount < 2 Then
		lw.WriteLine("Insufficient number of sheet bodies found, journal exiting")
		Exit Sub
	End If
 
	'reset sheetCount variable
	sheetCount = 0
 
	Dim markId1 As Session.UndoMarkId
        markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "Identify duplicate sheet bodies")
 
        'Optional: write output file with bounding box info that was used for preliminary comparison
        Dim myDocs As String
        myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
        Dim outputFile As String = IO.Path.Combine(myDocs, "compareFaces.csv")
 
	'layer we will move duplicate sheets to
        Const dumpLayer As Integer = 255
	'if any sheet bodies start out on the dumpLayer, move them to this layer
        Const holdingLayer As Integer = 251
	Dim numMatches As Integer = 0
 
	'time variables to record how long the operation took
	Dim startTime As DateTime = Now
	Dim endTime As DateTime
	Dim timeElapsed As TimeSpan
 
	Dim faceTable As New DataTable("Face")
        faceTable.Columns.Add("ID", GetType(Integer))
        faceTable.Columns.Add("Xmin", GetType(Double))
        faceTable.Columns.Add("Ymin", GetType(Double))
        faceTable.Columns.Add("Zmin", GetType(Double))
        faceTable.Columns.Add("Xmax", GetType(Double))
        faceTable.Columns.Add("Ymax", GetType(Double))
        faceTable.Columns.Add("Zmax", GetType(Double))
 
	Dim bbox(5) As Double
	Dim facePt(2) As Double
	Dim faceDir(2) As Double
 
	'collect info on each face
	For Each obj As Body In workPart.Bodies
		If obj.IsSheetBody Then
			Dim myFace As Face = obj.GetFaces(0)
			If obj.Layer = dumpLayer Then
				obj.Layer = holdingLayer
				obj.RedisplayObject()
			End If
			sheetCount += 1
			theUfSession.Modl.AskBoundingBox(myFace.Tag, bbox)
			faceTable.Rows.Add(myFace.Tag, bbox(0), bbox(1), bbox(2), bbox(3), bbox(4), bbox(5))
		End If
	Next
 
	endTime = Now
	timeElapsed = endTime.Subtract(startTime)
	lw.WriteLine("finished collecting surface data")
	lw.WriteLine("Time elapsed: " & timeElapsed.ToString)
	lw.WriteLine("")
 
	' Create DataView 
	Dim view As New DataView(faceTable)
 
        ' Sort by bounding box coordinates
	view.Sort = "Xmin ASC, Ymin ASC, Zmin ASC, Xmax ASC, Ymax ASC, Zmax ASC"
 
        '%%%%%%%%%%
        '% uncomment this section if you want a CSV file
        '% of the data used for the bounding box checks
        'Using myWriter As New IO.StreamWriter(outputFile, False)
        '    myWriter.WriteLine("Sheet Body Tag, Bounding Box X min, Bounding Box Y min, Bounding Box Z min, Bounding Box X max, Bounding Box Y max, Bounding Box Z max")
        '    For Each row As DataRowView In view
        '        myWriter.WriteLine(row("ID") & "," & row("Xmin") & "," & row("Ymin") & "," & row("Zmin") & "," & row("Xmax") & "," & row("Ymax") & "," & row("Zmax"))
        '    Next
        'End Using
        '% end of CSV writing loop
        '%%%%%%%%%%
 
        theUfSession.Modl.AskDistanceTolerance(myModelingTolerance)
 
        lw.WriteLine("count of sheet bodies: " & sheetCount)
        lw.WriteLine("")
 
        lw.WriteLine("modeling tolerance: " & myModelingTolerance)
        lw.WriteLine("")
 
        'loop through dataview looking for matches
        Dim i As Integer = 0
        For i = 0 To view.Count - 2
 
            If Math.Abs(view.Item(i).Row("Xmin") - view.Item(i + 1).Row("Xmin")) > myModelingTolerance Then
                'Xmin value is not a match, start next iteration
                Continue For
            End If
 
            If Math.Abs(view.Item(i).Row("Ymin") - view.Item(i + 1).Row("Ymin")) > myModelingTolerance Then
                'Ymin value is not a match, start next iteration
                Continue For
            End If
 
            If Math.Abs(view.Item(i).Row("Zmin") - view.Item(i + 1).Row("Zmin")) > myModelingTolerance Then
                'Zmin value is not a match, start next iteration
                Continue For
            End If
 
            If Math.Abs(view.Item(i).Row("Xmax") - view.Item(i + 1).Row("Xmax")) > myModelingTolerance Then
                'Xmax value is not a match, start next iteration
                Continue For
            End If
 
            If Math.Abs(view.Item(i).Row("Ymax") - view.Item(i + 1).Row("Ymax")) > myModelingTolerance Then
                'Ymax value is not a match, start next iteration
                Continue For
            End If
 
            If Math.Abs(view.Item(i).Row("Zmax") - view.Item(i + 1).Row("Zmax")) > myModelingTolerance Then
                'Zmax value is not a match, start next iteration
                Continue For
            End If
 
            'if we've made it this far, it is a possible match
            'run face equivalency check
 
            Dim face1 As Face
            Dim faceTag1 As Tag
            Dim body1 As Body
 
            Dim face2 As Face
            Dim faceTag2 As Tag
            Dim body2 As Body
 
            Dim equiv As Integer
 
            faceTag1 = CType(view.Item(i).Row("ID"), Tag)
            faceTag2 = CType(view.Item(i + 1).Row("ID"), Tag)
 
            face1 = NXObjectManager.Get(faceTag1)
            face2 = NXObjectManager.Get(faceTag2)
 
            Try
 
                theUfSession.Modl.Compare(face1.Tag, face2.Tag, 1, equiv)
                'for a more thorough but slower check, try Modl.CompareTopology
                If equiv = 1 Then
                    body1 = face1.GetBody
                    body1.Layer = dumpLayer
                    body1.RedisplayObject()
                    numMatches += 1
 
                End If
 
            Catch ex As NXException
                lw.WriteLine("error with face comparison method, checking geometry")
                lw.WriteLine("  " & ex.Message)
                lw.WriteLine("")
 
                Try
                    body1 = face1.GetBody
                    body2 = face2.GetBody
 
                    Dim badGeo1 As Integer = 0
                    Dim badGeo2 As Integer = 0
                    Dim sheet1FailMode As String = ""
                    Dim sheet2FailMode As String = ""
 
                    sheet1FailMode = CheckGeometry(face1, body1, badGeo1)
                    sheet2FailMode = CheckGeometry(face2, body2, badGeo2)
 
                    lw.WriteLine("sheet1: " & sheet1FailMode & " count: " & badGeo1)
                    lw.WriteLine("sheet2: " & sheet2FailMode & " count: " & badGeo2)
 
                    If sheet1FailMode = sheet2FailMode And badGeo1 = badGeo2 Then
                        'count it as a match
                        body1.Layer = dumpLayer
                        body1.RedisplayObject()
                        numMatches += 1
                    End If
 
                Catch ex2 As NXException
                    lw.WriteLine("error")
                    lw.WriteLine("  " & ex2.Message)
                    lw.WriteLine("")
                End Try
 
            End Try
 
        Next
 
        endTime = Now
        timeElapsed = endTime.Subtract(startTime)
        lw.WriteLine("possible matches found: " & numMatches)
        lw.WriteLine("  moved to layer: " & dumpLayer.ToString)
        lw.WriteLine("")
        lw.WriteLine("total time elapsed: " & timeElapsed.ToString)
        lw.WriteLine("sheet body info written to file: " & outputFile)
 
        lw.Close()
 
	End Sub
 
	Function CheckGeometry(ByVal someFace As Face, ByVal someBody As Body, ByRef numBad As Integer) As String
 
		Dim badFacesEdges As Integer
		Dim faultTags(-1) As Tag
		Dim faultTokens(-1) As Integer
		Dim faceSelfIntersectPoint(2) As Double
 
		'check body structures
		theUfSession.Modl.AskBodyStructures(someBody.Tag, badFacesEdges, faultTags)
		If badFacesEdges > 0 Then
			numBad = badFacesEdges
			Return "BodyStructure"
		End If
 
		'check body consistency
		theUfSession.Modl.AskBodyConsistency(someBody.Tag, badFacesEdges, faultTokens, faultTags)
		If badFacesEdges > 0 Then
			numBad = badFacesEdges
			Return "BodyConsistency"
		End If
 
		'check face-face intersection
		'don't think this applies to a collection of single face sheet bodies
		theUfSession.Modl.AskFaceFaceIntersect(someBody.Tag, badFacesEdges, faultTags)
		If badFacesEdges > 0 Then
			numBad = badFacesEdges
			Return "FaceFaceIntersection"
		End If
 
		'check for face self-intersection
		theUfSession.Modl.AskFaceSelfIntersect(someFace.Tag, badFacesEdges, faceSelfIntersectPoint)
		If badFacesEdges > 0 Then
			numBad = badFacesEdges
			Return "Self-Intersection"
		End If
 
		'made it through all the tests: good geometry
		Return "Good"
 
	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


And the award goes to...

Special thanks to Frank Swinkels for helping out with the overall search strategy.

Comments

Can someone create the same script for solids?
It's will be very useful. Thanks!