File Input/Output and the I/O Manager

Introduction

In HOOPS/MVO, the HIOManager manages all file input and output modules. A variety of input and output handlers come prebuilt which allows developers to import and export widely used file formats immediately.

To import a file, simply call HBaseModel::Read. This method automatically asks the HIOManager for the correct HInputHandler. The HIOManager selects input handlers based primarily on the input file’s extension. While information is loaded into the HOOPS database, you can query the HInputHandler class for progress information. You can also check for errors and generate a log file.

HOOPS/MVO also provides the ability to export to an extensive number of file formats. To export data to a file, use HBaseModel::Write. Like the import process, the export process begins with querying the HIOManager for the appropriate HOutputHandler. The output handlers, then, write model and/or scene information out to a file. In addition to saving to a file, HOOPS/MVO also allows you to print to hardcopy.

The following sample code shows how to create a simple program with HOOPS/MVO to read in an HSF file and write that information into a PLY file:

HDB * my_db = new HDB();
my_db->Init();

HBaseModel * my_model = new HBaseModel(my_db);
my_model->Init();

HBaseView * my_view = new HBaseView(my_model, NULL, NULL, NULL, NULL, NULL);
my_view->Init();

// write an HSF
my_model->Read("MyModel.hsf");

// write a PLY file
my_model->Write("MyModel.ply");

Importing Data

On any given system, you can query the HIOManager to find what input formats are supported by calling HIOManager::GetInputHandlerTypes. This method returns a semicolon delimited list of all extensions registered with available HInputHandlers. You can parse this list to create a file type list for input file dialogs. Input handlers not only report the type of file extensions they support but also the type of input style the work with. You can find the input style a handler supports by calling HInputHandler::GetInputStyle. It will return either HInputHandlerStyleModel, indicating it imports 3D model information, or HInputHandlerStyleImage, specifying that it imports image data.

When importing files, some formats have HInputHandlerOptions available to determine what and how information is brought into HOOPS. For some file formats, the options may be limited to one or two settings like HInputHandlerOptions::m_pImageName for image file import. On the other hand, you may have a multitude of options when importing third party format like RealDWG.

The following sample code shows how you can import an image file into HOOPS and use the InputHandlerOptions::m_pImageName to give it a name:

HInputHandlerOptions ImageOptions;
ImageOptions.m_pImageName = "OakTexture";

my_model->Read("oakply.jpg", my_view, true, &ImageOptions);

Exporting Data

To find out what file formats are available for export, you can call HIOManager::GetOutputHandlerTypes. You can use the semicolon delimited list of registered extensions to create a file list for save file dialogs.

HOOPS/MVO makes a distinction between the type of file an output handler can export but also what kind of data it can export. For instance, if you want to export OBJ, a 3D format, the HIOManager::GetOutputHandler method will return a pointer to an instance of HIOUtilityObj. Using the pointer to HIOUtilityObj, HBaseModel::Write queries the handler for the output style via HOutputHandler::GetOutputStyle to determine what type of data the handler can export. In this case, HIOUtilityObj returns HOutputHandlerStyleModel since OBJ is a geometry definition file format with little or no support for scene information like camera angle or lighting. Using this information, HBaseModel::Write calls HIOUtilityObj::FileOutputByKey passing the correct information. HIOUtilityObj::FileOutputByKey takes three parameters: a file name, a HOOPS database key and a pointer to an instance of HOutputHandlerOptions Because HBaseModel::Write knows the output style is HOutputHandlerStyleModel, it passes the model key for the second parameter. HOutputHandlerOptions, the third parameter, is not a parameter passed into HBaseModel::Write. Instead, HBaseModel::Write takes in the parameters height and width packaging them into an instance of HOutputHandlerOptions. Then it calls HBaseModel::WriteWithOptions.

HOutputHandlerOptions

For more control over the export process, you can use HBaseModel::WriteWithOptions passing your own instance of HOutputHandlerOptions. This class has a list of options which lets the output handler know in more detail how to export your data. HOutputHandlerOptions is most useful when exporting to handlers that report a HOutputHandlerStyleImage or HOutputHandlerStyleHardcopy output style. For these handlers, you can set window color, dimensions and whether the view window should match the size of the output using HOutputHandlerOptions::WindowColor, HOutputHandlerOptions::WindowHeight, HOutputHandlerOptions::WindowWidth and HOutputHandlerOptions::UseSubscreen, respectively. The following sample code shows how to export a model from the HOOPS database to a 640x480 image file:

#include <hc.h>
#include <stdio.h>
#include <stdlib.h>
#include <HDB.h>
#include <HBaseView.h>
#include <HBaseModel.h>
#include <HIOManager.h>

int main(int argc, char * argv[])
{
        HDB gHDB;
        gHDB.Init();

        HBaseModel model;
        model.Init();
        model.Read("Bicycle.hsf");

        HBaseView view(&model, 0, "printf", "null+1");
        view.Init();
        view.FitWorld();

        /* Write a 640x480 image, with Image DPI = 1
           Note that any pt-sized text depends on the Image DPI, so this example would
           result in pt-sized text not being visible.  */
        HOutputHandlerOptions options;
        options.WindowWidth(640);
        options.WindowHeight(480);
        options.ImageDpi(1);

        model.WriteWithOptions("bicycle1.png", &view, &options);

        /* Write a ~640 x ~480 image, but with Image DPI = 100
           Now, any existing pt-sized text will get drawn at 72 DPI and look correct.  */
        HOutputHandlerOptions options;
        options.WindowWidth(640/72.0);
        options.WindowHeight(480/72.0);
        options.ImageDpi(72);

        model.WriteWithOptions("bicycle2.png", &view, &options);

        return EXIT_SUCCESS;
}

Printing to Hardcopy

In addition to supporting many 2D and 3D formats, HOOPS/MVO also offers the ability to print to hardcopy formats such as PostScript and 2D-PDF via prebuilt output handlers. For instance, the output handler associated with PostScript is HIOUtilityPostscript. When the HIOUtilityPostscript::GetOutputStyle is called, it returns HOutputHandlerStyleHardcopy indicating that it can export to hardcopy. To begin the printing process, call HIOUtilityPostscript::FileOutputByKey.

HIOUtilityPostscript::FileOutputByKey takes three parameters: the filename, the key to the output data and a pointer to an instance of HOutputHandlerOptions. For printing, the options of note are HOutputHandlerOptions::PaperDpi, HOutputHandlerOptions::PaperWidth and HOutputHandlerOptions::PaperHeight. You can also determine if you want to include the window color in the printing using the HOutputHandlerOptions::UseWindowColor option.

The following sample shows how we can modify the previous code sample to print to a postscript file that is 8.5 x 11 inches, with 100 DPI:

    /*initial database*/
HDB gHDB;
    gHDB.Init();

    /*create a model*/
HBaseModel model;
model.Init();
model.Read("Bicycle.hsf");

    /*create a view*/
HBaseView view(&model, 0, "printf", "null+1");
view.Init();
view.FitWorld();

HOutputHandlerOptions options;

/* Write a postscript file that 8.5x11 inches with 100 DPI. */
    options.PaperWidth(8.5);
options.PaperHeight(11);
options.PaperDpi(100);
model.WriteWithOptions("bicycle.ps", &view, &options);

The prebuilt output handlers with output style HOutputHandlerStyleHardcopy use HUtility::TwoPassPrint to generate the image for the printer driver. The two pass printing process is effective way to produce high quality images. First, HUtility::TwoPassPrint creates a new instance under the printer driver path passed as the first parameter. Then, in the first pass, HUtility::TwoPassPrint takes the second parameter, the scene key,and creates an image with only faces data – excluding vector data like markers, lines and edges. This image is then set as the window background. In the second pass, hidden line rendering is turned on, drawing vector information into the scene. After the second pass is complete, Update is called on the display and the actual hardcopy is created.

Printing to Scale

It may be desirable to generate a hardcopy that is ‘to scale’. For example, if you have a 3D model of a ruler with units of inches, then enabling ‘printing to scale’ would result in a 1-inch section of the ruler taking up 1-inch on the printed page. This capability is enabled by setting HOutputHandlerOptions::PrintToScale() to specify a scaling ratio which indicates how many HOOPS units represent a centimeter. Refer to the Reference Manual entry for more detailed information.

Image I/O

HOOPS supports both the import and export of all the standard image formats viathe integration of widely used Image SDKs. For image I/O, HOOPS uses ImageMagick, which is distributed under a GPL-style license. Integration with this toolkit is done in the HIOUtilityMagick header and implementation files which are included in the source directory of the HOOPS/MVO project.

Additionally, a pre-built version of the HOOPS/MVO library which statically links the ImageMagick libraries is included in the hoops_mvo directory. Developers who want to use this should link the hoops_mvo_mgk library into their application instead of the standard hoops_mvo library. If you do use the ImageMagick variant of the MVO library then you will automatically have full import/export support of the standard raster formats.

Using the HIO Plug-In Architecture

If you want to import/export data to/from third party formats, HOOPS provides the HIO components for seamless third party integration. Under the HIO Plug-in architecture, there are HIO components for common formats like RealDWG and DGN which are loaded dynamically by HOOPS/MVO during start-up. No recompiling is required to use any or all of the HIO components. Just place the HIO components in your application’s working directory. HOOPS will automatically load in the necessary libraries and register the appropriate I/O handlers. To learn more about the HIO components and integration with third party libraries, please see the HOOPS/HIO Technical Overview.

I/O Progress and Error Reporting

During the data loading process, any class can access the progress information provided by HInputHandler either via polling or subscribing to the events via a callback. With the polling method, for instance, you can call HInputHandler::GetInputPercentProgress throughout the loading process to find how much data has been read. You can also find out what the HInputHandler is currently doing by calling HInputHandler::GetInputProgress. If you want to create a log file, use the HInputHandler::GetInputInformation. This method return the messages posted during the loading process one at a time in order of oldest to newest. When there are no more messages, it will return null.

If you prefer not to poll for information, you can set a callback for the input information, progress and percentage complete. To set a callback from percentage progress, use the method HInputHandler::SetInputPercentProgressNoticeCallback which expects a pointer to an instance of the HIOPercentNoticeCallback class. Since HIOPercentNoticeCallback is an abstract class, you need to create your own subclass and implement the pure virtual method HIOPercentNoticeCallback::Notice. The Notice method is called when the percent progress is updated in the HInputHandler. The HInputHandler::SetInputInformationNoticeCallback and HInputHandler::SetInputProgressNoticeCallback methods allow you to set callbacks for input information and progress, respectively. They both expect a pointer to an instance of the HIONoticeCallback class as a parameter. Since HIONoticeCallback is also an abstract class, you need to create a subclass and implement the pure virtual method HIONoticeCallback::Notice.

In the sample code below, we see how a callback class is created and then is registered to an input handler to receive progress information:

class MyNoticeCallback:public HIONoticeCallback, public HIOPercentNoticeCallback
{
public:
                ...
                void Notice(char const * msg);
                void Notice(float percent);
                ...
}

public void MyNoticeCallback::Notice(char const * msg)
{
        WriteLogFile(msg);
}

public void MyNoticeCallback::Notice(float percent)
{
        char tmp[100];
        sprintf(tmp,"Loading File ... %d%% ",(int)(notice*100));
        SetWindowText(tmp);
}

.
.
.
void MyBaseView::ReadHMF(char * pFileName)
{
        ...
        HInputHandler * hmf_handler = HDB::GetHIOManager()->GetInputHandler("hmf");
        if(hmf_handler)
        {
                MyNoticeCallback notice();
                hmf_handler->SetInputProgressNoticeCallback(notice);
                hmf_handler->SetInputPercentProgressNoticeCallback(notice);
                hmf_handler->SetInputInformationNoticeCallback(notice);
                HFileInputResult result = hmf_handler->FileInputByKey(pfilename, this->GetSceneKey(), NULL);

                //NULL out the progress callbacks when we are done with them.
                hmf_handler->SetInputProgressNoticeCallback(NULL);
                hmf_handler->SetInputPercentProgressNoticeCallback(NULL);
                hmf_handler->SetInputInformationNoticeCallback(NULL);
                ...
        }
}

The progress and error reporting interface in the HOutputHandler class is analagous to the HInputHandler interface.

Custom I/O Handlers

Developers who want their custom input/output handlers can do so by creating a handler class derived from the HInputHandler and/or HOutputHandler classes. This class must implement the HInputHandler::FileInputByKey and HOutputHandler::FileOutputByKey methods. Additionally, it must also implement the HOutputHandler::RegisterOutputHandlerTypes and/or HInputHandler::RegisterInputHandlerTypes methods. These methods are used by the HIOManager to determine which handler should be used to import/export a specific filetype.

Finally, the I/O handlers deal with two distinct data types. The developer specifies the datatype it was designed to import/export by implementing the HInputHandler::GetInputStyle and/or HOutputHandler::GetOutputStyle methods. If the input handler is of type model, then the data will either be read into a segment whose key is supplied by the developer. For output handlers with output style model, the model data is written directly out to a file whose handle is supplied by the developer. For output handlers with style image, the the contents of the segment tree will be rendered into a HOOPS image segment whose key is provide by the developer and then compressed. For the import case, the raster data will be uncompressed and then placed in the memory referenced by the supplied image key.

Example:

Say we have a filetype myFile which we want to support the input/output of within our HOOPS based application. The first thing we would do is derive our class from the HInputHandler and/or HOutputHandler method:

class MVO_API HIOUtilityMyFile: public HInputHandler, public HOutputHandler
{

        public:

                HIOUtilityMyFile()
                {
                        SetInputOps(HInputOpFileInputByKey);
                        SetOutputOps(HOutputOpFileOutputByKey);
                };

                ~HIOUtilityMyFile() {;};

                void RegisterInputHandlerTypes()
                {
                        HIORegisterInputType("myFile", this);
                };

                HInputHandlerStyle GetInputStyle() {return HInputHandlerStyleModel;};
                const char * GetInputTypesString() {return "myFile";};

                HFileInputResult FileInputByKey(const char * FileName, HC_KEY key, HInputHandlerOptions * options);

                void RegisterOutputHandlerTypes()
                {
                        HIORegisterOutputType("myFile",this);
                };

                const char * GetOutputTypesString() {return "myFile";};

                // You need to set a style to either Model or Image so that we can differentiate
                // between raster or facet based input.
                HOutputHandlerStyle GetOutputStyle() {return HOutputHandlerStyleModel;};

                HFileOutputResult FileOutputByKey(const char * filename, HC_KEY key, HOutputHandlerOptions * options);

};

Now, you simply register your new Input and Output handler as the handler for dealing with the .myFile extension. Note, we use the HIORegisterInputType and HIORegisterOutput type macro which registers the I/O handler with the currently active Input/Output Manager:

m_pIOUtilityMyFile = new HIOUtilityMyFile();
HIORegisterInputType("myFile", m_pIOUtilityMyFile);
HIORegisterOutputType("myFile", m_pIOUtilityMyFile);

Now HBaseModel will automatically detect and load the correct input handler in its Read method without you having to make any modification to the Read method. Here is the code for HBaseModel::Read. Notice how there is no extension specific code:

HUtility::FindFileNameExtension(FileName, ext);
HInputHandler * handler = HDB::GetHIOManager()->GetInputHandler(ext);

// if none found the quit
if(!handler)
        return InputNotHandled;

HFileInputResult result;

if(pHView)
{
        result = pHView->FileInput(FileName, handler, 0);
}
else
{
        if(handler->GetInputStyle() == HInputHandlerStyleModel)
        {
                result = handler->FileInputByKey(FileName, m_ModelKey, 0);
        }
        else if(handler->GetInputStyle() == HInputHandlerStyleImage)
        {
                HC_KEY image_key;
                result = handler->FileInputToImageKey(FileName, !_key, GetModelKey(), 0);
        }
                else
                        result = InputNotHandled;
}

Implementing Progress and Error Reporting

When you create a custom I/O handler, you have the option to provide progress and error reporting during the load and save process for your handler.

To report progress from an the input handler, call HInputHandler::SetStartingInput before loading your data. This method initializes and clears all the members associated with the progress reporting process. Once data loading has started, you can call HInputHandler::ReportInputPercentProgress passing a float to indicate how much of the loading process has been completed. If you want to indicate where the input handler is in the loading process, you can call HInputHandler::ReportInputProgress and pass a string like “Loading Annotations.” The HInputHandler class also has a method for reporting log file information via the HInputHandler::ReportInputInformation. When data loading is complete, call HInputHandler::SetFinishedInput to indicate the reporting process is done.

For HOutputHandler, the process for implementing progress and log information is analagous to HInputHandler. Before exporting data, call HOutputHandler::SetStartingOutput. During the exporting process, call HOutputHandler::ReportOutputPercentProgress passing a float to communicate how much of the export process has been performed. To indicate where the output handler is in the export process, you can call HOutputHandler::ReportOutputProgress and pass a string like “Writing Metadata.” The HOutputHandler class also has a method for reporting log file information via the HOutputHandler::ReportOutputInformation. When data export is complete, call HOutputHandler::SetFinishedOutput to indicate the reporting process has finished.