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!