Expressions: query existing expressions

Expressions are used extensively in NX; they are the parameters of your parametric model. If you write journal code, chances are good that you will need to deal with expressions sooner or later. The code below illustrates how to access all of the expressions in the work part and it shows how to use several key properties and methods of expression objects. When run, the journal will write information about each expression in the current work part to the information window.

The Code

The code was written for and tested on NX 9. There may be differences in the available properties, methods, and return values if you are using a version other than NX 9.

Option Strict Off
Imports System
Imports NXOpen
 
Module NXJ_Exp_1
 
    Sub Main()
 
        Dim theSession As Session = Session.GetSession()
        If IsNothing(theSession.Parts.Work) 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 = "NXJ query expressions"
        Dim markId1 As Session.UndoMarkId
        markId1 = theSession.SetUndoMark(Session.MarkVisibility.Visible, undoMarkName)
 
        For Each temp As Expression In workPart.Expressions
            lw.WriteLine("name: " & temp.Name)
            lw.WriteLine("type: " & temp.Type)
            lw.WriteLine("description: " & temp.Description)
            lw.WriteLine("descriptor: " & temp.GetDescriptor)
            lw.WriteLine("journal identifier: " & temp.JournalIdentifier)
            lw.WriteLine("measurement expression? " & temp.IsMeasurementExpression.ToString)
            lw.WriteLine("geometric expression? " & temp.IsGeometricExpression.ToString)
            lw.WriteLine("edit locked? " & temp.IsNoEdit.ToString)
            lw.WriteLine("locked by user? " & temp.IsUserLocked.ToString)
            lw.WriteLine("right hand side: " & temp.RightHandSide)
            lw.WriteLine("equation: " & temp.Equation)
 
            If temp.RightHandSide.Contains("//") Then
                'expression contains comment
                Dim expComment As String = ""
                expComment = temp.RightHandSide.Substring(temp.RightHandSide.IndexOf("//") + 2)
                lw.WriteLine("comment: " & expComment)
            End If
 
            Select Case temp.Type
                Case Is = "Number"
                    lw.WriteLine("value (base part units): " & temp.Value.ToString)
                    Try
                        lw.WriteLine("expression units: " & temp.Units.Name & " (" & temp.Units.Abbreviation & ")")
                        lw.WriteLine("value in expression units: " & temp.GetValueUsingUnits(Expression.UnitsOption.Expression).ToString)
                    Catch ex As NullReferenceException
                        lw.WriteLine("expression is constant (unitless)")
                    Catch ex2 As Exception
                        lw.WriteLine("!! error: " & ex2.Message)
                    End Try
 
                Case Is = "String"
                    lw.WriteLine("string value: " & temp.StringValue)
 
                Case Is = "Integer"
                    lw.WriteLine("integer value: " & temp.IntegerValue.ToString)
 
                Case Is = "Boolean"
                    lw.WriteLine("boolean value: " & temp.BooleanValue.ToString)
 
                Case Is = "Vector"
                    lw.WriteLine("vector value: " & temp.VectorValue.ToString)
 
                Case Is = "Point"
                    lw.WriteLine("point value: " & temp.PointValue.ToString)
 
                Case Is = "List"
                    'returns a generic object, not especially useful
                    'lw.WriteLine("list value: " & temp.GetListValue.ToString)
 
                    'parse the right hand side to get the list items
                    Dim theList As String = temp.RightHandSide
                    theList = theList.Replace("{", "")
                    theList = theList.Replace("}", "")
                    Dim theItems() As String = theList.Split(",")
                    For Each item As String In theItems
                        lw.WriteLine(item.Trim)
                    Next
 
                Case Else
                    lw.WriteLine("Type: " & temp.Type & " is not handled by this journal")
 
            End Select
 
            Dim theOwningFeature As Features.Feature = temp.GetOwningFeature
            If Not IsNothing(theOwningFeature) Then
                lw.WriteLine("owning feature: " & theOwningFeature.GetFeatureName)
            End If
 
            Dim theOwningRpoFeature As Features.Feature = temp.GetOwningRpoFeature
            If Not IsNothing(theOwningRpoFeature) Then
                lw.WriteLine("owning rpo feature: " & theOwningRpoFeature.GetFeatureName)
            End If
 
            Dim referencingExps() As Expression = temp.GetReferencingExpressions
            If referencingExps.Length > 0 Then
                lw.WriteLine(" $$ used by expression(s):")
                For Each refTemp As Expression In referencingExps
                    lw.WriteLine("    " & refTemp.Name)
                Next
            End If
 
            Dim referencingFeats() As Features.Feature = temp.GetUsingFeatures
            If referencingFeats.Length > 0 Then
                lw.WriteLine(" %% used by feature(s):")
                For Each featTemp As Features.Feature In referencingFeats
                    lw.WriteLine("    " & featTemp.GetFeatureName)
                Next
            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

Every NX part contains a collection of expression objects. This journal uses a For Each loop to process every expression in the file. I recommend creating a small test file to use when running the journal. Try creating some expression types that you don't normally work with such as vector or list types. Also, create a few simple features such as blocks, cones, etc and an associative measurement or two.

Ok, let's take a look at the expression properties and methods.

  • .Name - This is the unique identifier given to the expression. The expressions created by NX are mostly in the form "p1", "p2", ... "pN". User created expressions usually have more descriptive names such as: "outside_diameter" or "length".
  • .Type - This property will return a string value indicating the type of expression (Number, String, Integer, Boolean, Vector, Point, or List).
  • .Description - This property will return the description given to the expression. As of NX 9, only expressions created internally by NX may have descriptions. Descriptions generally inform you what feature and parameter this particular expression controls. If you create a block feature then open the expression editor, you may see an expression such as "p1 (Block(1) Length(XC))"; the portion "(Block(1) Length(XC))" is what will be returned by the .Description property.
  • .GetDescriptor - This method is closely related to the .Description property; it will return the parameter portion of the description. Using the same block example as above, .GetDescriptor would return "Length(XC);". Yes, there is a semi-colon at the end of the descriptor text; no, it wasn't a typo on my part; and no, I don't know why the .GetDesciptor text ends with a semi-colon.
  • .JournalIdentifier - This is the ID of the object used by the journal recorder. If you have used the journal recorder, you have probably seen code such as:
    Dim face1 As Face = CType(block1.FindObject("FACE 1 {(6,3,0.25) BLOCK(1)}"), Face)

    The string: "FACE 1 {(6,3,0.25) BLOCK(1)}" is the journal identifier of the face on a block feature that I just made. Normally, the .JournalIdentifier is not very useful to us as programmers. A big part of modifying recorded journals is removing these specific references with something more general so the code can be used on other objects. However, if you look closely at the output, you will see that the .JournalIdentifier of the expression object is exactly the same as the .Name. This will make finding an expression with a given name very easy. More on that later...

  • .IsMeasurementExpression - This will return True if the expression is owned by an associative measurement feature, False otherwise.
  • .IsGeometricExpression - This will return True if the expression is a "geometric expression", False otherwise. It is my understanding that measurement features/expressions have superseded geometric expressions. I don't think you can create geometric expressions in new files, but you may run into them in files created in older versions of NX.
  • .IsNoEdit - This read/write boolean property can be used to query or change the lock status. Using this property is equivalent to right clicking on an expression in the "Expressions" dialog and choosing "Lock formula" or "Unlock formula". When the expression is selected in the dialog, the formula entry will be greyed out and the user will not be able to edit the formula until s/he explicitly unlocks the expression first.
  • .IsUserLocked - This read/write boolean property can be used to query or change the interpart override lock status. When set to True, the value of this expression cannot be overridden by an interpart expression. Using this property is equivalent to right clicking on an expression in the "Expressions" dialog and choosing "Toggle interpart override lock".
  • .RightHandSide - This property returns a string value representing the formula of the expression. If there is an expression named "width" and the formula is "2 * thickness", .RightHandSide would return "2 * thickness". This property is read/write, giving you the opportunity to change the formula of a given expression. If you do change the .RightHandSide, be sure to call the Update.DoUpdate method so the changes take effect.
  • .Equation - This is very similar to the .RightHandSide property; it returns a string value representing the full equation of the expression. Following our "width" expression example above, .Equation would return "width = 2 * thickness". The .Equation property is read-only.

If the expression includes a comment it will show up in the .RightHandSide or .Equation property. Two consecutive slash characters (//) denote the start of a comment; the two slash characters and anything following will be ignored by NX when the expression value is parsed. The journal code above looks for the two slashes in the .RightHandSide and reports the comment (sans slashes) if found. The NXOpen API gives no direct way to read the comment, but it does provide the .EditComment method for adding or changing a comment. The .EditComment method is not used in the example journal code as I did not want to modify the existing expressions in any way.

The .*Value properties (.Value, .StringValue, .IntegerValue, .BooleanValue, .VectorValue, and .PointValue) of the expression class can only be used on the corresponding type of expression. So you cannot use the .StringValue property on a Number type expression or vice-versa. The journal uses a Select Case block to test the expression type and display the corresponding value. The .Value property returns a Double value; .StringValue, .IntegerValue, and .BooleanValue return their corresponding data types. The .VectorValue property returns an NXOpen.Vector3d structure and the .PointValue returns an NXOpen.Point3d structure. The List expression type is the "odd man out" as there is no .ListValue property; rather there is a .GetListValue method which returns a generic object. I didn't find that to be very useful, so instead the code breaks the .RightHandSide value into a string array and reports the value of each item.

The .GetOwningFeature method will return the feature that created and uses the given expression. "Nothing" will be returned from this method if the expression is not owned by a feature (this will be true of all user-created expressions).

The .GetOwningRpoFeature method will return the feature that uses the expression for a positioning parameter. These positioning parameters help to locate the feature, but do not define the feature shape. If you think of sketching a rectangle, the width and height dimensions define the rectangular shape. To fully constrain the sketch you will need two more dimensions or constraints to locate the rectangle in space. These rpo parameters are equivalent to the locating dimensions in the sketch. Some older features in NX used this workflow before sketching was cool. In the NX help files look up the "pocket" or "groove" commands, if you would like to see some examples.

The .GetReferencingExpressions method will return an array of expressions that use the given expression (if any). So if expB = expA * 2, expA.GetReferencingExpressions will return expB along with any other expressions that use it.

The .GetUsingFeatures method will return an array of features that use the given expression. So if you have an expression "length = 5", then use this length expression as the end distance in an extrude feature, expLength.GetUsingFeatures will return the extrude feature that references your expression (along with any other features that use it).

Finding an expression with a given name

The .FindObject method is very good at finding an object with a given journal identifier. Normally, the journal identifier of an object is so obtuse as to be worthless to us mere mortals. However, as previously mentioned, the .JournalIdentifier of an expression is the same as the expression's .Name. This provides a straight forward way to access an expression with a given name. The alternative would be to iterate through the entire expression collection checking the .Name property against the desired name. The code snippet below shows how you can get directly to an expression object given the name.

        Dim expNameToFind As String = "length"
        Dim myExp As Expression
 
        Try
            myExp = workPart.Expressions.FindObject(expNameToFind)
            lw.WriteLine(myExp.Name)
            lw.WriteLine(myExp.RightHandSide)
 
        Catch ex As NXException
            If ex.ErrorCode = 3520016 Then
                'no object found with this name
                'code to handle this error
                lw.WriteLine("expression not found with name: " & expNameToFind)
            Else
                'code to handle other errors
                lw.WriteLine(ex.ErrorCode & ": " & ex.Message)
            End If
        Finally
            lw.WriteLine("done processing")
        End Try

Conclusion

We took a quick look at how to access the ExpressionCollection of a given part, examined several useful properties and methods of expressions, and discovered a quick way to access an expression given its name. In a future article (or articles) we'll look at changing existing expressions and creating new ones.

Get comfortable with the expression object, because expressions are fundamental to how NX works! Check out the API reference for details on the properties and methods shown here and also to learn more about the ones that we didn't cover here.

Comments

Error 1 'NXOpen.Expression' does not contain a definition for 'IsNoEdit' and no extension method 'IsNoEdit' accepting a first argument of type 'NXOpen.Expression' could be found (are you missing a using directive or an assembly reference?)

I am using NX 9.0.3.4 MP4. Where is that property?

"I wana b, wanna be, wannna bee" ...but do I have the intestinal fortitude? Time will tell

According to the .net API docs, the .IsNoEdit property was added in NX 8.5. The wording of your error leads me to believe that you are using some flavor of an IDE; if so, does your project reference the NX 9 dll files?

Also, do your environment variables (UGII_ROOT_DIR and UGII_BASE_DIR) point to the NX 9 directory?

Hello, Just comment the line of code below which occurs error.

lw.WriteLine("edit locked? " & temp.IsNoEdit.ToString)
<code>