Move parts list callout leader location

The parts list autoballoon function can be a great time-saver. Unfortunately, it often picks undesirable locations when attaching the leader arrow to the component of interest. The leader locations are easily moved, but when working quickly or on a cramped drawing it can be easy to unintentionally pick the edge of a neighboring component. When the parts list updates, the callouts update and you are left with a component that has no callout.

The goal of this journal is to limit the selection to the edges of the component the callout belongs to. The journal will prompt you to select a callout balloon, it will then allow you to pick an edge of the corresponding component. The balloon leader will be reattached to the component near your pick point.

Limitations

This journal will only work with "Pre-NX 8.5 Exact" views. If you are running NX 8.5 or higher and using the "Exact" drafting view type, this journal will NOT work for you.

This journal has not been thoroughly tested with the "extracted edges" or "snapshot" view options. It may or may not work in this case.

Occasionally, the leader attachment point will be a bit "off" from the pick point. This is an issue that I have not dug into; if it happens to you, try picking a slightly different location.

The Code

Other than being an occasionally handy utility, it is also a good example of filtering the user selection "on the fly".

'NXJournaling.com
'September 30, 2014

'Allow user to select new leader point for parts list balloon,
'limit selection to drawing objects of the balloon's component.
'This will eliminate inadvertently picking a different
'component when all you want to do is move the leader point.

'Only works for "Pre-NX 8.5 Exact" views.
'May not work with the "extracted edges -> snapshot" option (not tested).

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

Module Module3

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession
Dim workPart As Part = theSession.Parts.Work
Dim displayPart As Part = theSession.Parts.Display
Dim lw As ListingWindow = theSession.ListingWindow
Dim lg As LogFile = theSession.LogFile

Dim curComp As Assemblies.Component = Nothing
Dim dicSectionEdges As New Dictionary(Of Tag, Tag)

Sub Main()

lg.WriteLine("~~ Reassociate ID symbol ~~")
lg.WriteLine(" timestamp: " & Now)

If IsNothing(theSession.Parts.Work) Then
'active part required
lg.WriteLine(" no active part, exiting journal")
Return
End If

lw.Open()

Const undoMarkName As String = "reassociate parts list callout"
Dim markId1 As Session.UndoMarkId
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)

Dim myIdSymbol As Annotations.IdSymbol
Dim newSelPoint(2) As Double
Dim newSelViewTag As Tag = Tag.Null

Do Until SelectIdSymbol("select ID Symbol", myIdSymbol) = Selection.Response.Cancel

lg.WriteLine(" waiting for user to select ID balloon...")

If Not IsAutoBalloon(myIdSymbol) Then
lg.WriteLine(" balloon selected is not part of a callout (does not belong to the parts list)")
lg.WriteLine(" skip processing, prompt user to select a different balloon")
'warn user it is not used in callout
'MsgBox("Selected balloon is not a parts list callout")
MessageBox.Show("Selected balloon is not a parts list callout." & ControlChars.NewLine & _
"Please select a callout balloon", "Invalid selection", MessageBoxButtons.OK, MessageBoxIcon.Information)
Continue Do
End If

curComp = GetComponentOfBalloon(myIdSymbol)

If IsNothing(curComp) Then
lg.WriteLine(" component of balloon = Nothing")
lg.WriteLine(" skip processing, prompt user to select different balloon")
Continue Do
Else
lg.WriteLine(" component of balloon: " & curComp.DisplayName)
End If

lg.WriteLine(" prompting user to select new edge")
Dim newRef As DisplayableObject = select_component_edge("select new edge", newSelPoint, newSelViewTag)
If IsNothing(newRef) Then
lg.WriteLine(" select_component_edge() returned Nothing, skip to next balloon selection")
Continue Do
End If
lg.WriteLine(" selected object type: " & newRef.GetType.ToString)
Dim selView As NXOpen.View = Utilities.NXObjectManager.Get(newSelViewTag)
lg.WriteLine(" selected in view: " & selView.Name)
lg.WriteLine(" selected point: " & newSelPoint(0) & ", " & newSelPoint(1) & ", " & newSelPoint(2))

Dim symPt As Point3d
symPt.X = newSelPoint(0)
symPt.Y = newSelPoint(1)
symPt.Z = newSelPoint(2)

ReassociateIdSymbol(myIdSymbol, newRef, selView, symPt)

Loop

lw.Close()

End Sub

Function SelectIdSymbol(ByVal prompt As String, ByRef selObj As TaggedObject) As Selection.Response

lg.WriteLine(" Function SelectIdSymbol()")

Dim theUI As UI = UI.GetUI
Dim title As String = "Select an ID Symbol"
Dim includeFeatures As Boolean = False
Dim keepHighlighted As Boolean = False
Dim selAction As Selection.SelectionAction = Selection.SelectionAction.ClearAndEnableSpecific
Dim cursor As Point3d
Dim scope As Selection.SelectionScope = Selection.SelectionScope.WorkPart
Dim selectionMask_array(0) As Selection.MaskTriple

With selectionMask_array(0)
.Type = UFConstants.UF_drafting_entity_type
.Subtype = UFConstants.UF_draft_id_symbol_subtype
End With

Dim resp As Selection.Response = theUI.SelectionManager.SelectTaggedObject(prompt, _
title, scope, selAction, _
includeFeatures, keepHighlighted, selectionMask_array, _
selObj, cursor)
If resp = Selection.Response.ObjectSelected OrElse resp = Selection.Response.ObjectSelectedByName Then
lg.WriteLine(" object selected")
lg.WriteLine("")
Return Selection.Response.Ok
Else
lg.WriteLine(" selection canceled")
lg.WriteLine("")
Return Selection.Response.Cancel
End If

End Function

Function IsAutoBalloon(ByVal theIdSymbol As Annotations.IdSymbol) As Boolean

lg.WriteLine(" Function IsAutoBalloon()")

Dim theCallout As Tag = Nothing
theUfSession.Drf.AskCalloutOfAnnotation(theIdSymbol.Tag, theCallout)
If theCallout <> Tag.Null Then
lg.WriteLine(" ID symbol is autoballoon")
lg.WriteLine("")
Return True
Else
lg.WriteLine(" ID symbol is NOT autoballoon")
lg.WriteLine("")
Return False
End If

End Function

Function GetComponentOfBalloon(ByVal theIdSymbol As Annotations.IdSymbol) As Assemblies.Component

lg.WriteLine(" Function GetComponentOfBalloon()")

Dim myIdBuilder As Annotations.IdSymbolBuilder

myIdBuilder = workPart.Annotations.IdSymbols.CreateIdSymbolBuilder(theIdSymbol)

'ignore IDSymbols with no leaders
If myIdBuilder.Leader.Leaders.Length = 0 Then
'ID Symbol has no leaders
myIdBuilder.Destroy()
Return Nothing
End If

Dim tagObj1 As Annotations.LeaderData
tagObj1 = myIdBuilder.Leader.Leaders.FindItem(0)

Dim leaderData1 As Annotations.LeaderData = CType(tagObj1, Annotations.LeaderData)
Dim theObject As DisplayableObject
Dim theView As Drawings.DraftingView
Dim thePoint As Point3d
leaderData1.Leader.GetValue(theObject, theView, thePoint)
myIdBuilder.Destroy()

If theObject Is Nothing Then
Return Nothing
End If

If theView.IsOutOfDate Then
'lw.WriteLine("view is out of date, updating...")
theView.Update()
End If

'lw.WriteLine("view up to date")

If TypeOf (theView) Is Drawings.SectionView Then
BuildSectionDictionary(theView)
End If

If theObject.IsOccurrence Then
'object is owned by a component (edge, face, etc)

Return theObject.OwningComponent
Else
'object is owned by the drawing (silhouette edge curve, section edge curve, extracted edge curve, etc)

If TypeOf (theObject) Is NXOpen.Line Or TypeOf (theObject) Is NXOpen.Arc Or _
TypeOf (theObject) Is NXOpen.Spline Or TypeOf (theObject) Is NXOpen.Conic Then

Dim groupTag As Tag
Dim groupType As Integer
Dim groupSubType As Integer
'determine if this is a section or silhouette curve
theUfSession.Draw.AskGroupOfCurve(theObject.Tag, groupTag, groupType, groupSubType)

Select Case groupType
Case Is = UFConstants.UF_solid_silhouette_type
'silhouette type 201
Dim theFaceTag As Tag
theUfSession.Draw.AskFaceOfSil(theObject.Tag, theFaceTag)

Dim theFace As Face = Utilities.NXObjectManager.Get(theFaceTag)
Return theFace.OwningComponent

Case Is = UFConstants.UF_solid_section_type
'section curve type 198

Dim tempComp As Assemblies.Component
tempComp = ComponentOfSectionEdge(theObject.Tag)

Return tempComp

Case Else
Return Nothing

End Select

End If
'theObject is not a line, arc, conic, or spline
Return Nothing
End If

End Function

Sub ReassociateIdSymbol(ByVal theIdSymbol As Annotations.IdSymbol, ByVal newObj As DisplayableObject, _
ByVal newView As NXOpen.View, ByVal newPt As Point3d)

Dim myIdBuilder As Annotations.IdSymbolBuilder

myIdBuilder = workPart.Annotations.IdSymbols.CreateIdSymbolBuilder(theIdSymbol)

'ignore IDSymbols with no leaders
If myIdBuilder.Leader.Leaders.Length > 0 Then

Dim tagObj1 As Annotations.LeaderData
tagObj1 = myIdBuilder.Leader.Leaders.FindItem(0)

Dim leaderData1 As Annotations.LeaderData = CType(tagObj1, Annotations.LeaderData)

leaderData1.Leader.SetValue(newObj, newView, newPt)

End If

myIdBuilder.Commit()
myIdBuilder.Destroy()

End Sub

Public Function select_component_edge(ByVal prompt As String, ByRef theCursor() As Double, _
ByRef theView As Tag) As TaggedObject

Dim response As Integer = 0
Dim user_data As System.IntPtr
Dim selObj As Tag = Nothing

theUfSession.Ui.LockUgAccess(UFConstants.UF_UI_FROM_CUSTOM)
Dim curCursorView As Integer
theUfSession.Ui.AskCursorView(curCursorView)

Try
theUfSession.Ui.SetCursorView(0)
theUfSession.Ui.SelectWithSingleDialog("Select component: ", prompt, _
UFConstants.UF_UI_SEL_SCOPE_ANY_IN_ASSEMBLY, AddressOf init_proc_body, _
user_data, response, selObj, theCursor, theView)
Finally
theUfSession.Ui.SetCursorView(curCursorView)
theUfSession.Ui.UnlockUgAccess(UFConstants.UF_UI_FROM_CUSTOM)
End Try

If Not selObj.Equals(Tag.Null) Then
'the SelectWithSingleDialog method returns a tag,
'return the object from the given tag
Dim myEdgeCurve As TaggedObject
theUfSession.Disp.SetHighlight(selObj, 0)
myEdgeCurve = Utilities.NXObjectManager.Get(selObj)
Return myEdgeCurve
Else
Return Nothing
End If

End Function

Public Function init_proc_body(ByVal select_ As IntPtr, _
ByVal userdata As IntPtr) As Integer
'this function must have the same signature as UFUi.SelInitFnT Delegate

'setup mask to filter for edges or curves
Dim num_triples As Integer = 7
Dim mask_triples(num_triples - 1) As UFUi.Mask

mask_triples(0).object_type = UFConstants.UF_solid_type
mask_triples(0).object_subtype = UFConstants.UF_UI_SEL_FEATURE_ANY_EDGE
mask_triples(0).solid_type = UFConstants.UF_UI_SEL_FEATURE_ANY_EDGE

mask_triples(1).object_type = UFConstants.UF_line_type
mask_triples(1).object_subtype = UFConstants.UF_all_subtype

mask_triples(2).object_type = UFConstants.UF_circle_type
mask_triples(2).object_subtype = UFConstants.UF_all_subtype

mask_triples(3).object_type = UFConstants.UF_conic_type
mask_triples(3).object_subtype = UFConstants.UF_all_subtype

mask_triples(4).object_type = UFConstants.UF_spline_type
mask_triples(4).object_subtype = UFConstants.UF_all_subtype

mask_triples(5).object_type = UFConstants.UF_solid_silhouette_type
mask_triples(5).object_subtype = UFConstants.UF_all_subtype

mask_triples(6).object_type = UFConstants.UF_section_edge_type
mask_triples(6).object_subtype = UFConstants.UF_all_subtype

theUfSession.Ui.SetSelMask(select_, _
UFUi.SelMaskAction.SelMaskClearAndEnableSpecific, _
num_triples, mask_triples)

theUfSession.Ui.SetSelProcs(select_, AddressOf component_edge_filter, Nothing, userdata)

Return UFConstants.UF_UI_SEL_SUCCESS

End Function

Public Function component_edge_filter(ByVal _object As Tag, _
ByVal type As Integer(), _
ByVal user_data As IntPtr, _
ByVal select_ As IntPtr) As Integer
'type, user_data, and select_ are unused, but this function must have
'the same signature as UFUi.SelFilterFnT Delegate

Dim dwgObj As NXObject
dwgObj = Utilities.NXObjectManager.Get(_object)

If TypeOf (dwgObj) Is Edge Then
If ReferenceEquals(dwgObj.OwningComponent, curComp) Then
Return UFConstants.UF_UI_SEL_ACCEPT
Else
Return UFConstants.UF_UI_SEL_REJECT
End If
End If

Dim groupTag As Tag
Dim groupType As Integer
Dim groupSubType As Integer
'determine if this is a section or silhouette curve
theUfSession.Draw.AskGroupOfCurve(dwgObj.Tag, groupTag, groupType, groupSubType)

Select Case groupType
Case Is = UFConstants.UF_solid_silhouette_type
'silhouette type 201
Dim theFaceTag As Tag
theUfSession.Draw.AskFaceOfSil(dwgObj.Tag, theFaceTag)

Dim theFace As Face = Utilities.NXObjectManager.Get(theFaceTag)
If ReferenceEquals(theFace.OwningComponent, curComp) Then
Return UFConstants.UF_UI_SEL_ACCEPT
Else
Return UFConstants.UF_UI_SEL_REJECT
End If

Case Is = UFConstants.UF_solid_section_type
'section curve type 198

Dim tempComp As Assemblies.Component
tempComp = ComponentOfSectionEdge(dwgObj.Tag)

If ReferenceEquals(tempComp, curComp) Then
Return UFConstants.UF_UI_SEL_ACCEPT
Else
Return UFConstants.UF_UI_SEL_REJECT
End If

Case Else
Return UFConstants.UF_UI_SEL_REJECT

End Select

End Function

Sub BuildSectionDictionary(ByVal sectionView As Drawings.SectionView)

Dim sxSolidTags() As Tag
Dim numSxSolids As Integer
theUfSession.Draw.AskSxsolidsOfSxview(sectionView.Tag, Nothing, numSxSolids, sxSolidTags)

For Each sxSolidTag As Tag In sxSolidTags

Dim numSxEdges As Integer
Dim sxEdgeTags() As Tag
theUfSession.Draw.AskSxedgesOfSxsolid(sxSolidTag, numSxEdges, sxEdgeTags)

For Each sxEdgeTag As Tag In sxEdgeTags

dicSectionEdges.Add(sxEdgeTag, sxSolidTag)

Next

Next

End Sub

Function ComponentOfSectionEdge(ByVal sectionEdgeTag As Tag) As Assemblies.Component

Dim tempComp As Assemblies.Component

If dicSectionEdges.ContainsKey(sectionEdgeTag) Then
Dim sxSolidTag As Tag = dicSectionEdges.Item(sectionEdgeTag)

Dim solidTag As Tag
theUfSession.Draw.AskSolidOfSection(sxSolidTag, solidTag)

Dim tempSolid As Body = Utilities.NXObjectManager.Get(solidTag)
tempComp = tempSolid.OwningComponent

Return tempComp

Else
Return Nothing
End If

End Function

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