Do you need to allow the user to select a screen position? Would you like to provide some feedback as they move the cursor around the screen?
If so, the SpecifyScreenPosition function and the overlay graphics primitives are just what you need. The SpecifyScreenPosition function allows you to define a "motion callback function" that will be called each time a mouse movement is detected. If you have created any Windows forms applications, this would be very similar to the MouseMove event. The motion callback works with Overlay Graphics Primitives (OGP) to allow for some cool graphical effects that are very useful for user feedback. Think about using a selection rectangle or lasso in NX; there is visual feedback regarding the size and position of the selection method, but they are just temporary objects - you don't end up with four new lines in your part file each time you use a selection rectangle.
So how does it work?
The SpecifyScreenPosition function allows you to supply a function of your design that it will call each time the mouse is moved. Within this motion callback, you can call special functions that draw lines, arcs, circles, and polylines using what are known as Overlay Graphics Primitives. The OGP's are special temporary objects in NX that know how to clean themselves up. Each time the motion callback function executes, the OGP's are removed from the screen before new ones are created. This can create a relatively smooth animation effect while the user is deciding on a position to use. Usually, when doing animation such as this, the programmer has to clean up any temporary objects before creating the new ones; but here, the NXOpen API takes care of the hard work for us.
Let's imagine that you want to create a circle of a given size at a position of the user's choosing. The journal below will show a circle of the desired size that moves around with the cursor as the user decides where to place it. When the user clicks to confirm their choice, an actual arc object will be created at the specified position.
'NXJournaling.com 'October 7, 2014 ' 'Overlay graphics demo with SpecifyScreenPosition function: 'Give the user visual feedback when picking a point on the screen. ' Option Strict Off Imports System Imports NXOpen Imports NXOpen.UF Module overlay_graphics Dim theSession As Session = Session.GetSession() Dim theUI As UI = UI.GetUI() Dim theUfSession As UFSession = UFSession.GetUFSession() Dim lw As ListingWindow = theSession.ListingWindow Dim workPart As Part = theSession.Parts.Work Const inchCircleDia As Double = 2 Const mmCircleDia As Double = 50 Dim circleDia As Double Dim circleOrientation(8) As Double Sub Main() If IsNothing(theSession.Parts.Work) Then 'active part required Return End If lw.Open() If workPart.PartUnits = BasePart.Units.Inches Then circleDia = inchCircleDia Else circleDia = mmCircleDia End If Const undoMarkName As String = "NXJ overlay graphics demo" Dim markId1 As Session.UndoMarkId markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName) 'move the WCS to absolute (to keep our example simple) Dim absXform As Xform = workPart.Xforms.CreateXform(SmartObject.UpdateOption.WithinModeling, 1) Dim absCsys As CartesianCoordinateSystem = workPart.CoordinateSystems.CreateCoordinateSystem(absXform, SmartObject.UpdateOption.WithinModeling) Dim csys3 As CartesianCoordinateSystem = workPart.WCS.SetCoordinateSystemCartesianAtCsys(absCsys) 'define the orientation of our circle object to lie on the absolute XY plane circleOrientation(0) = 1 circleOrientation(1) = 0 circleOrientation(2) = 0 circleOrientation(3) = 0 circleOrientation(4) = 1 circleOrientation(5) = 0 circleOrientation(6) = 0 circleOrientation(7) = 0 circleOrientation(8) = 1 Dim selectedPoint(2) As Double selectedPoint = SelectScreenPoint() If IsNothing(selectedPoint) Then Return End If Dim theCenPt As New Point3d(selectedPoint(0), selectedPoint(1), selectedPoint(2)) DrawCircle(theCenPt, circleDia / 2) lw.Close() End Sub Function SelectScreenPoint() As Double() 'Allow user to interactively pick the point where the circle 'will be placed. 'This Function needs Sub MotionCallback() to work properly. Dim myScreenPos(2) As Double Dim theViewTag As Tag = theSession.Parts.Display.Views.WorkView.Tag Dim theResponse As Integer theUfSession.Ui.LockUgAccess(UFConstants.UF_UI_FROM_CUSTOM) theUfSession.Ui.SpecifyScreenPosition("pick a point", AddressOf MotionCallback, Nothing, myScreenPos, theViewTag, theResponse) theUfSession.Ui.UnlockUgAccess(UFConstants.UF_UI_FROM_CUSTOM) If theResponse = UFConstants.UF_UI_PICK_RESPONSE Then Return myScreenPos Else Return Nothing End If End Function Sub MotionCallback(ByVal pos() As Double, _ ByRef motion_cb_data As UFUi.MotionCbData, _ ByVal client_data As System.IntPtr) 'This sub will be called every time a cursor move is detected during the 'SpecifyScreenPosition function. 'The parameters motion_cb_data and client_data are not used in this implementation, 'but the Sub must have the same signature as UFUI.MotionFnT to work properly. 'draw temporary circle for visual cue theUfSession.Disp.DisplayOgpCircle(theSession.Parts.Display.Views.WorkView.Tag, circleOrientation, pos, circleDia / 2) 'also see: ' DisplayOgpArc ' DisplayOgpLine ' DisplayOgpPolyline End Sub Sub DrawCircle(ByVal centerPoint As Point3d, ByVal radius As Double) Dim origin1 As Point = workPart.Points.CreatePoint(centerPoint) Dim nullFeatures_AssociativeArc As Features.AssociativeArc = Nothing Dim associativeArcBuilder1 As Features.AssociativeArcBuilder associativeArcBuilder1 = workPart.BaseFeatures.CreateAssociativeArcBuilder(nullFeatures_AssociativeArc) With associativeArcBuilder1 .Type = Features.AssociativeArcBuilder.Types.ArcFromCenter .Limits.FullCircle = True .CenterPoint.Value = origin1 .EndPointOptions = Features.AssociativeArcBuilder.EndOption.Radius .Radius.RightHandSide = radius.ToString .Associative = False End With Dim nXObject1 As NXObject nXObject1 = associativeArcBuilder1.Commit() Dim objects1() As NXObject objects1 = associativeArcBuilder1.GetCommittedObjects() associativeArcBuilder1.Destroy() End Sub End Module
Under the Hood
Shown below is the definition of the SpecifyScreenPosition function taken from the API help file:
Public Sub SpecifyScreenPosition ( _ message As String, _ motion_cb As UFUi.MotionFnT, _ motion_cb_data As IntPtr, _ <OutAttribute> screen_pos As Double(), _ <OutAttribute> ByRef view_tag As Tag, _ <OutAttribute> ByRef response As Integer _ )
Let's take a look at each of the parameters.
- message: text that will be shown in the NX prompt area (input)
- motion_cb: the motion callback function that we want to call during the selection process (input)
- motion_cb_data: any information that you need to pass into the motion callback function (input)
- screen_pos: an array of doubles representing the point picked by the user (output)
- view_tag: the tag of the view in which you want to perform the selection (output)
- response: an integer value representing the action taken by the user (did the user pick a point, or press cancel?) (output)
If you'd like to use the SpecifyScreenPosition function but do not need to use the callback functionality, pass null values (the Nothing keyword in VB.net) for the motion_cb and motion_cb_data. But since you are the more adventurous sort, let's take a closer look at using the callback function. For the motion_cb, it appears we need to pass in an object of type UFUi.MotionFnT; so what is that exactly? The motion callback function must have a very specific list of parameters; the SpecifyScreenPositon function is programmed to work with particular input/output. The UFUi.MotionFnT defines what the signature of our motion callback function must be. Below is the definition of the UFUi.MotionFnT:
Public Delegate Function MotionFnT ( _ screen_pos As Double(), _ ByRef motion_cb_data As UFUi.MotionCbData, _ data As IntPtr _ ) As Void
All of the arguments are inputs to the function, these inputs will be provided by the SpecifyScreenPosition function. Our callback function must accept the exact same arguments or errors will occur. The function is defined as Void, which simply means it will not return a value; we'll use a subroutine in our VB implementation rather than a function.
- screen_pos: an array of double values representing the current location of the cursor
- motion_cb_data: a structure containing information on which view the cursor is in and the start position of the cursor
- data: this is a reference to the motion_cb_data that we passed to the SpecifyScreenPosition function
So each time the SpecifyScreenPositon function detects a movement of the mouse, it will call our callback function and pass in the new position of the cursor, information about the view, and any custom data that we originally gave to it. Of these values, screen_pos is the most important; it is the only one required to make use of the overlay graphics and in the simple example above it is the only value that is used.
Ignoring the comments in the MotionCallback subroutine, we see that it boils down to a single line of code: the one that draws the overlay graphics primitive.
Sub MotionCallback(ByVal pos() As Double, _ ByRef motion_cb_data As UFUi.MotionCbData, _ ByVal client_data As System.IntPtr) theUfSession.Disp.DisplayOgpCircle(theSession.Parts.Display.Views.WorkView.Tag, circleOrientation, pos, circleDia / 2) End Sub
Most of the inputs to the DisplayOgpCircle function are module level variables defined elsewhere in the journal. Using module level variables is a quick way to access needed information in the MotionCallback function; the alternative is to use the client_data which involves marshaling memory locations which is beyond the scope of this article. The pos variable is an array of values representing the current cursor location, this point is used as the center point of the OGP circle. The example only makes one call to an OGP function, but you can use several calls to create more complex shapes such as polygons, stars, smiley faces, or whatever else you can come up with. However, bear in mind that the MotionCallback function may be called many times during the journal execution and must run quickly to keep a responsive feel. You will want to minimize the amount of processing done in the MotionCallback subroutine to maximize execution speed.
Providing some visual feedback to the user during a selection process can greatly improve the usability of your journal. Consider using overlay graphics primitives along with the SpecifyScreenPosition function when you need to place objects or symbols.