UnstructGrid: Create a Custom DataReader to Add Support for Your File Format

../_images/tut_custom_datareader.png

This tutorial shows how to get your own analysis results into the cee::ug::UnstructGridModel by creating a cee::ug::DataReader.

It is a very simple reader that returns one hexahedron element with one scalar, one vector, one displacement and one transformation result, but it will work as a starting point for building your own file reader.

Note

This example expect the application to have a correctly configured cee::vis::View in place. See demo applications on how to set up a cee::vis::View in your application.

Create the custom DataReader

To create a custom data reader, you have to sub-class the cee::ug::DataReader class.

First you need to implement a method to detect if the file is supported or not as well as open and close methods. We also need to specify how many geometries there are in our file. Usually this is 1.

class SingleHexReader : public cee::ug::DataReader
{
public:
    bool isSupportedFileFormat(const cee::Str& /*filename*/)
    {
        return true;
    }

    bool open(const cee::Str& /*filename*/, cee::ug::Error* /*error*/)
    {
        return true;
    }

    void close()
    {
    }

    size_t geometryCount() const
    {
        return 1;
    }

In this case this is really simple as we are doing a sample in-memory “reader”

The next step is to provide information (meta-data) regarding the file that was just opened. To specify the contents of the file (number of states, name and id of all the results, etc) you have to implement the cee::ug::DataReader::initializeDirectory() method. This method will be called by the system after the file is successfully opened.

    bool initializeDirectory(cee::ug::DataSourceDirectory* directory, cee::ug::Error* /*error*/)
    {
        directory->setPartInfo(0, cee::ug::PartInfo(1, "First part"));
        directory->setStateInfo(cee::ug::StateInfo(1, "First state", 0.0));
        directory->setResultInfo(cee::ug::ResultInfo(cee::ug::SCALAR, 10, "Per node result", cee::ug::PER_NODE));
		directory->setResultInfo(cee::ug::ResultInfo(cee::ug::VECTOR, 10, "Per node result", cee::ug::PER_NODE));
        directory->setResultInfo(cee::ug::ResultInfo(cee::ug::DISPLACEMENT, 10, "Displacement result", cee::ug::PER_NODE));
		directory->setTransformationResult(true);

        return true;
    }

Next, we need to provide the geometry. This is done by overriding the readGeometry method. In this method, you will have to provide the DataGeometry for the given state and geometry id. Please note that if you have a new geometry for each state (adaptive/remeshing model) you have to override the hasNewGeometryForEachState method and return true for the geometry/geometries that are remeshed.

    bool readGeometry(int /*stateId*/, size_t /*geometryIndex*/, cee::ug::DataGeometry* geometry, cee::ug::Error* /*error*/)
    {
        cee::PtrRef<cee::ug::DataPart> part = new cee::ug::DataPart(1);
        cee::PtrRef<cee::ug::DataElements> elements = new cee::ug::DataElements(false, 0);
        cee::PtrRef<cee::ug::DataNodes> nodes = new cee::ug::DataNodes(false);
        part->setElements(elements.get());
        part->setNodes(nodes.get());
        nodes->resize(8);
        nodes->setNode(0, cee::Vec3d(0,0,0));
        nodes->setNode(1, cee::Vec3d(1,0,0));
        nodes->setNode(2, cee::Vec3d(1,1,0));
        nodes->setNode(3, cee::Vec3d(0,1,0));
        nodes->setNode(4, cee::Vec3d(0,0,1));
        nodes->setNode(5, cee::Vec3d(1,0,1));
        nodes->setNode(6, cee::Vec3d(1,1,1));
        nodes->setNode(7, cee::Vec3d(0,1,1));

        std::vector<unsigned int> elementIndices;
        for (unsigned int i = 0; i < 8; ++i) elementIndices.push_back(i);
        elements->addElement(cee::ug::Element::HEXAHEDRONS, elementIndices);

        geometry->addPart(part.get());

        return true;
    }

Next, we provide the scalar result. This method is called whenever the UnstructGridModel (during updateVisualization()) needs a scalar result and it is not present in the data source. This could be because the result is specified as fringes result in the ModelSpec, specified to be mapped on a cutting plane or isosurface or specified to be mapped on a particle trace. Only the results added to the directory in the initializeDirectory() will be queried by this method.

    bool readScalarResult(int /*stateId*/, size_t /*geometryIndex*/, int /*resultId*/, cee::ug::DataResultScalar* scalarResult, cee::ug::Error* /*error*/)
    {
        std::vector<double> values;
        for (size_t i = 0; i < 8; ++i) values.push_back(static_cast<double>(i));

        cee::PtrRef<cee::ug::DataPartScalar> part = new cee::ug::DataPartScalar;
        part->setValues(values);
        scalarResult->addPart(part.get());

        return true;
    }

Next, we provide the vector result. This works in the same way as for scalar results.

    bool readVectorResult(int /*stateId*/, size_t /*geometryIndex*/, int /*resultId*/, cee::ug::DataResultVector* vectorResult, cee::ug::Error* /*error*/)
    {
        std::vector<cee::Vec3d> values;
        for (size_t i = 0; i < 8; ++i) values.push_back(cee::Vec3d(0.2*static_cast<double>(i), 0, 0));

        cee::PtrRef<cee::ug::DataPartVector> part = new cee::ug::DataPartVector;
        part->setValues(values);
        vectorResult->addPart(part.get());

        return true;
    }

Next, we provide a displacement result. Please note that the displacement results needs to contain new absolute node coordinates, not just the displaced value.

    bool readDisplacementResult(int /*stateId*/, size_t /*geometryIndex*/, int /*resultId*/, cee::ug::DataResultDisplacement* displacementResult, cee::ug::Error* /*error*/)
    {
        std::vector<cee::Vec3d> values;
        values.push_back(cee::Vec3d(0,0,0));
        values.push_back(cee::Vec3d(1.1,0,0));
        values.push_back(cee::Vec3d(1,1.2,0));
        values.push_back(cee::Vec3d(0,1,0));
        values.push_back(cee::Vec3d(0,0,1));
        values.push_back(cee::Vec3d(1,0,1));
        values.push_back(cee::Vec3d(1,1.1,1));
        values.push_back(cee::Vec3d(0,1.2,1));

        cee::PtrRef<cee::ug::DataPartDisplacement> part = new cee::ug::DataPartDisplacement;
        part->setValues(values);

        displacementResult->addPart(part.get());

        return true;
    }

Finally, we provide a transformation result. This is a rigid body transformation per part.

    bool readTransformationResult(int /*stateId*/, size_t /*geometryIndex*/, cee::ug::DataResultTransformation* transformationResult, cee::ug::Error* /*error*/)
    {
        cee::Mat4d partMatrix = cee::Mat4d::fromRotation(cee::Vec3d(1,1,1), 0.4);
        transformationResult->addPart(partMatrix);

        return true;

This concludes the custom DataReader and demonstrates how to use all the virtual methods in the interface.

Please not that the following methods must be overridden (pure virtual methods):

The rest of the methods are optional, but you need to implement read* methods for all the result you provide directory information about in the initializeDirectory() method.

Please note that the rest of this example would work with any reader and actually any data source, as it used the content from the data source directory to setup the model. The UnstructGridModel will use the reader when necessary to load the DataSource with the required results.

Create model and data source with our custom reader

Create a model and the DataSourceReader data source with our newly created DataReader. Then open the “file” so it is ready to use.

    cee::PtrRef<cee::ug::UnstructGridModel> ugModel = new cee::ug::UnstructGridModel();
    cee::PtrRef<cee::ug::DataSourceReader> dataSource = new cee::ug::DataSourceReader(1, new SingleHexReader);
    ugModel->setDataSource(dataSource.get());
    
    // "Open" the file
    dataSource->open("AnyFileWillDo.xyz");

Get the available states and results from our reader

Use the DataSourceDirectory to get information about states and result provided by our reader.

    const cee::ug::DataSourceDirectory* dir = dataSource->directory();
    std::vector<cee::ug::StateInfo>  stateInfos  = dir->stateInfos();
    std::vector<cee::ug::ResultInfo> scalarInfos = dir->scalarResultInfos();
    std::vector<cee::ug::ResultInfo> vectorInfos = dir->vectorResultInfos();
    std::vector<cee::ug::ResultInfo> displacementInfos = dir->displacementResultInfos();
    std::vector<cee::ug::PartInfo>   partInfos  = dir->partInfos(0);

Configure the ModelSpec to show the state and the results

Here we setup the model spec with the results found in the data source directory.

    ugModel->modelSpec().setStateId(stateInfos[0].id());
    ugModel->modelSpec().setFringesResultId(scalarInfos[0].id());
    ugModel->modelSpec().setVectorResultId(vectorInfos[0].id());
    ugModel->modelSpec().setDisplacementResultId(displacementInfos[0].id());
    ugModel->modelSpec().setTransformationResult(true);

Add the model to the view and update the visualization

Then we can add the model to the view and call updateVisualization(). This call will trigger loading of the results and states specified in the ModelSpec and our reader will get calls to readGeometry(), readScalar() etc. depending on what is specified in the ModelSpec.

    cee::vis::View* gcView = getTutorialView();
    gcView->removeAllModels();
    gcView->addModel(ugModel.get());

    ugModel->updateVisualization();

Setup the camera and update the viewer

Finally, as in all other examples, we need to set a default camera position and update the viewer.

    cee::BoundingBox bb = gcView->boundingBox();
    gcView->camera().fitView(bb, cee::Vec3d(0, 0, -1), cee::Vec3d(0, 1, 0));
    gcView->camera().inputHandler()->setRotationPoint(bb.center());

    gcView->requestRedraw();

See the complete source code here:

UnstructGrid: Create a Custom DataReader to Add Support for Your File Format