Skip to content

Commit

Permalink
Add ability to subset ProE tet mesh reading
Browse files Browse the repository at this point in the history
Fixes #1210 .  Adds the ability to read in a subset of a ProE mesh. The user supplies a lambda function to accept or reject tetrahedra. The PR also adds convenience functions to build a lambda from a user-specified bounding box.
  • Loading branch information
agcapps committed Mar 13, 2024
1 parent f59a1cc commit 2a57e12
Show file tree
Hide file tree
Showing 8 changed files with 801 additions and 15 deletions.
3 changes: 3 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
- Adds initial support for using Slic streams with tags
- Adds an example that finds intersection candidate pairs between two Silo
hexahedral meshes using either a BVH or Implicit Grid spatial index
- Quest: Adds `setTetPredFromBoundingBox()` and `setTetPred()` functions to
`quest::ProEReader` and `PProEReader` that set a tet predicate, allowing
user code to read in a subset of a Pro/E ASCII tetrahedron mesh file.

### Changed
- `DistributedClosestPoint` outputs are now controlled by the `setOutput` method.
Expand Down
64 changes: 63 additions & 1 deletion src/axom/quest/docs/sphinx/read_mesh.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ Applications commonly need to read a mesh file from disk. Quest provides the
``STLReader`` class, which can read binary or ASCII `STL`_ files, as well as the
``PSTLReader`` class for use in parallel codes. STL (stereolithography)
is a common file format for triangle surface meshes. The STL reader classes
will read the file from disk and build a ``mint::Mesh`` object.
will read the file from disk and build a ``mint::Mesh`` object. Quest also
provides the ``ProEReader`` class, for ASCII Pro/E files containing tetrahedra,
and the ``PProEReader`` class for use in parallel codes. PTC Creo is a modeling
application formerly known as Pro/ENGINEER, and its file format is in use among
Axom's users.

.. _STL: https://en.wikipedia.org/wiki/STL_(file_format)

Reading an STL file
-------------------

The code examples are excerpts from the file ``<axom>/src/tools/mesh_tester.cpp``.

We include the STL reader header
Expand Down Expand Up @@ -50,3 +57,58 @@ The following example shows usage of the STLReader class:
After reading the STL file, the ``STLReader::getMesh`` method gives access to the
underlying mesh data. The reader may then be deleted.

Reading a Pro/E file
--------------------

As read by Axom, an ASCII Pro/E tet file contains:

- Zero or more comment lines starting with a ``#`` character
- One line with two integers: the number of nodes ``n`` and the number of
tetrahedra ``t``
- ``n`` lines, one for each node; each line contains a contiguous integer ID
starting at 1 and three floating-point numbers specifying the node location
- ``t`` lines, one for each tetrahedron; each line contains a contiguous
integer ID starting at 1 and four integers specifying the tet's nodes

Reading an ASCII Pro/E tet file is similar to reading an STL file. The code
examples are excerpts from the file ``<axom>/src/axom/quest/examples/quest_proe_bbox.cpp``.
The Pro/E reader has the ability to read a subset of the mesh in the file,
defined by a user-supplied predicate function. The example code shows how
to use a convenience function to specify a predicate that keeps only tets
fully included in a user-supplied bounding box.

We include the ProEReader header

.. literalinclude:: ../../examples/quest_proe_bbox.cpp
:start-after: _read_proe_include1_start
:end-before: _read_proe_include1_end
:language: C++

and also the mint Mesh and UnstructuredMesh headers.

.. literalinclude:: ../../examples/quest_proe_bbox.cpp
:start-after: _read_proe_include2_start
:end-before: _read_proe_include2_end
:language: C++

For convenience, we specify some type aliases.

.. literalinclude:: ../../examples/quest_proe_bbox.cpp
:start-after: _read_proe_typealiases_start
:end-before: _read_proe_typealiases_end
:language: C++

The following example shows how to use the ProEReader class.
Calling ``reader.setTetPredFromBoundingBox(bbox, false)``, as shown in the
code, makes a tetrahedron predicate that accepts tets with all four nodes
falling in ``bbox`` and rejects others. Alternately, the user can specify
an arbitrary predicate function with ``setTetPred()``. If the user specifies
no tetrahedron predicate, the reader reads all tets in the file.

.. literalinclude:: ../../examples/quest_proe_bbox.cpp
:start-after: _read_proe_file_start
:end-before: _read_proe_file_end
:language: C++

After reading the Pro/E file, the ``ProEReader::getMesh`` method gives access
to the underlying mesh data. The reader may then be deleted.
9 changes: 9 additions & 0 deletions src/axom/quest/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ if (RAJA_FOUND AND UMPIRE_FOUND)
)
endif()

# Read ProE example -----------------------------------------------------------
axom_add_executable(
NAME quest_proe_bbox_ex
SOURCES quest_proe_bbox.cpp
OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY}
DEPENDS_ON ${quest_example_depends}
FOLDER axom/quest/examples
)

# BVH silo example ------------------------------------------------------------
if (CONDUIT_FOUND AND RAJA_FOUND AND UMPIRE_FOUND)
axom_add_executable(
Expand Down
167 changes: 167 additions & 0 deletions src/axom/quest/examples/quest_proe_bbox.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and
// other Axom Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: (BSD-3-Clause)

/*! \file quest_proe_bbox.cpp
* \brief This example code demonstrates how to read a subset of a ProE (Creo)
* text-format tetrahedron mesh using a bounding box. It reads a file
* specified with the -f argument and writes the mesh contained therein
* into a file specified with the -o argument.
*/

// Axom includes
#include "axom/config.hpp"
#include "axom/core.hpp"
#include "axom/slic.hpp"

#include "axom/CLI11.hpp"
#include "axom/fmt.hpp"

// _read_proe_include2_start
#include "axom/mint/mesh/Mesh.hpp"
#include "axom/mint/mesh/UnstructuredMesh.hpp"
#include "axom/mint/utils/vtk_utils.hpp" // for write_vtk
// _read_proe_include2_end

// _read_proe_include1_start
#include "axom/quest/readers/ProEReader.hpp"
// _read_proe_include1_end

// _read_proe_typealiases_start
namespace mint = axom::mint;
namespace primal = axom::primal;
namespace slic = axom::slic;

using IndexType = axom::IndexType;
using UMesh = mint::UnstructuredMesh<mint::SINGLE_SHAPE>;
// _read_proe_typealiases_end

//------------------------------------------------------------------------------
void initialize_logger()
{
// initialize logger
slic::initialize();
slic::setLoggingMsgLevel(slic::message::Info);

// setup the logstreams
std::string fmt = "";
slic::LogStream* logStream = nullptr;

fmt = "[<LEVEL>]: <MESSAGE>\n";
logStream = new slic::GenericOutputStream(&std::cout, fmt);

// register stream objects with the logger
slic::addStreamToAllMsgLevels(logStream);
}

//------------------------------------------------------------------------------
void finalize_logger()
{
slic::flushStreams();
slic::finalize();
}

struct Arguments
{
std::string file_name;
std::string outfile_name;
std::vector<double> bbox_min;
std::vector<double> bbox_max;

Arguments()
{
bbox_min.resize(3);
bbox_max.resize(3);
}

void parse(int argc, char** argv, axom::CLI::App& app)
{
app
.add_option("-i,--input", this->file_name, "specifies the input mesh file")
->check(axom::CLI::ExistingFile)
->required();

app
.add_option("-o,--outfile",
this->outfile_name,
"specifies the output mesh file")
->required();

app
.add_option("--min",
this->bbox_min,
"specifies the minimum of the bounding box")
->expected(3)
->required();

app
.add_option("--max",
this->bbox_max,
"specifies the maximum of the bounding box")
->expected(3)
->required();

app.get_formatter()->column_width(40);

// could throw an exception
app.parse(argc, argv);

slic::flushStreams();
}
};

int main(int argc, char** argv)
{
initialize_logger();
Arguments args;
axom::CLI::App app {"Example showing how to read a Pro/E mesh"};

try
{
args.parse(argc, argv, app);
}
catch(const axom::CLI::ParseError& e)
{
int retval = -1;
retval = app.exit(e);
finalize_logger();
return retval;
}

// The constructor of args resizes bbox_min and bbox_max to three elements.
double* bbox_min = args.bbox_min.data();
double* bbox_max = args.bbox_max.data();

SLIC_INFO("Reading file: '" << args.file_name << "'...\n");
// _read_proe_file_start
// Read file
axom::quest::ProEReader reader;
reader.setFileName(args.file_name);

// Set up a bounding box to keep only certain tets.
// bbox_min and bbox_max are pointers to double.
axom::quest::ProEReader::BBox3D bbox;
bbox.addPoint(axom::quest::ProEReader::Point3D {bbox_min, 3});
bbox.addPoint(axom::quest::ProEReader::Point3D {bbox_max, 3});
// Keep only tets with all four nodes inside the bounding box.
reader.setTetPredFromBoundingBox(bbox, false);
// Pass true as the second argument of setTetPredFromBoundingBox() to
// keep tets with at least one node inside the bounding box.
// To keep all tets, do not set a TetPred.

// Read in the file.
reader.read();

// Get surface mesh
UMesh mesh(3, axom::mint::TET);
reader.getMesh(&mesh);
// _read_proe_file_end

SLIC_INFO("Mesh has " << mesh.getNumberOfNodes() << " vertices and "
<< mesh.getNumberOfCells() << " triangles.");

axom::mint::write_vtk(&mesh, args.outfile_name);

finalize_logger();
}
62 changes: 55 additions & 7 deletions src/axom/quest/readers/ProEReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "axom/core/utilities/Utilities.hpp"
#include "axom/mint/mesh/CellTypes.hpp"
#include "axom/slic/interface/slic.hpp"
#include "axom/primal/geometry/Point.hpp"

// C/C++ includes
#include <fstream>
Expand Down Expand Up @@ -38,9 +39,6 @@ void ProEReader::clear()
//------------------------------------------------------------------------------
int ProEReader::read()
{
constexpr int NUM_NODES_PER_TET = 4;
constexpr int NUM_COMPS_PER_NODE = 3;

std::string junk;
int id;
int tet_nodes[NUM_NODES_PER_TET];
Expand Down Expand Up @@ -69,7 +67,6 @@ int ProEReader::read()
ifs >> m_num_nodes >> m_num_tets;

m_nodes.reserve(m_num_nodes * NUM_COMPS_PER_NODE);
m_tets.reserve(m_num_tets * NUM_NODES_PER_TET);

// Initialize nodes
for(int i = 0; i < m_num_nodes; i++)
Expand All @@ -83,21 +80,72 @@ int ProEReader::read()
}

// Initialize tets
int tet_count = 0;
for(int i = 0; i < m_num_tets; i++)
{
ifs >> id >> tet_nodes[0] >> tet_nodes[1] >> tet_nodes[2] >> tet_nodes[3];

for(int j = 0; j < NUM_NODES_PER_TET; j++)
if(!m_tetPredicate || m_tetPredicate(tet_nodes, i, m_nodes))
{
// Node IDs start at 1 instead of 0, adjust to 0 for indexing
m_tets.push_back(tet_nodes[j] - 1);
tet_count += 1;
for(int j = 0; j < NUM_NODES_PER_TET; j++)
{
// Node IDs start at 1 instead of 0, adjust to 0 for indexing
m_tets.push_back(tet_nodes[j] - 1);
}
}
}

ifs.close();

compact_arrays(tet_count);

return (0);
}

//------------------------------------------------------------------------------
void ProEReader::setTetPredFromBoundingBox(BBox3D& box, bool inclusive)
{
if(box.isValid())
{
if(!inclusive)
{
auto pred = [box](int tet_nodes[4], int, std::vector<double>& nodes) {
bool retval = true;
for(int i = 0; i < ProEReader::NUM_NODES_PER_TET; ++i)
{
// Node IDs start at 1 instead of 0, adjust to 0 for indexing
Point3D p(&nodes[3 * (tet_nodes[i] - 1)], 3);
retval = retval && box.contains(p);
}
return retval;
};
setTetPred(pred);
}
else
{
auto pred = [box](int tet_nodes[4], int, std::vector<double>& nodes) {
bool retval = false;
for(int i = 0; i < ProEReader::NUM_NODES_PER_TET; ++i)
{
// Node IDs start at 1 instead of 0, adjust to 0 for indexing
Point3D p(&nodes[3 * (tet_nodes[i] - 1)], 3);
retval = retval || box.contains(p);
}
return retval;
};
setTetPred(pred);
}
}
}

//------------------------------------------------------------------------------
void ProEReader::compact_arrays(int elt_count)
{
m_tets.resize(elt_count * NUM_NODES_PER_TET);
m_num_tets = elt_count;
}

//------------------------------------------------------------------------------
void ProEReader::getMesh(axom::mint::UnstructuredMesh<mint::SINGLE_SHAPE>* mesh)
{
Expand Down

0 comments on commit 2a57e12

Please sign in to comment.