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