NXOpen_Getting_Started
NXOpen_Getting_Started
■ What Is NX Open
NX Open is an Application Programming Interface (API) that lets you write programs to customize or extend NX.
The benefit is that applications created this way can often speed up repetitive tasks, and capture important design
process knowledge.
There is a broad range of NX Open functions, which provide capabilities like
▪ Creating part geometry, assemblies, drawings, and CAE and CAM objects
▪ Cycling through the objects in a part file, reading information or performing various operations on them
▪ Creating custom user interfaces that allow users to select objects and enter data
Some typical applications of these functions are:
▪ Creating part geometry or drawings according to your local standards
▪ Importing data from other sources, outside of NX
▪ Reading data from objects in a part file, and writing it out in some form of report
▪ Building custom applications to make processes faster or easier to understand
Of course, these are just a few examples of what is possible. You can probably think of many little repetitive
processes that you would like to automate to speed up your work or standardize your output.
If you’d like a little more background information, please continue reading here. If you can’t wait, and you just want
to start writing code immediately, please skip to chapter 2, where we show you how to proceed.
■ Other Documentation
The definitive source of information about the capabilities of NX Open is the NX Open Reference Guide, which you
can find in the NX documentation set in the location shown below:
The document is fully indexed and searchable, so we hope you’ll be able to find the information you need. It
describes all NX Open functions in detail.
If you get tired of clicking through all the security warnings that appear when you access the NX documentation,
you can fix this. In Internet Explorer, choose Tools → Internet Options → Advanced. Scroll down to the Security set
of options near the bottom of the list, and check “Allow active content to run in files on My Computer”.
In Visual Studio, another option is to use the Object Browser, which you can access from the View menu:
■ Example Code
Once you understand the basic ideas of NX Open, you may find that code examples are the best source of help. You
can find example programs in several places:
▪ In this guide. There are about a dozen example programs in chapters 2 and 3, along with quite detailed
descriptions. Also, the later chapters contain many “snippets” of code illustrating various programming
techniques.
▪ There are some examples in […NX]\UGOPEN\NXOpenExamples. There are two folders: the one called “Getting
Started Examples” contains the examples from this guide, and the “Example Parts” which contain part files you
will utilize throughout this guide. Here, and in the remainder of this document, the symbol […NX] denotes the
folder where the latest release of NX is installed, which is typically C:\Program Files\Siemens\NX <NX Version>,
or something similar.
▪ We will refer to user create files for Visual Studio, specifically Project and Templates. The path to this directory
is Visual Studio specific. For example, [My Documents]\<Visual Studio Version>
\Templates\ProjectTemplates\Visual C# holds the C# templates, and [My Documents]\ <Visual Studio
Version>\Projects holds the user created projects. Going forward, we will refer to this directory as
[My Documents]\[Visual Studio directory]. So to refer to the directory where the user create project files exist
will be referenced as [My Documents]\[Visual Studio directory].
▪ The GTAC web page has a large collection of example programs that you can search through to find useful code.
Log in with your webkey username and password. From the main menu choose “Symptom/Solution Information
Query”, and then “Search Solution Center”. Enter a search string that includes a phrase like “sample program”,
and click on the “Search” button. A list of results will appear, which you can filter by document type, software
product, and publish date. Set the document type filter to “nx_api” to find sample programs, and filter further by
programming language if you want to.
If you’ve read everything, and you’re still stuck, you can contact Siemens GTAC support, or you can ask questions in
the NX Customization and Programming Forum at the Siemens PLM Community site.
Finally, you can often get help at NXJournaling.com and in the NX forum at eng-tips.com.
NOTE: The sample example programs accessing NX Open UI APIs will only work on Windows platform. The
support for UI on Linux platform is stopped from NX 1847 release.
In this chapter, we will discuss creation of simple programs using the NX Journal Editor. This is not a very
supportive environment in which to write code, but it’s OK for very simple programs, and it requires no setup. In
the next chapter, we will discuss the use of Microsoft Visual Studio, instead. This requires a small preparation
effort, but provides a much nicer development environment.
If you don’t have the required version of .Net or later, please download and install it from this Microsoft site. You
will want to pick the .Net framework (not the .Net Core).
■ Typographic Conventions
In any document about programming, it’s important to distinguish between text that you’re supposed to read and
code that you’re supposed to type (which the compiler will read). In this guide, program text is either enclosed in
yellowish boxes, as you see on the next page, or it’s shown in this blue font. References to filenames, pathnames,
functions, classes, namespaces, and other computerish things will sometimes be written in this green color, if this
helps clarify an explanation.
■ Licensing
You don’t require any special license to record .NET journals and play them in the NX Journal Editor, as described in
this chapter. Alternatively, you can compile your journal code to produce an “executable” (an EXE or DLL file), as
described in the next chapter.
Working in Journal Editor imposes some restrictions: all of your code must be in one file, and you can only call
functions that reside in a small set of special libraries (the NX Open DLLs, the SNAP DLL, and a few basic Windows
DLLs). If the restrictions cause trouble for you, then you can purchase an “author” license (dotnet_author) that
makes it more convenient to work with compiled code. Specifically, the author license allows you to:
▪ Conveniently write large compiled programs whose code is distributed across several files, and which can call
any function in any .NET DLL.
▪ “Sign” the compiled programs you write (so that other people can run them more easily)
▪ Run compiled programs that call NXOpen functions, even if they have not been signed
When an NX Open program is running, it consumes licenses in the same way as an interactive NX session. So, if
your NX Open program calls some drafting function, for example, then it will consume a drafting license.
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 4
■ The Guide Functions
There are many places where we use certain “helper” functions to make the example code in this document shorter
and easier to understand. Since their only purpose is to improve the readability of this guide, we call these
functions Guide functions. For instance, we will often need to write out text to the NX Info window. Rather than
repeating the three of four lines of code required to do this, we have captured that code in the simple
Guide.InfoWriteLine function. This function is used in the first example below, and in many other places.
The Guide functions are described in detail in an Appendix, and in the NX Open Reference Guide. They are very
simple and limited, because our primary goal was to make them easy to call. Though you may find uses for them in
the code you write, their intended purpose is purely educational.
The Developer Tab contains several groups related to NX programming, including the Journal Group. The Journal
Group contains commands to record, play, and edit journals, as well as some commands to add comments or code
to a journal as it is recorded.
Choose Developer tab → Journal group → Edit.
In the Journal Editor dialog, Click Open in the Journal Editor toolbar and open the file NXOpenSample.cs, which you
can find in […NX]\UGOPEN\NXOpenExamples\CS\Templates . Remember that […NX] is just shorthand for the
location where NX is installed, which is typically somewhere like C:\Program Files\Siemens\NX <NX Version>. You
should see some text like this:
This journal just gets the NX session. Any text in a C# .NET file to the right of two slashes is treated as comment text
by the compiler.
Now we will add some code to print “Hello World” in the NX Information Window. In your journal, replace the line
of text that says //Your code goes here with the following line:
Guide.InfoWriteLine("Hello, World!");
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 5
In the Journal Editor, click Play, (the red triangular arrow icon) to play the journal. You should see the Information
Window appear containing the text “Hello, World!”.
If you receive some sort of error, rather than the output shown above, here are some possible causes:
▪ Maybe you typed something incorrectly, in which case the compiler will probably complain that it can’t
understand what you wrote. An error message will tell you in which line of code the problem occurred. The
description of the error might not be very helpful, but the line number should be.
▪ Maybe you don’t have an up-to-date version of the .NET framework installed, as mentioned above. This may
cause a mysterious error that reports an “Invalid attempt to load library”.
▪ Maybe you neglected to delete the quotation mark at the beginning of the line “Your code goes here”, in which
case your code will run without any errors, but the NX Information window will not appear
There is a troubleshooting guide in chapter 17 that will help you figure out what went wrong, and get it fixed.
Fortunately, you will only have to go through the troubleshooting exercise once. If you can get this simple “hello
world” program to work, then all the later examples should work smoothly, too.
■ Example 2: Collections
NX can create parts and assemblies with complex geometry and product structure. Sometimes you will need to
perform operations on a collection of objects in your parts or assemblies. Using a journal to cycle through a
collection will often make these tasks easier. We will start by creating some simple journals to understand how to
cycle though object collections.
An NX part has several collections, each holding objects of a certain type. For example, each part has a
CurveCollection that holds all the curve objects in that part. The property workPart.Curves accesses the
CurveCollection of the work part. You can use a CurveCollection to cycle over all types of curves in an NX part.
Choose File tab → Open to open the part file curves.prt, which you can find in
[…NX]\UGOPEN\NXOpenExamples\ExampleParts. This part file contains several types of curves (lines, arcs,
general conics, and splines) that we can cycle through to understand how collections work in NX Open.
Open the file NXOpenSample.cs in the Journal Editor, just like the steps in example 1.
using System;
using NXOpen;
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 6
{
public static void Main()
{
Session theSession = Session.GetSession();
}
}
Replace the comment text //Your code goes here with the following lines:
int numCurves = 0;
double curveLength = 0;
foreach (Curve cur in workPart.Curves)
{
numCurves = numCurves + 1;
curveLength = cur.GetLength();
Guide.InfoWriteLine("Curve " + numCurves + " has length " + curveLength);
}
You can print information about each of the curves as you cycle through the curve collection. The Curve class has a
method GetLength that returns the length of the curve. This code is cycling through the curves in the part and
printing the length of each curve to the information window. Once we finish cycling through the curves, we also
print out the number of curves to the information window.
The meanings of the more interesting lines of code are as follows:
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 7
You should see the following output in your listing window if you used the curves.prt part file:
This journal creates a line from (50, -100, 0) to (50, 100, 0) with a length of 200.
The meanings of the more interesting lines of code are as follows:
Point3d p1 = new Point3d(50, -100, 0); Declares a variable "p1" as an object of type Point3d. A
Point3d is a structure that contains three double
values named "X", "Y", and "Z" representing the x, y, z
coordinates of the point. The coordinates are
initialized to the values (50, -100, 0)
var line1 = workPart.Curves.CreateLine(p1, p2) Create a line between "p1" and "p2" using the
CreateLine method of the CurveCollection. The
CurveCollection of a part is represented by the
property Curves.
You can use journals to create curves programmatically in a pattern that would be difficult to do interactively in NX.
For example, the following journal creates a diagram of a parabolic mirror. It shows how rays of light are reflected
off the mirror towards a focus point.
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 8
Vector3d axisX = new Vector3d(1,0,0);
Vector3d axisY = new Vector3d(0,1,0);
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 9
}
}
Guide.InfoWriteLine("");
}
}
This journal cycles through the bodies in the work part and prints all the attributes on each body to the
Information window. Attributes can be defined to be certain types, such as Integer, Number, Time, and String, but
you will always be able to get a string representation of the attribute through the StringValue property.
The meanings of the more interesting lines of code are as follows:
Body[] bodies = Gets the BodyCollection of the work part and returns it
theSession.Parts.Work.Bodies.ToArray(); as an array
NXObject.AttributeInformation[] attributes = Get all the attributes defined on the body. The attributes
aBody.GetUserAttributes(); are returned in an array of AttributeInformation
structures.
Guide.InfoWriteLine(attribute.Title + " = " + Prints out the attribute title and string value of the
attribute.StringValue); attribute to the information window.
Running this code on Bracket.prt should produce the following Information window output:
This part only has one body, so the listed attributes are from that single body. Other NX objects may have attributes
attached to them. Open the journal file UserAttributesOnGeometry.cs to list any user attributes attached to bodies,
faces, and edges in the work part. The journal looks like this:
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 10
foreach (Face f in faces)
{
PrintAttributes(f);
}
}
Guide.InfoWriteLine("");
}
static void PrintAttributes(NXObject obj)
{
var attributes = obj.GetUserAttributes();
foreach (NXObject.AttributeInformation attribute in attributes)
{
Guide.InfoWriteLine(attribute.Title + " = " + attribute.StringValue);
}
}
}
The code that prints out the attributes of a given body might be re-usable elsewhere. To make the re-use easier, we
have placed this code in a new “subroutine”. We call this subroutine in our main subroutine when we want to print
out the attributes for any NXObject; in our case bodies, faces, or edges. Running this code on Bracket.prt should
produce the following listing window output:
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 11
When you run this application, you should see a WinForm appear, like this:
The WinForm is pretty boring, but it does have all the standard Windows functionality — you can move it around,
resize it, minimize it, and so on, in the usual way. The code calls some methods in a special FormUtilities class in the
NXOpenUI namespace to make our WinForm a bit more NX-specific. The method SetApplicationIcon creates the
form with an NX icon in its top left corner, which will help the user understand that it’s associated with NX. Also,
the method ReparentForm sets the main NX Window as the “parent” of our new form, which means that our form
will be minimized and restored along with the NX window, and will never get hidden underneath it. Actually, in the
current scenario, our form is “modal”, which means that you have to close it before you do anything with the NX
window, so the parenting arrangement doesn’t have much value. We got this modal behavior because we called
myForm.ShowDialog to display our form. There is also myForm.Show, which creates a non-modal form, but this
doesn’t work in the Journal Editor.
The next few lines of code adjust various properties of the form — we give it a red color, make it 50% transparent,
and put the words “Hi there” in its title bar. There are dozens of properties that influence the appearance and
behavior of a WinForm, but it’s best to wait until the next chapter to explore these, because it’s very easy using
Visual Studio.
To stop your code running and get back to the Journal Editor, you need to close the WinForm. You do this in the
usual way — click on the “X” in the top right corner.
Next, let’s add a button to our WinForm. Modify the code in NXOpenSample.cs as follows:
using NXOpen;
using NXOpenUI;
using System;
using System.Drawing;
using System.Windows.Forms;
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 12
public static void Main()
{
theSession = Session.GetSession(); // Get the NX Session
rand = new Random(); // Create a random number generator
Form myForm = new Form(); // Create a Windows form
myForm.Text = "Create Random Spheres";
FormUtilities.SetApplicationIcon(myForm); // Use an NX icon for the application icon
FormUtilities.ReparentForm(myForm); // Set NX as the parent of our form
myButton = new Button(); // Create a button
myButton.BackColor = Color.Yellow; // Color it yellow
myButton.Text = "Click me"; // Put some text on it
myForm.Controls.Add(myButton); // Add it to our form
myForm.ShowDialog(); // Display our form
}
}
First, note that we have added another line of “using” statements at the top of the file. These allow us to abbreviate
the names in our code. So, for example, we can refer to Color.Yellow instead of the full name
System.Drawing.Color.Yellow, and we can refer to Random instead of System.Random.
As you can see, we used the “new” keyword when creating the random number generator, the form, and the button.
We didn’t use the “new” when we created NX line objects in earlier examples, and you may be wondering why these
two types of objects get treated differently. The answer is given in chapter 5, in the section entitled “Factory
Objects”. Don’t worry about it for now — just accept that the “new” keyword isn’t needed when you’re creating NX
objects. Or, if the curiosity is overwhelming, you can read about this topic in chapter 5.
Try running this code. You will see that the form is displayed, but nothing happens if you click on the yellow button.
To change this, place the following code down near the bottom, just before the closing bracket of the NXJournal
class and place “myButton.Click += Handler;” just before “myForm.Controls.Add(myButton);”.
This is a new “subroutine”. So, as in the previous example, we now have two subroutines — one called “Main” and
one called “Handler”. This is a fairly typical situation — as your code gets longer, it’s easier to understand if you
break it up into several subroutines.
The “Handler” function is an event handler for the “click” event of the yellow button. In other words, this code gets
executed whenever you click on the yellow button in the form. As you can see, every time you click the button, the
code will create a randomly-located sphere. The completed sample is in the file CreateRandomSpheres.cs.
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 13
Designing buttons and writing event handlers is much easier in Visual Studio, as we will see in the next chapter.
■ What Next?
The examples in this chapter have given you a brief glimpse at some of the things you can do with NX Open. Using
the Journal Editor, we were able to start programming immediately, and we saw that NX Open allows us to build
simple user interfaces, do calculations, and create NX geometry. If you liked what you saw in this chapter, you’ll
probably like the next one, too. It shows you some further examples of NX Open capabilities, and also some much
easier and more pleasant ways to write code.
Getting Started with NX Open Chapter 2: Using the NX Journal Editor Page 14
Chapter 3: Using Visual Studio Community
In the previous chapter, we developed code using the NX Journal Editor. This is a convenient starting point, since it
requires no setup, but it is really a fairly primitive environment. Except for very short programs, it is far better to
use a more powerful “integrated development environment” (IDE). Microsoft Visual Studio Community for
Windows Desktop is a free, fully-featured IDE for students, open-source and individual developers. You can use this
“Community” version of Visual Studio with the Visual Basic, C#, and C++ programming languages. In this chapter,
we will be focusing on using C#.
After you’re done, you should see [Visual Studio Community] on your Programs menu, and you should see a folder
called [Visual Studio Community] in your My Documents folder. For example [My Documents]\[Visual Studio]
could be [My Documents]\Visual Studio 2017. If you run into trouble, it might help to watch this video.
Older versions of Visual Studio will not work because they don’t allow you to use version the version of .NET
Framework required by NX.
Unfortunately, the Visual Studio Community download is much larger than it was when it was first released — it
has grown from around 80 MB to over 1.1 GB. If you don’t have the patience or disk space to handle a package this
large, you can try the SharpDevelop IDE, instead. It’s only around 15MB, and provides everything you need. The
instructions you read in this document won’t match SharpDevelop exactly, but it should be fairly easy to adapt.
In the examples in this chapter, we’ll provide step-by-step instructions for writing the code, just as we did in
chapter 2, so it should be easy to follow. But if you’d like to get some additional information about the C# language
or Visual Studio, then one good place to start is this series of videos. There is a huge amount of other tutorial
material available on the internet, and you might find other sources preferable, especially if your native language is
not English.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 15
■ Example 1: Hello World Again
Our first exercise is to create a “Hello World” application again. Sorry, we know it’s boring, but it’s a tradition. After
you get Visual Studio Community installed and running, choose New Project from the File menu. A “project” is the
name Visual Studio uses for a collection of related files. You will see a list of available project templates
Choose the “NXOpenCSApplication” template. This is a special custom template designed to serve as a convenient
starting point for certain kinds of NXOpenCSApplications. Also, give your project a suitable name — something like
“HelloApp” would be good.
The NXOpenCSApplication template gives you a framework for a simple NXOpenCSApplication, as shown here:
In the left-hand pane, you can see some familiar C# code, which the template has placed in a file called Program.cs
for you. We need to make a couple of changes to this code: add a few lines that outputs some message to the
Information window, as shown here:
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 16
You should type the new code, rather than just copying and pasting it, because some interesting things happen as
you type (as you saw in the tutorial videos, if you watched them). In fact, it’s interesting to type the entire 11 lines
of code. You will find that you actually only have to type 1 line — Visual Studio will type the other ten for you.
Generally, Visual Studio helps you by suggesting alternatives, completing words, correcting mistakes, showing you
documentation, and so on. To accept the highlighted alternative, you can either press Tab, or type another
character, like a period or a parenthesis. All of this is called “Intellisense” by Microsoft’s marketeers. Despite its
dubious name, you’ll find it very helpful as your programming activities progress. Also, notice that Visual Studio
automatically makes comments green, literal text red, and language keywords blue, to help you distinguish them.
Next, you are ready to compile (or “build”) your code into an executable application. To do this, go to the Build
menu and choose Build HelloApp, or press Ctrl+Shift+B, which will send your code to the C# compiler. The
compiler will translate your code into an executable form that your computer can run, and will store this in a file
called HelloApp.dll. The extension “dll” stands for “Dynamic Link Library”, which is a type of file that holds
executable code. You should get the good news about the build succeeding down at the bottom left:
On the other hand, if you’re unlucky, you might get some error messages like these:
It’s not very likely that this problem will occur, so we don’t want to interrupt the flow by discussing all the details
here. The possible causes and corrective actions are described in chapter 17.
At some point, you should save your project by choosing Save All from the File menu. Visual Studio will offer to save
in your Projects folder, whose path is typically something like [My Documents]\[Visual Studio]\Projects.
Now, we are ready to run our new application. From within NX, choose File → Execute → NX Open (or press Ctrl+U).
Your version of the NX user interface might not have the Execute option installed in the File menu, but the Ctrl+U
shortcut will work anyway.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 17
A dialog will appear that allows you to find your executable. As mentioned earlier, it will be called HelloApp.dll, and
it will be located in [My Documents]\[Visual Studio]\Projects\HelloApp\HelloApp\bin\Debug along with two
other files that you don’t need to worry about.
To see HelloApp.dll, make sure you set the “Files of type” filter in the NX dialog to “Dynamic Loadable
Libraries (*.dll)”. Double-click on HelloApp.dll, and a friendly greeting should appear in your NX Listing window. If
you can’t find your application, try looking in the bin\Release folder, rather than the bin\Debug folder. If you still
can’t find it, it’s probably because you forgot to save it, or you didn’t set the file type filter correctly.
There’s a useful trick that allows you to locate your executable quickly. When you build the application, some text
like this will appear in the “Output” pane at the bottom of your Visual Studio window:
Copy
If the output pane is not visible, press Ctrl+Alt+O to display it (that’s the letter O, not the number zero). You can
then just copy the pathname of the newly-created application (highlighted in yellow above) and paste it into the
“Execute” dialog within NX. This technique is highly recommended — it avoids all the hunting around folders that
we described above, and it ensures that you are running the code that you just built. You only have to do this once
per NX session, because NX will remember the location for you.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 18
var selUI = NXOpen.UI.GetUISelectionManager;
var sel = selUI.SelectionManager;
View myView = null;
Point3d p1, p2, p3;
sel.SelectScreenPosition("Specify first point", out myView, out p1); // Get first point from user
sel.SelectScreenPosition("Specify second point", out myView, out p2); // Get second point
sel.SelectScreenPosition("Specify third point", out myView, out p3); // Get third point
Vector3d u = new Vector3d(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z); // Vector3d from p1 to p2
Vector3d v = new Vector3d(p3.X - p1.X, p3.Y - p1.Y, p3.Z - p1.Z); // Vector3d from p1 to p3
var uu = u.X * u.X + u.Y * u.Y + u.Z * u.Z; // Dot product of vectors
var uv = u.X * v.X + u.Y * v.Y + u.Z * v.Z;
var vv = v.X * v.X + v.Y * v.Y + v.Z * v.Z;
var det = uu * vv - uv * uv; // Determinant for solving linear equations
var alpha = (uu * vv - uv * vv) / (2 * det); // Bad code !! Should check that det is not zero
var beta = (uu * vv - uu * uv) / (2 * det);
var rx = alpha * u.X + beta * v.X; // Radius vector components
var ry = alpha * u.Y + beta * v.Y;
var rz = alpha * u.Z + beta * v.Z;
var radius = Math.Sqrt(rx*rx + ry*ry + rz*rz); // Radius is length (norm) of this vector
Again, you can gain some experience with Intellisense if you type this code, rather than copying and pasting it. The
only thing that’s new here is the function SelectScreenPosition, which allows you to get a screen point location from
the user.
As before, you can save this project, build it, and run it from within NX using File → Execute → NX Open (or Ctrl+U).
Now let’s see what happens if you make a typing error. Change the line that calculates “det” to read
det = uu * vv - uv * u;
In other words, change the last term from “uv” to “u”. Then build the project and try running it again. It will still
build successfully, but when you run it from within NX, you’ll get an error message like this:
If you choose Help → Log File from within NX, and hunt around the NX System Log, you will find some more error
messages about 50 lines from the bottom, most notably these ones
Caught exception while running: Main
System.InvalidCastException: Operator '*' is not defined for type 'Double' and type 'Vector3d'.
at Microsoft.VisualBasic.CompilerServices.Operators.InvokeObjectUserDefinedOperator … blah blah blah
at Microsoft.VisualBasic.CompilerServices.Operators.MultiplyObject(Object Left, Object Right)
at ThreePointRadius.ThreePointRadius.Main() in C:\Users\ … \ThreePointRadius.cs:line 21
Obviously it would be much better to discover errors like this earlier, as you’re writing the code, rather than when
you run the application. And, in fact, you can, if you change the way you write the code, and give the compiler a
little more information. The key is a process called “declaring” variables, which lets us tell the compiler about their
types.
To see how this works, change your code to read:
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 19
double vv = v.X * v.X + v.Y * v.Y + v.Z * v.Z;
The phrase “Vector3d u = new Vector3d ” tells the compiler that the variable u is supposed to hold a Vector3d
object, and so on. So, the compiler now knows that u and v are vectors, and uu, uv, and vv are numbers (doubles). So
the expression uv*u is trying to multiply a vector by a number, which is not a legal operation in this context. So we
get a “squiggly underline” error indicator, and we know immediately that we have made a mistake. And, if you
hover your mouse over the mistake, a message will appear telling you what you did wrong:
Up until now, our applications have been very simple, so there was not much justification for the extra effort of
declaring variables. But, as you start to write more complex applications, you will definitely want the compiler to
help you find your mistakes. And it can do this very effectively if you declare your variables. Actually, many
programming languages require you to declare all variables. But declaring variables is a good thing, so we’re going
to do it from now on. For further discussion of declaring variables (and avoiding or shortening declarations), please
see chapter 4.
You may need to double-click on Form1.cs to see the new Windows form in the left-hand pane. In the lower right-
hand pane, all the “properties” of the new WinForm are listed, along with their values. As you can see, the form has
a property called “Text”, and this property currently has the value “Form1”. This property actually represents the
text in the title bar of the dialog. Edit this text to read “Create Sphere”. When you do this, you will see that the dialog
title bar changes, too.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 20
Next, as before, we’re going to add a button to our form. On the left-hand side of the Visual Studio window, you
should see a Toolbox containing various types of user interface objects. If you don’t see the Toolbox, choose it from
the View menu, or press Ctrl+Alt+X.
Click on the “Button” object. The cursor will change to a small “+” sign, and you can then use it to graphically draw a
button on the form. Initially, the button will be labeled with the text “Button1”, but you can change this to “Click me”
or whatever you want by editing the text property of the button, just as we edited the text property of the form.
You can edit other properties of the button, too, like the font used and the background color. Your result might be
something like this:
Also, you can adjust the sizes of the button and the form by dragging on their handles:
Next, let’s make the button do something useful. Double-click the button, and a code window will appear, like this:
using System;
using NXOpen;
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 21
}
}
The function you see is an event handler for the button’s “click” event. Currently, it doesn’t do anything, but you can
edit it as shown below to make the click event create a sphere, or whatever else you want it to do.
When we created this dialog manually, in the previous chapter, you may recall that we wrote code like this:
This same sort of code exists in our current project, too, but it was written for us by Visual Studio, and it’s
somewhat hidden, because you’re not supposed to edit it. To see this code, click on the Show All Files button at the
top of the Solution Explorer window, and then double-click on the file named Form1.Designer.cs.
To display our dialog, we can add a couple of lines of code in Main in the file Program.cs:
As before, we’re using form.ShowDialog to display the dialog, so it will be “modal”, which means that we can’t do
anything else until we close the form. There is also myForm.Show, which creates a non-modal form, but to use this,
you have to change the GetUnloadOption function in the file Unload.cs. Specifically, you have to modify this function
to return NXOpen.UnloadOption.AtTermination instead of NXOpen.UnloadOption.Immediately. If you fail to do this,
your dialog will disappear a second or two after it’s displayed, so you’ll probably never see it.
Build the project, and run it from within NX, as usual. When your dialog appears, you can click on your button to
create spheres. When you get bored with this, click the “X” to close your dialog.
If you want to learn more about creation of WinForm-based user interfaces, there are many books and on-line
tutorials available on the subject, including this tutorial.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 22
■ Example 4: Hello World Yet Again (the Hard Way)
Sorry, but we’re going to create a “Hello World” application yet again. This time, we’re going to do it without getting
any assistance from the NX Open template we used last time. This will help you understand what is happening
“behind the scenes” so that you will know what to do if you run into problems later. If you’re not interested in this,
you can skip to the next example.
Run Visual Studio Community, and choose New Project from the File menu. You will see the available set of project
templates. But, this time, instead of choosing the NX Open Application template, choose the Class Library one:
You might be thinking that you could use the “Console Application” template, instead. Unfortunately, there are some
technical reasons why this will not work — on some systems, it will lead to a mysterious “failed to load image”
error when you try to run your application from within NX. Please see chapter 17 for more details.
This Class Library template gives you a framework for a C# class definition. You will see a file called Class1.cs that
contains a couple of lines of code. Delete this code and paste (or type) the contents of NXOpenSample.cs in its place.
Also, delete the line that says Your code goes here, and replace it by Guide.InfoWriteLine("Hello world!"), as we have
done several times before. You should end up with something that looks like this:
As you’re typing, you might notice that the usual “Intellisense” doesn’t work. This is the first indication that
something is wrong. Also, you will see several squiggly underlines, and some error and warning messages in the list
at the bottom of the window:
Most of the problems arise because our code is using the NXOpen libraries, and these are not connected in any way
to our current project. So the compiler doesn’t know anything about NX Open, or the NXOpen.Guide.InfoWriteLine
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 23
function. To fix this, we need to add a “reference” to the NXOpen libraries. From the Project menu, choose Add
Reference. In the dialog that appears, click on the Browse tab, and navigate to the folder […NX]\NXBIN\managed:
You will see a number of DLLs. We only need the NX Open DLLs in this example. Select five DLLs, as shown above,
and click OK. Your project now has references to the NX Open libraries, and this should remove the complaints
about them “containing no public members”. Now you can build and run the application, as usual.
The NX Open Application template that we used previously already includes the references to the NX Open
libraries, so you didn’t have to add them manually. But, it’s useful to know how to do this when you need to. For
example, if you want to use some .NET Framework functions in any journals you write, you may have to add
references to the assemblies where they reside. If you forget to do this, you will get “type not defined” errors, like
the ones we saw above. Please see chapter 17 for more information about problems with references.
A project based on the Class Library template has another deficiency — it doesn’t include a GetUnloadOption
function. This means that NX won’t know how to “unload” your code after it has finished executing — in some
sense, NX “holds onto” your code, and won’t let it go. So, if you try to change your code and rebuild the project,
you’ll get an error message telling you that you “can’t access the file because it is being used by another process”.
The other process is NX, and you’ll have to terminate NX to get it to release its hold on your DLL so that you can
rebuild it. The NX Open Application Template provides a GetUnloadOption function for you, so you won’t have
these sorts of problems. Writing your own GetUnloadOption function is fairly simple. The code is as follows:
It’s convenient to place this code in the same class or module as your “Main” function — in our case, this means
inside the NXOpenSample class that we created. So, you just need to paste this code immediately before the line that
says “End Class”. Please look up GetUnloadOption in the NX Open Programmer’s Guide for more information about
unloading code.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 24
can use in ChangeLayerOfBody.cs where we recorded a journal that changes the layer of a selected body to layer 45.
This journal is listed below:
// NX 12.0.0.8
using System;
using NXOpen;
After the lines creating an undo mark for the operation is a line that specifies the body that you want to move to
layer 45. This line:
NXOpen.Body body1 = (NXOpen.Body)(workPart.Bodies.FindObject(“CYLINDER(2)”));
looks for the body named “CYLINDER(2)”. We will replace this line by a selection. In the listing below we have
edited our journal to ask the user to select a body to edit:
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 25
// NX 12.0.0.8
using System;
using NXOpen;
/* ----------------------------------------------
Menu: Format->Move to Layer...
----------------------------------------------*/
NXOpen.Session.UndoMarkId markId1;
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Move Layer");
objectArray1[0] = body1;
objectArray1[0] = (DisplayableObject)selObj;
workPart.Layers.MoveDisplayableObjects(45, objectArray1);
displayModification1.Dispose();
/* ----------------------------------------------
Menu: Tools->Journal->Stop Recording
----------------------------------------------*/
}
}
This modified journal uses a selection dialog to ask the user to pick a body to move to layer 45. We can make this a
little neater by moving the selection code into a separate function and calling it from our main program. The
following listing shows a journal with the selection code placed in a Function called SelectBody. This function will
return the selected body if the user selects a body or null if the user presses Back or Cancel.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 26
// NX 12.0.0.8
using System;
using NXOpen;
/* ----------------------------------------------
Menu: Format->Move to Layer...
----------------------------------------------*/
NXOpen.Session.UndoMarkId markId1;
markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Move Layer");
The SelectBody function checks if the selected object is a Body. If it is not, then the function returns null for the
selected body. Other selection functions allow you to filter the selected objects to specific types. If you are
interested in filtering, you can read ahead in chapter 15, which covers the NX Open Selection API in more detail.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 27
■ Debugging in Visual Studio
The full version of Visual Studio (but not the Community edition) provides an excellent debugger that lets
you step through your code one line at a time, watching what’s happening as it executes. In particular, you
can set “breakpoints” that pause the execution of your code, allowing you to examine variable values. This is a very
good way to find problems, obviously. The techniques used with SNAP and NX Open programs are a little unusual
because you are debugging code called by a “Main” function that you don’t have access to (because it’s inside NX).
This means that using the normal “Start Debugging” command within Visual Studio is not appropriate. There are
two alternative approaches, as outlined below, but neither of these is available in Visual Studio Community
editions.
Using Debugger.Launch
First, you write System.Diagnostics.Debugger.Launch somewhere near the beginning of your code, and then you run
your application in the normal way using File → Execute → NX Open. When execution reaches the Debugger.Launch
call, the Just-In-Time Debugger dialog will appear, asking you which debugger you want to use:
Double-click on the debugger for your current project, as shown in the picture above, and you will be taken back to
Visual Studio with your code “paused” at the Debugger.Launch line, ready to begin stepping through it.
Execution
paused at first
breakpoint
Regardless of which of the two approaches you used, you are now ready to step through your code. The available
options are shown in the Debug menu or on the Debug Toolbar within Visual Studio. For information on how to use
the debugger facilities, please consult one of the many tutorials available on the internet.
Getting Started with NX Open Chapter 3: Using Visual Studio Community Page 28
Chapter 4: The C# Language
One of the strengths of NX Open is that it is based on standard mainstream programming languages. This means
there are many excellent tools you can use (like Visual Studio), and there’s lots of tutorial and help material
available. This chapter provides an introduction to the C# language (which we have been using for all of our
examples). There are many places where you can learn more about C# (like this series of videos, for example), so
our description here will be very brief.
When looking for books and on-line tutorials, you should be aware that the C# language has evolved significantly
over the years. So, when you start reading, make sure you are using fairly modern materials. If you really want the
complete story, you can read the Microsoft documentation on this web page.
If you prefer to use the Visual Basic language, instead of C#, then these videos should be helpful.
run
source object
code code
compiler
The process is quite simple, but unfortunately it typically involves quite a lot of programmer jargon. The C#
statements you write are known as “source code”. This code is typically contained in one or more text files with the
extension “.cs”. Your source code is then sent to a compiler, which converts it into “object code” that your computer
can actually understand and run. The object code is sometimes referred to as an “executable” or a “library”, or an
“assembly”, and is held in a file with the extension “.EXE” or “.DLL”.
■ Structure of a C# Program
A C# program has standard building blocks, typically present in the following sequence:
▪ Using statements
▪ The Main procedure
▪ Class and Module elements
Using Statements
Placing a using statement at the beginning of a source file allows you to use abbreviated names within that file
(rather than longer “fully qualified” ones), which reduces your typing effort. For example, suppose you will
frequently be using the System.Console.WriteLine function to output text. If you write using System at the beginning
of your source file, then you can refer to this function as simply Console.WriteLine whenever you need it.
In C#, the thing that appears in a using statement can be either a class or a namespace. Classes are explained later
in this chapter. Namespaces help you to organize large quantities of code into related subgroups, and to distinguish
different uses of the same name. Suppose you had a large application that performed operations on both fish and
musical instruments. This probably isn’t very likely, but it provides a convenient illustration. You might invent two
namespaces called Instruments and Fish to hold your code. You could use the name Bass within both of these
namespaces, because Instruments.Bass and Fish.Bass would be two different names. If you wrote using Instruments at
the top of a code file, you could use the name Bass instead of Instruments.Bass. If you wrote both using Instruments
and using Fish, then you would create a problem, of course, because then the name Bass would be ambiguous.
■ An Example Program
The listing below shows a simple program containing most of the elements mentioned above.
using System;
using NXOpen;
The program starts with a using statement. Then there is a single class called “MyProgram” that holds all the
executable code. Inside this module there is a “Main” procedure, as always, and then another function called
CircleArea.
The following table gives more details:
using NXOpen; Allows you to refer to functions in the NXOpen namespace using
short names
double radius = 3.75; Declares a variable of type double, gives it the name radius, and
stores the value 3.75 in it.
double area; Declares another variable of type double, and names it area
area = CircleArea(radius); Calls a function named CircleArea, which is defined below. The
variable radius is used as the input to this function, and the
output returned from the function is written into the variable
named area.
// Function to calculate circle area This is a “comment”. Comments are descriptive text to help you
and other readers understand the code. They are ignored by the
compiler.
double CircleArea(double r) This is the heading for the definition of a function named
CircleArea. The text in parentheses says that, when this function
is called, it should receive as input a variable of type Double,
which will be referred to as “r”. As output, the function will
return an item of type Double.
double pi = System.Math.PI; Defines a variable called pi and gives it the value π (accurate to
around 15 decimal places). The full name of the item on the
right is System.Math.PI. But we have uImportsing System at the
top of our file, so we can use the shortened name Math.PI.
double area = pi * r * r; Calculates the area, and stores it in a newly declared variable
called area. We do not need to write “dAs Double” because the
compiler can infer this.
return area; Returns the value area as the output of the function
■ Lines of Code
Generally, you place one statement on each line of your source file. But you can put several statements on a single
line if you separate them by the semicolon (;) character. So, for example, you might write
x1 = 3 ; y1 = 5 ; z1 = 7;
x2 = 1 ; y2 = 2 ; z2 = 9;
A statement usually fits on one line, but when it is too long, you can continue it onto the next line by placing a space
and hitting enter at the end of the first line. For example:
Note that “white space” (space and tab characters) don’t make any difference, except in readability. The following
three lines of code do exactly the same thing, but the first is much easier to read, in my opinion:
y = 3.5 * ( x + b*(z - 1) );
y=3.5*(x+b*(z-1));
y =3.5 * ( x+b * (z - 1) );
double Floating-point number 1.5, –3.27, 3.56E+2 4.9 × 10–324 to 1.8 × 10308, positive or negative
Note that variables of type double can use scientific notation: the “E” refers to a power of 10, so 3.56E+2 means
3.56 × 102, which is 356, and 3.56E-2 means 0.0356. There are many other built-in data types, including byte,
decimal, date, and so on, but the ones shown above are the most useful for our purposes.
int n = -45;
int triple = 3*n;
int biggestNumberExpected = 999;
double diameter = 3.875;
string companyName = "Acme Incorporated";
For more complex data types, you use the “new” keyword and call a “constructor” to declare and initialize a new
variable, like this:
<data type> <variable name> = new <data type>(constructor inputs);
A variable name may contain only letters, numbers, and underscores, and it must begin with either a letter or an
underscore (not a number). Variable names are case sensitive, so companyName and CompanyName are NOT the
same thing. Also, variable names must not be the same as C# keywords (like class or int).
There are some ways to omit or shorten variable declarations, as explained in the next section.
In the examples later in this document, we will sometimes use var to make the code shorter and easier to read.
You have to be a little careful, sometimes, because the guessing isn’t foolproof. Consider the following code:
The compiler will infer that x is an integer. So, in the second line of code, we’re trying to assign a double value to an
integer variable, and we’ll either get an error message, or the value of x will be rounded to 3 (instead of 3.14159...)
when it’s stored in the variable x. To avoid this sort of problem, you can write var x = 5.0 in the first line, which will
tell the compiler that x is supposed to be a double.
Explicit conversions, on the other hand, require so-called “cast” operators, as in the following examples.
You can perform casts with the general System.Convert function for integers and strings. The result is exactly the
same — the weight value is rounded and we get roughWeight = 501.
Even though m and n are both integers, performing a division produces a double (0.75) as its result. But then when
you assign this value to the integer variable p4, it gets rounded to 1. With either integer or double data types,
dividing by zero will cause trouble, of course.
The System.Math namespace contains all the usual mathematical functions, so you can write things like:
Note that the trigonometric functions expect angles to be measured in radians, not in degrees.
Other useful tools include hyperbolic functions (Sinh, Cosh, Tanh), logarithms (Log and Log10), and absolute value
(Abs). Visual Studio Intellisense will show you a complete list as you type.
In floating point arithmetic (with double variables), small errors often occur because of round-off. For example,
calculating 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 (10 times) won’t give you 1.0, you’ll get
0.99999999999999989, instead. Tiny errors like this usually don’t matter in engineering applications. But, in cases
where they do, you can use the float data type, instead of double. Arithmetic is much slower with float variables, but
more precise.
int four = 4;
int five = 5;
int six = 6;
int m, n;
bool b1, b2, b3, b4, b5, b6;
Note that the style of array declaration shown here is perfectly legal, but it is not the usual one. Most C#
programmers would write int people[], but I think the style shown above makes more sense — it says that people is
an int[] (i.e. it is an integer array). If you want to declare and initialize the array separately, then you write
something like:
In this case, you need to place an integer between the parentheses in the declaration. Note that the number you use
is one more than the upper bound of the array (the highest index), and is equal to the number of elements in the
array. So, in the example above, the “new int[4]” gave us an array of four integers with indices 0, 1, 2, 3.
You can also create two-dimensional (and higher dimension) arrays using declarations like
The .NET framework provides many useful functions for working with arrays. For example:
▪ The Length property returns the total number of elements in the array
▪ The GetUpperBound method returns the highest index value for the specified dimension
▪ The Sort method sorts the elements of a one-dimensional array
▪ The Find and FindIndex methods allow you to search for specific items
There is also a general collection called an ArrayList, which can hold elements of different types. So, you can write:
■ Strings
A string is essentially an array of characters. You can declare and initialize a string with one statement like:
You can extract characters from a string just as if it were an array of characters:
You can “concatenate” two strings (join them together into one) using the “+” or operator. Also, there are many
useful functions available for working with strings; some of them are: Trim, ToUpper, ToLower, Substring, StartsWith,
CompareTo, CopyTo, Split, Remove and Length. For example:
Strings are immutable, which means that once you assign a value to one, it cannot be changed. Whenever you
assign another value to a string, or edit it in some way, you are actually creating a new copy of the string variable
and deleting the old one. If you are doing a lot of modifications to a string variable, use the StringBuilder type,
instead, because it avoids this deletion/recreation and gives much better performance.
Any .NET object can be converted to string form using the ToString() method. So, for example, this code
double pi = System.Math.PI;
string piString = pi.ToString();
■ Enumerations
Enumerations provide a convenient way to work with sets of related constants. You can give names to the
constants, which makes your code easier to read and modify. For example, in NX Open, there is an enumeration that
represents the various types of line font that can be assigned to an object. In shortened form, its definition might
look something like this:
enum ObjectFont {
Solid = 0,
Dashed = 1,
Dotted = 2
}
Having made this definition, the symbol ObjectFont.Dotted now permanently represents the number 2. The benefit
is that a statement like myFont = ObjectFont.Dotted is much easier to understand than myFont = 2.
if (nullString == null) {
Console.WriteLine(”The string is null”); // Will print that the string is null
}
Simple data types like int, double, Vector3d and Point3d cannot have the value null, ordinarily — there is no such
thing as a null integer or a null Point3d. This is actually quite inconvenient, at times. For example, in a function that
computes the point of intersection of two curves, it would be natural to return null if the curves don’t actually
intersect. Fortunately, recent versions of C# provide a solution via a technology called “nullable value types”: by
declaring a variable as Nullable <type> variableName = null. You can allow variables of any data type, such as int, to
hold the value null, in addition to its “regular” values. Then you can use the HasValue function to find out whether or
not the variable holds a “real” value, rather than null. An example of this functionality can be found at this site.
■ Decision Statements
Simple decisions can be implemented using the if, else if, else construct, as shown in the following tax computation.
It assumes that we have already defined two variables called income and tax
If there were only two tax brackets, we wouldn’t need the else if clause, so our code could be simpler:
Finally, we can compress the if statement into a single line, if we want to:
if (income > 27000) { tax = 4000 + (income - 65000) * 0.35; } // 35% tax bracket
The variable i is called the loop counter. The statements between the for line and the } line are called the body of the
loop. These statements are executed n+1 times, with the counter i set successively to 0, 1, 2, …, n. It is often
convenient to declare the counter variable within the for statement.
In C#, a function that does not return a value is called a “subroutine” or just “void”. In the code above,
NXOpen.Guide.InfoWriteLine is a subroutine, but Math.Sqrt and CreatePoint are not. Even if a function does return a
value, you are not obligated to use this value. For example, in the code above, we didn’t use the value returned from
the CreatePoint function. A function can have any number of inputs (or “arguments”) including zero.
Near the start of this chapter, we saw an example of a function (CircleArea) that you might have written yourself:
Since you have the source code of this function, you could just use this code directly, instead of calling the function,
but we would not recommend this approach; calling functions makes your code less repetitive, easier to read, and
easier to change. The general pattern for a function definition is:
<ReturnType> <FunctionName>(arguments) {
<body of the function>;
}
Note that it’s perfectly legal to have several functions with the same name, provided they have different types of
inputs. This technique is called “overloading”, and the function name is said to be “overloaded”. For example, the
function name “Average” is overloaded in the list of function definitions above. When you call the function, the
compiler will decide which overload to call by looking at the types of inputs you provide.
■ Classes
In addition to the built-in types described earlier, C# allows you to define new data types of your own. The
definition of a new user-defined data type is held in a block of code called a class. The class represents a generic
object, and a specific concrete object of this type is called an “instance” of the class. So, for example, we might have
a “Sphere” class that represents spheres in general, and the specific sphere object with center at (0,0,0) and
radius = 3 would be an instance of this Sphere class.
New objects defined by classes have fields, properties and methods. Fields and properties can be considered as
items of data (like the radius of a sphere), and a method is a function that does something useful with an object of
the given class (like calculating the volume of a sphere). Properties are described in the next section, but, for now,
you can think of a property as just a field with a smarter and safer implementation — it provides controlled
read/write access to a hidden field.
A class typically includes one or more functions called “constructors” that are used to create new objects. So, a
typical class definition might look like this:
public Position Center; // Field to hold center point (should be a property, really)
public double Radius; // Field to hold radius value (should be a property, really)
Note that the constructors are “overloaded” — there are two of them, with different inputs. To create a Ball object,
you call a constructor using the new keyword. Properties and methods are both accessed using a “dot” notation. As
soon as you type a period in Visual Studio, Intellisense will show you all the available fields, properties and
methods.
In this class, Center and Radius are both public fields, so you can access them directly. It would be safer to make
them private fields and provide properties to access them. By doing this, we could prevent the calling code from
making balls with negative radius, for example. Code to use the Ball class looks like this:
■ Shared Functions
In the example above, we had a class called “Ball”, and this class contained functions (methods) like Volume and
Draw that operated on balls. This is the “object-oriented programming” view of life — the world is composed of
objects that have methods operating on them. This is all very nice, but some software doesn't fit naturally into this
model. Suppose for example that we had a collection of functions for doing financial calculations — for calculating
things like interest, loan payments, and so on. The functions might have names like SimpleInterest, and
LoanPayment, etc. It would be natural to gather these functions together in a class named FinanceCalculator. But the
situation here would be fundamentally different from the “ball” class. The SimpleInterest function lives in the
FinanceCalculator class, but it doesn’t operate on FinanceCalculator objects. Saying it another way, the SimpleInterest
function is associated with the FinanceCalculator class itself, not with instances of the FinanceCalculator class.
Functions like this are called “static” functions in C# (or “Shared” functions in VB and other languages). You have
already seen this word many times before because the “Main” function is always static. By contrast, the functions
Volume and Draw in the Ball class are called Member functions or Instance functions. So, in short, the
FinanceCalculator class is simply a collection of static functions.
Calls to Member functions and static functions look the same in our code, but they are conceptually different. For
example, look at:
Both the second and third lines use the “dot” notation to refer to a function. But, in these two cases, the thing that
comes before the “dot” is different. In myBall.Volume on the second line, myBall is an object (of type Ball), but in
FinanceCalculator.LoanPayment on the third line, FinanceCalculator is a class.
■ Object Properties
Each type of object we create in a C# program typically has a set of “properties” that we can access. For example, a
point has a position in space, an arc has a center and a radius, a curve has a length, and a solid body has a density.
In all cases, you can read (or “get”) the value of the property, and in many cases, you can also write (or “set”) the
value. Setting a property value is often a convenient way to modify an object.
If you are familiar with the GRIP language, these properties are exactly analogous to GRIP EDA (entity data access)
symbols.
Each property has a name. To get or set the property, you use a “dot” followed by the name of the property. So, if
myCircle is an arc, then you refer to its center as myCircle.Center, and its radius as myCircle.Radius.
If p1, p2, p3 are three given positions, then (conceptually) we can write code like this:
c1 = Circle(p1, p2, p3); // Creates a circle through three positions p1, p2, p3
r = c1.Radius; // Gets the radius of the circle
c1.Center = p2; // Moves the circle, placing its center at position p2
System.Object
As you can see, every object is derived from System.Object, and therefore inherits certain mysterious properties
from it (like the Finalize, GetHashCode, and MemberwiseClone functions). The tables in the following chapters
indicate the types of objects we will be using, and their properties. You might think you will need to keep these
tables handy as you are writing code, so that you know what properties are available. But, this is not the case
assuming you are using a modern IDE (Integrated Development Environment) to write your code. In a good IDE
(like Visual Studio), as soon as you type a dot, a list of available properties and methods will appear, and all you
have to do is choose the one you want. Some enthusiasts like to say that “the code writes itself” ☺
This chapter describes the overall structure of NX Open, and some of the underlying principles. The standard
NX Open Reference Guide tells you how to call any of the thousands of functions available in NX Open, but many
people find it hard to see the “big picture”, so they don’t know where to start. This chapter explains the conceptual
model behind NX Open programming, to make it easier to find the functions you need.
SNAP
NX/Open .NET API functions.
NX/Open .NET Some of these (left) call User Function
Others (right) call NX internal functions
NX/Open C API User Function
NX A B C D E F
NX internal
functions
NXRemotableObject
TaggedObject
NXObject So, we see that a Point is a kind of “SmartObject”,
DisplayableObject which is a kind of “DisplayableObject”, and so on.
Body The details are given later, but briefly, here are the
DatumAxis roles of the more important object types:
DatumPlane
Edge
RemotableObject
Face
FacetedBody Used for collections of preferences and also as the
Sketch basis of the “UF” classes
SmartObject
Point TaggedObject
Curve Used for lists of objects, for selections, and for
Line “builders” (to be described later)
Conic
Arc NXObject
Ellipse
Used for Part objects, and for objects that live inside
Spline
CoordinateSystem NX part files, but are not displayed — views,
Axis layouts, expressions, lights, and so on. NXObjects
Direction have names and other non-graphical attributes.
Plane
Scalar DisplayableObject
Xform Includes most of the object types familiar to users.
Feature
Things like annotations, bodies, facetted bodies,
BodyFeature
Sphere datum objects, CAE objects. Displayable objects
NXMatrix have colors, fonts, and other appearance attributes.
Expression Note that NX features are not displayable objects.
BasePart
Part SmartObject
Builder Includes points, curves, and some object types used
FourPointSurfaceBuilder as components of other objects when implementing
FeatureBuilder
associativity.
BooleanBuilder
SphereBuilder
BaseSession
Session
TaggedObjectCollection
BaseFeatureCollection
PartCollection
FeatureCollection
MathUtils
As you can see, we first get the current NX session object by calling the GetSession function. Every session object
has a PartCollection object called “Parts” which we obtained in the second line of code. Then we get the Work Part
Notice how the CreatePoint function does not return an NXOpen.Point object, it returns the NXOpen.Tag (pointTag)
of the point it created. Then, whenever we want to refer to this point in subsequent code, we use this tag. So, for
example, in the last line of code, pointTag is used as input to the SetLayer function.
We can contrast this with code that does the same operations using newer object-based functions:
Of course, there will be times when you want to use a mixture of NXOpen.UF functions and newer ones, so it’s
important to understand how objects and tags relate to one another. In one direction, the correspondence is very
simple: if you have an NX object called myObject, then myObject.Tag gives you its tag. So, we could do this:
In the opposite direction (from tag to object), the process is slightly more complicated:
NXOpen.Tag pointTag;
theUfSession.Curve.CreatePoint(coords, pointTag);
NXOpen.TaggedObject obj = NXOpen.Utilities.NXObjectManager.Get(pointTag);
NXOpen.Point myPoint = (NXOpen.Point) obj;
myPoint.Layer = 30;
As you can see, calling the NXObjectManager.Get function gives us an NXOpen.TaggedObject, and then we cast this
to type NXOpen.Point. In practice, you would probably shorten this by using an implicit cast, like this:
Sometimes the factory object provides functions for directly creating objects, as in the example above. Other times,
the factory object provides functions for creating “builder” objects, instead, as discussed in the next section. The
following table shows some common examples of factory objects, how you obtain instances of these factory objects,
and what sorts of creation functions they provide:
In this code, we have explicitly defined the factory objects, to emphasize the role that they play. But, in typical code,
they would not be mentioned explicitly, and you’d just write:
■ Object Collections
In many cases, the factory object described in the previous section has the word “collection” in its name. This is
because, in addition to its object creation duties, the factory object also provides us with a way of cycling through
objects of a specific type in a part file. This is useful if you want to cycle through the objects, performing some
operation on each of them. For example, to perform some operation on all the points in a given part file (myPart)
you can write:
Speaking more formally, the PointCollection class implements the IEnumerable interface, which means it’s a
collection of items that you can cycle through using a foreach loop.
builder.Type =
Features.SphereBuilder.Types.CenterPointAndDiameter
builder.CenterPoint = centerPoint
builder.Diameter.RightHandSide = diamString
builder.BooleanOption.Type =
NXOpen.GeometricUtilities.BooleanOperation.BooleanType.Create
The Commit function returns an NXOpen.NXObject, which is not immediately useful, in most situations. You
typically have to cast to a more specific type (NXOpen.Features.Sphere in our example above) before making
further use of the object. Builders for “feature” objects also have a CommitFeature method. This returns a very
general NXOpen.Features.Feature object, so a cast will still be necessary in many cases. You can either perform the
cast explicitly, or do it implicitly with an assignment statement, as shown here:
In some cases, both the Commit and CommitFeature methods return null, and you have to use the
GetCommittedObjects function to obtain the created object(s).
theSession.DisplayManager.BlankObjects(objects1);
If you replay this code, it’s just going to try to blank SPHERE(23) and SPHERE(24) again, which is probably useless.
There’s a good chance that SPHERE (23) and SPHERE (24) won’t exist at the time when you’re replaying the
journal, and, even if they do, it’s not likely that these are the objects you want to blank. Clearly we need to get rid of
the “FindObject” calls, and add some logic that better defines the set of objects we want to blank. There are a few
likely scenarios:
▪ Maybe we want to blank some objects that were created by code earlier in our application
▪ Maybe we want to blank some objects selected by the user when our application runs
▪ Maybe we want to blank all objects in the model, or all the objects that have certain characteristics
The first of these is easy: if we created the objects in our own code, then presumably we assigned them to program
variables, and they are easy to identify:
objects1[0] = myBall0;
objects1[1] = myBall1;
theSession.DisplayManager.BlankObjects(objects1);
For the second case, we need to add a selection step to our code as outlined in chapter 15, and then blank the
objects the user selects when the journal is replayed. Something like this:
if (result.Response != NXOpen.Selection.Response.Cancel) {
theSession.DisplayManager.BlankObjects(result.Objects);
}
For the third case (blanking all the objects with certain characteristics), we will need to cycle through all the
objects in our model, finding the ones that meet our criteria, and then pass these to the BlankObjects function. See
the last section in chapter 15 for information about cycling through the objects in a part file.
In the third line, we are passing a Snap.NX.Point object to a function that expects to receive an NXOpen.Point. But
the implicit conversion is invoked behind the scenes, and the function call just works as expected. Similarly, in the
fourth line, we are assigning a Snap.Position object to an NXOpen.Point3d object, and this works, too.
However, there are times when the implicit conversions don’t work, and you need to do something more explicit.
For example, if you want to use NXOpen member functions or properties, then you have to get an NXOpen object
from your SNAP object first. So suppose, for example, that we have a Snap.NX.Sphere object called snapSphere, and
we write the following code:
snapSphere.HideParents(); // Fails
var version = snapSphere.TimeStamp; // Fails
Both lines of code will fail, because a Snap.NX.Sphere object does not have a HideParents method or a TimeStamp
property. So, to proceed, you have to “unwrap” to get the enclosed NXOpen.Features.Sphere object.
You can do this in a couple of different ways, as shown below:
The first line just uses the standard C# implicit cast to do the conversion, and the second line uses the
NXOpenSphere property. The second approach, using properties, is the most convenient, so there are several
In the fourth line of code, we first get the tag of the NXOpen.Point object. Then we call the Wrap function, which
gives us a new Snap.NX.Point object that “wraps” it. Then, in the last line, we can use the Position property of this
new Snap.NX.Point object.
As we saw above, the Wrap function receives an NXOpen.Tag as input. So, if you are working with older NXOpen
functions that use tags, interoperability with SNAP is even easier. For example:
The next few chapters briefly outline the NX Open functions available for performing simple tasks. The function
descriptions are fairly brief, since we are just trying to show you the range of functions available. The NX Open
Reference Guide has much more detailed information, and this detailed information will also be presented to you
as you are writing your code, if you use a good development environment like Visual Studio. Specifically, as soon as
you type an opening parenthesis following a function name, a list of function inputs will appear, together with
descriptions. You can also get complete information about any function or object by using the Object Browser in
Visual Studio.
Following the descriptions of functions, we often give small fragments of example code, showing how the functions
can be used. The examples are very simple, but they should still be helpful. To keep things brief, the example code is
often not complete. For example, declarations are often left out, and a complete Main function is only included very
rarely. If you actually want to compile the example code, you will typically need to make some additions.
■ Point3d Objects
A Point3d object represents a location in 3D space. After basic numbers, positions and vectors are the most
fundamental objects in geometry applications, so we will describe them first. There are also Point2d and Point4d
objects, but these are not used as often, so we won’t discuss them here. Note that a Point3d is not a real NX object.
Point3d objects only exist in your NX Open program — they are not stored permanently in your NX model (or
anywhere else). So, as soon as your program has finished running, all your Point3d objects are gone. In this sense,
they are just like the numerical variables that you use in your programs. If you want to create a permanent NX
object to record a location, you should use an NXOpen.Point, not a Point3d. You can use the following function to
create a Point3d object:
In the first column, you see a formal description of the types of inputs you should provide when calling the
function — you have to provide three variables of type “double”.
This function is a constructor, so, when calling it, we have to use the “new” keyword in our code. Here are some
examples:
Getting Started with NX Open Chapter 6: Positions, Vectors, and Points Page 51
■ Vector3d Objects
A Vector3d object represents a direction or a displacement in 3D space. Like Point3d objects, Vector3d objects only
exist in your NX Open program — they are not stored permanently in your NX model (or anywhere else). You can
use the following constructor function to create Vector3d objects:
The NXOpen.VectorArithmetic class provides a Vector3 object that is very similar to NXOpen.Vector3d. This class
also provides functions for performing operations on Vector3 objects, like addition, subtraction, cross products,
and so on. In some cases, it might be convenient to use Vector3 objects for calculations, and then convert the
answers to NXOpen.Vector3d form for further use. The following code illustrates the approach:
■ Points
Points might seem a lot like Point3d objects, but they are quite different. A Point is an NX object, which is
permanently stored in an NX part file; Point3d and Vector3d objects are temporary things that exist only while your
NX Open program is running.
To create a point, we write code following the “factory” pattern explained in chapter 5. The basic idea is that a part
file contains “collections” of different object types. So, for example, given a Part object named myPart, there is a
collection called myPart.Points that contains all the Point objects in the part. Similarly, myPart.Arcs is a collection
that contains all the arcs in this part, and myPart.Curves includes all the curves.
These collections serve as “factory” objects that we can use to create new objects in a part file, as follows:
The last line of code is necessary because an NXOpen.Point is a “SmartObject”, which is invisible by default. The
code above is written out in a rather verbose way, to allow for complete explanation. In practice, you would
typically write something like this:
So, in summary, the following function creates a point in a part called myPart:
Getting Started with NX Open Chapter 6: Positions, Vectors, and Points Page 52
Function Inputs and Creation Method
myPart.Points.CreatePoint(double x, double y, double z); From x, y, z coordinates
There are many functions that require Point3d objects as inputs. If we have a Point, instead of a Point3d, we can
always get a Point3d. So, if pt is a Point, and we want to set the origin of an NXOpen.Direction (which requires a
Point3d object), then the necessary code is:
Getting Started with NX Open Chapter 6: Positions, Vectors, and Points Page 53
Chapter 7: Curves
This chapter briefly outlines the NX Open functions for creating and editing curves (lines, arcs, and splines). For
further details, please look at the NXOpen.CurveCollection and NXOpen.UF.UFCurve classes in the NXOpen
Reference Guide.
■ Lines
The NXOpen.CurveCollection class contains two functions for creating lines, as follows:
The following fragment of code creates two points and two lines in your Work Part:
The code that creates line1 above is what you will get if you record the creation of a line using
Insert → Curve → Basic Curves.
Note that we had to set the visibility of line2 because lines created via this method are invisible by default.
There are other ways to create lines, too. There is NXOpen.UF.UFCurve.CreateLine, and the NXOpen.LineCollection
class also has some functions for creating lines along the axes of various types of surfaces of revolution.
The geometric properties of lines are:
The StartPoint and EndPoint properties cannot be set directly, but the NXOpen.Line class provides SetStartPoint,
SetEndPoint, and SetEndPoints functions that let you modify a line:
There is also a Guide.CreateLine function, which makes it even easier to create a line.
// Create an AssociativeLineBuilder
NXOpen.Features.AssociativeLine lineNothing = null;
NXOpen.Features.AssociativeLineBuilder builder;
builder = workPart.BaseFeatures.CreateAssociativeLineBuilder(lineNothing);
builder.Associative = true;
The result object created by this code is actually an Associative Line feature, rather than a line. This has advantages,
sometimes — the feature appears in the Part Navigator and some forms of editing are easier. To obtain an old-
fashioned line from an AssociativeLine feature, add the following two lines to the code above:
public void CreateArc( From center point, radius, angles, orientation matrix.
Point3d center, The arc lies in the XY-plane of the matrix.
The center point is expressed using Absolute Coordinates, and
NXMatrix matrix,
double radius,
There are no specific functions for creating complete circles; we simply set endAngle = startAngle + 2𝜋.
Here is a simple program that uses lines and arcs to create a linkage bar lying in the YZ plane:
double length = 8;
double width = 4;
double half = width/2;
double holeDiam = half;
double pi = System.Math.PI;
double twopi = 2*pi;
There are other ways to create arcs, too. For example, the NXOpen.UF.UFCurve class has functions CreateArc,
CreateArcThru3pts, and CreateFillet. The NXOpen.ArcCollection class does not have any functions for creating arcs.
Finally, there are two Guide.CreateCircle functions that are very easy to use.
The properties of arc objects are as follows:
None of these properties can be set directly, but the NXOpen.Arc class includes two SetParameters functions that let
you modify an arc in any way you want.
// Create an AssociativeArcBuilder
NXOpen.Features.AssociativeArc arcNothing = null;
NXOpen.Features.AssociativeArcBuilder builder;
builder = workPart.BaseFeatures.CreateAssociativeArcBuilder(arcNothing);
builder.Associative = true;
The last line of code gets an ordinary NXOpen.Arc object from the AssociativeArc feature, which may or may not be
necessary, depending on your application.
An AssociativeArcBuilder object has a large number of properties — around 30 of them, in all. The best way to
understand what they all mean is to look at the dialog for creating an arc in interactive NX. For example, we defined
the start and end angles of the arc using two expressions that give the angular limits in degrees. If you edit the arc
we created, you will see these expressions near the bottom of the edit dialog:
You can create a full 360 degree circle by setting the limiting angles to 0 and 360, of course. Alternatively, you can
just set builder.Limits.FullCircle = true.
double pi = System.Math.PI;
This creates half of a full ellipse, lying in a plane parallel to the work plane, with its center at the absolute origin.
The ellipse is rotated in its plane by an angle of 30 degrees (𝜋/6 radians).
The NXOpen.Features.GeneralConicBuilder class allows you to create conic section curves by different techniques,
by specifying various combinations of point and tangency constraints.
The NXOpen.UF.UFCurve class also provides the CreateConic, and EditConicData functions.
■ Splines
The NX Open functions for handling splines use a fairly conventional NURBS representation that consists of:
▪ Poles — An array of 𝑛 3D vectors representing poles (control vertices)
▪ Weights — An array of 𝑛 weight values (which must be strictly positive)
▪ Knots — An array of 𝑛 + 𝑘 knot values: 𝑡[0], … , 𝑡[𝑛 + 𝑘 − 1]
The order and degree of the spline can be calculated from the sizes of these arrays, as follows:
▪ Let 𝑛 = number of poles = Poles.Length
▪ Let 𝑛𝑝𝑘 = 𝑛 + 𝑘 = number of knots = Knots.Length
Then the order, 𝑘, is given by 𝑘 = 𝑛𝑝𝑘 − 𝑛. Finally, as usual, the degree, 𝑚, is given by 𝑚 = 𝑘 − 1.
You may not be familiar with the “weight” values associated with the poles, since these are not very visible within
interactive NX — you can see them in the Info function, but you can’t modify them. So, in this case, the NX Open API
actually gives you more power than interactive NX. Generally, the equation of a spline curve is given by a rational
function (the quotient of two polynomials). This is why spline curves are sometimes known as NURBS
(Non-Uniform Rational B-Spline) curves. If the weights are all equal (and specifically if they are all equal to 1), then
some cancellation occurs, and the equation becomes a polynomial.
// 3D coordinates of poles
double[,] p = new double[,] { {1,0,0}, {3,1,0}, {5,1,0}, {6,0,0} };
// Weights
double[] w = new[] {1, 1, 0.7, 1};
// Construct 4D poles
double[] poles4D = new double[4 * n - 1 + 1];
for (int i = 0; i <= n-1; i++) {
poles4D[4*i] = w[i] * p[i,0];
poles4D[4*i + 1] = w[i] * p[i,1];
poles4D[4*i + 2] = w[i] * p[i,2];
poles4D[4*i + 3] = w[i];
}
NXOpen.Tag splineTag;
int knotFixup = 0;
int poleFixup = 0;
Note how the 3D poles and the weights are combined into 4D elements of the form (𝑤𝑥, 𝑤𝑦, 𝑤𝑧, 𝑤).
The code above is somewhat unusual because it uses weights that are not all equal, and therefore it creates a
rational curve (rather than a polynomial one). In most cases, you would set all weights equal to one, so the poles4D
array would simply be: 1,0,0,1, 3,1,0,1, 5,1,0,1, 6,0,0,1.
To construct a curve of order 𝑘 with 𝑛 poles, you need 𝑛 + 𝑘 knots. So, in our case, we need 7 knots. Since the curve
has order 3, the knot sequence should begin with 3 0’s and end with 3 1’s. That only leaves one knot value
undecided, and the code above assigns it a value of 0.6.
The CreateSpline function returns two integers knotFixup and poleFixup that indicate whether or not any “fixup” of
the data was performed inside NX. A typical fixup is a slight adjustment of knot values or poles that are very close
together but not identical. In almost all cases, you will find that both fixup values are zero, indicating that no
adjustments were required.
There are several other functions for creating and editing splines. The NXOpen.UF.UFCurve class provides a
function CreateSplineThruPts that allows you to construct a spline that passes through given points, and also lets
you specify slopes and curvatures at these points. Also, in NXOpen.UF.UFModl, there is a function called
CreateFittedSpline that performs smoothing by creating a spline that approximates given points without
necessarily passing through them exactly.
■ Studio Splines
The functions described in the previous section all create NXOpen.Spline objects. In some situations, you might
want to create a Studio Spline feature, instead, because this feature will appear in the Part Navigator and some
forms of editing are easier. You proceed in the standard way, by first creating a StudioSplineBuilderEx object, and
then setting its properties. Many of the properties take the form of geometric constraints that control the shape of
the curve. For example, you can specify points that the spline should pass through, tangent directions, curvatures,
and so on. To make the coding more convenient, let’s first write a small “helper” function that provides an easy way
to add a “point” constraint to a StudioSplineBuilderEx object:
Using this helper function, here’s how we construct a Studio Spline feature:
builder.Destroy();
Notice that we have set IsAssociative = true. If we had set this property to false, instead, then splineFeature would be
Nothing. However, an NXOpen.Spline curve would still be created, which we could then use in subsequent
operations.
p1 p0
We begin by creating a datum plane and a datum axis to control the orientation of our sketch:
Next, we create an empty sketch (that does not yet contain any curves), using the familiar builder technique:
NXOpen.SketchInPlaceBuilder sketchBuilder;
sketchBuilder = workPart.Sketches.CreateNewSketchInPlaceBuilder(null);
sketchBuilder.PlaneOrFace.Value = sketchPlane;
sketchBuilder.Axis.Value = horizAxis;
sketchBuilder.SketchOrigin = workPart.Points.CreatePoint(origin);
sketchBuilder.PlaneOption = NXOpen.Sketch.PlaneOption.Inferred;
sketchBuilder.Destroy();
bridgeSketch.Activate(NXOpen.Sketch.ViewReorient.False);
The last line of code activates the sketch, allowing us to add curves and constraints to it. Next, we create an arc
through three points, and add it to our sketch:
theSession.ActiveSketch.AddGeometry(bridge,
NXOpen.Sketch.InferConstraintsOption.InferNoConstraints);
In this construction, the middle point pm is somewhat arbitrary; after solving, the arc will no longer pass through
this point.
NXOpen.Sketch.ConstraintGeometry arcPt0;
arcPt0.Geometry = bridge;
arcPt0.PointType = NXOpen.Sketch.ConstraintPointType.StartVertex;
arcPt0.SplineDefiningPointIndex = 0;
NXOpen.Sketch.ConstraintGeometry pt0;
pt0.Geometry = workPart.Points.CreatePoint(p0);
pt0.PointType = NXOpen.Sketch.ConstraintPointType.None;
pt0.SplineDefiningPointIndex = 0;
theSession.ActiveSketch.CreateCoincidentConstraint(arcPt0, pt0);
As you can see, we do not use the point p0 and the arc end-point directly — we first construct ConstraintGeometry
objects that are then used as input to the CreateCoincidentConstraint function. The code for constraining the arc’s
end-point is analogous:
NXOpen.Sketch.ConstraintGeometry arcPt1;
arcPt1.Geometry = bridge;
arcPt1.PointType = NXOpen.Sketch.ConstraintPointType.EndVertex;
arcPt1.SplineDefiningPointIndex = 0;
NXOpen.Sketch.ConstraintGeometry pt1;
pt1.Geometry = workPart.Points.CreatePoint(p1);
pt1.PointType = NXOpen.Sketch.ConstraintPointType.None;
pt1.SplineDefiningPointIndex = 0;
theSession.ActiveSketch.CreateCoincidentConstraint(arcPt1, pt1);
The “coincidence” constraint we have used here is the most common type, but the Sketch class provides functions
for creating many other types. For example, parallel, perpendicular and concentric constraints are supported, as in
interactive NX. Next, we create a “perimeter” dimension to control the length of the arc:
Typically, the perimeter of a sketch will consist of an array of curves, of course, but here we have only one. Again,
the Sketch class provides functions for creating various other types of dimensional constraints (linear, angular,
diameter, and so on). Finally, we “update” the sketch, and deactivate it.
theSession.ActiveSketch.LocalUpdate();
theSession.ActiveSketch.Deactivate(NXOpen.Sketch.ViewReorient.False,
NXOpen.Sketch.UpdateLevel.Model);
When we call the LocalUpdate function, the sketch is solved, but the children of the sketch (if any) are not updated.
After executing the code, the user can adjust the value of the “length” expression to modify the shape of the curve.
The picture below shows some sample curves with lengths 2.3, 2.4, and 2.5:
This chapter briefly outlines a few of the NX Open functions that are available for creating simple solid and sheet
bodies. Typically, these functions create features, so you sometimes have to do a bit of extra work to get the
constituent bodies, as explained later in chapter 10.
NXOpen.Features.SphereBuilder builder;
builder = workPart.Features.CreateSphereBuilder(null);
So, we see that the creation process follows the “builder” pattern that we explained in chapter 5. The general
approach is to
▪ Create a “builder” object
▪ Modify its properties as desired
▪ “Commit” the builder to create a new feature
Functions to create various types of “builder” objects are methods of a FeatureCollection object, and we can get one
of these from the workPart.Features property.
You can create Block, Cylinder and Cone features using similar techniques. As you would expect, the relevant
builder objects are BlockFeatureBuilder, CylinderBuilder, and ConeBuilder. Of these, the ConeBuilder object is the
most complex, because it has several different values for its “Type” property, and several dimensional parameters
(diameters, height, angle) that are interdependent. The set of relevant parameters depends on the value of the Type
property. For example, if you set Type = DiametersAndHeight, then the only relevant parameters are BaseDiameter,
TopDiameter, and Height. You can assign a value to the HalfAngle parameter, too, but this setting will simply be
ignored, as the following code illustrates:
Getting Started with NX Open Chapter 8: Simple Solids & Sheets Page 63
var builder = workPart.Features.CreateConeBuilder(null);
It’s usually fairly obvious which parameters are used with each setting of the Type property. If you’re in doubt, you
can experiment with the Cone dialog in interactive NX. As you change the Type setting, the relevant set of
parameters will be shown in the lower portion of the dialog.
The examples in this document often use spheres and cylinders to illustrate some point, so we provide simple
Guide.CreateSphere and Guide.CreateCylinder functions to make these easy to create.
■ Sections
Before we discuss Extruded and Revolved features, we need to explain the concept of a “section”. When you are
selecting curves for use in Extrude, or Revolve, or many other NX functions, a menu appears in the top border bar
showing you the available “Selection Intent” rules.
This menu allows you to define a collection of curves that is dynamic in the sense that its members are determined
on-the-fly based on the rule you specify. So, for example, if you select a face F and choose the “Face Edges” rule,
your collection will contain all the edges of the face F. If the face F happens to change, as the result of model editing,
then your collection will still consist of all the edges of F, whatever these might now be. The collection of curves is
“smart” in the sense that it responds to changes in the model; in fact, as we will see, a collection defined in this way
is sometimes referred to as a “Smart Collector”.
In NX Open, there is a corresponding SelectionIntentRule class, which has numerous derived classes, including
▪ CurveDumbRule
▪ CurveChainRule
▪ CurveFeatureChainRule
▪ CurveFeatureRule
▪ CurveFeatureTangentRule
▪ CurveGroupRule
▪ CurveTangentRule
The simplest type of these is the CurveDumbRule, which just collects a specific list of curves, as its name suggests. In
an NX Open program, this is often appropriate, since the collection logic will reside in your code, rather than in NX
data structures.
To create a selection intent rule of type CurveDumbRule from a given array of curves, the code is just:
Getting Started with NX Open Chapter 8: Simple Solids & Sheets Page 64
The “Sc” in ScRuleFactory stands for “Smart Collector”. Then, once we have this rule, we can use it to add curves to a
section. So, if we have a single curve named arc, the code to create a section is:
If we want a rectangular section consisting of four lines, then we add these one at a time, as follows:
Using other types of rules is quite similar. For example, if we want a section that gathers together all the edges of a
face myFace, we write:
We can use the section to create an Extrude feature, a Revolve feature, or numerous other types.
Getting Started with NX Open Chapter 8: Simple Solids & Sheets Page 65
■ Extruded Bodies
Once we have created a section, creating an Extrude feature is quite straightforward. So, suppose we have created a
section called mySection, as in the code above. To extrude this section in the z-direction we write:
// Create an ExtrudeBuilder
var builder = workPart.Features.CreateExtrudeBuilder(null);
builder.Destroy();
If your section consists of an open string of curves that do not enclose a region, then the result will be a sheet body,
of course. On the other hand, when you extrude a closed section, you can decide whether you want a sheet body or
a solid body as the result. The draft angle(s) of the extruded body can be controlled by using the
extrudeBuilder.Draft property, and thin-walled extrusions can be created using the extrudeBuilder.Offset property. So,
to create a sheet body with a 15 degree draft angle, we write:
builder.FeatureOptions.BodyType = NXOpen.GeometricUtilities.FeatureOptions.BodyStyle.Sheet;
builder.Draft.DraftOption =
NXOpen.GeometricUtilities.SimpleDraft.SimpleDraftType.SimpleFromProfile;
builder.Draft.FrontDraftAngle.RightHandSide = "15";
Using the rectangular section named rect that we defined above, the result is:
Getting Started with NX Open Chapter 8: Simple Solids & Sheets Page 66
■ Revolved Bodies
Creating Revolved features is quite similar to creating Extruded ones. Again, most of the work is in the creation of
the section that we revolve. So, suppose we have already created the rect section as shown above. To revolve this
section around the y-axis, the code is:
// Define the section for the Revolve (see above for details)
builder.Section = rect;
// Define the axis to revolve around (the y-axis of the Absolute Coordinate System)
NXOpen.Point3d axisPoint3d = new NXOpen.Point3d(0, 0, 0);
NXOpen.Vector3d axisVector = new NXOpen.Vector3d(0, 1, 0);
var updateOption = SmartObject.UpdateOption.WithinModeling;
// Define a direction to pass the revolve point and axis to the builder
var direction = workPart.Directions.CreateDirection(axisPoint3d, axisVector, updateOption);
NXOpen.Point axisPoint = workPart.Points.CreatePoint(axisPoint3d);
builder.Axis = workPart.Axes.CreateAxis(axisPoint, direction, updateOption);
Getting Started with NX Open Chapter 8: Simple Solids & Sheets Page 67
Chapter 9: Object Properties & Methods
The objects in the NXOpen namespace have a rich set of properties that let us get information about the objects and
(in some cases) modify them. The complete properties of each object are documented in the NX Open Reference
Guide, so the overview provided here is just to help you understand the basic concepts.
As we mentioned in chapter 4, objects inherit properties from the parent classes from which they are derived, in
addition to having properties of their own. So, since NXOpen.Arc inherits from NXOpen.Conic, which in turn
inherits from NXOpen.Curve, an NXOpen.Arc object has all the properties of an NXOpen.Conic object and all the
properties of an NXOpen.Curve object, in addition to specific properties of its own.
In the NX Open Reference Guide, you can control whether or not inherited members are displayed by clicking in the
check-box circled in red below:
As you can see, there are two members that NXOpen.Arc inherits from NXOpen.DisplayableObject, one that it
inherits from NXOpen.Conic, and one that it inherits from NXOpen.DisplayableObject. All four of these will be
hidden if you uncheck the “inherited” box.
■ NXObject Properties
Most objects in the NX Open object hierarchy inherit from NXOpen.NXObject, so its properties are very important
because they trickle down to all the lower-level objects. The properties can be divided into several categories, as
outlined below:
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 68
Suppose the user has selected an object, for example. You might want to test whether this object is an ellipse before
processing it.
The code to do this would be as follows:
In some cases, it might be more convenient to test the type of an object using the standard C# is keyword. For
example, the code above could be written as:
Display Properties
Many of the objects that NX users deal with are of type NXOpen.DisplayableObject (a subtype derived from
NXOpen.NXObject). These objects have the following properties:
Note that the Color attribute is a color index into the color palette for the part. The NXOpen.UF.UFDisp class
contains several functions for working with NX color indices. For example, NXOpen.UF.UFDisp.AskColor gets the
RGB values associated with a given color index, and NXOpen.UF.UFDisp.AskClosestColor does the reverse.
Attribute Properties
For technical reasons, attributes cannot be implemented as “real” properties, so they are accessed via
old-fashioned “Get” and “Set” methods on the NXOpen.NXObject class. All NX objects that can contain attributes
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 69
inherit from NXObject. A few of the available methods are listed below, and the complete set is covered in the
documentation for the NXOpen.NXObject class in the NX Open Reference Guide:
Evaluators
Some of the most useful methods when working with curves or edges are the so-called “evaluator” functions. At a
given location on a curve (defined by a parameter value t), we can ask for a variety of different values, such as the
position of the point, or the tangent or curvature of the curve. The evaluator functions are provided by the
NXOpen.UF.UFEval class. The most important functions are Evaluate and EvaluateUnitVectors
The following code uses the Evaluate function to compute a position and tangent at a location along myCurve, which
is assumed to be of type NXOpen.Curve or NXOpen.Edge
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 70
// Get the UFSession
NXOpen.UF.UFSession ufs = NXOpen.UF.UFSession.GetUFSession();
In other software systems, a common approach is to “normalize” the parameter value (t) that is passed to these
sorts of evaluator functions, so that it lies in the range 0 ≤ t ≤ 1. With this approach, the parameter value t = 0.5
used in the code above would correspond to the parametric mid-point of the curve. In NX Open, this normalization
process is not used, so the parameter values used are the original “native” parameters of the curve. So, in the
example above, if myCurve was a circular arc, the parameter value t = 0.5 would be the point at an angle of 0.5
radians.
If you want to use normalized parameter values, you can construct these yourself. The following code shows you
how to compute a point that is 25% of the way along a given curve or edge denoted by myCurve:
As we have seen above, the evaluator functions use an “evaluation structure” that is returned by an Initialize2
function, rather than directly using the curve or edge itself. Then, after you have finished using this structure, you
should call the Free function to release the memory it has been using. In between the Initialize and Free steps, you
can use an evaluation structure as many times as you like. The code below shows a common technique for creating
a sequence of points along a curve; as you can see, we initialize the evaluation structure once, use it 101 times, and
then free it.
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 71
The example uses a spline curve, so we can safely assume that the parameter limits are 0 and 1:
There is another function NXOpen.UF.Modl.EvaluateCurve that also allows you to calculate a point at a given
parameter value. It is slightly simpler to use, but it only works with curves, not with edges.
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 72
■ Face Properties
Like edges, faces have evaluator functions, topological properties, and geometric properties.
Evaluators
As with curves, we can call an “evaluator” function to calculate certain values at a given point on a surface (or a
face). So, as you might expect, we can get the location of the point, the surface normal at the point, and so on. To
indicate which point we’re interested in, we have to give two parameter values, traditionally denoted by u and v.
The following code illustrates the approach:
// Evaluate at uv mid-point
ufs.Modl.EvaluateFace(faceTag, request, uv, out faceValues);
As you can see, the first step is to get the parametric mid-point of the face. Of course, if we wanted to evaluate at
some other point of the face, this step would not be necessary. By setting request = UF_MODL_EVAL_UNIT_NORMAL,
we have asked for calculation of a position, first partial derivatives, and a unit surface normal, so these are available
in the faceValues structure that is returned. Various other request constants are provided in the UFConstants class;
the most comprehensive of these is UF_MODL_EVAL_ALL, which allows you to calculate position, surface normal,
and all the partial derivatives up to the third order. There is a related function, NXOpen.UF.UFModl.AskFaceProps,
that provides additional information about curvature.
Despite its name, the EvaluateFace function we used above is actually doing computations on a surface, rather than
a face. So, when performing evaluations, you do not need to restrict yourself to uv values that correspond to
locations inside the given face. Even the uv mid-point we used above is not guaranteed to lie within the face,
because the face might have some hole or notch that excludes it.
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 73
Face Geometry Properties
To get information about the geometry of a face, you use the NXOpen.UF.UFModl.AskFaceData function. For
example, the following code gets information about a face myFace:
The box argument provides a bounding box for the face, with axes aligned with the Absolute Coordinate System.
The box is represented by 6 numbers in the order minX, minY, minZ, maxX, maxY, maxZ.
The flip argument is equal to ±1, and indicates on which side of the surface material lies. Specifically, if 𝐒 𝑢 and 𝐒 𝑣
are the first partial derivatives of the surface, then the vector 𝑓𝑙𝑖𝑝 ∗ (𝐒 𝑢 × 𝐒 𝑣 ) points away from material, into “air”.
The meanings of the other parameters, for various different surface types, are given in the following table:
There is another function NXOpen.UF.UFModl.AskBsurf that provides detailed information about b-surfaces.
Getting Started with NX Open Chapter 9: Object Properties & Methods Page 74
Chapter 10: Feature Concepts
The NXOpen.Features class contains a wide variety of functions for creating “features”. At one extreme, features can
be very simple objects like blocks or spheres; at the other extreme, features like ThroughCurveMesh can be very
complex. In this chapter, we explain what a feature is, and give some samples of the NX Open functions that create
them. As usual, the full details can be found in the NX Open Reference Guide.
■ What is a Feature ?
Though you have probably created hundreds of features while running NX interactively, perhaps you never stopped
to think what a “feature” really is. So, here is the definition ...
A feature is a collection of objects created by a modeling operation that remembers the inputs and the procedure
used to create it.
The inputs used to create the feature are called its “parents”, and the new feature is said to be the “child” of these
parents. This human family analogy can be extended in a natural way to provide a wealth of useful terminology. We
can speak of the grandchildren or the ancestors or the descendants of an object, for example, with the obvious
meanings. An object that has no parents (or has been disconnected from them) is said to be an “orphan”, or
sometimes a “dumb” object, or an “unparameterized” one. The inputs and the procedure are also known as the
“history” of the object, or the “recipe”, or the “parameters”. There is no shortage of terminology in this area.
The great power of features is that they capture the process (i.e. the history, or recipe) used to create an object. You
can change the inputs, and then re-execute this process, which gives you some remarkable editing capabilities. You
can also re-order features, delete them, or insert new ones in the middle of the “recipe”, which again provides very
powerful editing techniques.
■ Types of Features
There are many different types of features, plus two important subclasses: Body Features and Curve Features.
Some of the more common examples are listed in the table below:
Member Description
FeatureType Returns the feature type as a string (see below)
GetEntities Returns the entities created by the feature
GetExpressions Returns the expressions created by the feature
GetFeatureName Returns the displayed name of the feature.
Some of the feature type strings are rather strange and cryptic, especially in older models; you may see things like
BREP, CPROJ, FRENET_DATUM_PLANE, SKIN, SWP104, META, and so on. It’s usually better to use the standard C#
TypeOf or GetType operators to find out the type of a feature.
As the name suggests, a BodyFeature is a feature that produces a body or a collection of bodies as its result.
Similarly, a CurveFeature typically produces curves (or points). So, these classes have some additional members. For
example, a BodyFeature has GetBodies, GetFaces, and GetEdges functions, and a CurveFeature has Color, Font, and
Width properties. For a BodyFeature, the GetEntities function typically returns an array of length zero, so you should
use the GetBodies function instead.
The following code cycles through the work part, writing out some information about each feature it finds:
// Get the two displayable objects of the Extrude feature (two bodies)
NXOpen.Body[] bodies = pegs.GetBodies();
NXOpen.Features.Sphere s1 = NXOpen.Guide.CreateSphere(0,0,0,1);
NXOpen.Features.Sphere s2 = NXOpen.Guide.CreateSphere(1,0,0,2);
NXOpen.Features.BooleanFeature union = NXOpen.Guide.Unite(s1, s2); // Doesn’t work
double volume = // <Compute volume of union>; // Doesn’t work
The Unite function expects two bodies as input, but s1 and s2 are features, so the operation will not work. Similarly,
the function that computes volume expects to receive a body, so this won’t work, either. We can fix the code, by
getting bodies from the features before performing the unite or the volume calculation. So, the corrected version of
the code above is:
■ Units
The parameters of features are typically described by expressions, as discussed below. But expressions involve
units, so first we have to understand units.
In each part file, there is a UnitCollection, which has an associated collection of “measures”. Typical measures are
things like length, volume, mass, angle, or velocity. These are also called Dimensionality in the NX docs. Then each
measure has an associated collection of units, which are objects of type NXOpen.Unit. Among these units, one
particular one is singled out as the BaseUnit for that measure. For example, in a metric part, the Base Unit for the
measure “Length” will be millimeters; this is the length unit that is actually used for representing objects in the part
file. Typically, we obtain the measures and units for the UnitCollection of the work part using code like this:
A UnitCollection will generally contain a large number of measures (80 or more). A few of the less exotic ones,
together with their base unit names, are as follows:
You can use the unit names to obtain other units (other than the base units) from the FindObject function
Note that the names used here are case sensitive — for example, you have to use “MilliMeter”, not “Millimeter”, and
“Kilogram” rather than “kilogram” or “KiloGram”.
■ Expressions
Expressions are used to control the sizes and positions of features, so it’s important for us to know how to work
with them. The general form of an expression is
name = right-hand-side
To understand the details, let’s look at some example expressions defined in interactive NX:
The third of these is the most interesting. It has three important pieces, indicated by the colored boxes:
The second expression has a simple name (just “p0”), which was made up by NX. But there is also some extra text
in parentheses following this name. This extra text is called the “description” of the expression, and it consists of a
feature name plus a “descriptor” indicating which feature parameter the expression controls. In summary:
If an expression does not control a feature, then its Description and Descriptor strings will be empty (zero length
strings). All of these various elements of an expression can be controlled using NX Open functions, as follows:
Function/Property Purpose
Description Gets the description of the expression
EditComment() Changes the comment.
Equation Returns the equation of the expression in the form: name = right_hand_side.
GetDescriptor() Returns the descriptor for the expression
GetValueUsingUnits() Get the value of the expression, in either base units or the expression's units.
Name Gets the name of the expression
RightHandSide Returns or sets the right hand side of the expression.
SetName() Sets the name of the expression.
Type A string indicating the type of expression, which can be Number, String, Integer,
Boolean, Vector, Point, or List.
Units Returns or sets the units for the expression.
Value Returns or sets the value of the expression in base units.
If we execute this code with a work part that contains the following three expressions
The radius expression does not (directly) control a feature, so its Description and Descriptor strings are empty.
Also, note the mysterious semi-colon at the end of the two descriptor strings.
■ Creating Expressions
Functions for creating expressions are provided in the ExpressionCollection class, as follows:
Function Creates …
Create(string) An expression
CreateExpression(string, string) An expression of the specified type.
CreateWithUnits(string, Unit) An expression with units.
CreateSystemExpression(string) A system expression.
CreateSystemExpression(string, string) A system expression of the specified type.
CreateSystemExpressionWithUnits(string, Unit) A system expression with units.
When creating expressions of a specified type, the type is indicated by a string, which can be one of “Number”,
“String”, “Boolean”, “Integer”, “Point” or “Vector”. The following code shows how these functions are used:
System expressions are less permanent than ordinary (non-system) ones. A system expression will be deleted
when the last feature using it is deleted, and it will also be deleted by the Delete Unused Expressions function,
which is usually the behavior that’s desirable.
If you don’t specify a unit when creating an expression, or you specify a unit of null, you get an expression that is
“constant”. Despite the name, this doesn’t mean the expression value is fixed, it just means that it represents a
dimensionless quantity, like an angle or a parameter percentage on a curve.
So, the most useful function of the six mentioned above is CreateSystemExpressionWithUnits, and you will see this
function many times in recorded journals.
When writing code that creates expressions, you have to bear in mind that they must have unique names. This can
be inconvenient during debugging — if you run the same code twice in the same part file, you’ll get an error
message telling you that “The specified expression variable already exists”. To avoid this, delete the previously
created expressions before re-executing your code.
// Create a CylinderBuilder
var builder = workPart.Features.CreateCylinderBuilder(null);
This will cause the creation of two expressions that we can use to modify the diameter and height of the cylinder,
but this form of editing is rather dull and uninteresting. Suppose we wanted a more intelligent cylindrical container
that let us specify its depth and volume, and then calculated the required diameter. We could achieve this with the
following code:
builder.Height.RightHandSide = depth.ToString();
builder.Diameter.RightHandSide = diameter.ToString();
We have now defined the diameter of the cylinder as a function of its depth and volume:
volume
diameter = 2√
depth ∗ 𝜋
builder.Height.RightHandSide = "depth";
builder.Diameter.RightHandSide = "diameter";
The two code examples show two different ways to capture “intelligence”: in the first case the intelligence is solely
in our code, and in the second case it has been captured in expressions. The first approach is simpler, but the code
will need to be re-executed if any of the inputs change. In the second approach, the logic in our code has essentially
been replicated with NX expressions, which will “replay” automatically if any inputs change.
■ Introduction
Unless you’re in the brick business, most of your products will probably be assemblies — combinations of simpler
lower-level items, rather than just homogeneous hunks of material. This chapter outlines how NX represents
assemblies, and describes the NX Open functions that you can use to work with them. Most of the discussion is
related to reading information about assemblies, rather than creating them, since the most common applications
involve extracting information and writing reports of one sort or another. Typically, your code will traverse though
the items in an assembly, gathering information (from attributes, usually), and writing this into a report document
of some kind.
Many of the code examples given below are just fragments, as usual. Complete working code and the part files for a
simple car assembly are provided in the folder […NX]\UGOPEN\SNAP\Examples\More Examples\CarAssembly.
Note that some of the code in this chapter will work properly only if the car assembly is fully loaded.
As you can see, the car consists of an engine (the green block), an exterior shape (the blue thing), two axles, and a
spare wheel. Each axle consists of a shaft and two wheels. The exterior shape is a sheet body in Car_Assembly.prt,
so you don’t see it in the Assembly Navigator. Similarly, the red shaft is a solid body in Axle_Assembly.prt, so you
don’t see this, either.
Car
Axle
Front Axle
Left wheel
Rear Axle
Right wheel
Wheel
Spare Wheel
Engine
Wheel
Wheel
Rear Axle
Wheel
Wheel
Spare Wheel
Engine
The top-level car assembly has four subassemblies: two axles, a spare wheel, and an engine. The axle assembly, in
turn, has two subassemblies, namely its left and right wheels. In this situation, the axles, spare wheel and engine
are said to be children of the car assembly. Or looking at it the other way around, the car assembly is said to be the
parent of each of these four. This human-family terminology can be extended further: we might say that each of the
four main wheels is a grandchild of the car assembly, and all the parts shown are descendants, and so on.
Note that this is the reverse of feature terminology. In the feature modeling world, if object-A and object-B are
constituents of object-C (in the sense that they are used to create object-C), then they are called the parents of
object-C, not its children. This inconsistency is unfortunate, but it’s very well established, and is not likely to
change, so we have to live with it.
In addition to the parent-child terminology, there are some useful terms that we can borrow from computer
science. A computer scientist would regard the assembly structure as a tree, and the various parts and assemblies
would be called the nodes in the tree. The node at the top of a sub-tree (denoted by the symbol in the
diagram) is called the root node of that sub-tree. Nodes at the bottom (like the wheels and engine) are called leaf
nodes; these are easy to identify because they have no children. Trees in computer science are strange — their
roots are always at the top, and their leaves are at the bottom ☺.
In engineering, a leaf node in an assembly tree is sometimes referred to as a piece part. This is a somewhat
misleading term because it suggests that the part consists of a single solid body, which is not always true. To avoid
any possible misunderstandings, we will use the term “leaf” in this document.
We can measure the depth of a node in a tree by counting its ancestors, including parents, grandparents, and so on,
up to the root node of the tree. So, in our car example, the car itself is at depth zero, the axles and engine are at
depth = 1, and the four main wheels are at depth = 2. In NX documentation, nodes with depth = 1
(i.e. immediately below the root node) are sometimes known as “top level” nodes.
Car_Assembly
ROOT
FRONT_AXLE
FRONT_LEFT_WHEEL
FRONT_RIGHT_WHEEL
REAR_AXLE
REAR_LEFT_WHEEL
REAR_RIGHT_WHEEL
SPARE_WHEEL
ENGINE
The yellow items are called “Components” or sometimes “Part Occurrences”. The tree of components inside a
part file replicates the tree structure of the subassemblies themselves. So, if we want to know about this structure,
we can simply traverse through the tree of component objects in the file, without opening any other part files.
An NX part file that represents an assembly has a ComponentAssembly object that provides most of the functions
related to assemblies. The ComponentAssembly object has a RootComponent object, which serves as the root
node for the part’s tree of components. You can get to all the other components in the part file by traversing
downwards from the RootComponent. The RootComponent will be null if the part file is not an assembly.
Each component contains a list of links to its children, a link to its parent, and a link to the corresponding part file,
which is called the Prototype of the component. In the diagram below, the parent-child relationships are shown by
the blue lines, and the component-to-prototype links are shown as red arrows:
Car
Axle
ROOT
ROOT
FRONT-AXLE
LEFT-WHEEL
FRONT-LEFT-WHEEL Wheel
RIGHT-WHEEL
FRONT-RIGHT-WHEEL Wheel
Wheel
Axle
ROOT
REAR-AXLE
LEFT-WHEEL
REAR-LEFT-WHEEL Wheel
RIGHT-WHEEL
REAR-RIGHT-WHEEL Wheel
SPARE-WHEEL Wheel
ENGINE Engine
So, for example, as you can see, the axle part is the prototype corresponding to each of the components
FRONT_AXLE and REAR_AXLE. Or, looking at it the other way around, FRONT_AXLE and REAR_AXLE are
occurrences of the axle part.
Many additional properties and methods are inherited from NXOpen.DisplayableObject. For example, you can
change the color of a component, hide it, move it between layers, assign attributes to it, and so on.
As you can see, the DoSomething function is recursive — it calls itself. So, what happens when the system executes
the line of code that says DoSomething(root) in the Main function? Well, first of all, the name of the root component
will be written out. Then, DoSomething is applied to each of the children of root, causing their component names to
be written out. But, then, through the magic of recursion, applying DoSomething to a child causes DoSomething to be
applied to its children, in turn, and so on. In the end, the result is that DoSomething gets applied to all the
descendants of root, so all of their names are written to the Info window. Of course, in practice, you would probably
replace the Guide.InfoWriteLine call with some more interesting code, but the principle would be exactly the same.
Once we have this function, creating indented listings is straightforward. The key is to keep track of our current
“depth” as we cycle through the assembly. We use a global variable called Depth to do this. So, each time we
descend a level, we increment our depth (Depth = Depth + 1), and each time we pop back up a level, we decrement it
(Depth = Depth – 1). We modify our DoSomething function as follows:
Then, if we make Car_Assembly.prt our work part, and call this function recursively, as before, we get the following
nicely indented listing:
ENGINE
SPARE_WHEEL
REAR_AXLE
REAR_RIGHT_WHEEL
REAR_LEFT_WHEEL
FRONT_AXLE
FRONT_RIGHT_WHEEL
FRONT_LEFT_WHEEL
Guide.InfoWrite(comp.Name);
Guide.InfoWrite( "; Position = " + pt.ToString());
Guide.InfoWrite( "; AxisZ = " + axisZ.ToString());
To understand what this means, let’s first look at how the wheel part itself was designed. The left-hand picture
below shows a section view in the wheel part. As you can see, the inside center of the rim (the purple point
labeled “P”) is at the origin, and the rotational axis of the wheel is along the z-axis.
When the front left wheel gets inserted into the car assembly, this point P gets placed at (950, 0, 0). So, if comp is
the FRONT_LEFT_WHEEL component, then comp.Position is (950, 0, 0). Similarly, the REAR_LEFT_WHEEL
component has Position = (950, 2000, 0).
Orientations are a bit more interesting: when the front left wheel gets inserted into the car assembly, its z-axis gets
aligned with the x-axis of the car. So, the z-axis of the orientation of the FRONT_LEFT_WHEEL component is
(1, 0, 0). On the right-hand side of the car, the wheel is flipped, of course, so, the FRONT_RIGHT_WHEEL has its
AxisZ in the opposite direction, equal to (–1, 0, 0). Similarly, the SPARE_WHEEL component has an orientation
whose z-axis is (0, 1, 0).
We could also study the x-axis and the y-axis of the orientations of various components, of course. But, in the case
of an axi-symmetric object like a wheel, these are not important.
■ Object Occurrences
When a part is inserted into an assembly, we know that an occurrence of this part (i.e. a component object) gets
created in the parent assembly. But, the story doesn’t end there. In addition to the occurrence of the inserted part
itself, the system also creates occurrences of all the objects inside it.
(Technically, this is an oversimplification, the system creates occurrences of some of the objects, not all of them.
But for the purposes of this section we will pretend it creates occurrences of all the objects. The real situation will
be discussed in the later section “Reference Sets”.)
To understand what happens, let’s look at the structure of the Axle part in our car example. As we know, this part
contains a solid body representing a shaft, plus two components (LEFT_WHEEL and RIGHT_WHEEL) which are
occurrences of Wheel_Part. The wheel part contains two solid bodies called TIRE_BODY and RIM_BODY. The
structure is shown in the diagram below:
ROOT
LEFT_WHEEL Wheel_Part
LEFT_WHEEL_TIRE_BODY TIRE_BODY
LEFT_WHEEL_RIM_BODY RIM_BODY
RIGHT_WHEEL Wheel_Part
RIGHT_WHEEL_TIRE_BODY TIRE_BODY
RIGHT_WHEEL_RIM_BODY RIM_BODY
SHAFT_BODY
Looking at the top half of the diagram, we see that the wheel part has been inserted into the axle assembly. As a
result of this, a part occurrence called LEFT_WHEEL has been created in the Axle_Assembly part. But, in addition to
this, we see the pink boxes, LEFT_WHEEL_TIRE_BODY and LEFT_WHEEL_RIM_BODY. These are object
occurrences; LEFT_WHEEL_TIRE_BODY is an occurrence of TIRE_BODY, and LEFT_WHEEL_RIM_BODY is an
occurrence of RIM_BODY. We say that these object occurrences are members of the LEFT_WHEEL component, as
indicated by the green lines. The red arrows show how part and object occurrences both refer back to the original
objects, which are called their prototypes. Only solid bodies are shown in the diagram, but, in fact, the
LEFT_WHEEL component will have members that are occurrences of all the objects in the wheel part (datums,
wireframe geometry, etc.).
In many ways, the LEFT_WHEEL_TIRE_BODY occurrence looks and behaves just like a normal solid body in the axle
part. You can blank it, move it to another layer, assign attributes to it, or even calculate its weight and center of
gravity. But, on the other hand it is fundamentally different from SHAFT_BODY, which is a “real” solid body. The
difference is that SHAFT_BODY includes its own geometric data, whereas LEFT_WHEEL_TIRE_BODY merely has
links to geometric data that actually reside in the wheel part. So, in some sense, an occurrence is a “phantom” or
“proxy” object, rather than a “real” one. Or, borrowing some terminology from Microsoft Office products, we might
say that an occurrence is a “linked” object, whereas a “real” object like SHAFT_BODY is an “embedded” one. The
technology used in NX is completely different, but the basic concept is similar.
The diagram below shows the difference between the data structures of occurrence and “real” objects, using a
simple example of three point objects in the axle and wheel parts:
Axle_Assembly Wheel_Part
Point1 is embedded in the axle part, and Point2 is an occurrence whose prototype (Point3) resides in the wheel
part. As usual, green boxes denote “real” embedded objects and pink ones denote occurrences. As you can see,
Point2 has a color and a layer, but it has no coordinate data of its own. Whenever we ask for the coordinates of
Point2, they will be derived by suitably transforming the coordinates of Point3.
The diagram above illustrates another important fact: even though Point2 is an occurrence, its object type is still
“Point”. There is no special “occurrence” type in NX; any NX object can either be an occurrence (a linked object), or
a “real” local embedded one. An NXOpen.NXObject has a property IsOccurrence, which allows you to find out
whether or not it’s an occurrence. Then, if IsOccurrence is true, there are ProtoType and OwningComponent
properties with the obvious meanings.
Then you can use the following code to cycle through the work part reporting on object occurrences:
do {
nextTag = ufs.Obj.CycleAll(workPart.Tag, nextTag);
if (nextTag == NXOpen.Tag.Null) { return; }
obj = ObjectFromTag(nextTag);
if (obj.IsOccurrence && obj is NXOpen.Body) {
string occName = obj.Name;
string protoName = obj.Prototype.Name;
Guide.InfoWrite("Occurrence: " + occName + " ; ");
Guide.InfoWrite("Owning component: " + obj.OwningComponent.Name + " ; ");
Guide.InfoWriteLine("Prototype: " + protoName);
}
} while (nextTag != NXOpen.Tag.Null);
Since this code is only examining objects of type NXOpen.Body, you may be wondering why we didn’t simply cycle
through the workPart.Bodies collection. This would be simpler because we wouldn’t have to concern ourselves
with tags. However, if you cycle through workPart.Bodies, you won’t find bodies that are occurrences, you will only
find the ones that are embedded in the work part.
If you run the code above with Axle_Assembly.prt as your work part, the output will be as follows:
Occurrence: RIGHT_TIRE_BODY ; Owning component: RIGHT-WHEEL ; Prototype: TIRE_BODY
Occurrence: RIGHT_RIM_BODY ; Owning component: RIGHT-WHEEL ; Prototype: RIM_BODY
Occurrence: LEFT_TIRE_BODY ; Owning component: LEFT-WHEEL ; Prototype: TIRE_BODY
Occurrence: LEFT_RIM_BODY ; Owning component: LEFT-WHEEL ; Prototype: RIM_BODY
There is a distinction between temporary and permanent object occurrences. Most object occurrences are
temporary, they are used by NX’s display and selection systems, but they are not stored permanently in the part
file when it is saved. This avoids bloating the file unnecessarily. However, if an occurrence is referenced by
another permanent object, then it must become permanent itself. For example, if you attach a drawing note to an
occurrence of a solid body, then the solid body’s occurrence will become permanent.
Permanent object occurrences will be discussed further in the “Reference Sets” section.
■ Creating an Assembly
The most common way to create an assembly is to insert parts as components into a parent assembly file. We will
use this technique to create the assembly shown below. It is a simple circular door assembly, as you might find in a
submarine or space ship, consisting of a circular plate with a “grip” or handle located at its center.
door.prt Z grip.prt Z
P
X X
The point P where the base of the handle is located (the red point) has coordinates (0,0,1).
Combining these two parts to form the assembly shown above is very easy because the “door” and “grip” objects
are already located correctly in space. This is not an unusual situation — quite a few companies design components
in “absolute position” so that no further positioning is required when they are assembled into products. So, to
create a new assembly file and add these two parts to it, we proceed as follows:
partFilePath = @"C:\Temp\grip.prt";
compName = "gripComp";
compAssy.AddComponent(partFilePath, refSetName, compName, origin, matrix, layers, out status);
The code assumes that two files door.prt and grip.prt are in your C:\Temp folder. You can either put them there, or
you can change the code to use different path names. The real work is done by the call to the AddComponent
function. The meanings of its various arguments are as follows:
Reference sets provide a way to use simplified representations of components in assemblies, which can improve
performance and reduce memory usage. You can read about these in the “Assemblies” section of the NX
documentation. You can either create your own custom reference sets, or you can use the standard ones that NX
creates for you automatically. The names of the standard ones are “MODEL”, “Entire Part”, and “Empty”. A little later,
we will tell you how to write code that replaces one reference set by another.
The origin and matrix arguments specify the position and orientation of the component part in the assembly, as
described earlier in the section entitled “Component Positions & Orientations”. In the example above, the
positioning and orientation logic was rather dull because the parts were already in the correct locations and did
not need to be moved; a more interesting example is given below.
The layers argument indicates the destination layers on which the component itself and its members (occurrence
objects) should be placed. The meanings of the available settings are as follows:
Value Destination Layer for Component Destination Layer for Component Members
layers = 0 Work layer Work layer
layers = -1 Work layer Original layers (layers of prototype objects)
layers = n Layer n Layer n
Top Grip
Right Grip
We create a new assembly part and insert the door component into it, just as before. Next, we have to position and
orient the two handles as shown below:
X
Y
Y
Y
X
X
Right Grip
The Right Grip is easy, because it just needs to be translated, not rotated. The code is as follows:
Since no rotation is needed, the matrix used is just the identity. The only new idea here is the use of the point
pt1 = (10,0,0) to position the component. Note that we used the point (10,0,0), not (10,0,1) because an offset of
1 mm in the z-direction is already built into the design in grip.prt.
The positioning of TopGrip is a little more interesting. The code is:
We want the grip’s x-axis to be aligned with the vector (0,1,0) in the assembly part, so we set (Xx, Xy, Xz) = (0,1,0) in
the definition of matrix2. The other two rows of the matrix are defined using similar reasoning.
doorAssy.ComponentAssembly.ReplaceReferenceSet(rightGrip, "WIRE");
doorAssy.ComponentAssembly.ReplaceReferenceSet(topGrip, "WIRE");
In the previous section “Object Occurrences”, we mentioned that for each part occurrence (component), the system
will create object occurrences of some of the objects inside that part. Reference sets are the key to this. Each
component has a current reference set (shown in the “Reference Set” column in the Assembly Navigator). So for
each component, NX creates temporary object occurrences of precisely those objects which are members of the
component’s current reference set.
In the example above (grip.prt), the “WIRE” reference set contains two lines, and does not contain the solid body.
Whereas the “MODEL” reference set contains the solid body but not the two lines. When you change the grip
component’s reference set from “MODEL” to “WIRE”, the system deletes the object occurrence of the solid body, and
creates object occurrences of the two lines.
Also in the section “Object Occurrences” we discussed permanent vs. temporary object occurrences. Permanent
object occurrences continue to exist even if you change to a refset that does not contain the prototype object.
However the occurrence will no longer be displayed in that case.
To continue the example from above, suppose you attach a drawing note to the grip component’s object occurrence
of the solid body. This will make the solid body occurrence permanent. Then, if you subsequently change the
component’s reference set to “WIRE”, the solid body occurrence is hidden from the display, but it is not deleted.
Some NXOpen methods are capable of creating occurrences even if they “should not” exist according to the
reference set. NXOpen.Assemblies.Component.FindOccurrence is one such method. This method, given a
component and an object in that component’s prototype part, locates the corresponding object occurrence. If no
object occurrence exists, because the prototype object is not in the component’s current reference set,
FindOccurrence will create a permanent object occurrence (and hide it from the display).
■ Other Topics
NX Open has a very rich and complex collection of functions for working with assemblies. After reading the
material in this chapter, you should be ready to start using these functions. In addition to the functions in the
NXOpen.Assemblies namespace, which we have used here, there are older functions in the NXOpen.UF.UFAssem
This chapter discusses NX Open functions for working with drawings and annotations.
■ Drawings
In NX Open, functions related to drawings can be found in the NXOpen.Drawings namespace, and in the
NXOpen.UF.UFDraw class. Note that the documentation for the NXOpen.UF.UFDraw class contains many sample
programs. While these are written in the C language, conversion to other languages is typically straightforward.
A drawing is represented by a collection of NXOpen.Drawings.DrawSheet objects in NX Open. The set of all
DrawingSheet objects in the work part (or any part file) is a DrawingSheetCollection object, which you can get by
using the workPart.DrawingSheets property.
Each sheet has a SheetDraftingViewCollection object, which is important because you use it to work with the views
on the sheet (to create and delete views, for example). You can get this object by using the SheetDraftingViews
property of the sheet. Some typical operations are as follows:
Code Description
sheets = workPart.DrawingSheets
Create a drawing sheet
sheets.InsertSheet( )
assoc.FirstObject = myArc;
assoc.SecondObject = null;
assoc.ObjectView = workPart.Views.WorkView;
Point3d PickPoint = new Point3d(350, 650, 0);
assoc.PickPoint = PickPoint;
NXOpen.Annotations.ArcLengthDimension arcLengthDim;
arcLengthDim = workPart.Dimensions.CreateArcLengthDimension(dimData, origin);
NXOpen.Annotations.CurveLengthDimensionBuilder builder;
builder = workPart.Dimensions.CreateCurveLengthDimensionBuilder(null);
builder.Origin.Anchor = NXOpen.Annotations.OriginBuilder.AlignmentPosition.MidCenter;
builder.Origin.Origin.SetValue(null, null, new Point3d(370, 670, 0));
builder.Origin.SetInferRelativeToGeometry(true);
NXOpen.Annotations.ArcLengthDimension arcLengthDim;
arcLengthDim = (NXOpen.Annotations. ArcLengthDimension) builder.Commit();
builder.Destroy();
Arclength dimensions are not very common, of course, so this might seem like a strange example to choose. We
chose it because arclength dimensions can easily be created either directly or by using a builder, so we could
illustrate both approaches. The direct creation functions might appear simpler, but the builder approach provides
much more flexibility, so it’s worth spending a bit of extra time to become familiar with it.
■ Notes
To create a Note, typical code is:
The types and subtypes of tools are handled in a different fashion. As the code above shows, there is a
GetTypeAndSubtype function, which returns values from two enumerations, CAM.Tool.Types and CAM.Tool.Subtypes.
As you can see, the code turns on collision checking for all hole-drilling operations. For each operation, it creates a
builder, sets its CollisionCheck property to true, and then commits the builder to effect the change. To use this
approach, you have to know where to find the functions that create builders for various types of CAM objects (like
the CreateHoleDrillingBuilder function we used above). They can be found in two places. First, the
NXOpen.CAM.OperationCollection class contains functions that create builders for operations:
Secondly, the NXOpen.CAM.NCGroupCollection class contains functions that create builders for various types of CAM
“groups”, which include tools, CAM geometry, and machining methods:
As you can see, the code sets CoolantThrough = true for every milling tool.
■ CAM Views
Within a given setup, the NCGroup and Operation objects are arranged hierarchically. There are actually four
independent tree structures: the Geometry view, the MachineMethod view, the MachineTool view, and the
ProgramOrder view, which correspond with the four possible views shown in the Operation Navigator in
interactive NX:
Any given operation will appear in all four of these views. As the name implies, the four views just provide us with
four different ways of looking at the same set of operations. In NX Open, the four view types are described by the
four values of the NXOpen.CAM.CAMSetup.View enumeration. An NCGroup object has GetParent and GetMembers
functions, so we can navigate up and down each tree. An Operation object has a GetParent function that tells us its
parent in each of the four views. There is also a GetRoot function that gives us the root of each view tree.
So, the code to get the root of each view and the first-level members is as follows:
When we create a new “group” object (like a tool), it must be correctly placed in one of these four views, by
indicating which group should be its parent. When we create an operation object, it must be correctly placed in all
four views, so we need to specify four parents. Further details can be found in the next section, which discusses
creation of tools.
■ Creating a Tool
The NX Open process for creating a tool involves several steps. The basic code begins with something like the
following:
NXOpen.CAM.NCGroupCollection.UseDefaultName camFalse =
NXOpen.CAM.NCGroupCollection.UseDefaultName.False;
NXOpen.CAM.NCGroup toolGroup;
toolGroup = groups.CreateTool(machineRoot, "mill_planar", "BALL_MILL", camFalse, "T24");
NXOpen.CAM.Tool myTool = (NXOpen.CAM.Tool) toolGroup;
The definition of camFalse is not important; it’s only purpose is to avoid writing a very long line of code later on.
The most important function shown is CreateTool which (not surprisingly) creates a tool object. The first parameter
indicates which group should be the parent of the new tool; by specifying the machineRoot group, we are indicating
that the new tool should be placed at the top level of the MachineTool view hierarchy.
The “mill_planar” and “BALL_MILL” strings indicate the tool type and subtype respectively. These are the same
strings that appear in the Insert Tool dialog in interactive NX. Some example values for this pair of strings are:
Our next task is to specify specific values for various tool parameters like diameter and length. Since we have not
yet provided these values, our tool is just a generic “default” one. Continuing from above, the necessary code is:
toolBuilder.TlDiameterBuilder.Value = 4.5;
toolBuilder.TlHeightBuilder.Value = 15;
toolBuilder.TlNumFlutesBuilder.Value = 4;
toolBuilder.Description = "Example ball mill";
toolBuilder.HelicalDiameter.Value = 80.0;
toolBuilder.Commit();
toolBuilder.Destroy();
The pattern should be familiar, by now: we create a builder, modify its values, and then commit and destroy. This is
essentially the same editing process that we used in an earlier example. The only difference here is that we had to
create a default tool before we started the editing process.
Since around 2007, the NX user interface has been based on “block-based” dialogs, so-called because they are built
from a common collection of user interface “blocks”. So, for example, this dialog consists of four blocks, whose types
are indicated by the labels to the right
Each block has a specific type and purpose. So, looking at the four examples from the dialog above:
▪ An Enumeration block presents a set of options to the user, and asks him to choose one of them
▪ An Integer block allows the user to enter an integer (by typing, or by using a slider, for example)
▪ An Action Button block performs some action when the user clicks on it
▪ A String block displays text that the user can (sometimes) edit
Blocks of any given type are used in many different dialogs throughout NX. Application developers build dialogs
from blocks, rather than from lower-level items. This reduces programming effort for NX developers, and
guarantees consistency. Constructing a new Enumeration block (for example) requires very little code, and this
new Enumeration block is guaranteed to look and behave in exactly the same way as all other Enumeration blocks
within NX.
You can construct these same “block-based” dialogs in NX Open, so your add-on applications can look and behave
like the rest of NX. This chapter tells you how to do this. We will show you how to use Block UI Styler to design your
dialog. After your dialog is designed, we will show you how to make it function by adding code to the dialog
callbacks.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 103
■ How Block-Based Dialogs Work
The diagram below shows how your code interacts with a block-based dialog
First, your code creates and displays the dialog. Then, when the user starts to interact with the dialog, NX sends
messages back to your code, telling you what “events” occurred in the dialog. For example, NX might tell you that
the user entered some number, or clicked on the Apply button. Your code should have functions called “event
handlers” or “callbacks” that determine what should happen (if anything) in response to each event. The code
generator for Block UI Styler can create template functions for these event handlers. The dialog constructor
contains code to register the event handlers for specific dialog events, so that NX knows which event handler to call
for a particular dialog event; for example, we might stipulate that NX should call an event handler named
“apply_cb” when the user clicks the Apply button. If you want to create some geometry when the user clicks the
Apply button, you would put the code to create this geometry in your apply_cb function.
In this chapter, we’ll discuss how to create block-based dialogs. We will use Block UI Styler to define blocks and
arrange them on our dialog.
We’ll use an “OrthoLines” example that provides a simple dialog that lets the user create “infinite” lines in the
horizontal or vertical directions in the XY-plane.
It only has two blocks – an “Enumeration” block to let the user choose either horizontal or vertical, and a “Double”
block in which the user enters the offset distance (the distance from the line to the origin).
If you don’t want to create this dialog yourself, using the instructions in this chapter, then you can find a completed
version in […NX]\UGOPEN\NXOpenExamples\GS Guide\OrthoLines.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 104
dlx
Arrange
file
blocks Program
execution
Edit
Template Final
code code
Block Styler
If you click on one of the blocks shown above, its properties will be shown in the lower half of Block UI Styler
window, and you can edit them as you wish. Some of the more important properties are shown below:
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 105
When you have established all the blocks and properties you want, switch to the Code Generation tab in
Block UI Styler, and define the settings as shown below:
Finally, choose File → Save, which will generate a C# file, called OrthoLines.cs, and another file called
OrthoLines.dlx.
■ Template Code
When you save a dialog in Block UI Styler, a C# file is created containing template code. The idea is that you “fill in
the blanks” in this template code to define the way you want your dialog to behave. The contents of the C# file will
depend on the options you chose in Block UI Styler.
The code shown below is a bare minimum. We have removed all the error-checking and most of the comments, in
order to focus clearly on the essential concepts. In real working code, you should not do this, of course.
When you look at the code in your favorite editor, you will see something like this:
and so on ...
As you can see, we are defining a new class called “OrthoLines” to represent instances of our dialog. Notice that
there are two lines that declare variables called directionBlock and offsetBlock to hold the two blocks that make up
an “OrthoLines” dialog.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 106
Then, further down, you will see a constructor (we have removed the Try/Catch blocks to focus on the code):
Most of this code is adding “event handler” callbacks to our dialog, as we requested when we saved the dialog from
Block UI Styler. You do not need to edit this part of the generated file. You just need to add your code inside the
handler functions. This is where we can write code that responds to “events” in the dialog. For example, when the
user clicks the “Apply” button in the dialog, the “apply_cb” function will be called, so any code we place in that
function (see below) will be executed. In this way, we can make the Apply button do something useful when the
user clicks it.
Next, let’s look at the sections of the OrthoLines.cs file containing the handler functions we are supposed to edit so
that our dialog performs the tasks we want. Again, we have removed some error checking code to make the
concepts clearer. First, there is the “Main” routine:
The first two lines are automatically generated code that create a new “OrthoLines” dialog, and display it using the
“Show” function. You will usually not need to add any code here unless you have some special setup logic for your
dialog that you need to execute before the dialog is constructed.
The most interesting part of a dialog implementation is the code you put in the event handler functions, since this
code determines how the dialog will react. When working with BlockDialog objects, we normally use the term
“callback” rather than “event handler”, but the meaning is the same. In fact, the event handler functions used with
BlockDialog objects all have the suffix “_cb” for “Callback” appended to their names.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 107
So, let’s begin by making the Apply button do something interesting. In the apply_cb function, after the comment
that says “Enter your callback code here”, let’s add some code that writes a message to the Info Window:
Build and run the project. When the dialog appears, click on the Apply button, and this should cause a message to
be displayed in the NX Info window. This is not terribly exciting, admittedly, but it shows that the basic mechanism
is working — when the user clicks the Apply button, the code in our apply_cb function is getting executed.
You should try clicking the OK button, too. You will see that this also causes the same message to appear in the Info
window. This is because the default implementation of the ok_cb event handler just calls the apply_cb function and
then closes the dialog. So, our apply_cb code is getting executed when the user clicks OK, also.
Of course, what we’d really like to do is create a line when the user clicks the Apply button. Here’s a new version of
the apply_cb function that will do exactly that. Type it in, or copy/paste it, as usual, inside the Try block, after the
comment that says “Enter your callback code here”:
This code shows the typical pattern of an event handler — you retrieve information from the dialog blocks, and
then use this information to do what the user requested. As you can see, we use the ValueAsString property of
directionBlock to decide whether to create a horizontal or vertical line, and we read the offset distance from the
offsetBlock.Value property. We’re assuming that the user has set these values appropriately before clicking the
Apply button. The value we’re using for infinity is arbitrary, of course, and you will probably want to change it to
something larger if you design aircraft or ships.
If you build and run this code, you should find that it works nicely. Entering some information and clicking Apply
will create a line, as we expect. Clicking OK will also create a line, for the reasons outlined above. Happily, this is
exactly what we want.
To make our code a bit cleaner, and to prepare for the steps ahead, let’s re-organize a little. For reasons that will
become clear later, we’re going to package the code that creates an infinite line into a nice tidy function. Copy the
following code, and place it somewhere inside the OrthoLines class. Right at the bottom, just before the class ends
is a good place for it.
Note that we have made the function private, since it wouldn’t make sense to use it outside the OrthoLines class.
Now that we have this CreateLine function, we can make a much simpler version of our apply_cb function, like this
(the Try/Catch block has been removed):
The basic version of your OrthoLines function is now complete. Congratulations. In the next section we’ll add a
little more functionality to it, and learn how to use the update_cb function.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 108
■ The update_cb Event Handler
Suppose we want to create two different kinds of infinite lines — thin dashed ones and thick solid ones. A
convenient way to do this would be to place two new buttons on our dialog, like this:
Let’s suppose that we’re going to call these new buttons thinDashedButton and thickSolidButton. You can use Block
UI Styler to add two buttons to the bottom of your dialog. We have done this for you in the example OrthoLines2 in
[…NX]\UGOPEN\NXOpenExamples\CS\GSGuide\OrthoLines2. If you open the file OrthoLines2.cs for this example,
you will see two more lines near the top of the file, which declare the variables for the new buttons, like this:
Next, the initialize_cb function contains code to initialize the variables for the new buttons, where you will see the
following two lines:
You can build the project and run this code, and it should produce the dialog shown above. But, of course, the new
buttons won’t do anything until we write some event handler code for them.
The event handler code for the two new buttons should go in the update_cb function, like this:
if (block == thinDashedButton) {
myLine = CreateLine();
myLine.LineWidth = DisplayableObject.ObjectWidth.Thin;
myLine.LineFont = DisplayableObject.ObjectFont.Dashed;
myLine.RedisplayObject();
}
if (block == thickSolidButton) {
myLine = CreateLine();
myLine.LineWidth = DisplayableObject.ObjectWidth.Thick;
myLine.LineFont = DisplayableObject.ObjectFont.Solid;
myLine.RedisplayObject();
}
return 0;
}
You can see now why we wrote the CreateLine function — because we need to call it in two places in this code. We
are creating the lines when we click on either of the new buttons, so you can remove the code in apply_cb that we
used in the previous section to create the lines. The dialog should just close when we click on OK. Clicking on Apply
will execute the code in the apply_cb without closing the dialog. You could modify the dialog so that it only has a
Close button, but for now we will just leave the OK and Apply buttons on the dialog.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 109
NX calls our update_cb function whenever the user does anything with any block on the dialog. As you can see, the
update_cb function receives a UI block called block as input, which tells us which block the user “touched”. We write
a series of “If” clauses that test the value of block, and do different things in different cases. If we find that block has
the value thinDashedButton, for example, then we know that the user clicked the thinDashedButton button, so we
create a line that’s thin and dashed.
Of course, it’s possible that the user changed the line direction or the offset distance (rather than clicking one of
our two buttons). We could put some more code in the update_cb function to handle these events, too, if we
wanted. But let’s quit here. Build and run the project, and have some fun making infinite lines.
■ Callback Details
We’ve discussed the update_cb event handler and the apply_cb event handler quite a bit in the last few sections. But
some additional event handlers (callbacks) are available, too. The complete list of available callbacks is shown in
the Code Generation tab of Block UI Styler, and there you can choose the ones for which you want “stub” code
generated. The table below indicates when NX calls each of these:
The OK, Apply and Cancel callbacks should each return an integer value. In the Cancel callback, this returned value
is ignored, so its value doesn’t matter. In the OK and Apply callbacks, returning zero will cause the dialog to be
closed, and a positive value will cause it to remain open.
■ Precedence of Values
In many situations, the values the user enters into a dialog are stored internally within NX, so that they can be
reloaded and used as default values the next time the dialog is displayed. You may have noticed this happening in
the example above. This facility is called “dialog memory”. If your code is trying to control the contents of a dialog, it
is important to understand how this reloading from dialog memory fits into the overall process. The chain of events
is as follows:
(1) Values and options from the corresponding dlx file are used, then …
(2) Values and options specified in the initialize_cb function are applied, and then …
(3) Values from dialog memory are applied, and then …
(4) Values and options specified in the dialogShown_cb function are applied, and then finally …
(5) The dialog is displayed
So, you can see that values and options you set in the initialize_cb function might get overwritten by values from
dialog memory. Since the dialogShown_cb function is executed later, it does not suffer from this drawback. On the
other hand, the initialize_cb function can set values that the dialogShown_cb function cannot. So, in short, the
initialize_cb function gives you broader powers, but the dialogShown_cb function gives you stronger ones.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 110
Block UI Styler. Also, the NXOpen samples folder contains eight examples of Block UI Styler dialogs. Its location is
typically […NX]\UGOPEN\SampleNXOpenApplications\.NET\BlockStyler. The dialog elements used in Block UI
Styler dialogs are documented in the NXOpen.BlockStyler namespace section of the NX Open .NET API Reference
Manual.
Getting Started with NX Open Chapter 14: Block-Based Dialogs Page 111
Chapter 15: Selecting NX Objects
In order to perform some operation on an NX object, the user will often have to select it, first. So, we need some
way to support selection in our NX Open programs. You can use either a free-standing Selection object or a
SelectObject block on a block-based dialog. The two approaches have much in common, and this chapter describes
both of them.
■ Selection Dialogs
One way to support selection in NX Open is to use the tools in the NXOpen.Selection class. The general process is:
▪ You get the Selection object from the NXOpen.UI
▪ You define some variables for the selection parameters, if necessary
▪ You call one of the selection methods on it, so that it can gather information from the user
▪ A Selection.Response is returned to you, as well as the selected objects if the user did not cancel the selection
Here is a short snippet of code illustrating this process:
var response = selManager.SelectTaggedObject(cue, title, scope, highlight, types, out obj, out
cursor);
When the code shown above is executed, a small dialog appears giving the user the opportunity to select a curve.
If the user selects a curve and clicks OK, the selected curve will be returned to your code in the selectedObject
variable, so you can do whatever you want with it. In the example above, we chose to make the curve hidden
(blanked).
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 112
Following are some details of the variables that affect the behavior of the dialog:
The cue and title variables are self-explanatory, so we won’t discuss them further.
The scope argument indicates the domain from which the user will be allowed to select objects. In this case, we
have specified that the selection scope should be the work part. The scope options correspond exactly to the
choices shown by the Selection Scope menu on the Selection toolbar in interactive NX.
The typeArray argument determines what type of object the dialog will allow the user to select. The NX Selection
Filter will be pre-set according to the value of the type argument, and this restricts the user to choosing only
certain types of objects. There are several other ways of specifying the types of entities that will be eligible for
selection. Details are given below.
The response object returned by the function indicates how the user interacted with and closed the dialog
(whether he clicked OK or Cancel, for example). The function also returns the selection results through two output
arguments: the selectedObject argument indicates which object was selected, and the cursor argument returns the
pick point of the selection. You can think of selection as a process of shooting an infinite line (the cursor ray) at
your model. The object that gets selected is one that this ray hits, or the one that’s closest to the ray. The pick point
is the intersection of the cursor ray with your model.
The example code shows the typical process — you normally check the value of the response and then do
something to the selected object based on this value.
Mask Triples
If you need more control over the types of objects that you want to select, you can use the other SelectTaggedObject
overloaded methods on the Selection object. These methods use mask triples to specific the type of object to be
selected. Mask triples are a set of three integers in a structure called MaskTriple. The parts of this structure are
integers called Type, Subtype, and SolidBodySubtype. The class NXOpen.UF.UFConstants contains labeled integer
constants used in NX Open and some of these constants are the parts of the mask triple. Usually, you set the Type to
select a particular type of object, and Subtype to select those object of that Type that have a particular property.
The Type and Subtype in the mask triple usually correspond with the type and subtype of the object. The
SolidBodySubtype is usually 0 except for solid geometry types and some other special object types where it
represents another detail subtype.
The following table lists the mask triples for some commonly used objects. The Type and Subtype are the named
constants from the NXOpen.UF.UFConstants class. These constants are actually defined in the files uf_object_types.h
and uf_ui_types.h, which you can find in […NX]\UGOPEN. In some cases, the constants might be easier to find in
these two files, rather than in the UFConstants documentation.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 113
Object Type Subtype
Point UF_point_type 0
Line UF_line_type 0
Circles and Arcs UF_circle_type 0
Conic - Ellipse UF_conic_type UF_conic_ellipse_subtype
Conic - Parabola UF_conic_type UF_conic_parabola_subtype
Datum Axis UF_datum_axis_type 0
Datum Plane UF_datum_plane_type 0
Spline UF_spline_type 0
Horizontal Dimension UF_dimension_type UF_dim_horizontal_subtype
Vertical Dimension UF_dimension_type UF_dim_vertical_subtype
Parallel Dimension UF_dimension_type UF_dim_parallel_subtype
Drafting Note UF_drafting_entity_type UF_draft_note_subtype
Drafting Centerline UF_drafting_entity_type UF_draft_cntrline_subtype
If you wish to select all objects of a particular type, you can use the special value UF_all_subtype for the Subtype of
the mask triple.
Mask triples for elements of solid or sheet bodies (bodies, faces, and edges) use a type of UF_solid_type, a subtype
of 0, and use the SolidBodyType to specify the type of the geometry. The following table lists some of the
SolidBodySubtype values for different types of geometry on solid or sheet bodies.
Object SolidBodySubtype
Solid Body UF_UI_SEL_FEATURE_BODY
Sheet Body UF_UI_SEL_FEATURE_SHEET_BODY
Any Edge UF_UI_SEL_FEATURE_ANY_EDGE
Linear Edge UF_UI_SEL_FEATURE_LINEAR_EDGE
Circular Edge UF_UI_SEL_FEATURE_CIRCULAR_EDGE
Any Curve or Edge UF_UI_SEL_FEATURE_ANY_WIRE_OR_EDGE
Any Face UF_UI_SEL_FEATURE_ANY_FACE
Planar Face UF_UI_SEL_FEATURE_PLANAR_FACE
Cylindrical Face UF_UI_SEL_FEATURE_CYLINDRICAL_FACE
You can look at the NXOpen.UF.UFConstants class for a more complete set of values. The values associated with
UF_solid_type objects all use the prefix UF_UI_SEL_FEATURE, so they are not too difficult to find. Again, if you
prefer, you can find the same values in the file uf_ui_types.h in […NX]\UGOPEN.
You use different methods from the NXOpen.Selection class to select objects using mask triples. The following code
snippet selects lines using a mask triple.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 114
var theUI = UI.GetUI();
var selMgr = theUI.SelectionManager;
TaggedObject selectedObject;
Point3d cursor;
var cue = "Please select a line to be hidden";
var title = "Select Lines";
var scope = NXOpen.Selection.SelectionScope.AnyInAssembly;
var action = NXOpen.Selection.SelectionAction.ClearAndEnableSpecific;
var includeFeatures = false;
var keepHighlighted = false;
var lineMask = new NXOpen.Selection.MaskTriple(NXOpen.UF.UFConstants.UF_line_type, 0, 0);
NXOpen.Selection.MaskTriple[] maskArray = new[] { lineMask };
var response = selMgr.SelectTaggedObject(cue, title, scope, action,
includeFeatures, keepHighlighted, maskArray, out selectedObject, out cursor);
The primary reason to use mask triples over the simpler SelectionType is to allow finer granularity over the types
of objects you are selecting. This is illustrated in the following example, where we want to allow the user to select
either a circular edge or a cylindrical face (because either of these could represent a hole in a part, perhaps):
Selecting a Feature
The method SelectFeatures will display a selection dialog with a list of the features in the work part. You can select
a feature from among the feature names in the list, the feature geometry in the graphics region, or the feature node
in the Part Navigator. This code snippet shows how to use the method and the following picture shows an example
of the feature list dialog.
UI theUI = UI.GetUI();
var selMgr = theUI.SelectionManager;
var cue = "Please select a feature to get info";
var featType = NXOpen.Selection.SelectionFeatureType.Browsable;
NXOpen.Features.Feature[] featArray;
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 115
Specifying a Screen Position
The method SelectScreenPosition allows you to prompt the user to pick a location on the graphics display. The
coordinates of the point are given by the intersection of the cursor ray of the selection with the X-Y plane of the
WCS. The following snippet prints the coordinates of the selected screen location and the view name to the listing
window.
UI theUI = UI.GetUI();
var selMgr = theUI.SelectionManager;
var cue = "Please select screen position";
View theView;
Point3d pt;
if (resp == NXOpen.Selection.DialogResponse.Pick) {
Guide.InfoWriteLine(string.Format("Point location: ({0:F3}, {1:F3}, {2:F3})", pt.X, pt.Y,
pt.Z));
Guide.InfoWriteLine("View name: " + theView.Name);
}
Multiple Selection
So far, the NXOpen.Selection methods we have been discussing only let you select one object at a time. There are a
set of methods similar to the ones covered above that allow you to select one or more objects in a single selection
operation. All the methods allow you to specify the cue, title, and selection scope for the selection and they all
return the selected object or objects. The following table summarizes the different selection methods we have been
talking about that only select a single object during the selection.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 116
The following table summarizes the equivalent methods that allow selecting multiple objects in a single selection:
Using these SelectTaggedObjects methods will cause the standard NX multi-selection dialog to appear
This dialog allows the user to select objects in all the usual ways. As with single selection, the available options in
the selection filter will be pre-set to restrict the range of different object types that are selectable.
The selection result is returned in a TaggedObject array that holds all the selected objects. Typically, your code will
cycle through this array, doing something to each object in turn. For example:
UI theUI = UI.GetUI();
var selMgr = theUI.SelectionManager;
var response = NXOpen.Selection.SelectTaggedObjects(cue, title, scope, action,
includeFeatures, keepHighlighted, maskArray, objects);
if (response != NXOpen.Selection.Response.Cancel) {
foreach (var obj in objects) {
var dispObj = (DisplayableObject) obj;
dispObj.Blank();
}
}
You can use standard .NET functions on the array of selected objects. For example, objects.Length gives you the
number of objects selected, and objects.ConvertAll lets you convert it to some other type.
■ SelectObject Blocks
Sometimes, you will want to support selection inside a larger block-based dialog, rather than using a standalone
selection dialog. To do this, you place a SelectObject block on your dialog. As we know from the previous chapter,
you use Block UI Styler to create block-based dialogs in NX Open. We’ll be creating a simple Block Dialog containing
a Select Object block in the example below. The basic steps are as follows:
▪ You open Block UI Styler
▪ You add a SelectObject block to your dialog
▪ You adjust the block’s characteristics and behavior, if necessary
▪ You adjust the code generation settings for the dialog
▪ You save your dialog to a C# file and a dll file
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 117
▪ You edit the callbacks in the generated C# file to add the behavior for your dialog.
Here are some snippets of the dialog callbacks illustrating the use of SelectObject block on a Block Styler dialog. We
have omitted the class declaration and the New method since you should not have to change the code generated
from Block Styler.
The MaximumScope property has type “Enum” when shown in Block Styler, but, as you can see, the code above sets
its value using a string. The only legal values of the string are “Within Work Part Only”, “Within Work Part and
Components”, or “Entire Assembly”. These strings are case sensitive, and spaces do count. You can find the legal
string values by looking at the property in Block Styler, or by calling the function GetMaximumScopeMembers.
Using string values to work with Block Styler “Enum” properties is a fairly common practice — the SelectMode and
StepStatus properties use the same technique, for example.
When this code is executed, a small dialog appears, giving the user the opportunity to select a curve or edge:
If the user selects a curve and clicks OK, the curve will be hidden (blanked). With the filter set to
FilterTypes.CurvesAndEdges, the user can select edges, too. However, an edge can never be hidden — its visibility is
always determined by the visibility of its owning body.
Just as we saw with the Selection.Dialog earlier, there is a SetFilter function that determines what type of object the
block will allow the user to select. Several properties of the SelectObject block let you control what type of objects
to select. The following table lists some of the SelectObject block properties that control selection. More details are
in the Block Styler Reference Guide. As we saw above, you often use string variables to work with properties that
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 118
have type “Enum” in Block Styler. Properties that use this approach are listed as Type “string (Enum)” in the table
below:
Several methods allow you to filter the type of objects to select, and to get the objects selected by the user.
You can use the AddFilter methods if you just need to select objects from one of the broad categories listed above or
from one mask triple type. If you need to select objects from several distinct types, or need more control of the type
of object to be selected, use the SetSelectionFilter method.
After the user has selected some objects, you can retrieve the selected objects using the GetSelectedObjects method
and process them however you wish.
CurveCollector Block
The CurveCollector block has some integer properties where the bits of the integer represent options that you can
turn on or off by setting that particular bit to 0 or 1. The integer property CurveRules specifies which curve
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 119
selection intent rules should be available for your block. Curve rules that are set to 1 will be added to the Curve
Rule drop down menu on the Selection Bar. When your CurveCollector block is active, the user may select one of
these curve selection rules to use for selecting curves. The integer property EntityTypes specifies which entity types
should be selectable by your block. Block UI Styler creates helper variables in the code generated for your dialog to
make it easier to set these integer properties. The following tables list some commonly used helper variables that
Block UI Styler creates for these properties. A list of curve rules with detailed information is contained in “Selection
Intent rules and options on the Top Border bar” in the Fundamentals chapter of the NX documentation.
For example, if you want to select either curves or edges, and use the Single Curve, Tangent Edges, or Vertex Edges
rules, you would use the following code in your initialize callback:
FaceCollector Block
The FaceCollector block has some integer properties where the bits of the integer represent options that you can
turn on or off by setting the particular bit to 0 or 1. The integer property FaceRules specifies which face selection
intent rules should be available for your block. The integer property EntityTypes specifies which entity types should
be selectable by your block. Block UI Styler creates helper variables in the code generated for your dialog to make it
easier to set these integer properties. The following tables list some commonly used helper variables that Block UI
Styler creates for these properties. A list of face rules with detailed information is contained in “Selection Intent
rules and options on the Top Border bar” in the Fundamentals chapter of the NX documentation.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 120
Face Rule Bit Value Meaning
Adjacent Faces FaceRules_AdjacentFaces Picking a face will select that face plus the faces adjacent to it.
All Blend Faces FaceRules_AllBlendFaces Picking a blend face will select all the faces of the blend.
Body Faces FaceRules_BodyFaces Picking any face of a body will select all the faces of that body.
Feature Faces FaceRules_FeatureFaces Picking any face of a feature will select all the faces of the feature.
Single Face FaceRules_SingleFace Supports single selection of faces. This rule is required to be on
(required) for the FaceCollector block.
Tangent Faces FaceRules_TangentFaces Picking a face will select all the faces tangent to the selected one.
For example, if you want to select either faces or datums, and use the Single Face, Tangent Faces, or Body Faces
rules, you would use the following code in your initialize callback:
For more details about selection intent rules, see the “Controlling object selection using the Top Border bar”
category of the “Selecting objects” section of the Fundamentals chapter of the NX documentation. For more details
about the CurveCollector and FaceCollector blocks, look in the Block UI Styler Guide.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 121
This next example moves all the sheet bodies in the work part to layer 200:
Next, this example assigns color #36 (which is a green color, by default) to each planar face:
Cycling through all of the objects in a part file is a bit more complex. The following code shows one approach. For
each object encountered, we write its name (which could possibly be an empty string) to the Info window:
NXObject thisObject;
NXOpen.Tag thisTag = NXOpen.Tag.Null;
do {
thisTag = ufs.Obj.CycleAll(workPart.Tag, thisTag);
if (thisTag != NXOpen.Tag.Null) {
thisObject = (NXOpen.TaggedObject) NXOpen.Utilities.NXObjectManager.Get(thisTag);
NXOpen.Guide.InfoWriteLine("Name: " + thisObject.Name);
}
For further information, please refer to the documentation in the NXOpen Reference Guide for the functions
CycleAll, CycleObjsInPart, and CycleTypedObjsInPart.
Getting Started with NX Open Chapter 15: Selecting NX Objects Page 122
Chapter 16: Exceptions
Throughout most of this document, we have assumed that all code works without errors, because we did not want
error handling issues to complicate the discussion. But in reality, almost all code could potentially run into
problems of one sort or another, so proper error handling and recovery is very important. Without it, there is some
danger that NX will be left in an unpredictable state.
■ Exceptions
When some piece of code encounters a situation that it cannot handle, it must signal this somehow. In modern C#
code, an error condition is indicated via an “exception”. We say that the problematic code “raises” or “throws” an
exception. Some examples of situations that might cause this to happen are:
▪ Trying to perform some operation on an object that is null
▪ Trying to divide by zero
▪ Trying to access an array element that is beyond the bounds of the array
▪ Trying to access a file that doesn’t exist
▪ Trying to create an NX circle with zero radius
In order for the program to continue, the exception must be handled by the function that encountered it, by the
function that calls this function, or by some other higher level function. The exception is passed up the “call stack”
from called function to calling function until it is handled. If the exception is not handled anywhere in the call stack,
the program will terminate.
The code to handle an exception has the following basic structure:
try {
// Some code that might encounter a problem
}
catch (System.Exception ex) {
// Code to react to the problem
}
So, the code that might encounter a problem is placed in a “try” block. If a problem arises, an exception is raised,
and control is transferred immediately to the “catch” block, where we insert some code to react to the problem. The
exception object is available in the variable named in the catch statement (the variable “ex” in the example above),
so the code within the catch block can use it.
Your code might include several catch blocks, each handling exceptions of a specific type. The system examines
these catch blocks in order, looking for one that handles the type of exception that arose. Here are some examples
of common types of exceptions, corresponding to the problems listed above:
This method of dealing with errors is called “structured exception handling”, and it is widely used in modern C#
programs, and also in other programming languages, so you can easily find tutorial materials discussing it.
string s1 = "hello";
double x1 = double.Parse(s1);
}
}
This code tries to parse a given string and convert it to a double. This will work fine with a string like “3.14”, but it
obviously won’t work with the string “hello”. The example is rather silly, and we can immediately see what the
problem is. However, a very similar situation arises if we ask the user to type in a number — there is nothing to
stop someone from typing “hello”, instead of a number, so parsing errors of this type are quite common. If we run
this code from the command prompt, here is what happens:
As you can see, the System.Number.ParseDouble function raises a System.FormatException, complaining that the
input string was not in a correct format. The exception is not handled, so it is passed up the call stack to the
System.Double.Parse function, which again does not handle it. Eventually, the exception reaches our MyCode.Main
function, where it again goes unhandled, so our program crashes.
The situation is slightly better if we run this code from inside NX using File → Execute. We get the following error:
This is almost exactly the same sequence of error messages that we saw before. The only difference is that the first
line now says that the exception was caught, and did not go unhandled. If we run the same code in the NX Journal
Editor, we get a slightly more helpful error message that tells us in which line of code the error occurred:
■ Handling an Exception
Next, let’s modify our flawed code, and handle the System.FormatException ourselves, so that it does not “bubble
up” to the high-level exception handling mechanism inside NX. Here is the revised version:
string s1 = "hello";
try {
double x1 = double.Parse(s1);
}
catch (System.FormatException ex) {
Guide.InfoWriteLine(ex.Message);
Guide.InfoWriteLine ("Idiot. That string isn't a number.");
}
This code runs without any visible errors, and we get the following output in the NX Info window:
The erroneous call to double.Parse is inside a try block, so the exception is caught, control passes to our catch block,
and two lines of text are written out to the Info window. The first line is the text from the Message property of the
exception, and the second line provides some further information about what (probably) went wrong.
■ Exception Properties
In the code above, we made use of the Message property of an Exception. There are some other properties that are
also useful, sometimes:
Property Description
Message The error message associated with this exception.
InnerException The Exception instance that caused the current exception.
Source The name of the application or the object that caused the error.
StackTrace A string representation of the call stack at the time the exception was thrown.
TargetSite The method that threw the current exception.
ToString() Returns a string representation of the exception
We have seen this sort of text before in various error messages, of course — it appears that those error messages
might have been constructed just by using the output from the ToString() function.
■ NX Exceptions
The exceptions thrown by NX are all of type NXOpen.NXException, which is derived (indirectly) from
System.Exception. In addition to the general properties of System.Exception listed above, an NXOpen.NXException
has a useful property called ErrorCode, which allows us to distinguish one type of error from another. Typically,
your code will test the value of the ErrorCode property, and branch accordingly. Here is an example that deals with
some problems that might arise when creating a circular arc:
try {
workPart.Curves.CreateArc(center, axisX, axisY, radius, angle0, angle1);
}
catch (NXOpen.NXException ex) {
if (ex.ErrorCode == 1710021) {
Guide.InfoWriteLine("Radius must be at least 1e-9.");
Guide.InfoWriteLine(ex.ToString());
}
else if (ex.ErrorCode == 1710014) {
Guide.InfoWriteLine("Angular span must be at least 1e-11 radians.");
Guide.InfoWriteLine(ex.ToString());
}
else {
Guide.InfoWriteLine("Unknown problem in creating arc.");
Guide.InfoWriteLine(ex.ToString());
}
}
By testing the value of the ErrorCode, we can determine what went wrong and provide error messages that are a bit
more helpful than “Invalid Arc Radius” or “Illegal Arc Length”.
For a given NX Open function, there is unfortunately no documentation that indicates what values of ErrorCode it
might return, so you have to discover these by trial and error.
try {
// Try something risky (more risky than just creating a sphere, typically)
Guide.CreateSphere(3,0,0, 1);
// It worked, so remove the Undo mark
theSession.DeleteUndoMark(myMark, myMarkName);
}
catch (NXOpen.NXException ex1) {
// Sphere creation failed, so Undo back to the mark
theSession.UndoToMark(myMark, myMarkName);
}
■ Avoiding Exceptions
In many cases, it’s possible to avoid exceptions. For example, you can often test input data before passing it to a
function that might have trouble with certain values. This might improve performance slightly if many exceptions
are involved, because raising exceptions is time-consuming. More importantly, removing try/catch blocks
sometimes makes your code easier to read. In the arc creation shown in the code above, we could have easily
avoided the two specific exceptions by writing:
Of course, the try/catch block will still be needed unless you can anticipate all conceivable problems that might
arise when calling the CreateArc function.
In some cases, the .NET framework provides functions that are specifically designed to help you avoid exceptions.
Failure of the Parse function when converting a string to a number (as in our earlier example) is very common, so
there is a special TryParse function that will not throw an exception if it fails.
However, there are certain exceptions are simply unavoidable. For example, when you try to open a file, it may
happen that the file does not exist, in which case a FileNotFound exception will be raised. You could test to see if
the file exists before trying to open it, but even this is not fool-proof – there is some (very small) chance that the file
was deleted after you tested but before you opened it.
try
// Some code that might encounter a problem
catch (Exception ex)
// Code to react to the problem
finally
// Cleanup code that must be executed
try {
// Try to create and display a WidgetDialog
theWidgetDialog = new WidgetDialog();
theWidgetDialog.Show();
}
catch (Exception ex) {
// If an exception was raised, display an error message
var theUI = NXOpen.UI.GetUI();
var errorType = NXOpen.NXMessageBox.DialogType.Error;
theUI.NXMessageBox.Show("WidgetDialog error", errorType, ex.ToString());
}
finally {
// Regardless of what happened, free the resources used by the dialog
theWidgetDialog.Dispose();
}
}
The call to the Dispose function is needed to ensure that resources used by theWidgetDialog are correctly released.
By placing this call in the finally block, we are ensuring that it will be executed regardless of whether an exception
occurred or not.
This chapter describes a few things that might go wrong as you are working through the examples in this guide,
and how you can go about fixing them. If they occur at all, you will probably encounter these problems fairly early
in your learning process. But then, once you solve them, they will probably not re-appear, and you should be able to
continue your exploration without any further troubles.
The “external library” is your code, and the message is telling you there’s something wrong with it. The “system
log” that the message mentions is the NX Log File (traditionally known as the NX “syslog”), which you can access
via the Help → Log File command from within NX. This log file typically contains a large amount of text, some of
which can be very useful in diagnosing problems. After an error, the useful information is usually at the bottom of
the syslog, so you should start at the end and work backwards in your search for information. The typical text,
about a dozen lines from the end of the syslog, will look something like this:
Caught exception while running: Main
NXOpen.NXException: Attempt to use an object that is not alive
at NXOpen.TaggedObject.get_Tag()
at NXOpen.DisplayableObject.Blank()
at MyApp.MyProgram.Main() in c:\users\yamada\Projects\MyApp\MyApp\MyProgram.cs:line 13
I deliberately caused this error by deleting an object and then trying to “Blank” it (make it hidden). As you can see,
NX is quite rightly complaining that I am attempting to use an object that is no longer alive, and this caused the
get_Tag function to fail. The syslog text is quite helpful here, as is often the case. When things go wrong, it’s usually
a good idea to look at the messages near the end of the syslog, to see if there is any useful information.
If you run into this problem at all, it will probably be the first time you try to build an NX Open application in Visual
Studio. It arises because your code is using the NXOpen library, and this is not connected in any way to your current
project. The message is misleading — Session certainly is a member of NXOpen, as we well know, but the compiler
doesn’t know anything about NXOpen, so it complains.
For confirmation, look in the References folder in the Solution Explorer pane (usually in the upper right of the
Visual Studio window). If you don’t see NXOpen listed there, then this explains the problem. This situation could
arise because you used some generic template (rather than an NXOpen template) to create your project, as we
described in example 4 in chapter 3. Fortunately, this problem is easy to fix. From the Project menu, choose Add
Reference. In the dialog that appears, click on the Browse tab, and navigate to the […NX]\NXBIN\managed folder:
Select the five needed DLLs, as shown above, and click OK. Your project now has references to the NX Open
libraries, and this should stop the complaints.
This problem will happen only when using Visual Studio. When you run code in the Journal Editor, referencing of
the various NX/Open libraries is all handled inside NX, so it’s not likely to go wrong.
In this case, you will receive error messages like this when you build your project:
To fix the problem, you have to delete the broken references and create new ones. Right-click on each reference in
Solution Explorer, and choose “Remove”. Then create new references as described in the previous section.
The NX Open application templates use the UGII_BASE_DIR environment variable to establish the references, so, if
this environment variable is set incorrectly, you’ll get annoying broken references in every project you create.
Again, this indicates that 64-bit NX was unable to load and run your application because it was built for a 32-bit
architecture. With the full version of Visual Studio, you can avoid this problem by specifying what type of
application you want to build. Choose Project → Properties, and set the Target CPU to x64 (not x86 or AnyCPU), as
shown below:
In Visual Studio Community, there is less flexibility in this area, so you have to be careful to base your projects on
the right type of template. With some of the Visual Studio “Console Application” templates, the default target is x86
or AnyCPU, so you will run into problems if you are using a 64-bit version of NX. If you always use the NXOpen
project templates we provide, then things should go smoothly.
Here we describe a few “helper” functions that are intended to make the example code in this document shorter
and easier to understand. Since their only purpose is to improve the readability of this guide, we call them Guide
functions. For instance, our example code often uses sphere features to illustrate some concept. Rather than
repeating the dozen or so lines of code required to create a sphere, we have captured that code in the simple
Guide.CreateSphere function shown below.
The functions are very simple and limited. For example, they create “dumb” curves, rather than associative ones,
and they don’t use expressions. The goal was to make the functions easy to understand and easy to call. Though you
may find uses for them in the code you write, their intended purpose is purely expository.
The descriptions below are provided here just for convenience. The Guide functions are also described in the
NX Open Reference Guide, of course.
■ InfoWrite
Writes a string to the Info window (with no newline added)
InfoWrite(string info)
■ InfoWriteLine
Writes a string to the Info window (with a newline added)
InfoWriteLine(string info)
■ CreatePoint
Creates an NXOpen.Point object
Point CreatePoint(double x, double y, double z)
■ CreateLine
Creates an NXOpen.Line object
Line CreateLine(double x0, double y0, double z0, double x1, double y1, double z1)
■ Unite
Unites two bodies to create a boolean feature
NXOpen.Features.BooleanFeature Unite(NXOpen.Body target, NXOpen.Body tool)
■ CreateCylinder
Creates a cylinder feature, given its base point, axis vector, diameter, and height
CreateCylinder(Point3d origin, Vector3d axis, double diameter, double height)
■ CurvePoint
Calculates a point on a curve at a given parameter value
Point3d CurvePoint(Curve curve, double t)
■ CurveTangent
Calculates a unit tangent vector on a curve at a given parameter value
Vector 3d CurveTangent(Curve curve, double t)