Interfacing with the Delphi drawing format program to query, create, and/or modify the drawing format is a fairly easy process when using the available API classes. There are two C++ classes provided to interface with the drawing format and the blocks on it. These classes are Format and Block. This tutorial presents a simple project that demostrates the main methods that third parties will use to query and manipulate the drawing format. This tutorial will assume the user is using the windows platform, but the completed project under DSL_BASE_DIR_/format_api_examples/basic has a makefile that will work for HPUX.
The first thing is to download the Boost C++ library. The drawing format API currently uses one class(shared_ptr) in this library to simplify the implementation and use. Simply download the latest release and extract it somewhere on your system. No need to build or install anything since you will only be using a header file and not linking to anything.
At the time of writing, NX9.0 programing in C++ requires VS .NET 2012(11.05). This version will probably be required through NX9 with NX10 needing an upgrade to VS. NET 2013(12.0). The exact compiler version must be used due to differences in the C++ runtime libraries between the various VS versions. We are essentially developing a plugin for NX so we have to link against the same C++ runtime that NX does so no conflicts occur. So make sure the correct VS version for NX development is installed before proceeding with the tutorial.
If you installed NX before the required VS version, read these instructions to install the NX VS Wizard for NX 3.
Here is a updated link from GTAC, you will need a WebKey Username and Password from Siemens to access:
Getting Started: Using the NXOpen Wizards to set up a Visual Studio project.The VS project that is created by the NX wizard uses the UGII_BASE_DIR environment variable to locate the NX headers and libraries. Open the System control panel and make sure this variable is pointing to where NX is installed.
Now that we have the development environment setup, we can begin the project.


You can press the Finish button here since we do not care about the generated source code created by the wizard. We plan on totally replace it.
We now have a created NX Open C++ API project. Next we need to modify the project settings and delete the generated source code.

Now, select Project->Properties to bring up the solution properies dialog.

Whew, now the project should be ready to start adding code and building.
Before we do anything with the format program API, let's create a basic program skeleton and some useful utilities.

#include <string>
#include <uf.h>
#include <uf_ui.h>
#include <NXOpen/NXException.hxx>
namespace Utils
{
// throw exception if error is returned from Open C API function
inline void handleLegacyError( int error )
{
if ( error )
throw NXOpen::NXException::Create(error);
}
// Grabs a UFUNC license for legacy Open C API calls.
// This will not be needed starting in NX 4 due to license changes.
class LegacyNxSession
{
public:
LegacyNxSession()
{
handleLegacyError(UF_initialize());
}
~LegacyNxSession()
{
handleLegacyError(UF_terminate());
}
};
// convenient free functions to display message dialogs
int confirmDialog( const std::string& title, const std::string& message );
void postInfoMessage( const std::string& title, const std::string& message );
void postErrorMessage( const std::string& title, const std::string& message );
}
#include "util.hpp"
namespace
{
int displayDialog( const std::string& title, const std::string& message,
UF_UI_MESSAGE_DIALOG_TYPE type,
UF_UI_message_buttons_t *buttons )
{
int response;
char *msg[1] = {const_cast<char*>(message.c_str())};
Utils::handleLegacyError(UF_UI_message_dialog( (char*)title.c_str(),
type, msg, 1, TRUE,
buttons, &response ));
return (response);
}
}
namespace Utils
{
int confirmDialog( const std::string& title, const std::string& message )
{
UF_UI_message_buttons_t theButtons = { TRUE, FALSE, TRUE, 0, 0, 0,
UF_UI_OK, 0, UF_UI_CANCEL };
return ( displayDialog( title, message, UF_UI_MESSAGE_QUESTION,
&theButtons ) );
}
void postInfoMessage( const std::string& title, const std::string& message )
{
UF_UI_message_buttons_t theButtons = { TRUE, FALSE, FALSE, 0, 0, 0,
UF_UI_OK, 0, 0 };
displayDialog( title, message, UF_UI_MESSAGE_INFORMATION, &theButtons );
}
void postErrorMessage( const std::string& title, const std::string& message )
{
UF_UI_message_buttons_t theButtons = { TRUE, FALSE, FALSE, 0, 0, 0,
UF_UI_OK, 0, 0 };
displayDialog( title, message, UF_UI_MESSAGE_ERROR, &theButtons );
}
}
We now have a few utility functions and class that we can use later. Now let's create the application skeleton in basic.cpp. Since this is a simple application, I will do everything using free functions and not create any application specific classes. Place the following code in basic.cpp.
#include <dph_format.hpp>
#include "util.hpp"
#include <sstream>
#include <NXOpen/ugmath.hxx>
#include <NXOpen/Part.hxx>
#include <NXOpen/Session.hxx>
#include <NXOpen/PartCollection.hxx>
#include <NXOpen/Drawings_DrawingSheetCollection.hxx>
using NXOpen::Drawings::DrawingSheet;
using namespace Utils;
namespace
{
void createFormat( DphFormat::Format& format )
{
}
void displayFormatInfo( DphFormat::Format& format )
{
}
}
void ufusr(char *param, int *retcod, int param_len)
{
try
{
// get a UFUNC license for any Open C API functions that are used
LegacyNxSession session;
NXOpen::Part *workPart = NXOpen::Session::GetSession()->GetParts()->GetWork();
if ( workPart == NULL )
{
postErrorMessage("Error", "There is no work part." );
return;
}
DrawingSheet *currentDrawing = workPart->GetDrawingSheets()->GetCurrentDrawingSheet();
if ( currentDrawing == NULL )
{
postErrorMessage("Error", "There is no current drawing." );
return;
}
DphFormat::Format format(currentDrawing);
if ( format.borderId().empty() )
createFormat(format);
else
displayFormatInfo(format);
}
catch ( const std::exception& error )
{
postErrorMessage( "Error", error.what());
}
}
After saving basic.cpp, go ahead and build the project and try running the resultant dll in NX. It does not do much except complain if you tried running it without a work part or an active drawing. Examining the code you just blindlessly copy and paste, there is this little tidbit:
DphFormat::Format format(currentDrawing);
if ( format.borderId().empty() )
createFormat(format);
else
displayFormatInfo(format);
The above code tells the application to create a drawing format on the current drawing if one does not exist. If a drawing format does exist, display some information about the format to the user. Looking further up the source file you see the createFormat and displayFormatInfo free functions already defined but empty. They will be filled in momentarily.
Now that we have the skeleon of the application created, we are ready to actually do something. Let's create a Delphi Corporate Cut Size format at size A3. Before proceeding, we need to know the IDs for the border and size. I fired up the NX and select File->Utilities->Drawing Format Configuration and open the dph_corp.fordef file. You will immedidately see a border with ID dphCut and edit that border will verify that is the border we want. While still in the dialog, we also find that the size we want has ID a3.
Armed with this information, we can now create the drawing format. Paste the following code in the createFormat function.
using NXOpen::Session;
Session *theSession = NXOpen::Session::GetSession();
Session::UndoMarkId markId;
try
{
markId = theSession->SetUndoMark(NXOpen::Session::MarkVisibilityInvisible, "Create Format");
handleLegacyError(UF_DISP_set_display(UF_DISP_SUPPRESS_DISPLAY));
format.addBorder( "dphCut", "a3" );
// here is where the block stuff will be done
handleLegacyError(UF_DISP_set_display(UF_DISP_UNSUPPRESS_DISPLAY));
handleLegacyError(UF_DISP_regenerate_display());
}
catch ( const std::exception& )
{
if ( markId != 0 )
{
theSession->UndoToMark(markId, theSession->GetUndoMarkName(markId));
theSession->DeleteUndoMarksUpToMark(markId,theSession->GetUndoMarkName(markId), false );
}
handleLegacyError(UF_DISP_set_display(UF_DISP_UNSUPPRESS_DISPLAY));
handleLegacyError(UF_DISP_regenerate_display());
throw;
}
theSession->DeleteUndoMarksUpToMark(markId,theSession->GetUndoMarkName(markId), false );
The above code may look a little overwhelming, but it includes some needed infrastructure. The first thing is to create a try...catch block so we can put things back the way they were before running the application if an error happens. We set an invisible undo marker to accomplish this. If an error happens, we simply rollback to this undo marker in the catch block. Another thing we are doing is turning off the graphics window while we are creating the drawing format. This improves performance and the user really does not want to see the flashing and moving geometry. The format creation is really only one line and this is it:
format.addBorder( "dphCut", "a3" );
Why don't you build the project and run it. It should now create a drawing format.
Inserting a block is pretty easy. Let's add the GD&T block to the format. The ID is dphGDT. We should also make sure the block has not already been added, too. Paste this in the createFormat function after the format creation.
if ( !format.hasBlock("dphGDT") )
format.insertBlock( "dphGDT" );
That is all there is to it. You could also specify the block style and location, but the defaults are fine here.
The titleblock is something that usually will need to be modified so let's change the part number and drawing name fields. After looking up the IDs for the titleblock and the fields, we enter this code in the createFormat function after the insert block code.
DphFormat::BlockPtr titleBlock(format.getBlock("dphTitle"));
titleBlock->beginUpdate();
titleBlock->setFieldValue("drawingName", "TEST API OF\nDELPHI FORMAT PROGRAM");
titleBlock->setFieldValue("drawingNumber", "12345678");
titleBlock->finishUpdate();
The code first gets a pointer to the titleblock's so we can manipulate it. It then tells the block that an update is going to happen. This allows the block to do some setup before any modifications take place. After modifying the two fields, the block is informed the update is done so the tabular note can be updated.
I should also note that the newline in the drawing name field value is probably not needed or wanted since it will wrap on its own. I just did it to show how multiple lines can be done if you need to force the issue.
The last interesting thing we can do is add an entry to the revision block. The code is very similar to normal block field editing except we append an entry and get the index to the entry we want to modify the field of. Here is the code:
DphFormat::BlockPtr revBlock(format.getBlock("dphRevision"));
revBlock->beginUpdate();
int entry = revBlock->appendEntry();
revBlock->setEntryFieldValue(entry, "revision", "000");
revBlock->setEntryFieldValue(entry, "history", "INITIAL RELEASE");
revBlock->finishUpdate();
revBlock->placeSymbol(entry, "", NXOpen::Point3d(50,50,0));
You may have noticed that I also tied a symbol to the entry. The second argument is an empty string because we want the default value.
Querying the drawing format is fairly straightforward, so here is the code for displayFormatInfo:
std::stringstream msg;
msg "Border Info: " std::endl
" ID: " format.borderId() std::endl
" Size ID: " format.sizeId() std::endl std::endl
"Blocks on Drawing:" std::endl;
DphFormat::BlockList blocks(format.blocks());
DphFormat::BlockList::const_iterator it = blocks.begin();
for ( ; it != blocks.end() ; ++it )
{
DphFormat::BlockPtr block = *it;
msg " " block->identification() std::endl;
}
postInfoMessage( "Drawing Info", msg.str() );