ENH: add alternative STL ASCII parsers

- In addition to the traditional Flex-based parser, added a Ragel-based
  parser and a handwritten one.

  Some representative timings for reading 5874387 points (1958129 tris):

      Flex   Ragel   Manual
      5.2s   4.8s    6.7s         total reading time
      3.8s   3.4s    5.3s         without point merging
This commit is contained in:
Mark Olesen 2018-04-16 10:20:45 +02:00
parent a8da75d27e
commit ea71484efa
21 changed files with 3373 additions and 193 deletions

View File

@ -191,7 +191,12 @@ int main(int argc, char *argv[])
Info<<"camel-case => " << (word("camel") & "case") << nl;
for (const auto& s : { " text with \"spaces'", "08/15 value" })
{
Info<<"validated \"" << s << "\" => "
// Character sequence
Info<<"validated 5 chars from \" => "
<< word::validate(s, s+5, true) << nl;
Info<<"validated (via string convert) \"" << s << "\" => "
<< word::validate(s, true) << nl;
}
Info<< nl;

View File

@ -0,0 +1,3 @@
Test-surfaceReading.C
EXE = $(FOAM_APPBIN)/Test-surfaceReading

View File

@ -0,0 +1,6 @@
EXE_INC = \
-I$(LIB_SRC)/fileFormats/lnInclude \
-I$(LIB_SRC)/surfMesh/lnInclude
EXE_LIBS = \
-lsurfMesh

View File

@ -0,0 +1,145 @@
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Application
Test-surfaceReading
Description
Test basic surface format reading capabilities (and speeds)
Note
The filename extensions are used to determine the file format type.
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "Time.H"
#include "clockTime.H"
#include "triSurface.H"
#include "MeshedSurfaces.H"
#include "UnsortedMeshedSurfaces.H"
#include "STLReader.H"
using namespace Foam;
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
int main(int argc, char *argv[])
{
argList::addNote
(
"Test basic surface format reading capabilities (and speeds)"
);
argList::noParallel();
argList::addArgument("inputFile");
argList::addBoolOption
(
"triSurface",
"Use triSurface for read"
);
argList::addBoolOption
(
"triFace",
"Use triFace instead of face"
);
argList::addBoolOption
(
"unsorted",
"Use UnsortedMeshedSurface instead of MeshedSurface, "
"or unsorted output (with -triSurface option)"
);
argList::addOption
(
"ext",
"name",
"Force alternative extension"
);
argList::addOption
(
"stl-parser",
"N",
"ASCII parser type: 0=Flex, 1=Ragel, 2=Manual"
);
#include "setRootCase.H"
const fileName importName = args[1];
word ext;
if (!args.readIfPresent("ext", ext))
{
ext = importName.ext();
if (ext == "gz")
{
ext = importName.lessExt().ext();
}
}
args.readIfPresent("stl-parser", fileFormats::STLReader::parserType);
clockTime timing;
if (args.found("triSurface"))
{
triSurface surf(importName, ext);
Info<< "Read surface:" << endl;
surf.writeStats(Info);
Info<< "Area : " << sum(surf.magSf()) << nl << endl;
}
else if (args.found("triFace"))
{
MeshedSurface<triFace> surf(importName, ext);
Info<< "Read surface:" << endl;
surf.writeStats(Info);
Info<< "Area : " << sum(surf.magSf()) << nl << endl;
}
else if (args.found("unsorted"))
{
UnsortedMeshedSurface<face> surf(importName, ext);
Info<< "Read surface:" << endl;
surf.writeStats(Info);
Info<< "Area : " << sum(surf.magSf()) << nl << endl;
}
else
{
MeshedSurface<face> surf(importName, ext);
Info<< "Read surface:" << endl;
surf.writeStats(Info);
Info<< "Area : " << sum(surf.magSf()) << nl << endl;
}
Info<< nl << "Reading took " << timing.elapsedTime() << "s" << nl
<< "\nEnd\n" << endl;
return 0;
}
// ************************************************************************* //

View File

@ -121,8 +121,12 @@ OptimisationSwitches
// Force dumping (at next timestep) upon signal (-1 to disable) and exit
stopAtWriteNowSignal -1;
//- Choose STL ASCII parser: 0=Flex, 1=Ragel, 2=Manual
fileFormats::stl 0;
}
/* Can specify fallback profiling settings
profiling
{

View File

@ -50,7 +50,7 @@ Foam::fileName Foam::fileName::validate
out.resize(s.size());
char prev = 0;
std::string::size_type count = 0;
std::string::size_type len = 0;
// Largely as per stripInvalid
for (auto iter = s.cbegin(); iter != s.cend(); ++iter)
@ -66,17 +66,17 @@ Foam::fileName Foam::fileName::validate
}
// Only track valid chars
out[count++] = prev = c;
out[len++] = prev = c;
}
}
if (doClean && prev == '/' && count > 1)
if (doClean && prev == '/' && len > 1)
{
// Avoid trailing '/'
--count;
--len;
}
out.resize(count);
out.resize(len);
return out;
}

View File

@ -222,17 +222,18 @@ inline String Foam::string::validate(const std::string& str)
String out;
out.resize(str.size());
size_type count = 0;
size_type len = 0;
for (auto iter = str.cbegin(); iter != str.cend(); ++iter)
{
const char c = *iter;
if (String::valid(c))
{
out[count++] = c;
out[len] = c;
++len;
}
}
out.resize(count);
out.resize(len);
return out;
}

View File

@ -41,7 +41,7 @@ Foam::word Foam::word::validate(const std::string& s, const bool prefix)
word out;
out.resize(s.size() + (prefix ? 1 : 0));
std::string::size_type count = 0;
std::string::size_type len = 0;
// As per validate, but optionally detect if the first character
// is a digit, which we'd like to avoid having since this will
@ -52,17 +52,51 @@ Foam::word Foam::word::validate(const std::string& s, const bool prefix)
if (word::valid(c))
{
if (!count && prefix && isdigit(c))
if (!len && prefix && isdigit(c))
{
// First valid character was a digit - prefix with '_'
out[count++] = '_';
out[len++] = '_';
}
out[count++] = c;
out[len++] = c;
}
}
out.resize(count);
out.resize(len);
return out;
}
Foam::word Foam::word::validate
(
const char* first,
const char* last,
const bool prefix
)
{
std::string::size_type len = (last - first) + (prefix ? 1 : 0);
word out;
out.resize(len);
for (len=0; first != last; ++first)
{
const char c = *first;
if (word::valid(c))
{
if (!len && prefix && isdigit(c))
{
// First valid character was a digit - prefix with '_'
out[len++] = '_';
}
out[len++] = c;
}
}
out.resize(len);
return out;
}

View File

@ -148,6 +148,16 @@ public:
// that work nicely as dictionary keywords.
static word validate(const std::string& s, const bool prefix=false);
//- Construct validated word (no invalid characters) from a sequence
//- of characters in the range [first,last),
// Optionally prefix any leading digit with '_'.
static word validate
(
const char* first,
const char* last,
const bool prefix=false
);
// File-like Functions

View File

@ -12,7 +12,9 @@ fire/FIRECore.C
starcd/STARCDCore.C
stl/STLCore.C
stl/STLReader.C
stl/STLReaderASCII.L
stl/STLAsciiParseFlex.L
stl/STLAsciiParseManual.C
stl/STLAsciiParseRagel.C
vtk/core/foamVtkCore.C
vtk/core/foamVtkPTraits.C

View File

@ -0,0 +1,153 @@
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Class
Foam::Detail::STLAsciiParse
Description
Internal class used when parsing STL ASCII format
SourceFiles
STLAsciiParse.C
\*---------------------------------------------------------------------------*/
#ifndef STLAsciiParse_H
#define STLAsciiParse_H
#include "DynamicList.H"
#include "HashTable.H"
#include "STLpoint.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
namespace Foam
{
namespace Detail
{
/*---------------------------------------------------------------------------*\
Class Detail::STLAsciiParse Declaration
\*---------------------------------------------------------------------------*/
class STLAsciiParse
{
protected:
// Protected Data
bool sorted_;
label groupId_; // The current solid group
label lineNum_;
//- The number of local points on the current facet
int nFacetPoints_;
//- Current vertex component when reading 'vertex'
int nVertexCmpt_;
//- Scratch space for reading 'vertex'
STLpoint currVertex_;
DynamicList<STLpoint> points_;
DynamicList<label> facets_;
DynamicList<word> names_;
DynamicList<label> sizes_;
HashTable<label> nameLookup_;
// Protected Member Functions
//- Action when entering 'solid'
inline void beginSolid(word solidName);
//- Action when entering 'facet'
inline void beginFacet();
//- Reset vertex component to zero
inline void resetVertex();
//- Add next vertex component. On each third call, adds the point.
// \return true when point has been added (on the last component)
inline bool addVertexComponent(float val);
//- Add next vertex component. On each third call, adds the point.
// \return true when point has been added (on the last component)
inline bool addVertexComponent(const char* text);
//- Action on 'endfacet'
inline void endFacet();
//- No copy construct
STLAsciiParse(const STLAsciiParse&) = delete;
//- No copy assignment
void operator=(const STLAsciiParse&) = delete;
public:
// Constructors
//- From input stream and the approximate number of vertices in the STL
inline STLAsciiParse(const label approxNpoints);
// Member Functions
//- Reset stored values
inline void clear();
//- Do all the solid groups appear in order?
inline bool sorted() const;
//- A list of unstitched triangle points
inline DynamicList<STLpoint>& points();
//- A list of facet IDs (group IDs)
//- corresponds to the number of triangles
inline DynamicList<label>& facets();
//- Solid names in the order of their appearance.
inline DynamicList<word>& names();
//- Solid sizes in the order of their appearance.
inline DynamicList<label>& sizes();
};
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
} // End namespace Detail
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#include "STLAsciiParseI.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
#endif
// ************************************************************************* //

View File

@ -3,7 +3,7 @@
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2016-2017 OpenCFD Ltd.
\\/ M anipulation | Copyright (C) 2016-2018 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -21,18 +21,21 @@ License
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Description
Flex-based parsing of STL ASCII format
\*---------------------------------------------------------------------------*/
%option prefix="yySTL"
%option yyclass="yySTLFlexLexer"
%{
/* ------------------------------------------------------------------------ *\
------ local definitions
\* ------------------------------------------------------------------------ */
#include "STLAsciiParse.H"
#include "STLReader.H"
#include "OSspecific.H"
@ -40,6 +43,7 @@ License
#pragma clang diagnostic ignored "-Wdeprecated-register"
using namespace Foam;
// Dummy yyFlexLexer::yylex() to keep the linker happy. It is not called
//! \cond dummy
#if YY_FLEX_MAJOR_VERSION <= 2 && YY_FLEX_MINOR_VERSION <= 5 && YY_FLEX_SUBMINOR_VERSION < 34
@ -67,111 +71,67 @@ int yySTLFlexLexer::yywrap()
}
//! \endcond
//- A lexer for parsing STL ASCII files.
// Returns DynamicList(s) of points and facets (zoneIds).
// The facets are within a solid/endsolid grouping
class STLASCIILexer
class STLAsciiParseFlex
:
public Detail::STLAsciiParse,
public yySTLFlexLexer
{
// Private data
bool sorted_;
label groupID_; // current solid group
label lineNo_;
word startError_;
DynamicList<STLpoint> points_;
DynamicList<label> facets_;
DynamicList<word> names_;
DynamicList<label> sizes_;
HashTable<label> lookup_;
word startError_;
public:
// Constructors
//- From input stream and the approximate number of vertices in the STL
STLASCIILexer(istream* is, const label approxNpoints);
//- From input stream and the approximate number of vertices in the STL
STLAsciiParseFlex(istream* is, const label approxNpoints)
:
Detail::STLAsciiParse(approxNpoints),
yySTLFlexLexer(is)
{}
// Member Functions
//- The lexer function itself
int lex();
//- The lexer function itself
int lex();
// Access
//- Do all the solid groups appear in order?
inline bool sorted() const
{
return sorted_;
}
//- A list of unstitched triangle points
inline DynamicList<STLpoint>& points()
{
return points_;
}
//- A list of facet IDs (group IDs)
// corresponds to the number of triangles
inline DynamicList<label>& facets()
{
return facets_;
}
//- Solid names in the order of their appearance.
inline DynamicList<word>& names()
{
return names_;
}
//- Solid sizes in the order of their appearance.
inline DynamicList<label>& sizes()
{
return sizes_;
}
//- Execute lexer
void execute()
{
while (lex()) {}
}
};
STLASCIILexer::STLASCIILexer(istream* is, const label approxNpoints)
:
yySTLFlexLexer(is),
sorted_(true),
groupID_(-1),
lineNo_(1),
points_(approxNpoints),
facets_(approxNpoints)
{}
/* ------------------------------------------------------------------------ *\
------ cppLexer::yylex()
\* ------------------------------------------------------------------------ */
#undef YY_DECL
#define YY_DECL int STLASCIILexer::lex()
#define YY_DECL int STLAsciiParseFlex::lex()
%}
one_space [ \t\f\r]
space {one_space}*
some_space {one_space}+
white [ \t\f\r]
space {white}*
some_space {white}+
alpha [_A-Za-z]
digit [0-9]
integer {digit}+
signedInteger [-+]?{integer}
intNum [-+]?{digit}+
word ([[:alnum:]]|[[:punct:]])*
string {word}({some_space}{word})*
exponent_part [eE][-+]?{digit}+
fractional_constant [-+]?(({digit}*"."{digit}+)|({digit}+"."?))
expon [Ee][-+]?{digit}+
fract [-+]?(({digit}*"."{digit}+)|({digit}+"."?))
floatNum (({fractional_constant}{exponent_part}?)|({digit}+{exponent_part}))
floatNum (({fract}{expon}?)|({digit}+{expon}))
x {floatNum}
y {floatNum}
@ -205,13 +165,6 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
%%
%{
// End of read character pointer returned by strtof
// char* endPtr;
label cmpt = 0; // Component index when reading vertex
STLpoint vertex;
// STLpoint normal;
static const char* stateNames[7] =
{
"reading solid",
@ -247,62 +200,13 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
}
<readSolidName>{string} {
const word solidName(word::validate(YYText()));
auto iter = lookup_.cfind(solidName);
if (iter.found())
{
if (groupID_ != iter.object())
{
sorted_ = false; // Group appeared out of order
groupID_ = iter.object();
}
}
else
{
groupID_ = sizes_.size();
if (lookup_.insert(solidName, groupID_))
{
names_.append(solidName);
sizes_.append(0);
}
else
{
FatalErrorInFunction<< "Duplicate solid-name: " << solidName
<< exit(FatalError);
}
}
beginSolid(word::validate(YYText()));
BEGIN(INITIAL);
}
<readSolidName>{space}\n {
const word solidName("solid"); // Could also use solid0, solid1, ...
auto iter = lookup_.cfind(solidName);
if (iter.found())
{
if (groupID_ != iter.object())
{
sorted_ = false; // Group appeared out of order
groupID_ = iter.object();
}
}
else
{
groupID_ = sizes_.size();
if (lookup_.insert(solidName, groupID_))
{
names_.append(solidName);
sizes_.append(0);
}
else
{
FatalErrorInFunction<< "Duplicate solid-name: " << solidName
<< exit(FatalError);
}
}
++lineNo_;
beginSolid("solid"); // Could also use solid0, solid1, ...
++lineNum_;
BEGIN(INITIAL);
}
@ -311,6 +215,7 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
}
{facet} {
beginFacet();
BEGIN(readFacet);
}
@ -337,24 +242,16 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
BEGIN(readVertex);
}
<readVertex>{space}{signedInteger}{space} {
vertex[cmpt++] = atol(YYText());
if (cmpt == 3)
<readVertex>{space}{intNum}{space} {
if (addVertexComponent(float(::atol(YYText()))))
{
cmpt = 0;
points_.append(vertex);
BEGIN(readVertices);
}
}
<readVertex>{space}{floatNum}{space} {
vertex[cmpt++] = atof(YYText());
if (cmpt == 3)
if (addVertexComponent(::atof(YYText())))
{
cmpt = 0;
points_.append(vertex);
BEGIN(readVertices);
}
}
@ -364,8 +261,7 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
}
<readFacet>{endfacet} {
facets_.append(groupID_);
sizes_[groupID_]++;
endFacet();
BEGIN(INITIAL);
}
@ -376,7 +272,7 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
/* ---------------- Ignore remaining spaces and newlines ------------------ */
<*>{space} {}
<*>\n { ++lineNo_; }
<*>\n { ++lineNum_; }
/* ------------------- Any other characters are errors -------------------- */
@ -392,7 +288,7 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
<stlError>.* {
yy_pop_state();
FatalErrorInFunction
<< "while " << stateNames[YY_START] << " on line " << lineNo_ << nl
<< "while " << stateNames[YY_START] << " on line " << lineNum_ << nl
<< " expected " << stateExpects[YY_START]
<< " but found '" << startError_.c_str() << YYText() << "'"
<< exit(FatalError);
@ -408,15 +304,13 @@ endsolid {space}("endsolid"|"ENDSOLID")({some_space}{word})*
//
// member function
// Member Function
//
bool Foam::fileFormats::STLReader::readASCII
bool Foam::fileFormats::STLReader::readAsciiFlex
(
const fileName& filename
)
{
format_ = STLFormat::UNKNOWN;
IFstream is(filename);
if (!is)
{
@ -425,20 +319,12 @@ bool Foam::fileFormats::STLReader::readASCII
<< exit(FatalError);
}
// Create the lexer with the approximate number of vertices in the STL
// from the file size
STLASCIILexer lexer(&(is.stdStream()), Foam::fileSize(filename)/400);
while (lexer.lex() != 0) {}
// Create with approx number of vertices in the STL (from file size)
STLAsciiParseFlex lexer(&(is.stdStream()), Foam::fileSize(filename)/400);
lexer.execute();
sorted_ = lexer.sorted();
transfer(lexer);
// Transfer to normal lists
points_.transfer(lexer.points());
zoneIds_.transfer(lexer.facets());
names_.transfer(lexer.names());
sizes_.transfer(lexer.sizes());
format_ = STLFormat::ASCII;
return true;
}

View File

@ -0,0 +1,192 @@
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
inline void Foam::Detail::STLAsciiParse::beginSolid(word solidName)
{
if (solidName.empty())
{
solidName = "solid"; // Could also use solid0, solid1, ...
}
auto iter = nameLookup_.cfind(solidName);
if (iter.found())
{
if (groupId_ != iter.object())
{
sorted_ = false; // Group appeared out of order
groupId_ = iter.object();
}
}
else
{
groupId_ = sizes_.size();
if (nameLookup_.insert(solidName, groupId_))
{
names_.append(solidName);
sizes_.append(0);
}
else
{
FatalErrorInFunction<< "Duplicate solid-name: " << solidName
<< exit(FatalError);
}
}
}
inline void Foam::Detail::STLAsciiParse::beginFacet()
{
nFacetPoints_ = 0;
nVertexCmpt_ = 0;
}
inline void Foam::Detail::STLAsciiParse::resetVertex()
{
nVertexCmpt_ = 0;
}
inline bool Foam::Detail::STLAsciiParse::addVertexComponent(float val)
{
currVertex_[nVertexCmpt_] = val;
if (++nVertexCmpt_ == 3)
{
points_.append(currVertex_);
nVertexCmpt_ = 0;
++nFacetPoints_;
}
return !nVertexCmpt_;
}
inline bool Foam::Detail::STLAsciiParse::addVertexComponent(const char* text)
{
//-> safer, but slower: readFloat(text, currVertex_[nVertexCmpt_]);
currVertex_[nVertexCmpt_] = ::atof(text);
if (++nVertexCmpt_ == 3)
{
points_.append(currVertex_);
nVertexCmpt_ = 0;
++nFacetPoints_;
}
return !nVertexCmpt_;
}
inline void Foam::Detail::STLAsciiParse::endFacet()
{
if (nFacetPoints_ == 3)
{
facets_.append(groupId_);
sizes_[groupId_]++;
}
else
{
if (nFacetPoints_ > 3)
{
nFacetPoints_ -= 3;
}
if (nFacetPoints_)
{
points_.resize(points_.size() - nFacetPoints_);
}
}
nFacetPoints_ = 0;
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
inline Foam::Detail::STLAsciiParse::STLAsciiParse(const label approxNpoints)
:
sorted_(true),
groupId_(-1),
lineNum_(1),
nFacetPoints_(0),
nVertexCmpt_(0),
points_(approxNpoints),
facets_(approxNpoints/2)
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
inline void Foam::Detail::STLAsciiParse::clear()
{
sorted_ = true;
groupId_ = -1;
lineNum_ = 0;
nFacetPoints_ = 0;
nVertexCmpt_ = 0;
points_.clear();
facets_.clear();
names_.clear();
sizes_.clear();
nameLookup_.clear();
}
inline bool Foam::Detail::STLAsciiParse::sorted() const
{
return sorted_;
}
inline Foam::DynamicList<Foam::STLpoint>& Foam::Detail::STLAsciiParse::points()
{
return points_;
}
inline Foam::DynamicList<Foam::label>& Foam::Detail::STLAsciiParse::facets()
{
return facets_;
}
inline Foam::DynamicList<Foam::word>& Foam::Detail::STLAsciiParse::names()
{
return names_;
}
inline Foam::DynamicList<Foam::label>& Foam::Detail::STLAsciiParse::sizes()
{
return sizes_;
}
// ************************************************************************* //

View File

@ -0,0 +1,423 @@
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Description
Hand-written parsing of STL ASCII format
\*---------------------------------------------------------------------------*/
#include "STLAsciiParse.H"
#include "STLReader.H"
#include "OSspecific.H"
#include "stringOps.H"
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
static inline std::string perrorEOF(std::string expected)
{
return "Premature EOF while reading '" + expected + "'";
}
static inline std::string perrorParse(std::string expected, std::string found)
{
return "Parse error. Expecting '" + expected + "' found '" + found + "'";
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
namespace Foam
{
namespace Detail
{
//- A lexer for parsing STL ASCII files.
// Returns DynamicList(s) of points and facets (zoneIds).
// The facets are within a solid/endsolid grouping
class STLAsciiParseManual
:
public Detail::STLAsciiParse
{
enum scanState
{
scanSolid = 0,
scanFacet,
scanLoop,
scanVerts,
scanEndLoop,
scanEndFacet,
scanEndSolid
};
scanState state_;
std::string errMsg_;
//- Like std:csub_match
typedef std::pair<const char*, const char*> tokenType;
// Tokenized line
DynamicList<tokenType, 16> tokens_;
//- Tokenize
inline std::string::size_type tokenize(const char *p, const char *pe)
{
const char* start = p;
tokens_.clear();
// Find not space
while (p < pe && isspace(*p))
{
if (*p == '\n' && lineNum_)
{
++lineNum_;
}
++p;
}
while (p != pe)
{
const char* beg = p;
// Find space
while (p < pe && !isspace(*p))
{
++p;
}
tokens_.append(tokenType(beg, p));
// Find next
while (p < pe && isspace(*p))
{
if (*p == '\n')
{
++lineNum_;
return (p - start);
}
++p;
}
}
return (p - start);
}
public:
//- From input stream and the approximate number of vertices in the STL
STLAsciiParseManual(const label approxNpoints)
:
Detail::STLAsciiParse(approxNpoints)
{}
//- Execute parser
void execute(std::istream& is);
};
} // end of namespace Detail
} // end of namespace Foam
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Length of the input read buffer
#define INBUFLEN 16384
void Foam::Detail::STLAsciiParseManual::execute(std::istream& is)
{
if (!is)
{
return;
}
// Buffering
char inbuf[INBUFLEN];
std::streamsize pending = 0;
lineNum_ = 0;
state_ = scanSolid;
errMsg_.clear();
// Line-oriented processing loop
while (is)
{
if (pending >= INBUFLEN)
{
// We overfilled the buffer while trying to scan a token...
FatalErrorInFunction
<< "buffer full while scanning near line " << lineNum_ << nl;
break;
}
char *data = inbuf + pending; // current data buffer
const std::streamsize buflen = INBUFLEN - pending; // space in buffer
is.read(data, buflen);
const std::streamsize gcount = is.gcount();
if (!gcount)
{
// EOF
// If scanning for next "solid" this is a valid way to exit, but
// an error if scanning for the initial "solid" or any other token
switch (state_)
{
case scanSolid:
{
if (!lineNum_) errMsg_ = perrorEOF("solid");
break;
}
case scanFacet: { errMsg_ = perrorEOF("facet"); break; }
case scanLoop: { errMsg_ = perrorEOF("outer loop"); break; }
case scanVerts: { errMsg_ = perrorEOF("vertex"); break; }
case scanEndLoop: { errMsg_ = perrorEOF("endloop"); break; }
case scanEndFacet: { errMsg_ = perrorEOF("endfacet"); break; }
case scanEndSolid: { errMsg_ = perrorEOF("endsolid"); break; }
}
// Terminate the parsing loop
break;
}
// p,pe = Ragel parsing point and parsing end (default naming)
// eof = Ragel EOF point (default naming)
char *p = inbuf;
char *pe = data + gcount;
// Line-oriented: search backwards to find last newline
{
--pe;
while (*pe != '\n' && pe >= inbuf)
{
--pe;
}
++pe;
}
std::string cmd;
do
{
// Tokenize
const auto parsedLen = tokenize(p, pe);
p += parsedLen;
if (!parsedLen || tokens_.empty())
{
break;
}
// Ensure consistent case on the first token
cmd.assign(tokens_[0].first, tokens_[0].second);
stringOps::lower(cmd);
// Handle all expected parse states
switch (state_)
{
case scanSolid:
{
if (cmd == "solid")
{
if (tokens_.empty())
{
beginSolid(word::null);
}
else
{
beginSolid
(
word::validate(tokens_[1].first, tokens_[1].second)
);
}
state_ = scanFacet; // Next state
}
else
{
errMsg_ = perrorParse("solid", cmd);
}
break;
}
case scanFacet:
{
if (cmd == "color")
{
// Optional 'color' entry (after solid)
// - continue looking for 'facet'
continue;
}
else if (cmd == "facet")
{
beginFacet();
state_ = scanLoop; // Next state
}
else if (cmd == "endsolid")
{
// Finished with 'endsolid' - find next solid
state_ = scanSolid;
}
else
{
errMsg_ = perrorParse("facet", cmd);
}
break;
}
case scanLoop:
{
if (cmd == "outer")
{
// More pedantic would with (tokens_[1] == "loop") too
state_ = scanVerts; // Next state
}
else
{
errMsg_ = perrorParse("outer loop", cmd);
}
break;
}
case scanVerts:
{
if (cmd == "vertex")
{
if (tokens_.size() > 3)
{
// Although tokens are not nul-terminated,
// they are space delimited and thus good enough for atof()
addVertexComponent(tokens_[1].first);
addVertexComponent(tokens_[2].first);
addVertexComponent(tokens_[3].first);
}
else
{
errMsg_ = "Error parsing vertex value";
}
}
else if (cmd == "endloop")
{
state_ = scanEndFacet; // Next state
}
else
{
errMsg_ = perrorParse("vertex", cmd);
}
break;
}
case scanEndLoop:
{
if (cmd == "endloop")
{
state_ = scanEndFacet; // Next state
}
else
{
errMsg_ = perrorParse("endloop", cmd);
}
break;
}
case scanEndFacet:
{
if (cmd == "endfacet")
{
endFacet();
state_ = scanFacet; // Next facet, or endsolid
}
else
{
errMsg_ = perrorParse("endfacet", cmd);
}
break;
}
case scanEndSolid:
{
if (cmd == "endsolid")
{
state_ = scanSolid; // Start over again
}
else
{
errMsg_ = perrorParse("endsolid", cmd);
}
break;
}
}
}
while (errMsg_.empty());
// How much still in the bufffer?
pending = data + gcount - pe;
if (pending)
{
memmove(inbuf, pe, pending);
}
if (gcount < buflen)
{
break; // done
}
if (!errMsg_.empty())
{
break;
}
}
if (!errMsg_.empty())
{
FatalErrorInFunction
<< errMsg_ << nl;
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//
// Member Function
//
bool Foam::fileFormats::STLReader::readAsciiManual
(
const fileName& filename
)
{
IFstream is(filename);
if (!is)
{
FatalErrorInFunction
<< "file " << filename << " not found"
<< exit(FatalError);
}
// Create with the approximate number of vertices in the STL from file size
Detail::STLAsciiParseManual lexer(Foam::fileSize(filename)/400);
lexer.execute(is.stdStream());
transfer(lexer);
return true;
}
// ************************************************************************* //

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,310 @@
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2018 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Description
Ragel-based parsing of STL ASCII format.
The goto-based finite state machine (FSM) is generated with
ragel -G2 -o STLAsciiParseRagel.C STLAsciiParseRagel.rl
\*---------------------------------------------------------------------------*/
#include "STLAsciiParse.H"
#include "STLReader.H"
#include "OSspecific.H"
// https://en.wikipedia.org/wiki/STL_%28file_format%29#ASCII_STL
//
// Format
//
// solid [name]
//
// * where name is an optional string.
// * The file continues with any number of triangles,
// each represented as follows:
//
// [color ...]
// facet normal ni nj nk
// outer loop
// vertex v1x v1y v1z
// vertex v2x v2y v2z
// vertex v3x v3y v3z
// endloop
// endfacet
//
// * where each n or v is a floating-point number.
// * The file concludes with
//
// endsolid [name]
// We take some parsing shortcuts.
// - Ignore 'color' lines
// - Only look for initial 'facet '. Ignore 'normal ...'
// - Ignore name for 'endsolid'
//
// Ragel machine definition
// Ragel variables (p, pe, eof, cs, top, stack, ts, te, act) defined later...
//
// Can use 'variable p xxx;' etc to change these names
// Define the machine actions
%%{
machine stlAscii;
action buffer { tok = p; /* Local token start */ }
action nl { ++lineNum_; }
action bsolid { beginSolid(word::validate(tok, p)); }
action bfacet { beginFacet(); }
action efacet { endFacet(); }
action bvertex { resetVertex(); }
action vertexCmpt
{
const char saveC = *p;
*p = '\0'; // Make nul-terminated
addVertexComponent(tok);
*p = saveC; // Restore previous character
}
}%%
%%{
machine stlAscii;
white = [ \t\f\r]; # Horizontal whitespace
nl = (white* '\n' %nl); # Newline
dnl = ([^\n]* '\n' %nl); # Discard up to and including newline
decimal = ((digit* '.' digit+) | (digit+ '.'?)) ;
number = [\-+]? (digit+ | decimal) ([Ee][\-+]? digit+)? ;
bfacet = space* ("facet"|"FACET") white %bfacet dnl;
efacet = space* ("endfacet"|"ENDFACET") %efacet dnl;
solidName =
('' >buffer %bsolid nl)
| ((white+ [^\n]*) >buffer %bsolid nl);
bsolid =
space* ("solid"|"SOLID") solidName ;
esolid = space* ("endsolid"|"ENDSOLID") dnl;
color = space* ("color"|"COLOR") dnl;
bloop = space* ("outer" white+ "loop")|("OUTER" white+ "LOOP") dnl;
eloop = space* ("endloop"|"ENDLOOP") dnl;
vertex = space* ("vertex"|"VERTEX")
((white+ (number > buffer %vertexCmpt)){3} nl);
main := space*
(
bsolid
color?
( bfacet bloop (vertex)* eloop efacet )*
esolid
)+ space*;
}%%
//
// FSM globals
//
%% write data nofinal;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
namespace Foam
{
namespace Detail
{
//- A lexer for parsing STL ASCII files.
// Returns DynamicList(s) of points and facets (zoneIds).
// The facets are within a solid/endsolid grouping
class STLAsciiParseRagel
:
public Detail::STLAsciiParse
{
// Private Data
word startError_;
public:
//- From input stream and the approximate number of vertices in the STL
STLAsciiParseRagel(const label approxNpoints)
:
Detail::STLAsciiParse(approxNpoints)
{}
//- Execute lexer
void execute(std::istream& is);
};
} // end of namespace Detail
} // end of namespace Foam
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Length of the input read buffer
#define INBUFLEN 16384
void Foam::Detail::STLAsciiParseRagel::execute(std::istream& is)
{
if (!is)
{
return;
}
// cs = code state
int cs;
%%{write init;}%% /* ^^^ FSM initialization here ^^^ */;
// Local token start
char *tok = nullptr;
// Buffering
char inbuf[INBUFLEN];
std::streamsize pending = 0;
// Line-oriented processing loop (as per Ragel pdf example)
while (is)
{
if (pending >= INBUFLEN)
{
// We overfilled the buffer while trying to scan a token...
FatalErrorInFunction
<< "buffer full while scanning near line " << lineNum_ << nl;
break;
}
char *data = inbuf + pending; // current data buffer
const std::streamsize buflen = INBUFLEN - pending; // space in buffer
is.read(data, buflen);
const std::streamsize gcount = is.gcount();
if (!gcount)
{
break;
}
// p,pe = Ragel parsing point, parsing end (default naming)
// eof = Ragel EOF point (default naming)
char *p = inbuf;
const char *pe = data + gcount;
const char *eof = nullptr;
if (!is)
{
eof = pe; // Tag 'pe' as being the EOF for the FSM as well
}
// Line-oriented: search backwards to find last newline
{
--pe;
while (*pe != '\n' && pe >= inbuf)
{
--pe;
}
++pe;
}
%%{write exec;}%% /* ^^^ FSM execution here ^^^ */;
if (%%{write error;}%% == cs)
{
// FSM failed before finding a token
FatalErrorInFunction
<< "parse error while scanning near line " << lineNum_ << nl;
if (p)
{
std::string::size_type errLen = (pe - p);
if (errLen > 80)
{
errLen = 80;
}
FatalErrorInFunction
<< "context: " << std::string(p, errLen) << nl;
}
break;
}
// How much still in the bufffer?
pending = data + gcount - pe;
if (pending)
{
memmove(inbuf, pe, pending);
}
if (gcount < buflen)
{
break; // done
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//
// Member Function
//
bool Foam::fileFormats::STLReader::readAsciiRagel
(
const fileName& filename
)
{
IFstream is(filename);
if (!is)
{
FatalErrorInFunction
<< "file " << filename << " not found"
<< exit(FatalError);
}
// Create with approx number of vertices in the STL (from file size)
Detail::STLAsciiParseRagel lexer(Foam::fileSize(filename)/400);
lexer.execute(is.stdStream());
transfer(lexer);
return true;
}
// ************************************************************************* //

View File

@ -24,14 +24,60 @@ License
\*---------------------------------------------------------------------------*/
#include "STLReader.H"
#include "STLAsciiParse.H"
#include "Map.H"
#include "IFstream.H"
#include "mergePoints.H"
#undef DEBUG_STLBINARY
/* * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * * */
int Foam::fileFormats::STLReader::parserType
(
Foam::debug::optimisationSwitch("fileFormats::stl", 0)
);
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
void Foam::fileFormats::STLReader::transfer
(
Detail::STLAsciiParse& parsed
)
{
sorted_ = parsed.sorted();
points_.transfer(parsed.points());
zoneIds_.transfer(parsed.facets());
names_.transfer(parsed.names());
sizes_.transfer(parsed.sizes());
format_ = STLFormat::ASCII;
parsed.clear();
}
bool Foam::fileFormats::STLReader::readASCII
(
const fileName& filename
)
{
// No runtime selection of parser (only via optimisationSwitch)
// this is something that is infrequently changed.
if (parserType == 1)
{
return readAsciiRagel(filename);
}
else if (parserType == 2)
{
return readAsciiManual(filename);
}
return readAsciiFlex(filename);
}
bool Foam::fileFormats::STLReader::readBINARY
(
const fileName& filename
@ -189,12 +235,6 @@ Foam::fileFormats::STLReader::STLReader
}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::fileFormats::STLReader::~STLReader()
{}
// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
void Foam::fileFormats::STLReader::clear()

View File

@ -45,6 +45,15 @@ SourceFiles
namespace Foam
{
namespace Detail
{
// Forward Declarations
class STLAsciiParse;
}
namespace fileFormats
{
@ -78,7 +87,19 @@ class STLReader
// Private Member Functions
//- Read ASCII
//- Transfer parsed information to normal lists
void transfer(Detail::STLAsciiParse& parsed);
//- Parse/read ASCII using Flex-based parser
bool readAsciiFlex(const fileName& filename);
//- Parse/read ASCII using Ragel-based parser
bool readAsciiRagel(const fileName& filename);
//- Parse/read ASCII using simple handwritten parser
bool readAsciiManual(const fileName& filename);
//- Parse/read ASCII
bool readASCII(const fileName& filename);
//- Read BINARY
@ -88,15 +109,21 @@ class STLReader
bool readFile(const fileName& filename, const STLFormat format);
//- Disallow default bitwise copy construct
//- No copy construct
STLReader(const STLReader&) = delete;
//- Disallow default bitwise assignment
//- No copy assignment
void operator=(const STLReader&) = delete;
public:
// Static Data
//- ASCII parser types (0=Flex, 1=Ragel, 2=Manual)
static int parserType;
// Constructors
//- Read from file, filling in the information.
@ -109,7 +136,7 @@ public:
//- Destructor
~STLReader();
~STLReader() = default;
// Member Functions

View File

@ -82,10 +82,10 @@ class STLsurfaceFormat
);
//- Disallow default bitwise copy construct
//- No copy construct
STLsurfaceFormat(const STLsurfaceFormat<Face>&) = delete;
//- Disallow default bitwise assignment
//- No copy assignment
void operator=(const STLsurfaceFormat<Face>&) = delete;

View File

@ -491,19 +491,30 @@ Foam::triSurface::triSurface
}
Foam::triSurface::triSurface(const fileName& name, const scalar scaleFactor)
Foam::triSurface::triSurface
(
const fileName& name,
const scalar scaleFactor
)
:
triSurface(name, name.ext(), scaleFactor)
{}
Foam::triSurface::triSurface
(
const fileName& name,
const word& ext,
const scalar scaleFactor
)
:
ParentType(List<Face>(), pointField()),
patches_(),
sortedEdgeFacesPtr_(nullptr),
edgeOwnerPtr_(nullptr)
{
const word ext = name.ext();
read(name, ext);
scalePoints(scaleFactor);
setDefaultPatches();
}

View File

@ -285,6 +285,14 @@ public:
// Optional (positive, non-zero) point scaling is possible.
triSurface(const fileName& name, const scalar scaleFactor = -1);
//- Construct from file name (uses extension to determine type)
triSurface
(
const fileName& name,
const word& ext,
const scalar scaleFactor = -1
);
//- Construct from Istream
triSurface(Istream& is);