Placing a dimension break

When manually placing a dimension gap symbol (a symbol that creates a small break in a dimension line), it can be difficult to get the proper placement because the gap symbol has no visible geometry and therefore gives no useful feedback during the placement process. However, if you move the symbol after placing it (as you will likely need to), a small, temporary rectangle is drawn (overlay graphics) to show the outline of where the gap will be placed. Why the rectangle isn't shown when initially placing the symbol seems to be a bit of an oversight - one that we can correct with a bit of code.

The Code

The journal below will prompt the user to select a dimension. The linear element nearest the selection point will be selected as the target of the gap symbol. A single selection determines both the dimension and the element within that dimension to modify. Make your selection pick with this in mind.

This journal makes use of the Overlay Graphics Primitives, which we have covered in a previous tutorial. The code has been tested on NX 8.5, and 9.


'NXJournaling.com
'September 24, 2014
'tested on NX 8.5 & 9
'
'based on code from https://bbs.industrysoftware.automation.siemens.com/vbulletin/showthread.php?t=52025
'posted by user "rossobryan" (Clayton)
'
'Add gap to dimension extension line (or leader line). This version only works with linear dimension elements
' (i.e. it won't add a break to an angular arc leader segment).
'Journal prompts user to select a dimension; the user is then prompted to select a location
'to add the break symbol. Visual feedback is given as to where the symbol will be placed
'(similar to what is seen when {Drafting} Edit -> component (move option) is used.
'If the break is no longer needed, the gap can be moved or deleted by using Edit -> component;
'select the dimension that the gap symbol is applied to, the cursor will then change to a
'"point selection" type cross, click on the location of the gap symbol and press Apply or OK.
'Alternately, the gap can be deleted by toggling the extension line off then back on. Right click the
'dimension and choose Style -> Dimensions, toggle the extension line off, press Apply, toggle the line
'on, and press Apply or OK.
'
'update
'September 25, 2014
'Minor code cleanup and additional comments.
'
'update
'October 7, 2014
'Removed reference to CreateRuleBaseCurveDumb(IBaseCurve()) so that code would be compatible with NX 8.
'
'update
'January 16, 2015
'In Sub AdjustLineLength: added the invariant culture specifier in the "adjustLength.ToString()" command.
'The .ToString command was using the local decimal separator (aka radix) which, if it happened to be a comma, caused an error.
'
'send feedback and bug reports to info@nxjournaling.com

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

Module PlaceBreakSymbol

Dim theSession As Session = Session.GetSession()
Dim theUfSession As UFSession = UFSession.GetUFSession
Dim ufs As UFSession = UFSession.GetUFSession
Dim workPart As Part = theSession.Parts.Work
Dim lw As ListingWindow = theSession.ListingWindow

'variable to hold a reference line (placed on the dimension's linear element)
' closest to the user's pick point
Dim closeLine As Line
'direction of the line above, used to help build the rectangular
' visual cue when placing the dim break symbol
Dim closeLineDir(2) As Double
'direction 90° to above, used for the short side of the rectangle
Dim shortLineDir(2) As Double

'change the break symbol size to your liking
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Const inchSymbolLength As Double = 0.25
Const mmSymbolLength As Double = 6.35
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Sub Main()

Dim dim1ExtensionLines As New List(Of Line)

Dim undoStack As New Stack(Of Session.UndoMarkId)

lw.Open()

Dim myDim1 As Annotations.Dimension = Nothing
Dim mySelPt As Point3d

Dim keepGoing As Boolean = True

Dim partSymbolLength As Double
If workPart.PartUnits = BasePart.Units.Inches Then
partSymbolLength = inchSymbolLength
Else
partSymbolLength = mmSymbolLength
End If

While keepGoing

Dim markId1 As Session.UndoMarkId
Dim markText As String = "Break Dimension Line"
markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, markText)

undoStack.Push(markId1)

'select the dimension
If SelectDimension("Select first dimension", myDim1, mySelPt) = Selection.Response.Cancel Then
keepGoing = False
Dim myUndo As Session.UndoMarkId = undoStack.Pop
theSession.UndoToMark(myUndo, markText)
theSession.DeleteUndoMark(myUndo, markText)
Return
End If

Try
GetDimLines(myDim1, dim1ExtensionLines)

Catch ex As Exception

Dim myUndo As Session.UndoMarkId = undoStack.Pop
theSession.UndoToMark(myUndo, markText)
theSession.DeleteUndoMark(myUndo, markText)

If ex.Message = "Dimension has no extension lines" Then
MessageBox.Show("The selected dimension has no extension lines, please choose new dimensions or press Cancel", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Continue While
Else
'lw.WriteLine("Error: " & ex.Message)
MessageBox.Show("An unexpected error has occurred, the journal will now exit", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If

End Try

'Find the dimension's linear element closest to the user's pick point.
'This will be the element that the dim break symbol is added to.
closeLine = ClosestDimLine(dim1ExtensionLines, mySelPt)
'Shorten the 'closeLine' by the length of the symbol, this will
'help limit where the symbol can be placed.
AdjustLineLength(closeLine, partSymbolLength)

CalculateCueVectors(partSymbolLength)

Dim myBreakPoint(2) As Double
myBreakPoint = SelectBreakPoint()
If myBreakPoint Is Nothing Then
DeleteTempLines(dim1ExtensionLines)
Dim myUndo As Session.UndoMarkId = undoStack.Pop
theSession.UndoToMark(myUndo, markText)
theSession.DeleteUndoMark(myUndo, markText)
Continue While
End If

Dim breakPoint As Point
breakPoint = workPart.Points.CreatePoint(New Point3d(myBreakPoint(0), myBreakPoint(1), myBreakPoint(2)))

'for debugging
'CreateTempOutline(myBreakPoint)
AdjustIntersectionPoint(breakPoint, closeLineDir, partSymbolLength)
BreakDim(myDim1, breakPoint, partSymbolLength)
DeleteTempLines(dim1ExtensionLines)

End While

End Sub

Function SelectDimension(ByVal prompt As String, ByRef selObj As TaggedObject, ByRef selPoint As Point3d) As Selection.Response

'allow user to interactively select a dimension

Dim theUI As UI = UI.GetUI
Dim title As String = "Select a dimension"
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_dimension_type
.Subtype = UFConstants.UF_all_subtype
End With

Dim resp As Selection.Response = theUI.SelectionManager.SelectTaggedObject(prompt, _
title, scope, selAction, _
includeFeatures, keepHighlighted, selectionMask_array, _
selObj, selPoint)
If resp = Selection.Response.ObjectSelected OrElse resp = Selection.Response.ObjectSelectedByName Then
Return Selection.Response.Ok
Else
Return Selection.Response.Cancel
End If

End Function

Sub GetDimLines(ByVal tempDim As Annotations.Dimension, ByRef extLines As List(Of Line))

'add the linear extension and leader elements of the given dimension to the given list of lines

Dim cd1 As Annotations.ComponentData = workPart.Annotations.CreateComponentData(tempDim)
Dim lc1() As Annotations.LineComponent = cd1.GetLineComponents()

For Each thisLineComponent As Annotations.LineComponent In lc1

If thisLineComponent.Type = Annotations.LineComponent.LineType.Extension _
Or thisLineComponent.Type = Annotations.LineComponent.LineType.Leader Then

Dim sp As Point3d = thisLineComponent.StartPoint
Dim ep As Point3d = thisLineComponent.EndPoint
Dim extLine As Line = workPart.Curves.CreateLine(sp, ep)
extLines.Add(extLine)
End If

Next

If extLines.Count = 0 Then
Throw New ApplicationException("Dimension has no extension lines")
End If

End Sub

Function ClosestDimLine(ByVal dimLines As List(Of Line), ByVal pickPt As Point3d) As Line

'return the line in the list that is closest to the given point

If dimLines.Count = 0 Then
Return Nothing
End If

Dim minDistance As Double
minDistance = MeasurePointToObject(pickPt, dimLines.Item(0))
Dim closestItemIndex As Integer = 0

Dim tempDistance As Double

For i As Integer = 1 To dimLines.Count - 1
tempDistance = MeasurePointToObject(pickPt, dimLines.Item(i))
If tempDistance < minDistance Then
minDistance = tempDistance
closestItemIndex = i
End If
Next

Return dimLines.Item(closestItemIndex)

End Function

Function MeasurePointToObject(ByVal thePoint As Point3d, ByVal theObject As NXObject) As Double

Dim nullNXObject As NXObject = Nothing

Dim measureDistanceBuilder1 As MeasureDistanceBuilder
measureDistanceBuilder1 = workPart.MeasureManager.CreateMeasureDistanceBuilder(nullNXObject)

measureDistanceBuilder1.Mtype = MeasureDistanceBuilder.MeasureType.Minimum

Dim unit1 As Unit = CType(workPart.UnitCollection.FindObject("Inch"), Unit)

Dim point1 As Point
point1 = workPart.Points.CreatePoint(thePoint)

measureDistanceBuilder1.Object1.Value = point1

measureDistanceBuilder1.Object2.Value = theObject

Dim measureDistance1 As MeasureDistance
measureDistance1 = workPart.MeasureManager.NewDistance(unit1, MeasureManager.MeasureType.Minimum, point1, theObject)

'lw.WriteLine("distance: " & measureDistance1.Value.ToString)
Return measureDistance1.Value
measureDistance1.Dispose()

measureDistanceBuilder1.Destroy()

End Function

Sub BreakDim(ByVal theDim As Annotations.Dimension, ByVal thePoint As Point, ByVal theSymbolLength As Double)

'This Sub does the work of creating the Gap symbol and adding it
'to the given dimension.

Dim myDimLinePrefs As Annotations.LineAndArrowPreferences
myDimLinePrefs = theDim.GetLineAndArrowPreferences

Dim breakLocation As Point3d = thePoint.Coordinates

' Create the symbol
ufs.Drf.SetUgdefaultSbfFile()

Dim symbol_data As UFDrf.SymbolCreateData
ufs.Drf.InitSymbolCreateData(symbol_data)

symbol_data.length = theSymbolLength
symbol_data.height = theSymbolLength

symbol_data.angle = 0

Dim anchor As Point = workPart.Points.CreatePoint(breakLocation)
symbol_data.anchor_tag = anchor.Tag
symbol_data.symbol_name = "GAP25"

Dim symbol_tag As Tag
ufs.Drf.PlaceSymbol(symbol_data, False, False, symbol_tag)

ufs.Drf.AddSymbolToObject(symbol_data, theDim.Tag)

End Sub

Function SelectBreakPoint() As Double()

'Allow user to interactively pick the point where the dimension break symbol
'will be placed.

'This Function needs Sub MotionCallback() to work properly.

Dim myScreenPos(2) As Double
Dim junk(2) As Double
Dim theViewTag As Tag = theSession.Parts.Display.Views.WorkView.Tag
Dim theResponse As Integer
Dim minDist As Double

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
'The AskMinimumDist function will return a point on the selected objects.
'Calculate the minimum distance from the cursor pick point to the dimension line,
'use the point on the line as the selected point rather than the cursor position,
'which was probably not on the line.
theUfSession.Modl.AskMinimumDist(Tag.Null, closeLine.Tag, 1, myScreenPos, 0, Nothing, minDist, junk, myScreenPos)

'for debugging
'theUfSession.Disp.DisplayTemporaryPoint(workPart.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, myScreenPos, Nothing, UFDisp.PolyMarker.Asterisk)
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.

Dim point1 As Point3d = New Point3d(pos(0), pos(1), pos(2))
Dim minDist As Double

Dim breakPt(2) As Double
breakPt(2) = 0

Dim topLeft(2) As Double
topLeft(2) = 0
Dim bottomLeft(2) As Double
bottomLeft(2) = 0
Dim topRight(2) As Double
topRight(2) = 0
Dim bottomRight(2) As Double
bottomRight(2) = 0

'The AskMinimumDist function will return a point on each of the objects measured.
'We'll use this returned point (the one on the closeLine) to ensure the visual cue
'rectangle is drawn on the dimension line.
theUfSession.Modl.AskMinimumDist(Tag.Null, closeLine.Tag, 1, pos, 0, Nothing, minDist, pos, breakPt)

'Calculate points of rectangular visual cue using the vectors calculated earlier.
'The rectangle will be drawn relative to the cursor position, but limited to lying on the dimension line.

'Subtract the short line vector from the currently chosen point on the dim line to
'calculate the top left corner of our rectangular visual cue.
topLeft(0) = breakPt(0) - shortLineDir(0)
topLeft(1) = breakPt(1) - shortLineDir(1)

bottomLeft(0) = topLeft(0) + shortLineDir(0) * 2
bottomLeft(1) = topLeft(1) + shortLineDir(1) * 2

'closeLineDir is a vector parallel to the dimension line with a length of the desired break symbol.
topRight(0) = topLeft(0) + closeLineDir(0)
topRight(1) = topLeft(1) + closeLineDir(1)

bottomRight(0) = bottomLeft(0) + closeLineDir(0)
bottomRight(1) = bottomLeft(1) + closeLineDir(1)

'draw temporary rectangle for visual cue
theUfSession.Disp.DisplayOgpLine(theSession.Parts.Display.Views.WorkView.Tag, topLeft, bottomLeft)
theUfSession.Disp.DisplayOgpLine(theSession.Parts.Display.Views.WorkView.Tag, topRight, bottomRight)
theUfSession.Disp.DisplayOgpLine(theSession.Parts.Display.Views.WorkView.Tag, topLeft, topRight)
theUfSession.Disp.DisplayOgpLine(theSession.Parts.Display.Views.WorkView.Tag, bottomLeft, bottomRight)

End Sub

Sub AdjustLineLength(ByVal theLine As Line, ByVal adjustLength As Double)

'Sub to change the length of a given line by the given amount.
'Length will be added or removed from the 'end' of the line (the line object end point will move).

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

Dim section1 As Section
section1 = workPart.Sections.CreateSection(0.00095, 0.001, 0.5)
section1.SetAllowedEntityTypes(Section.AllowTypes.OnlyCurves)
section1.AllowSelfIntersection(True)

Dim curves1(0) As Curve
curves1(0) = theLine

Dim curveDumbRule1 As CurveDumbRule
curveDumbRule1 = workPart.ScRuleFactory.CreateRuleCurveDumb(curves1)

Dim rules1(0) As SelectionIntentRule
rules1(0) = curveDumbRule1
Dim nullNXObject As NXObject = Nothing

Dim helpPoint1 As Point3d = theLine.StartPoint
section1.AddToSection(rules1, theLine, nullNXObject, nullNXObject, helpPoint1, Section.Mode.Create, False)

Dim nullFeatures_Feature As Features.Feature = Nothing

Dim curveLengthBuilder1 As Features.CurveLengthBuilder
curveLengthBuilder1 = workPart.Features.CreateCurvelengthBuilder(nullFeatures_Feature)

With curveLengthBuilder1
.Section = section1
.DistanceTolerance = 0.001
.CurveOptions.Associative = False
.CurveOptions.InputCurveOption = GeometricUtilities.CurveOptions.InputCurve.Replace
.CurvelengthData.ExtensionMethod = GeometricUtilities.ExtensionMethod.Incremental
.CurvelengthData.ExtensionSide = GeometricUtilities.ExtensionSide.StartEnd
.CurvelengthData.ExtensionDirection = GeometricUtilities.ExtensionDirection.Linear
.CurvelengthData.SetStartDistance("0")
.CurvelengthData.SetEndDistance("-" & adjustLength.ToString("F12", Globalization.CultureInfo.InvariantCulture))

End With

Dim nXObject1 As NXObject
nXObject1 = curveLengthBuilder1.Commit()

curveLengthBuilder1.Destroy()

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

End Sub

Sub DeleteTempLines(ByVal extLines As List(Of Line))

'delete the temp lines created on the dimension linear objects (extension and leader lines)
Try
Dim markId2 As Session.UndoMarkId
markId2 = theSession.SetUndoMark(Session.MarkVisibility.Invisible, "Delete")

Dim nErrs1 As Integer
nErrs1 = theSession.UpdateManager.AddToDeleteList(extLines.ToArray)

Dim notifyOnDelete2 As Boolean
notifyOnDelete2 = theSession.Preferences.Modeling.NotifyOnDelete

Dim nErrs2 As Integer
nErrs2 = theSession.UpdateManager.DoUpdate(markId2)

'remove the references from the list
extLines.Clear()

Catch ex As NXException

End Try

End Sub

Sub CreateTempOutline(ByVal thePoint() As Double)

'Draw temporary lines around the rectangular visual cue.
'This sub was used for debugging to check cue size vs. that used in the Edit -> component (move) option.

Dim topLeft(2) As Double
topLeft(2) = 0
Dim bottomLeft(2) As Double
bottomLeft(2) = 0
Dim topRight(2) As Double
topRight(2) = 0
Dim bottomRight(2) As Double
bottomRight(2) = 0

'calculate points of rectangular visual cue
topLeft(0) = thePoint(0) - shortLineDir(0)
topLeft(1) = thePoint(1) - shortLineDir(1)

bottomLeft(0) = topLeft(0) + shortLineDir(0) * 2
bottomLeft(1) = topLeft(1) + shortLineDir(1) * 2

topRight(0) = topLeft(0) + closeLineDir(0)
topRight(1) = topLeft(1) + closeLineDir(1)

bottomRight(0) = bottomLeft(0) + closeLineDir(0)
bottomRight(1) = bottomLeft(1) + closeLineDir(1)

theUfSession.Disp.DisplayTemporaryLine(theSession.Parts.Display.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, topLeft, bottomLeft, Nothing)
theUfSession.Disp.DisplayTemporaryLine(theSession.Parts.Display.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, topRight, bottomRight, Nothing)
theUfSession.Disp.DisplayTemporaryLine(theSession.Parts.Display.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, topLeft, topRight, Nothing)
theUfSession.Disp.DisplayTemporaryLine(theSession.Parts.Display.Views.WorkView.Tag, UFDisp.ViewType.UseWorkView, bottomLeft, bottomRight, Nothing)

End Sub

Sub AdjustIntersectionPoint(ByRef thePoint As Point, ByVal theVector() As Double, ByVal theSymbolLength As Double)

Dim offsetPt As Point
Dim angle As Double
Dim offsetDirection As Integer

Dim distScalar As Scalar
distScalar = workPart.Scalars.CreateScalar(theSymbolLength, Scalar.DimensionalityType.Length, SmartObject.UpdateOption.WithinModeling)

angle = Math.Atan2(theVector(1), theVector(0))
angle = (angle * 180) / Math.PI

'normalize the angle
If angle < 0 Then
angle += 360
End If

'gap location changes slightly based on the angle of the extension line
'lines in quadrant 1 & 4 need to be shifted in (toward start point of extension line)
'lines in quadrant 2 & 3 need to be shifted out (toward end point of extension line)
If angle > 270 Or angle <= 90 Then
'line is somewhere in quadrant 1 or 4, shift break point toward start point of line
'offsetDirection = Sense.Reverse
Else
'line is somewhere in quadrant 2 or 3, shift break point toward end point of line
offsetDirection = Sense.Forward
'offset breakpoint by the gap width
offsetPt = thePoint
thePoint = workPart.Points.CreatePoint(closeLine, offsetPt, distScalar, PointCollection.AlongCurveOption.Distance, offsetDirection, SmartObject.UpdateOption.WithinModeling)

End If

End Sub

Sub CalculateCueVectors(ByVal theSymbolLength As Double)

'When placing the break symbol, a rectangular visual cue will be drawn on screen.
'This sub will calculate the vector of the chosen dim line and use that to calculate
'the vectors to use when drawing the rectangular visual cue.

'Calculate the direction of the closeLine.
theUfSession.Vec3.Sub(New Double() {closeLine.EndPoint.X, closeLine.EndPoint.Y, closeLine.EndPoint.Z}, New Double() {closeLine.StartPoint.X, closeLine.StartPoint.Y, closeLine.StartPoint.Z}, closeLineDir)
Const vecTol As Double = 0.0001
Dim closeLineDirMag As Double
'unit vector in the direction of the dimension line
theUfSession.Vec3.Unitize(closeLineDir, vecTol, closeLineDirMag, closeLineDir)
'scale up the vector to be the same length as the desired dim break symbol
theUfSession.Vec3.Scale(theSymbolLength, closeLineDir, closeLineDir)

'Get vector perpendicular to the dim line to be used when drawing the short side
'of the visual cue rectangle.
theUfSession.Vec3.AskPerpendicular(closeLineDir, shortLineDir)
Dim shortLineDirMag As Double
theUfSession.Vec3.Unitize(shortLineDir, vecTol, shortLineDirMag, shortLineDir)
'Scale the vector to be 1/4 the symbol length.
'Consider changing this to a constant value so that the visual cue rectangle
'isn't dependent on the chosen symbol size?
theUfSession.Vec3.Scale(theSymbolLength / 4, shortLineDir, shortLineDir)

End Sub

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


Next Steps

In its current form, the code above only works on linear elements of a given dimension. With some work, it can be modified to work with circular elements, such as the arc leader lines in an angular dimension.

Hopefully, this example of overlay graphics will give you some ideas of how they can be used in your own journals.

Good luck and keep coding!

Comments

While using this code, I am getting compile error as:

Line160: 'SelectTaggedObject' is not a member of 'NXOpen.Selection'

Can some one short this out. I am using version 7.5 on temcenter

The journal is intended for NX 8.5 or above. To get the code to run on NX 7.5, one would need to rework the "SelectDimension" function to use the .SelectObject method rather than the .SelectTaggedObject method. This is the cause of the error message that you are seeing. There may be other changes required, but this would certainly need to change.