Basic Creation and Querying Tutorial

Overview

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.

Preliminaries

Obtain and Install Boost

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.

Be Sure Using Correct Visual Studio Version

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.

Install NX VS Wizard

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.

Verify Environment

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.

Project Setup

Now that we have the development environment setup, we can begin the project.

  1. Start Visual Studio
  2. Select File->New->Project... to bring up the new project dialog
  3. Select Visual C++ Projects under Project Types, then NX 3 Open Wizard under Templates, and enter the project name and location and press OK.

    New Project Dialog

  4. In the wizard dialog, press the Next button. On the next page, select an internal application and C++ language.

    Application Settings

    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.

  1. Open the basic.cpp and delete all the code that was generated by the wizard.
  2. Select Project->Configuration Manager and make the Active Configuration the Release configuration. Using the default Debug configuration will sometimes cause a NX application to kick into the debugger when freeing memory. The reason for the this is a debug check that is out of scope for this tutorial. A release build does not include the check that causes this so I never mess with using the debug configuration.
  3. In the Solution Explorer panel, select the basic project.

    solution explorer

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

  4. Select C/C++->General in the left area and select the Additional Include Directories input field. A ... button will appear on the right, so select that to bring up the include directory dialog. Add the Boost install directory and the DSL's include directory to the list and press OK.

    Include Dialog

  5. Select Linker->General on the left and select the Additional Library Directories input field. Add the DSL_BASE_DIR\win\shlib directory to the list
  6. Select Linker->Input and on the left and select the Additional Dependencies input field and add libdph_format.lib to the list.

Whew, now the project should be ready to start adding code and building.

Program Skeleton

Before we do anything with the format program API, let's create a basic program skeleton and some useful utilities.

  1. Select the basic project in the Solution Explorer, select Project->Add New Item... and create the util.hpp header file.

    new header dialog

  2. Enter the following code in util.hpp.
    #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 );
    }
  3. Add a new C++ source file to the project called util.cpp with this content:
    #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.

Format Creation

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.

Insert a Block

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.

Modifying a Block Field

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.

Insert and Modifying Table Entry

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

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() );