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