From 7655e7347d7fd521551e860392f9f38d7745459e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 22 Sep 2021 11:11:59 -0500 Subject: [PATCH] [Mesh] Improve NASTRAN input support Adds support for original-NASTRAN fixed-field-width low-precision GRID element (the existing code assumed space-delimited input). --- src/Mod/Mesh/App/Core/MeshIO.cpp | 110 ++++++++++++++++++++++++------- src/Mod/Mesh/Gui/Command.cpp | 6 +- 2 files changed, 89 insertions(+), 27 deletions(-) diff --git a/src/Mod/Mesh/App/Core/MeshIO.cpp b/src/Mod/Mesh/App/Core/MeshIO.cpp index f0a1e0b7e7..cb23619c94 100644 --- a/src/Mod/Mesh/App/Core/MeshIO.cpp +++ b/src/Mod/Mesh/App/Core/MeshIO.cpp @@ -148,6 +148,8 @@ std::vector MeshInput::supportedMeshFormats() fmt.emplace_back("stl"); fmt.emplace_back("ast"); fmt.emplace_back("obj"); + fmt.emplace_back("nas"); + fmt.emplace_back("bdf"); fmt.emplace_back("off"); fmt.emplace_back("smf"); return fmt; @@ -1636,10 +1638,6 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) if ((!rstrIn) || (rstrIn.bad() == true)) return false; - boost::regex rx_p("\\s*GRID\\s+([0-9]+)" - "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)" - "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)" - "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*"); boost::regex rx_t("\\s*CTRIA3\\s+([0-9]+)\\s+([0-9]+)" "\\s+([0-9]+)\\s+([0-9]+)\\s+([0-9]+)\\s*"); boost::regex rx_q("\\s*CQUAD4\\s+([0-9]+)\\s+([0-9]+)" @@ -1656,6 +1654,8 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) std::map mTria; std::map mQuad; + int badElementCounter = 0; + while (std::getline(rstrIn, line)) { upper(ltrim(line)); if (line.empty()) { @@ -1664,22 +1664,24 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) else if (line.rfind("GRID*", 0) == 0) { // This element is the 16-digit-precision GRID element, which occupies two lines of the card. Note that // FreeCAD discards the extra precision, downcasting to an four-byte float. - // - // The two lines are: - // 1 8 24 40 56 - // GRID* Index(16) Blank(16) x(16) y(at least one) - // * z(at least one) - // - // The first character is typically the sign, and may be omitted for positive numbers, - // so it is possible for a field to begin with a blank. Trailing zeros may be omitted, so - // a field may also end with blanks. No space or other delimiter is required between - // the numbers. The following is a valid NASTRAN GRID* element: - // - // GRID* 1 0.1234567890120. - // * 1. - // - if (line.length() < 8 + 16 + 16 + 16 + 1) // Element type(8), index(16), empty(16), x(16), y(>=1) + // + // The two lines are: + // 1 8 24 40 56 + // GRID* Index(16) Blank(16) x(16) y(at least one) + // * z(at least one) + // + // The first character is typically the sign, and may be omitted for positive numbers, + // so it is possible for a field to begin with a blank. Trailing zeros may be omitted, so + // a field may also end with blanks. No space or other delimiter is required between + // the numbers. The following is a valid NASTRAN GRID* element: + // + // GRID* 1 0.1234567890120. + // * 1. + // + if (line.length() < 8 + 16 + 16 + 16 + 1) { // Element type(8), index(16), empty(16), x(16), y(>=1) + badElementCounter++; continue; + } auto indexView = std::string_view(&line[8], 16); auto blankView = std::string_view(&line[8+16], 16); auto xView = std::string_view(&line[8+16+16], 16); @@ -1688,8 +1690,10 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) std::string line2; std::getline(rstrIn, line2); if ((!line2.empty() && line2[0] != '*') || - line2.length() < 9) + line2.length() < 9) { + badElementCounter++; continue; // File format error: second line is not a continuation line + } auto zView = std::string_view(&line2[8]); // We have to strip off any whitespace (technically really just any *trailing* whitespace): @@ -1700,19 +1704,23 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) auto converter = boost::cnv::spirit(); auto indexCheck = boost::convert(indexString, converter); - if (!indexCheck.is_initialized()) + if (!indexCheck.is_initialized()) { // File format error: index couldn't be converted to an integer + badElementCounter++; continue; - index = indexCheck.get(); + } + index = indexCheck.get() - 1; // Minus one so we are zero-indexed to match existing code // Get the high-precision versions first auto x = boost::convert(xString, converter); auto y = boost::convert(yString, converter); auto z = boost::convert(zString, converter); - if (!x.is_initialized() || !y.is_initialized() || !z.is_initialized()) + if (!x.is_initialized() || !y.is_initialized() || !z.is_initialized()) { // File format error: x, y or z could not be converted + badElementCounter++; continue; + } // Now drop precision: mNode[index].x = (float)x.get(); @@ -1720,13 +1728,63 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) mNode[index].z = (float)z.get(); } else if (line.rfind("GRID", 0) == 0) { - if (boost::regex_match(line.c_str(), what, rx_p)) { + + boost::regex rx_spaceDelimited("\\s*GRID\\s+([0-9]+)" + "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)" + "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)" + "\\s+([-+]?[0-9]*)\\.?([0-9]+([eE][-+]?[0-9]+)?)\\s*"); + + if (boost::regex_match(line.c_str(), what, rx_spaceDelimited)) { // insert the read-in vertex into a map to preserve the order index = std::atol(what[1].first)-1; mNode[index].x = (float)std::atof(what[2].first); mNode[index].y = (float)std::atof(what[5].first); mNode[index].z = (float)std::atof(what[8].first); } + else { + // Classic NASTRAN uses a fixed 8 character field width: + // 1 8 16 24 32 40 + // $-------ID------CP------X1------X2------X3------CD------PS------9-------+------- + // GRID 1 1.2345671.2345671.234567 + // GRID 112 6.0000000.5000000.00E+00 + + if (line.length() < 41) { // Element type(8), id(8), cp(8), x(8), y(8), z(at least 1) + badElementCounter++; + continue; + } + auto indexView = std::string_view(&line[8], 8); + auto xView = std::string_view(&line[24], 8); + auto yView = std::string_view(&line[32], 8); + auto zView = std::string_view(&line[40], 8); + + auto indexString = boost::trim_copy(std::string(indexView)); + auto xString = boost::trim_copy(std::string(xView)); + auto yString = boost::trim_copy(std::string(yView)); + auto zString = boost::trim_copy(std::string(zView)); + + auto converter = boost::cnv::spirit(); + auto indexCheck = boost::convert(indexString, converter); + if (!indexCheck.is_initialized()) { + // File format error: index couldn't be converted to an integer + badElementCounter++; + continue; + } + index = indexCheck.get() - 1; // Minus one so we are zero-indexed to match existing code + + auto x = boost::convert(xString, converter); + auto y = boost::convert(yString, converter); + auto z = boost::convert(zString, converter); + + if (!x.is_initialized() || !y.is_initialized() || !z.is_initialized()) { + // File format error: x, y or z could not be converted + badElementCounter++; + continue; + } + + mNode[index].x = x.get(); + mNode[index].y = y.get(); + mNode[index].z = z.get(); + } } else if (line.rfind("CTRIA3 ", 0) == 0) { if (boost::regex_match(line.c_str(), what, rx_t)) { @@ -1749,6 +1807,10 @@ bool MeshInput::LoadNastran (std::istream &rstrIn) } } + if (badElementCounter > 0) { + Base::Console().Warning("Found bad elements while reading NASTRAN file."); + } + float fLength[2]; if (mTria.empty()) index = 0; diff --git a/src/Mod/Mesh/Gui/Command.cpp b/src/Mod/Mesh/Gui/Command.cpp index 0110e54240..f92ca46735 100644 --- a/src/Mod/Mesh/Gui/Command.cpp +++ b/src/Mod/Mesh/Gui/Command.cpp @@ -449,15 +449,15 @@ void CmdMeshImport::activated(int) { // use current path as default QStringList filter; - filter << QString::fromLatin1("%1 (*.stl *.ast *.bms *.obj *.off *.ply)").arg(QObject::tr("All Mesh Files")); + filter << QString::fromLatin1("%1 (*.stl *.ast *.bms *.obj *.off *.iv *.ply *.nas *.bdf)").arg(QObject::tr("All Mesh Files")); filter << QString::fromLatin1("%1 (*.stl)").arg(QObject::tr("Binary STL")); filter << QString::fromLatin1("%1 (*.ast)").arg(QObject::tr("ASCII STL")); filter << QString::fromLatin1("%1 (*.bms)").arg(QObject::tr("Binary Mesh")); filter << QString::fromLatin1("%1 (*.obj)").arg(QObject::tr("Alias Mesh")); filter << QString::fromLatin1("%1 (*.off)").arg(QObject::tr("Object File Format")); - filter << QString::fromLatin1("%1 (*.iv)").arg(QObject::tr("Inventor V2.1 ascii")); + filter << QString::fromLatin1("%1 (*.iv)").arg(QObject::tr("Inventor V2.1 ASCII")); filter << QString::fromLatin1("%1 (*.ply)").arg(QObject::tr("Stanford Polygon")); - //filter << "Nastran (*.nas *.bdf)"; + filter << QString::fromLatin1("%1 (*.nas *.bdf)").arg(QObject::tr("NASTRAN")); filter << QString::fromLatin1("%1 (*.*)").arg(QObject::tr("All Files")); // Allow multi selection