Commit bd395985 authored by David Thompson's avatar David Thompson
Browse files

Generalize the Exodus session; add a SLAC reader.

This changes the way datasets are referenced in the Exodus session
so that the structure of the reader's output does not have to
match the Exodus reader's convention. Instead of using handles
that refer to specific blocks in an Exodus reader's output,
reference the VTK datasets themselves.

This also adds support for reading SLAC NetCDF files via VTK's reader.

Finally, this fixes several SMTK issues and provides a new operator:
+ Provide a global operator to export model JSON ("export model json").
+ Fix `model::Manager::modelOwningEntity`.
+ Fix problems with the SetProperty operator.
+ Fix the Exodus reader test to prevent the image test from failing.
parent 5f55678d
......@@ -57,7 +57,7 @@ function(smtk_public_headers)
if (IS_ABSOLUTE "${header}")
file(RELATIVE_PATH header_sub "${CMAKE_CURRENT_BINARY_DIR}" "${header}")
if (NOT header STREQUAL "${CMAKE_CURRENT_BINARY_DIR}/${header_sub}")
message(FATAL_ERROR "Could not determine subdirectoy path for '${header}' relative to '${CMAKE_CURRENT_BINARY_DIR}'")
message(FATAL_ERROR "Could not determine subdirectory path for '${header}' relative to '${CMAKE_CURRENT_BINARY_DIR}'")
endif ()
else ()
set(header_sub "${header}")
......
......@@ -26,9 +26,11 @@ target_link_libraries(smtkExodusSession
smtkCore
LINK_PRIVATE
vtkIOExodus
vtkIONetCDF
vtkIOParallelExodus
vtkFiltersGeometry
vtkCommonDataModel
${Boost_LIBRARIES}
)
smtk_export_header(smtkExodusSession Exports.h)
......
......@@ -31,7 +31,7 @@ vtkDataObject* Operator::exodusData(const smtk::model::EntityRef& smtkEntity)
if (!brdg)
return NULL;
return brdg->toBlock<vtkDataObject>(brdg->toEntity(smtkEntity));
return brdg->toEntity(smtkEntity).object<vtkDataObject>();
}
/**\brief A helper to return the Exodus handle associated with an \a smtkEntity.
......
......@@ -13,6 +13,7 @@
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"
......@@ -21,11 +22,17 @@
#include "smtk/model/Model.h"
#include "vtkExodusIIReader.h"
#include "vtkHyperTreeGrid.h"
#include "vtkSLACReader.h"
#include "vtkFieldData.h"
#include "vtkDataArray.h"
#include "vtkStringArray.h"
#include "vtkInformation.h"
#include "boost/filesystem.hpp"
using namespace smtk::model;
using namespace smtk::common;
using namespace boost::filesystem;
namespace smtk {
namespace bridge {
......@@ -41,6 +48,83 @@ smtk::model::OperatorResult ReadOperator::operateInternal()
std::string filename = filenameItem->value();
std::string filetype = filetypeItem->value();
if (filetype.empty())
{ // Infer file type from name
std::string ext = path(filename).extension().string();
if (ext == ".nc" || ext == ".ncdf")
filetype = "slac";
else if (ext == ".exo" || ext == ".g" || ext == ".ex2" || ext == ".exii")
filetype = "exodus";
}
// Downcase the filetype (especially for when we did not infer it):
std::transform(filetype.begin(), filetype.end(), filetype.begin(), ::tolower);
if (filetype == "slac")
return this->readSLAC();
// The default is to assume it is an Exodus file:
return this->readExodus();
}
static void MarkMeshInfo(
vtkDataObject* data, int dim, const char* name, EntityType etype, int pedigree)
{
if (!data)
return; // Skip empty leaf nodes
vtkInformation* info = data->GetInformation();
info->Set(Session::SMTK_DIMENSION(), dim);
info->Set(Session::SMTK_GROUP_TYPE(), etype);
info->Set(vtkCompositeDataSet::NAME(), name);
// If a UUID has been saved to field data, we should copy it to the info object here.
vtkStringArray* uuidArr =
vtkStringArray::SafeDownCast(
data->GetFieldData()->GetAbstractArray("UUID"));
if (uuidArr && uuidArr->GetNumberOfTuples() > 0)
info->Set(Session::SMTK_UUID_KEY(), uuidArr->GetValue(0).c_str());
info->Set(Session::SMTK_PEDIGREE(), pedigree);
}
static void MarkExodusMeshWithChildren(
vtkMultiBlockDataSet* data, int dim, const char* name, EntityType etype, EntityType childType,
vtkExodusIIReader* rdr, vtkExodusIIReader::ObjectType rdrIdType)
{
MarkMeshInfo(data, dim, name, etype, -1);
int nb = data->GetNumberOfBlocks();
for (int i = 0; i < nb; ++i)
{
std::ostringstream autoName;
autoName << EntityTypeNameString(childType) << " " << i;
const char* name = data->GetMetaData(i)->Get(vtkCompositeDataSet::NAME());
int pedigree = rdr->GetObjectId(rdrIdType, i);
MarkMeshInfo(data->GetBlock(i), dim, name && name[0] ? name : autoName.str().c_str(), childType, pedigree);
}
}
static void MarkSLACMeshWithChildren(
vtkMultiBlockDataSet* data, int dim, const char* name, EntityType etype, EntityType childType)
{
MarkMeshInfo(data, dim, name, etype, -1);
int nb = data->GetNumberOfBlocks();
for (int i = 0; i < nb; ++i)
{
std::ostringstream autoName;
autoName << EntityTypeNameString(childType) << " " << i;
const char* name = data->GetMetaData(i)->Get(vtkCompositeDataSet::NAME());
MarkMeshInfo(data->GetBlock(i), dim, name && name[0] ? name : autoName.str().c_str(), childType, i);
}
}
smtk::model::OperatorResult ReadOperator::readExodus()
{
smtk::attribute::FileItem::Ptr filenameItem =
this->specification()->findFile("filename");
std::string filename = filenameItem->value();
vtkNew<vtkExodusIIReader> rdr;
rdr->SetFileName(filenameItem->value(0).c_str());
rdr->UpdateInformation();
......@@ -62,16 +146,105 @@ smtk::model::OperatorResult ReadOperator::operateInternal()
modelOut->ShallowCopy(
vtkMultiBlockDataSet::SafeDownCast(
rdr->GetOutputDataObject(0)));
// Set the DIMENSION information key so that transcription
// can properly set dimension bits on the model and groups
// without access to the reader:
modelOut->GetInformation()->Set(
vtkHyperTreeGrid::DIMENSION(),
rdr->GetDimensionality());
int dim = rdr->GetDimensionality();
// Now iterate over the dataset and mark each block (leaf or not)
// with information needed by the session to determine how it should
// be presented.
MarkMeshInfo(modelOut, dim, path(filename).stem().c_str(), EXO_MODEL, -1);
vtkMultiBlockDataSet* elemBlocks =
vtkMultiBlockDataSet::SafeDownCast(
modelOut->GetBlock(0));
MarkExodusMeshWithChildren(
elemBlocks, dim,
modelOut->GetMetaData(0u)->Get(vtkCompositeDataSet::NAME()),
EXO_BLOCKS, EXO_BLOCK, rdr.GetPointer(), vtkExodusIIReader::ELEM_BLOCK);
vtkMultiBlockDataSet* sideSets =
vtkMultiBlockDataSet::SafeDownCast(
modelOut->GetBlock(4));
MarkExodusMeshWithChildren(
sideSets, dim - 1,
modelOut->GetMetaData(4)->Get(vtkCompositeDataSet::NAME()),
EXO_SIDE_SETS, EXO_SIDE_SET, rdr.GetPointer(), vtkExodusIIReader::SIDE_SET);
vtkMultiBlockDataSet* nodeSets =
vtkMultiBlockDataSet::SafeDownCast(
modelOut->GetBlock(7));
MarkExodusMeshWithChildren(
nodeSets, 0,
modelOut->GetMetaData(7)->Get(vtkCompositeDataSet::NAME()),
EXO_NODE_SETS, EXO_NODE_SET, rdr.GetPointer(), vtkExodusIIReader::NODE_SET);
// Now that the datasets we wish to present are marked,
// have the Session create entries in the model manager for us:
Session* brdg = this->exodusSession();
smtk::model::Model smtkModelOut =
brdg->addModel(modelOut);
smtkModelOut.setStringProperty("url", filename);
smtkModelOut.setStringProperty("type", "exodus");
// Now set model for session and transcribe everything.
smtk::model::OperatorResult result = this->createResult(
smtk::model::OPERATION_SUCCEEDED);
smtk::attribute::ModelEntityItem::Ptr resultModels =
result->findModelEntity("model");
resultModels->setValue(smtkModelOut);
smtk::attribute::ModelEntityItem::Ptr created =
result->findModelEntity("created");
created->setNumberOfValues(1);
created->setValue(smtkModelOut);
created->setIsEnabled(true);
return result;
}
smtk::model::OperatorResult ReadOperator::readSLAC()
{
smtk::attribute::FileItem::Ptr filenameItem =
this->specification()->findFile("filename");
smtk::attribute::IntItem::Ptr readVolumes =
this->specification()->findInt("readSLACVolumes");
std::string filename = filenameItem->value();
vtkNew<vtkSLACReader> rdr;
rdr->SetMeshFileName(filenameItem->value(0).c_str());
rdr->SetReadInternalVolume(readVolumes->discreteIndex());
rdr->ReadExternalSurfaceOn();
rdr->ReadMidpointsOn();
// Read in the data (so we can obtain tessellation info)
rdr->Update();
vtkSmartPointer<vtkMultiBlockDataSet> modelOut =
vtkSmartPointer<vtkMultiBlockDataSet>::New();
vtkNew<vtkMultiBlockDataSet> surfBlocks;
surfBlocks->ShallowCopy(
vtkMultiBlockDataSet::SafeDownCast(
rdr->GetOutputDataObject(0)));
vtkNew<vtkMultiBlockDataSet> voluBlocks;
voluBlocks->ShallowCopy(
vtkMultiBlockDataSet::SafeDownCast(
rdr->GetOutputDataObject(1)));
modelOut->SetNumberOfBlocks(2);
modelOut->SetBlock(0, surfBlocks.GetPointer());
modelOut->SetBlock(1, voluBlocks.GetPointer());
MarkMeshInfo(modelOut.GetPointer(), 3, path(filename).stem().c_str(), EXO_MODEL, -1);
MarkSLACMeshWithChildren(surfBlocks.GetPointer(), 2, "surfaces", EXO_SIDE_SETS, EXO_SIDE_SET);
MarkSLACMeshWithChildren(voluBlocks.GetPointer(), 3, "volumes", EXO_BLOCKS, EXO_BLOCK);
Session* brdg = this->exodusSession();
smtk::model::Model smtkModelOut =
brdg->addModel(modelOut);
smtkModelOut.setStringProperty("url", filename);
smtkModelOut.setStringProperty("type", "slac");
// Now set model for session and transcribe everything.
smtk::model::OperatorResult result = this->createResult(
......@@ -85,6 +258,7 @@ smtk::model::OperatorResult ReadOperator::operateInternal()
created->setValue(smtkModelOut);
created->setIsEnabled(true);
/*
// The side and node sets now exist; go through
// and use the Exodus reader's private information
// to correct the property information.
......@@ -116,6 +290,7 @@ smtk::model::OperatorResult ReadOperator::operateInternal()
}
git->setIntegerProperty("exodus id", oid);
}
*/
return result;
}
......
......@@ -26,6 +26,8 @@ public:
protected:
virtual smtk::model::OperatorResult operateInternal();
virtual smtk::model::OperatorResult readExodus();
virtual smtk::model::OperatorResult readSLAC();
};
} // namespace exodus
......
......@@ -7,9 +7,16 @@
<ItemDefinitions>
<File Name="filename" NumberOfRequiredValues="1"
ShouldExist="true"
FileFilters="Exodus II Datasets (*.e *.exo *.ex2);;All files (*.*)">
FileFilters="Exodus II Datasets (*.e *.exo *.ex2);;NetCDF files (*.nc *.ncdf);;All files (*.*)">
</File>
<String Name="filetype" NumberOfRequiredValues="1"/>
<Int Name="readSLACVolumes" NumberOfRequiredValues="1">
<DefaultValue>0</DefaultValue>
<DiscreteInfo DefaultIndex="0">
<Structure><Value Enum="no">0</Value></Structure>
<Structure><Value Enum="yes">1</Value></Structure>
</DiscreteInfo>
</Int>
</ItemDefinitions>
</AttDef>
<!-- Result -->
......
......@@ -19,7 +19,6 @@
#include "vtkCellArray.h"
#include "vtkGeometryFilter.h"
#include "vtkHyperTreeGrid.h"
#include "vtkInformation.h"
#include "vtkInformationIntegerKey.h"
#include "vtkInformationIntegerVectorKey.h"
......@@ -35,6 +34,9 @@ namespace smtk {
namespace bridge {
namespace exodus {
vtkInformationKeyMacro(Session,SMTK_DIMENSION,Integer);
vtkInformationKeyMacro(Session,SMTK_GROUP_TYPE,Integer);
vtkInformationKeyMacro(Session,SMTK_PEDIGREE,Integer);
vtkInformationKeyMacro(Session,SMTK_UUID_KEY,String);
enum smtkCellTessRole {
......@@ -43,6 +45,103 @@ enum smtkCellTessRole {
SMTK_ROLE_POLYS
};
/// Return a string representing the type of object
std::string EntityTypeNameString(EntityType etype)
{
switch(etype)
{
case EXO_MODEL: return "model";
case EXO_BLOCK: return "element block";
case EXO_SIDE_SET: return "side set";
case EXO_NODE_SET: return "node set";
case EXO_BLOCKS: return "element blocks";
case EXO_SIDE_SETS: return "side sets";
case EXO_NODE_SETS: return "node sets";
default: break;
}
return "invalid";
}
/// Construct an invalid handle.
EntityHandle::EntityHandle()
: m_modelNumber(-1), m_object(NULL), m_session(NULL)
{
}
/// Construct a possibly-valid handle (of a top-level model).
EntityHandle::EntityHandle(int emod, vtkDataObject* obj, Session* sess)
: m_modelNumber(emod), m_object(obj), m_session(sess)
{
}
/// Construct a possibly-valid handle (of a non-top-level entity).
EntityHandle::EntityHandle(int emod, vtkDataObject* obj, vtkMultiBlockDataSet* parent, int idxInParent, Session* sess)
: m_modelNumber(emod), m_object(obj), m_session(sess)
{
if (sess && obj && parent && idxInParent > 0)
{
sess->ensureChildParentMapEntry(obj, parent, idxInParent);
}
}
/// Returns true when the object is owned by a session and has a non-NULL pointer.
bool EntityHandle::isValid() const
{
return
this->m_session &&
this->m_object &&
this->m_modelNumber >= 0 &&
this->m_modelNumber < static_cast<int>(this->m_session->numberOfModels());
}
/// Return the type of object this handle represents (or EXO_INVALID).
EntityType EntityHandle::entityType() const
{
vtkDataObject* obj = this->object<vtkDataObject>();
if (!obj)
return EXO_INVALID;
int etype = obj->GetInformation()->Get(Session::SMTK_GROUP_TYPE());
return etype > 0 ? static_cast<EntityType>(etype) : EXO_INVALID;
}
/// Return the name assigned to this object.
/// Note that this is *not* the same as the block name that VTK uses!
std::string EntityHandle::name() const
{
vtkDataObject* obj = this->object<vtkDataObject>();
if (!obj)
return std::string();
return obj->GetInformation()->Get(vtkCompositeDataSet::NAME());
}
/// Return the pedigree ID assigned to this object.
/// For Exodus files, this is the block or set ID. For SLAC files, it is the block index.
int EntityHandle::pedigree() const
{
vtkDataObject* obj = this->object<vtkDataObject>();
if (!obj)
return -1;
return obj->GetInformation()->Get(Session::SMTK_PEDIGREE());
}
/// Given a handle, return its parent if it has one.
EntityHandle EntityHandle::parent() const
{
EntityType etype = this->entityType();
// Top-level and invalid handles have an invalid parent.
if (etype == EXO_MODEL || etype == EXO_INVALID)
return EntityHandle();
return EntityHandle(this->m_modelNumber, this->m_session->parent(this->m_object), this->m_session);
}
// ++ 2 ++
Session::Session()
{
......@@ -68,7 +167,7 @@ EntityHandle Session::toEntity(const smtk::model::EntityRef& eid)
// ++ 4 ++
smtk::model::EntityRef Session::toEntityRef(const EntityHandle& ent)
{
vtkDataObject* entData = this->toBlock<vtkDataObject>(ent);
vtkDataObject* entData = ent.object<vtkDataObject>();
if (!entData)
return EntityRef(); // an invalid entityref
......@@ -83,54 +182,20 @@ smtk::model::EntityRef Session::toEntityRef(const EntityHandle& ent)
{
uid = smtk::common::UUID(uuidChar);
}
const char* name = entData->GetInformation()->Get(vtkCompositeDataSet::NAME());
return EntityRef(this->manager(), uid);
}
// -- 4 --
// ++ 5 ++
std::vector<EntityHandle> Session::childrenOf(const EntityHandle& ent)
{
std::vector<EntityHandle> children;
if (ent.entityType != EXO_MODEL)
return children; // element blocks, side sets, and node sets have no children (yet).
vtkMultiBlockDataSet* model = this->toBlock<vtkMultiBlockDataSet>(ent);
if (!model)
return children;
struct {
EntityType entityType;
int blockId;
} blocksByType[] = {
{EXO_BLOCK, 0},
{EXO_SIDE_SET, 4},
{EXO_NODE_SET, 7}
};
const int numBlocksByType =
sizeof(blocksByType) / sizeof(blocksByType[0]);
for (int i = 0; i < numBlocksByType; ++i)
{
vtkMultiBlockDataSet* typeSet =
dynamic_cast<vtkMultiBlockDataSet*>(
model->GetBlock(blocksByType[i].blockId));
if (!typeSet) continue;
for (unsigned j = 0; j < typeSet->GetNumberOfBlocks(); ++j)
children.push_back(
EntityHandle(blocksByType[i].entityType, ent.modelNumber, j));
}
return children;
}
// -- 5 --
// ++ 6 ++
/// Add the dataset and its blocks to the session.
smtk::model::Model Session::addModel(
vtkSmartPointer<vtkMultiBlockDataSet>& model)
{
EntityHandle handle;
handle.modelNumber = static_cast<int>(this->m_models.size());
handle.entityType = EXO_MODEL;
handle.entityId = -1; // unused for EXO_MODEL.
EntityHandle handle(
static_cast<int>(this->m_models.size()),
model.GetPointer(),
this);
this->m_models.push_back(model);
smtk::model::Model result = this->toEntityRef(handle);
this->m_revIdMap[result] = handle;
......@@ -153,15 +218,15 @@ SessionInfoBits Session::transcribeInternal(
if (!handle.isValid())
return actual;
vtkDataObject* obj = this->toBlock<vtkDataObject>(handle);
vtkDataObject* obj = handle.object<vtkDataObject>();
// ...
// -- 7 --
if (!obj)
return actual;
int dim = obj->GetInformation()->Get(vtkHyperTreeGrid::DIMENSION());
int dim = obj->GetInformation()->Get(Session::SMTK_DIMENSION());
// Grab the parent entity early if possible... we need its dimension().
// Grab the parent entity early if possible...
EntityRef parentEntityRef;
EntityHandle parentHandle = handle.parent();
if (parentHandle.isValid())
......@@ -174,7 +239,6 @@ SessionInfoBits Session::transcribeInternal(
this->declareDanglingEntity(parentEntityRef, 0);
this->transcribe(parentEntityRef, requestedInfo, true, depth < 0 ? depth : depth - 1);
}
dim = parentEntityRef.embeddingDimension();
}
// ++ 8 ++
......@@ -184,7 +248,7 @@ SessionInfoBits Session::transcribeInternal(
{
// -- 8 --
// ++ 9 ++
switch (handle.entityType)
switch (handle.entityType())
{
case EXO_MODEL:
mutableEntityRef.manager()->insertModel(
......@@ -196,26 +260,50 @@ SessionInfoBits Session::transcribeInternal(
entityDimBits = Entity::dimensionToDimensionBits(dim);
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), MODEL_DOMAIN | entityDimBits,
this->toBlockName(handle));
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(VOLUME);
break;
// .. and other cases.
// -- 9 --
case EXO_SIDE_SET:
entityDimBits = 0;
for (int i = 0; i < dim; ++i)
for (int i = 0; i <= dim; ++i)
entityDimBits |= Entity::dimensionToDimensionBits(i);
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), MODEL_BOUNDARY | entityDimBits,
this->toBlockName(handle));
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(CELL_ENTITY | entityDimBits);
break;
case EXO_NODE_SET:
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), MODEL_BOUNDARY | DIMENSION_0,
this->toBlockName(handle));
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(VERTEX);
break;
// Groups of groups:
case EXO_BLOCKS:
entityDimBits = Entity::dimensionToDimensionBits(dim);
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), GROUP_ENTITY | entityDimBits,
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(VOLUME | GROUP_ENTITY);
break;
case EXO_SIDE_SETS:
entityDimBits = 0;
for (int i = 0; i <= dim; ++i)
entityDimBits |= Entity::dimensionToDimensionBits(i);
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), GROUP_ENTITY | entityDimBits,
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(CELL_ENTITY | GROUP_ENTITY | entityDimBits);
break;
case EXO_NODE_SETS:
mutableEntityRef.manager()->insertGroup(
mutableEntityRef.entity(), GROUP_ENTITY | DIMENSION_0,
handle.name());
mutableEntityRef.as<Group>().setMembershipMask(VERTEX | GROUP_ENTITY);
break;
// ++ 10 ++
default:
return actual;
......@@ -241,8 +329,8 @@ SessionInfoBits Session::transcribeInternal(
mutableEntityRef.findOrAddRawRelation(parentEntityRef);
}
// Now add children.
std::vector<EntityHandle> children = this->childrenOf(handle);
std::vector<EntityHandle>::iterator cit;
EntityHandleArray children = handle.childrenAs<EntityHandleArray>(0); // Only immediate children.
EntityHandleArray::iterator cit;
for (cit = children.begin(); cit != children.end(); ++cit)
{
EntityRef childEntityRef = this->toEntityRef(*cit);
......@@ -252,7 +340,10 @@ SessionInfoBits Session::transcribeInternal(
this->declareDanglingEntity(childEntityRef, 0);
this->transcribeInternal(childEntityRef, requestedInfo, depth < 0 ? depth : depth - 1);
}
mutableEntityRef.as<smtk::model::Model>().addGroup(childEntityRef);
if (handle.entityType() == EXO_MODEL)
mutableEntityRef.as<smtk::model::Model>().addGroup(childEntityRef);
else
mutableEntityRef.as<smtk::model::Group>().addEntity(childEntityRef);
}
// Mark that we added this information to the manager:
......@@ -272,6 +363,41 @@ SessionInfoBits Session::transcribeInternal(
if (requestedInfo & smtk::model::SESSION_PROPERTIES)
{
// Set properties.
EntityType etype = handle.entityType();