Drawexe Very Simple Examples (no plugin)

This page is part of the Tutorial Started by D. Semikin.



Introduction

NOTE: In this example I use the setup established in previous section "Building OCCT660 With MSVS 11 Express for Desktop Development". If you have other setup, you should adjust instructions accordingly.

DRAWEXE (or OpenCASCADE Test Harness) is very useful thing. I will not list all ways to use it, please refer to %CASROOT%\doc\thug.pdf for more details. It provides general information about DRAWEXE and reference of commands.

I will concentrate on one use of the DRAWEXE application. If you need to check some OpenCASCADE algorithm or algorithm developed by you, or visualize something, in most cases it can be done with DRAWEXE easily.

The idea is very simple: you implement the piece of code you want to check into function of special form and then add it to DRAWEXE as custom command. Then you can execute the command within DRAWEXE and see the result. Even better, you can use other facilities of the DRAWEXE to generate input data for your command or to examine output of your command.

There are two ways to introduce custom commands into DRAWEXE:

  1. Develop "custom" DRAWEXE executable reusing standard DRAWEXE functionality by linking it with "TKDraw.lib".
  2. Develop dynamically loaded plugin, which can be loaded into existing DRAWEXE executable.

In my opinion, the second approach is more suitable for distributing custom plugins for DRAWEXE to other people or for automated testing (as you can load/not load some plugins depending on the environment in which you do testing). But for quick ad-hock testing of your/OCCT algorithm the first approach is more suitable as it is much simpler.

So here I will describe the minimalistic custom DRAWEXE executable.

Among other things I will discuss here how to use OCCT libraries from you application (in this case it is custom DRAWEXE, but similar approach suites other applications either). Note, that examples provided with OCCT itself generally uses approach to reuse "env.bat", "custom.bat" etc. to establish paths to OCCT and 3rd party libs and include dirs. (and to ensure they are the same as for building OCCT itself). But when I first met OCCT examples this approach looked to me as kind of "magic" if it worked and kind of not understandable thing if it did not work. So I will do all the configuration in the MSVS project properties itself without any ".bat" or ".cmd" scripts, so that we see that there is no magic an general C++ include and linkage mechanisms are used. Possibly at the end I will tell several words about how to use ".cmd" scripts to simplify setup of MSVC solutions (as it indeed may become annoying to add all 3-rd party bin directories to the PATH each time you crate new project).

So, we are going to create custom DRAWEXE executable, which introduces one new command "create_box", which is to be used as follows:

create_box box_var_name 100 200 300

As a result the box with side lengths 100, 200 and 300 will be created and assigned to the "box_var_name" variable.

Standard DRAWEXE implementation

I think, to start it is good idea to create custom DRAWEXE application, which does not add anything new to the original one. And to understand, how it can be done, I think it is good idea to look inside original DRAWEXE project of the OpenCASCADE solution.

If you open OpenCASCADE solution and look inside DRAWEXE project (within Draw project folder), you will find that it only have single file, which contains effectively only two things:

  1. Implementation of the Draw_InintAppli() function and
  2. Use of DRAW_MAIN macro.

DRAW_MAIN macro is expanded to main() function and some auxiliary platform specific stuff. It uses Draw_InitAppli() function with this specific name and specific signature, so before use of DRAW_MAIN this function must be at least declared. The purpose of this function (as I understand) is to add some commands to the interpretor. Commands may be added by one using Add() method of the Draw_Interpretor object supplied to the function. Or you can use some functions of standard packages as Draw::Commands() to add standard commands from packages.

Custom DRAWEXE with standard functionality

So now we are ready to create solution to build custom DRAWEXE application (which does not do anything special yet).

main.cpp file

For this open MSVS 11 Express (for desktop development) and create new solution and project (I called it "DrawSimple"). Select C++ project, empty project.

In the "Source Files" project folder create new "main.cpp" file.

I put the following contents to this file (the components used are similar to found in original Draw.cxx file, but I arranged them in such a way, that they better meet my preferences):

/// @file main.cpp

#include <Standard_TypeDef.hxx>
#include <Draw_Main.hxx>
#include <Draw_Interpretor.hxx>

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor);

//==============================================================================
DRAW_MAIN;

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor)
{
    Draw::Commands(drawInterpretor);

} // Draw_InitAppli()

//==============================================================================

Configuring the solution to build and execute

Now we want to compile this code. To do this we have to define three things:

  • Where to search for include files (this is also required for intellisense to work),
  • Where to search for library files to link
  • Which files to link

To run our application we either need to

  • add directories, which contains required DLLs to PATH.

Either to make DRAWEXE normally find all resources required to load standard commands with "pload ALL" we have to

  • define CASROOT environment variable to the "ros" directory of our OpenCASCADE setup.

Let's start with include directories. Open properties of DrawSimple project. Go to "Configuration Properties\C/C++\General -> Additional Include Directories". Add there directory "%CASROOT%\inc".

Now, let's add library directories. Note, that we do not explicitly use any facilities from libraries except for OpenCASCADE itself. So, we only have to add directory, which contains OpenCASCADE's lib files. For this open properties of the DrawSimple project and go to "Configuration Properties\Linker\General -> Additional Library Directories" Add there %CASROOT%\win32\vc11\libd directory (note, that for release you probably would prefer "lib" at the end, if you have built OpenCASCADE in release configuration.

Now the trickier thing: we have to define, which libraries to link. So you may wonder how to find out which one should be linked. Generally I do it in following way: I start build process and get some "unresolved references" linkage errors. I see, which symbols cannot be resolved, open reference documentation from %CASROOT%\doc\ReferenceDocumentation\index.html. And search there for the function, which cannot be resolved. Then I see, in which Toolkit it is situated (see "bar" at the top of thepage). The library with corresponding name is what we need.

For example if we do not define any libraries to linke and start build, then the first link error is:

1>main.obj : error LNK2019: unresolved external symbol "public: static void __cdecl Draw::Commands(class Draw_Interpretor &)" (?Commands@Draw@@SAXAAVDraw_Interpretor@@@Z) referenced in function "void __cdecl Draw_InitAppli(class Draw_Interpretor &)" (?Draw_InitAppli@@YAXAAVDraw_Interpretor@@@Z)

We see that the first undefined reference is to Draw::Commands(). If we find it in the reference documentation, we see, that it is located in "Module Draw \ Tookit TKDraw \ Package Draw", so we know, that we should linke to TKDraw.lib library.

To do so, open DrawSimple project's properties, go to "Configuration Properties\Linker\Input -> Additional Dependencies" Add "TKDraw.lib to this field.

If we continue this investigation, the next unresolved symbol is "Standard", which (according to reference documentation) can be found in toolkit TKernel, so we have to add TKernel.lib.

After we add TKDraw.lib and TKernel.lib, the DRAWEXE application can be built successfully.

Now we want to start it, but to do so we have to add to PATH environment variable directories, which contain OpenCASCADE DLLs, and moreover, directories, which contain DLLs of all 3rd party libraries.

Also we have to define CASROOT environment variable for resources mechanism (plugins mechanism) to work properly.

For this, go to properties of DrawSimple project to "Configuration Properties\Debugging -> Environment" and add there:

PATH=$(PATH);D:\Libs\OCCT\3rd_parties_660\tk8.6.0-install\bin;D:\Libs\OCCT\3rd_parties_660\tcl8.6.0-install\bin;D:\Libs\OCCT\3rd_parties_660\FreeImage-3.15.4\Dist;D:\Libs\OCCT\OpenCASCADE660\ros\win32\vc11\bind;D:\Libs\OCCT\3rd_parties_660\tbb41_20130314oss\bin\ia32\vc11
CASROOT=D:\Libs\OCCT\OpenCASCADE660\ros

Now you should be able to press <F5> key or select from menu DEBUG\Start Debugging to start your custom DRAWEXE application.
You should be able either to input

pload ALL ;# To load command
axo ;# To open viewer
box b 100 200 300 ;# to create box
fit ;# to fit view
r ;# rotate view right
;# etc...
exit ;# exit DRAWEXE

Hello World custom command

But having custom DRAWEXE application, which do the same stuff, which normal DRAWEXE do is not too interesting. So let's add some custom command to it.

Let's start with classit "Hello World!".

Custom commands in DRAWEXE are implemented as functions with special signature. Then pointer to this function is passed to the Add() method of the Draw_Interpretor class instance together with other options.

So, let's implement such a function, which implements "Hello World" command. Add to the project files "HelloWorld.cpp" and "HelloWorld.h" files.

/// @file HelloWorld.h
#pragma once

//==============================================================================
#include <Standard_TypeDef.hxx>
#include <Draw_Interpretor.hxx>

//==============================================================================
/// @brief Implementation of the hello_world command for custom DRAWEXE.
extern Standard_Integer HelloWorld(
    Draw_Interpretor& interpretor,
    Standard_Integer argc,
    const char** argv
    );

//==============================================================================

And source file:

/// @file HelloWorld.cpp

//==============================================================================
#include <tcl.h> // Tcl return codes

#include <Standard_TypeDef.hxx>
#include <Draw_Interpretor.hxx>

#include "HelloWorld.h"

//==============================================================================
/// @details Implementation of the hello_world command of our custom DRAWEXE.
///
/// @returns Tcl status (TCL_OK, or TCL_ERROR - see tcl.h for details).
///
extern Standard_Integer HelloWorld(
    Draw_Interpretor& /* interpretor */, ///< Not used for now
    Standard_Integer  /* argc */,        ///< Not used for now
    const char**      /* argv */         ///< Not used for now
    )
{
    cout << "Hello, World!" << endl;

    return TCL_OK;

} // HelloWorld()

//==============================================================================

Note, that DRAWEXE is customized Tcl interpreter. So it is good idea to use standard Tcl return codes for the commands. This is why we include "tcl.h" and use "TCL_OK" as return value from our custom command. But to compile this we have to add directory with this header file into include directories of our projects. In my case it is "D:\Libs\OCCT\3rd_parties_660\tcl8.6.0-install\include".

As you can see, any DRAWEXE command takes three arguments. These are "interpretor", which can be used to query data from DRAWEXE or to intrude data back to it (e.g. some geometric or topological entities), and argc and argv arguments with their standard meaning. Those are set of strings, which are input arguments supplied to the command, when it is called in runtime. In our hello wolrd command we do need none of them.

Now we can build our project (and run it), but the "hello world" command is still not available. To make it available we have to add it to the interpretor during the initialization of DRAWEXE application (i.e. within Draw_InitAppli() function). Here is revised main.cpp file:

/// @file main.cpp

#include <Standard_TypeDef.hxx>
#include <Draw_Main.hxx>
#include <Draw_Interpretor.hxx>

#include "HelloWorld.h" // implementatio of the hello_world command

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor);

//==============================================================================
DRAW_MAIN;

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor)
{
    Draw::Commands(drawInterpretor);

    // Add hello_world command (implemented as HelloWorld() function) to the
    // interpretor.
    drawInterpretor.Add(
        "hello_world", // Command name (as it will be called from interpretor)
        "print \"Hello, World!\"", // Help string for the command
        HelloWorld, // Pointer to function implementing the command
        "Custom commands" // Name of the group to put command to
        );

} // Draw_InitAppli()

//==============================================================================

After building and running our solution we can have following session:

Draw[1]> hello_world
Hello, World!
Draw[2]> help hello_world
hello_world     : print "Hello, World!"
Draw[3]> help

Custom commands

  hello_world

DRAW General Commands

  batch          chrono         cpulimit       dbreak

  ... <output trimmed here>

Here we can see meaning of "command name" and "help string" and "command group" arguments to Add() method of Draw_Interpretor class (note, that I trimmed output of the latest "help" command).

Print Shape Type custom command

The next step in our journey is to create some command, which works with geometry and uses some OpenCASCADE facility. Besides, it will communicate with DRAWEXE interpretor. Let's implement command, which takes shape and prints out the type of the shape.

The command will be called print_shape_type and it will be implemented in PrintShapeType() function in PrintShapeType.h and PrintShapeType.cpp files.

But before we start let's note, that to query/intrude topological shapes with interpretor there are DBRep::Get() and DBRep::Set() functions. The same is possible with geometrical entities using DrawTrSurf package.

Now, the implementation of our command:

/// @file PrintShapeType.h
#pragma once

//==============================================================================
#include <Standard_TypeDef.hxx>
#include <Draw_Interpretor.hxx>

//==============================================================================

/// @brief Implemnetation of the print_shape_type command.
extern Standard_Integer PrintShapeType(
    Draw_Interpretor& interpretor,
    Standard_Integer argc,
    const char** argv
    );

//==============================================================================
/// @file PrintShapeType.cpp

//==============================================================================
#include <cassert>
#include <string>

#include <tcl.h>

#include <Standard_TypeDef.hxx>
#include <Draw_Interpretor.hxx>
#include <DBRep.hxx>
#include <TopoDS_Shape.hxx>
#include <TopAbs_ShapeEnum.hxx>

#include "PrintShapeType.h"

//==============================================================================
/// @details Implementation of print_shape_type command.
///
/// @returns TCL_OK - success; TCL_ERROR - error.
///
extern Standard_Integer PrintShapeType(
    Draw_Interpretor& interpretor,
    Standard_Integer argc,
    const char** argv
    )
{
    using std::string;

    assert(argc > 0);
    const Standard_CString commandName = argv[0]; // It always must be there.

    // Check command input arguments
    if (argc != 2) // First argument is command name)
    {
        cerr << "ERROR: " << commandName
                << " takes exactly 1 arguments, which is name of shape to test."
                << endl;
        return TCL_ERROR;

    } // if argc != 2

    Standard_CString inputVariableName = argv[1];

    // Query the shape from the interpretor:
    TopoDS_Shape inputShape = DBRep::Get(inputVariableName);
    if (inputShape.IsNull())
    {
        cerr << "ERROR: " << commandName << ": Variable " << inputVariableName 
            << " does not contain topological shape." << endl;
        return TCL_ERROR;

    } // if inputShape.IsNull

    // Get shape type
    TopAbs_ShapeEnum inputShapeType = inputShape.ShapeType();

    // Print out shape type
    switch (inputShapeType)
    {
    case TopAbs_VERTEX:
        cout << "VERTEX" << endl;
        break;

    case TopAbs_COMPOUND:
        cout << "COMPOUND" << endl;
        break;

    case TopAbs_COMPSOLID:
        cout << "COMPSOLID" << endl;
        break;

    case TopAbs_EDGE:
        cout << "EDGE" << endl;
        break;

    case TopAbs_FACE:
        cout << "FACE" << endl;
        break;

    case TopAbs_SHAPE:
        cout << "SHAPE" << endl;
        break;

    case TopAbs_SHELL:
        cout << "SHELL" << endl;
        break;

    case TopAbs_SOLID:
        cout << "SOLID" << endl;
        break;

    case TopAbs_WIRE:
        cout << "WIRE" << endl;
        break;

    default:
        cerr << "ERROR: " << commandName << ": Internal error." << endl;
        return TCL_ERROR;

    } // switch inputShapeType

    return TCL_OK;

} // PrintShapeType()

//==============================================================================

Updated main.cpp (which registers new command):

/// @file main.cpp

#include <Standard_TypeDef.hxx>
#include <Draw_Main.hxx>
#include <Draw_Interpretor.hxx>

#include "HelloWorld.h" // implementatio of the hello_world command
#include "PrintShapeType.h" // implemetation of the print_shape_type command

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor);

//==============================================================================
DRAW_MAIN;

//==============================================================================
void Draw_InitAppli(Draw_Interpretor& drawInterpretor)
{
    Draw::Commands(drawInterpretor);

    // Add hello_world command (implemented as HelloWorld() function) to the
    // interpretor.
    drawInterpretor.Add(
        "hello_world", // Command name (as it will be called from interpretor)
        "print \"Hello, World!\"", // Help string for the command
        HelloWorld, // Pointer to function implementing the command
        "Custom commands" // Name of the group to put command to
        );

    // Add print_shape_type command to the interpretor.
    drawInterpretor.Add(
        "print_shape_type", // Command name
        "Print out type of the given shape.", // command help
        PrintShapeType, // pointer to function implementing the command
        "Custom commands" // Command category
        );

} // Draw_InitAppli()

//==============================================================================

Note, that to build it we have to add TKMath.lib (if you did not do this and try to build you would get link error. Tracking undefined reference in reference documentation you can find, that this is TKMath.lib, which is missing.

Now if you run you program and type in

pload ALL

Then error will arise, telling that FreeImage.dll cannot be found. To solve this, we have to put path to corresponding 3-rd party library into the Environment property of the Debugging section of out Project properties.

Then we can test our new command:

Draw[2]> pload ALL
Plugin file name has not been specified. Defaults to DrawPlugin
Parse Value ==> MODELING, OCAFKERNEL, DATAEXCHANGE
Parse aCurKey = MODELING
... <output is trimmed>
Draw[3]> box b 100 200 300 ;# Create box
Draw[4]> help print_shape_type ;# Get help for print_shape_type
print_shape_type : Print out type of the given shape.
Draw[5]> print_shape_type a ;# Error: a is not defined
ERROR: print_shape_type: Variable a does not contain topological shape.
Draw[6]> print_shape_type ;# Error: no argument is supplied
ERROR: print_shape_type takes exactly 1 arguments, which is name of shape to test.
Draw[7]> print_shape_type b ;# OK: b is our box, which is solid
SOLID
Draw[8]>

Make The Bottle custom command for standard OCCT Tutorial

This tutorial (Tutorial Started by D. Semikin) does not have section dedicated to work with geometry and topology. This is because official OCCT documentation have document %CASROOT%\doc\tutorial.pdf, which covers basics of fundamental classes and basics of work with geometry and topology in really great way (at least I enjoyed working out that tutorial). The only thing which annoyed be is that corresponding example "%CASROOT%\samples\qt\Tutorial" contains too much beyond the material discussed in the tutorial (e.g. OCAF etc.). Besides it requires Qt to work with it.

So, when I was working out with this tutorial I decided that it is better to wrap it into custom DRAWEXE application. And at this point you know how to do this :).

For those who did not yet looked inside that tutorial.pdf, I will say that as the result of the tutorial you get the function, which creates the compound shape representing the bottle.

So now to wrap the tutorial into the DRAWEXE application you have to introduce file

/// @file MakeTheBottle.h

#include <TopoDS_Shape.hxx>

extern TopoDS_Shape MakeTheBottle();
/// @file MakeTheBottle.cpp

#include <TopoDS_Shape.hxx>
// ... other includes

#include "MakeTheBottle.h"

extern TopoDS_Shape MakeTheBottle()
{
    // Implementation of the command from the tutorial goes here.

    return theBottle; // theBottle is compound solid
} // MakeTheBottle()

And now use this function to implement your custom command in the same manner as "print shape type" command. The only difference is that now we need to put topology shape into interpretor (but not query one) so we need to use DBRep::Set() function (instead of DBRep::Get().


This page is part of the Tutorial Started by D. Semikin.