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

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):

- `isSupportedFileFormat(const Str& filename)`
- `open(const Str& filename, Error* error)`
- `close()`
- `geometryCount()`
- `initializeDirectory(DataSourceDirectory* directory, Error* error)`
- `readGeometry(int stateId, size_t geometryIndex, DataGeometry* geometry, Error* error)`

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

Conversation saved
HOOPSY BETA

### Hello! I'm HOOPSY

Your multilingual generative AI assistant for documentation. Ask me anything about HOOPS Envision for Desktop in your own language, or choose a sample question below to start a conversation:

What is HOOPS Envision for Desktop?
What are the latest release notes for HOOPS Envision for Desktop?
How do I start evaluating HOOPS Envision for Desktop?
How do I write my first sample application?
How can I get assistance?

HOOPSY may store conversations according to OpenAI's policy. Responses may not always be accurate.

HOOPSY is thinking...