How to perform sketch reattach via NXOpen (vb)

Hello,
I need to perform an action similar to the interactive Sketch "Reattach" for multiple sketches in a model if the sketches exhibit a warning about "Referenced measure parent is missing associated parents and may be out of date".
We have found that performing a reattach interactively mends this issue, so we'd like to create a programmatic fix for this.
I have already set up a framework to cycle through all sketches of a model, check for its associative placement status and decide whether to try modifying it.
BUT: I have not succeeded in actually performing that "Reattach" action.
So, if anyone migth contribute this "bit", I would very much appreciate it.
Of course I recorded a journal of an interactive reattach action to see what gets recorded but what I get does not seem fit for reuse in a code. Not even for exactly the same sample sketch and even less for sketches being placed with all sorts of methods.
I will place my "framework" code here - of course you will see the "Try" part is not yet doint what a simple interactive "reattach" action without selecting anything and just clicking [OK] would do.

Hope someone knows how to achieve the result in question.
Thanks for supporting & best regards
/Udo


Option Strict Off
Option Explicit On
Imports System
Imports NXOpen
Imports NXOpen.UF

Module Module1

Sub Main()

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

For Each sketch1 As Sketch In workPart.Sketches
sketch1.Activate(Sketch.ViewReorient.False)
Dim sketchInPlaceBuilder1 As SketchInPlaceBuilder
sketchInPlaceBuilder1 = workPart.Sketches.CreateNewSketchInPlaceBuilder(theSession.ActiveSketch) 'nur zum testen der Assoziativität mit dem deprecated-Befehl!
'lw.WriteLine("Sketch=" + sketch1.Name + " Associative Origin=" + sketchInPlaceBuilder1.MakeOriginAssociative.ToString())
Dim sketchInPlaceBuilder2 As SketchInPlaceBuilder
sketchInPlaceBuilder2 = workPart.Sketches.CreateSketchInPlaceBuilder2(theSession.ActiveSketch) 'für plane-Zugriff!
If sketchInPlaceBuilder1.MakeOriginAssociative = False Then
lw.WriteLine("Sketch=" + sketch1.Name + " Associative Origin=" + sketchInPlaceBuilder1.MakeOriginAssociative.ToString())

'mend sketch
'enter edit sketch environment
theSession.BeginTaskEnvironment()

'try to apply reattach for XY plane
Try
Dim plane1 As NXOpen.Plane = Nothing
plane1 = sketchInPlaceBuilder2.PlaneReference
plane1.Evaluate()
sketchInPlaceBuilder2.PlaneOption = NXOpen.Sketch.PlaneOption.ExistingPlane

Dim sketchAlongPathBuilder1 As NXOpen.SketchAlongPathBuilder = Nothing
sketchAlongPathBuilder1 = workPart.Sketches.CreateSketchAlongPathBuilder(theSession.ActiveSketch)
sketchAlongPathBuilder1.Section.PrepareMappingData()

plane1.SetMethod(NXOpen.PlaneTypes.MethodType.Coincident)
plane1.SetMethod(NXOpen.PlaneTypes.MethodType.Coincident)

Dim geom1(0) As NXOpen.NXObject
Dim datumPlane1 As NXOpen.DatumPlane = CType(workPart.Datums.FindObject("DATUM_CSYS(0) XZ plane"), NXOpen.DatumPlane)
'Skizzenebene auf Datum Plane 1 setzen
'ermitteln, auf welcher Ebene die aktuelle Skizze platziert ist
Dim surface1 As ISurface = sketch1.AttachPlane

geom1(0) = datumPlane1
'geom1(0) = surface1
plane1.SetGeometry(geom1)
'plane1.SetAlternate(NXOpen.PlaneTypes.AlternateType.One)
plane1.Evaluate()

Dim nXObject1 As NXOpen.NXObject = Nothing
nXObject1 = sketchInPlaceBuilder2.Commit()
Dim feature1 As NXOpen.Features.Feature = Nothing
feature1 = theSession.ActiveSketch.Feature

sketchInPlaceBuilder1.Destroy()
sketchInPlaceBuilder2.Destroy()
sketchAlongPathBuilder1.Destroy()

Catch ex As Exception
lw.WriteLine("Error:" + ex.Message)
End Try

'finish sketch
theSession.EndTaskEnvironment()

End If
Next
End Sub

Public Function GetUnloadOption(ByVal dummy As String) As Integer

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

'----Other unload options-------
'Unloads the image immediately after execution within NX
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Immediately

'Unloads the image explicitly, via an unload dialog
'GetUnloadOption = NXOpen.Session.LibraryUnloadOption.Explicitly
'-------------------------------

End Function

End Module

"Referenced measure parent is missing associated parents and may be out of date"

I'm a bit concerned about the warning message. It sounds like it might not be a sketch plane reattachment issue, but something further up the tree. This might simply be a translation issue, however, as I see some of your code comments are not in English (looks like German?).

It might be warning you that a "measure" is out of date. For instance, perhaps the sketch is placed on the face of an extrude and the extrude "end distance" is controlled by a "measure" from within the extrude command. If the measure loses its parents, the extrude will lock in at the last known location. This would, in turn, affect the sketch plane location. If you reattach the sketch, it might convert the location to a fixed plane that no longer follows the face location of the extrude. Now, this is a bit of speculation on my part (not having seen the part in question), but you can see how we might be trying to fix the wrong issue.

Can you share a part file (or better, a simplified model) where this issue occurs?

If so, you can email it to info@nxjournaling.com. Note that currently I can only open part files in NX2206 or older.

Hello,
thank you for your hint.
Yes, something like what you suspect might be the case. Unfortunateily it is a customer's model and I am not entitled to undisclose it. The history of that model starts with a datum csys, which the sketch seems to be placed on (to the XZ plane), but the next feature is a linked body (WAVE), and then there is the 1st in a series of many - mostly internal - sketches showing those warnings.
So when I just look at the 1st sketch: after making it an external sketch, there is no feature in this model which could posses a measurement like you proposed. But if I follow the WAVE linked body, in it's parent part there may be more to find.
I'll try to find out, so long...
Udo

/UdoMM

Hello again,
what if I changed my tactics and only tried to delete the specific warning message:
I would only want to address just this one type of alert.
So I think, I need to change my code to not loop though all sketches, but all features instead and I need a method to get possible warning alerts for each feature and compare them to the one message I am looking for.

In the "open for .NET" help, I only found something like
JournalManager.PlayDotNetJournalInformation.PlayDotNetJournalInformation(String ErrorMessage, String WarningMessage )
which doesn't look to be useful to me. Or I don't understand how to use it.

The alert is not on the attributes either (I tried to list all attribute titles).

Does anyone know how to get hold of the the warning messages?

This is my current experimental code:

Option Strict Off
Option Explicit On
Imports System
Imports NXOpen
Imports NXOpen.UF
Imports NXOpen.Features

Module Module1

Sub Main()

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

For Each thisFeat As Feature In workPart.Features.ToArray()
lw.WriteLine("Feature: " & thisFeat.FeatureType() & " (" & thisFeat.Timestamp.ToString() & ")")

Dim ents() As NXObject = thisFeat.GetEntities()

For Each thisObj As NXObject In ents
'thisObj.GetAttributeTitlesByType()
' thisObj.GetUserAttributes().ToString()

'Inquire attribute title
Dim atts() As NXObject.AttributeInformation = workPart.GetUserAttributes()
Dim titles(atts.Length - 1) As String
For ii As Integer = 0 To atts.Length - 1
titles(ii) = atts(ii).Title
lw.WriteLine("Attributtitel: " & titles(ii))
Next
lw.WriteLine(" Entity: " & thisObj.ToString() & " Attributes: " & thisObj.GetUserAttributes().ToString())

Next

'JournalManager.PlayDotNetJournalInformation.PlayDotNetJournalInformation(String ErrorMessage, String WarningMessage )
'lw.WriteLine("ErrorMessage " & ErrorMessage & " WarningMessage " & WarningMessage)

' Delete warning alert
' thisFeat.DeleteWarningAlerts()

Next

End Sub

End Module

/UdoMM

You can get the warning from the feature itself. The warning messages (there may be more than one) are returned as a string array.

The sketch object has a .Feature property that will give you direct access to the sketch feature, no need to iterate through the features collection. The downside to this is that the sketch features may not be returned in "part navigator order"; this may or may not be important to you.

Clearing the warning (or informational) alert is a function of the feature collection. The .DeleteWarningAlerts method requires an array of features as input. It will clear the alerts for all the given features. If an empty array is provided, it will clear the warnings for all the features. Note that the curly braces {} surrounding a variable tells the VB compiler to treat the variable as an array with a single element. This is a handy shortcut; otherwise you would need to declare a one element array of features, assign your desired feature to the array, and pass the array to the function.

Some example code below:

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

Module Module211

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

Sub Main(args As String())
lw.Open()

For Each sketch1 As Sketch In theSession.Parts.Work.Sketches

'get the feature of the sketch
Dim sketch1Feat As Features.SketchFeature = sketch1.Feature

'get sketch feature warnings
Dim sketchWarnings() As String = sketch1Feat.GetFeatureWarningMessages

If sketchWarnings.Length > 0 Then
lw.WriteLine(sketch1Feat.GetFeatureName)
For Each myLine As String In sketchWarnings
lw.WriteLine(" " & myLine)
Next

'take action based on the warning message
'this journal will just clear feature warning
theSession.Parts.Work.Features.DeleteWarningAlerts({sketch1Feat})
End If
Next

End Sub

End Module

Wow - that's great!
There's one downside now: When I execute the code once, it displays the warnings in the information window, and I am confident that it also deletes the warnings, because when I rerun the code a 2nd time, it does not display any warnings anymore - because they had been deleted during the 1st run. However: The part navigator still displays all the warnings, both as the warning symbol and as the alert text.
To double check, I tried saving the 'cleaned' part as an new file, closed everything and reopened it, and then the PNT display is OK - no warnings indicated anymore.

Does it take some kind of commit of flush command to update the PNT display? (I looked around the 'Open for .NET reference guide but found nothing related to 'part navigator')

Regards,

/UdoMM

Unfortunately, my test file has only a single sketch with a warning and when I run the code, the warning is immediately removed from the PNT. I'm not sure if this will work in your case, but I suggest doing an update after your code to see if it clears up the PNT icons.

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "clean sketch warnings")

'...your code

Dim nErrs1 As Integer
nErrs1 = theSession.UpdateManager.DoUpdate(markId1)

Thank you again.
I can confirm that for external sketches, the warning gets removed from PNT alright, but for internal sketches, the warning remains displayed in PNT.
If I make all sketches external, and then run the script, all warnings will be gone.
And the other way round: If I run the script on internal sketches, the warnings will remain until I make any of the sketches external. This action will "refresh" the PNT and all the warning symbols are gone.
So, as a workaround I'd need to find the 1st internal sketch, make it external and make it internal again.
This brings up a new question: How do I identify an internal sketch (with warning state possibly), and make it external once, and then internal again. I'll see what I can do...
Cheers,
Udo

/UdoMM

It seems I'd need to get access to the feature, which is related to the internal sketch. Once I have that, I can simply use
feature1.MakeSketchExternal()
and
feature1.MakeSketchInternal()
So I need to find, out how te get from the sketch to its related feaure...

/UdoMM

I ran into a situation a few years ago where internal features were causing issues. If a feature is internal to another, the .IsInternal property will return "True". There is probably a very simple way to get the "containing feature", but I could not find it. What I noticed is that the timestamp of the internal feature will be the same as the containing feature. I used this fact to keep track of internal vs. external features.

I wrote some test code while working on this issue. It runs through the feature collection and sorts the features into "internal" and "external" groups. I used dictionaries to keep track of which was which. The code below simply reports out all the features found, but you could pass in a specific feature to the "internal feature dictionary" to find out its containing feature.

The code below is my old test code. I've not reviewed it recently and it could probably use some polish, but it still seems to work.

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

Module Module1

Sub Main()

Dim theSession As Session = Session.GetSession()
If IsNothing(theSession.Parts.BaseWork) Then
'active part required
Return
End If

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

Const undoMarkName As String = "report internal features"
Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)

'*************************************************************************************************
'Query the features of the current work part
'
'Create some dictionary objects to hold information on internal features.
'
'internal feature = key; containing feature = value
Dim internalFeatureDictionary As New Dictionary(Of Features.Feature, Features.Feature)
'"container" feature = key, list of internal features = value
Dim externalFeatureDictionary As New Dictionary(Of Features.Feature, List(Of Features.Feature))

Dim curTimeStamp As Integer = -1
Dim containerFeature As Features.Feature = Nothing
Dim internalFeats As New List(Of Features.Feature)
For Each tempFeat As Features.Feature In workPart.Features

If tempFeat.Timestamp = curTimeStamp Then

If tempFeat.IsInternal Then
'lw.WriteLine("adding feature to 'internal' list")
internalFeats.Add(tempFeat)

Else 'same timestamp, external feature
containerFeature = tempFeat

End If

Else 'different timestamp

If Not IsNothing(containerFeature) Then
externalFeatureDictionary.Add(containerFeature, New List(Of Features.Feature)(internalFeats))
For Each temp As Features.Feature In internalFeats
internalFeatureDictionary.Add(temp, containerFeature)
Next
End If
internalFeats.Clear()
curTimeStamp = tempFeat.Timestamp

If tempFeat.IsInternal Then
internalFeats.Add(tempFeat)
containerFeature = Nothing

Else 'different timestamp, external feature
containerFeature = tempFeat

End If

End If

Next

If Not IsNothing(containerFeature) Then
externalFeatureDictionary.Add(containerFeature, New List(Of Features.Feature)(internalFeats))
For Each temp As Features.Feature In internalFeats
internalFeatureDictionary.Add(temp, containerFeature)
Next
End If
'
'End of Query
'*************************************************************************************************

'Report out on the features contained in the work part using the dictionary objects created earlier.
For Each temp As Features.Feature In workPart.Features
lw.WriteLine("feature: " & temp.GetFeatureName & " [" & temp.Tag.ToString & "]")
If internalFeatureDictionary.ContainsKey(temp) Then
lw.WriteLine(" is internal to: " & internalFeatureDictionary.Item(temp).GetFeatureName & " [" & internalFeatureDictionary.Item(temp).Tag.ToString & "]")
End If

If externalFeatureDictionary.ContainsKey(temp) Then
If externalFeatureDictionary.Item(temp).Count > 0 Then
lw.WriteLine(" contains the features:")
For Each internalFeat As Features.Feature In externalFeatureDictionary.Item(temp)
lw.WriteLine(" " & internalFeat.GetFeatureName & " [" & internalFeat.Tag.ToString & "]")
Next

Else
lw.WriteLine(" contains no internal features")
End If
End If

lw.WriteLine("")
Next

lw.Close()

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

Thank you for all the input. I have a working solution now. Maybe not the best one but a working one:

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

Module Module211

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

Sub Main(args As String())
lw.Open()

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "clean sketch warnings")

For Each sketch1 As Sketch In theSession.Parts.Work.Sketches

'get the feature of the sketch
Dim sketch1Feat As Features.SketchFeature = sketch1.Feature

'get sketch feature warnings
Dim sketchWarnings() As String =
sketch1Feat.GetFeatureWarningMessages

If sketchWarnings.Length > 0 Then
lw.WriteLine(sketch1Feat.GetFeatureName)
For Each WarnText As String In sketchWarnings
'lw.WriteLine(" " & WarnText)
KeepText = WarnText
Next

'take action based on the warning message
'this journal will just clear feature warning
If KeepText = "Der referenzierten Messungsüberordnung fehlen verknüpfte übergeordnete Elemente und es ist ggf. veraltet." _
Or KeepText = "Referenced measure parent is missing associated parents and may be out of date." Then
theSession.Parts.Work.Features.DeleteWarningAlerts({sketch1Feat})
Dim features = sketch1Feat.GetParents()
For Each thisFeat As Feature In features
Try
thisFeat.MakeSketchExternal()
thisFeat.MakeSketchInternal()
Catch ex As Exception
End Try
Next
End If
End If
Next
End Sub

End Module

I got a proposal how to check if the sketches are really internal, but haven't tried to integrate that yet, it would go like this:

Option Strict Off
Imports System
Imports NXOpen

Module NXJournal

Dim theSession As Session = Session.GetSession()

Sub Echo(ByVal output As String)
theSession.ListingWindow.Open()
theSession.ListingWindow.WriteLine(output)
theSession.LogFile.WriteLine(output)
End Sub

Sub Main()

Dim workPart As Part = theSession.Parts.Work
Dim sketches() As Sketch = workPart.Sketches.ToArray()

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Invisible, "")

For Each aSketch As Sketch In sketches
If aSketch.IsInternal Then

Try
Dim owner() As Features.Feature = aSketch.Feature.GetChildren()
owner(0).MakeSketchExternal()
Echo("Internal " & aSketch.Name & " can be made External to " & owner(0).GetFeatureName())
Catch ex As Exception
End Try

End If
Next

theSession.UndoToMark(markId1, "")
theSession.DeleteUndoMark(markId1, "")

End Sub
End Module

Best regards
Udo

/UdoMM


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

Module Module211

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

Sub Main(args As String())
lw.Open()

Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, "clean sketch warnings")

For Each sketch1 As Sketch In theSession.Parts.Work.Sketches

'get the feature of the sketch
Dim sketch1Feat As Features.SketchFeature = sketch1.Feature

'get sketch feature warnings
Dim sketchWarnings() As String =
sketch1Feat.GetFeatureWarningMessages

If sketchWarnings.Length > 0 Then
lw.WriteLine(sketch1Feat.GetFeatureName)
For Each WarnText As String In sketchWarnings
'lw.WriteLine(" " & WarnText)
KeepText = WarnText
Next

'take action based on the warning message
'this journal will just clear feature warning
If KeepText = "Der referenzierten Messungsüberordnung fehlen verknüpfte übergeordnete Elemente und es ist ggf. veraltet." _
Or KeepText = "Referenced measure parent is missing associated parents and may be out of date." Then
'lw.WriteLine(">Trying to delete the warning...")
theSession.Parts.Work.Features.DeleteWarningAlerts({sketch1Feat})
'Dim features = sketch1Feat.GetParents()
'For Each thisFeat As Feature In features
' Try
' thisFeat.MakeSketchExternal()
' thisFeat.MakeSketchInternal()
' Catch ex As Exception
' End Try
'Next
End If
End If

If sketch1.IsInternal Then
Try
Dim owner() As Features.Feature = sketch1.Feature.GetChildren()
owner(0).MakeSketchExternal()
owner(0).MakeSketchInternal()
Catch ex As Exception
End Try
End If
Next

End Sub

End Module

/UdoMM