Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: ReadStlFile-Fix error logic when reading an ASCII STL File. #839

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -112,37 +112,28 @@ IFilter::PreflightResult ReadStlFileFilter::preflightImpl(const DataStructure& d

std::vector<PreflightValue> preflightUpdatedValues;

// If the filter needs to pass back some updated values via a key:value string:string set of values
// you can declare and update that string here.

// Collect all the errors
std::vector<Error> errors;

// Validate that the STL File is binary and readable.
int32_t stlFileType = StlUtilities::DetermineStlFileType(pStlFilePathValue);
if(stlFileType < 0)
StlConstants::StlFileType stlFileType = StlUtilities::DetermineStlFileType(pStlFilePathValue);
if(stlFileType == StlConstants::StlFileType::ASCI)
{
return {MakeErrorResult<OutputActions>(
StlConstants::k_UnsupportedFileType,
fmt::format("The Input STL File is ASCII which is not currently supported. Please convert it to a binary STL file using another program.", pStlFilePathValue.string()))};
}
if(stlFileType == StlConstants::StlFileType::FileOpenError)
{
Error result = {StlConstants::k_UnsupportedFileType,
fmt::format("The Input STL File is ASCII which is not currently supported. Please convert it to a binary STL file using another program.", pStlFilePathValue.string())};
errors.push_back(result);
return {MakeErrorResult<OutputActions>(StlConstants::k_ErrorOpeningFile, fmt::format("Error opening the STL file.", pStlFilePathValue.string()))};
}
if(stlFileType > 0)
if(stlFileType == StlConstants::StlFileType::HeaderParseError)
{
Error result = {StlConstants::k_ErrorOpeningFile, fmt::format("Error reading the STL file.", pStlFilePathValue.string())};
errors.push_back(result);
return {MakeErrorResult<OutputActions>(StlConstants::k_ErrorOpeningFile, fmt::format("Error reading the header from STL file.", pStlFilePathValue.string()))};
}

// Now get the number of Triangles according to the STL Header
int32_t numTriangles = StlUtilities::NumFacesFromHeader(pStlFilePathValue);
if(numTriangles < 0)
{
Error result = {StlConstants::k_ErrorOpeningFile, fmt::format("Error reading the STL file.", pStlFilePathValue.string())};
errors.push_back(result);
}

if(!errors.empty())
{
return {nonstd::make_unexpected(std::move(errors))};
return {MakeErrorResult<OutputActions>(numTriangles, fmt::format("Error extracting the number of triangles from the STL file.", pStlFilePathValue.string()))};
}

// This can happen in a LOT of STL files. Just means the writer didn't go back and update the header.
Expand Down
108 changes: 96 additions & 12 deletions src/Plugins/SimplnxCore/src/SimplnxCore/utils/StlUtilities.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
#include "StlUtilities.hpp"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using namespace nx::core;

int32_t StlUtilities::DetermineStlFileType(const fs::path& path)
StlConstants::StlFileType StlUtilities::DetermineStlFileType(const fs::path& path)
{
// Open File
FILE* f = std::fopen(path.string().c_str(), "rb");
if(nullptr == f)
{
return StlConstants::k_ErrorOpeningFile;
return StlConstants::StlFileType::FileOpenError;
}

// Read the first 256 bytes of data, that should be enough but I'm sure someone will write
Expand All @@ -17,29 +24,29 @@ int32_t StlUtilities::DetermineStlFileType(const fs::path& path)
if(std::fread(header.data(), 1, StlConstants::k_STL_HEADER_LENGTH, f) != StlConstants::k_STL_HEADER_LENGTH)
{
std::ignore = std::fclose(f);
return StlConstants::k_StlHeaderParseError;
return StlConstants::StlFileType::HeaderParseError;
}
// close the file
std::ignore = std::fclose(f);

size_t solid_pos = header.find("solid", 0);
size_t solidPos = header.find("solid", 0);
// The word 'solid' was not found ANYWHERE in the first 80 bytes.
if(solid_pos == std::string::npos)
if(solidPos == std::string::npos)
{
return 0;
return StlConstants::StlFileType::Binary;
}
// 'solid' was found as the first 5 bytes of the header. This is am ambiguous case so let's try to find 'facet'
if(solid_pos == 0)
if(solidPos == 0)
{
size_t facet_pos = header.find("facet", solid_pos + 6);
if(facet_pos == std::string::npos)
size_t facetPos = header.find("facet", solidPos + 6);
if(facetPos == std::string::npos)
{
// 'facet' was NOT found so this is a binary file.
return 0;
return StlConstants::StlFileType::Binary;
}
return 1;
return StlConstants::StlFileType::ASCI;
}
return 0;
return StlConstants::StlFileType::Binary;
}

int32_t StlUtilities::NumFacesFromHeader(const fs::path& path)
Expand Down Expand Up @@ -71,3 +78,80 @@ int32_t StlUtilities::NumFacesFromHeader(const fs::path& path)
std::ignore = std::fclose(f);
return triCount;
}

struct Triangle
{
float normal[3];
float vertex1[3];
float vertex2[3];
float vertex3[3];
};

void StlUtilities::ConvertAsciiToBinaryStl(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath)
{
std::ifstream asciiFile(inputPath);
std::ofstream binaryFile(outputPath, std::ios::binary);

if(!asciiFile.is_open() || !binaryFile.is_open())
{
throw std::runtime_error("Could not open files");
}

// Write the header
std::string header = "Converted by ChatGPT generated Algorithm";
header.resize(80, ' ');
binaryFile.write(header.c_str(), 80);

// Read ASCII STL and store triangles
std::vector<Triangle> triangles;
std::string line;
Triangle tri;

while(std::getline(asciiFile, line))
{
std::istringstream iss(line);
std::string token;
iss >> token;

if(token == "facet")
{
iss >> token; // Skip "normal" word
for(int i = 0; i < 3; ++i)
{
iss >> tri.normal[i];
}
}
else if(token == "vertex")
{
if(iss >> tri.vertex1[0] >> tri.vertex1[1] >> tri.vertex1[2])
{
std::getline(asciiFile, line); // Read next vertex line
std::istringstream iss2(line);
iss2 >> token; // Skip "vertex" word
iss2 >> tri.vertex2[0] >> tri.vertex2[1] >> tri.vertex2[2];

std::getline(asciiFile, line); // Read next vertex line
std::istringstream iss3(line);
iss3 >> token; // Skip "vertex" word
iss3 >> tri.vertex3[0] >> tri.vertex3[1] >> tri.vertex3[2];

triangles.push_back(tri);
}
}
}

// Write the number of triangles
uint32_t numTriangles = static_cast<uint32_t>(triangles.size());
binaryFile.write(reinterpret_cast<const char*>(&numTriangles), sizeof(numTriangles));

// Write triangles
for(const auto& t : triangles)
{
binaryFile.write(reinterpret_cast<const char*>(&t), sizeof(Triangle));
uint16_t attributeByteCount = 0;
binaryFile.write(reinterpret_cast<const char*>(&attributeByteCount), sizeof(attributeByteCount));
}
}

// Example usage:
// convertAsciiToBinarySTL("input_ascii.stl", "output_binary.stl");
27 changes: 25 additions & 2 deletions src/Plugins/SimplnxCore/src/SimplnxCore/utils/StlUtilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,28 @@ inline constexpr int32_t k_StlHeaderParseError = -1104;
inline constexpr int32_t k_TriangleCountParseError = -1105;
inline constexpr int32_t k_TriangleParseError = -1106;
inline constexpr int32_t k_AttributeParseError = -1107;

enum class StlFileType : int
{
Binary = 0,
ASCI = 1,
FileOpenError = 2,
HeaderParseError = 3
};
} // namespace StlConstants

namespace StlUtilities
{
// -----------------------------------------------------------------------------
// Returns 0 for Binary, 1 for ASCII, anything else is an error.
int32_t DetermineStlFileType(const fs::path& path);
/**
* @brief This function will determine if the given STL file is ASCII or BINARY.
*
* This could give a false positive for BINARY for _any_ file that doesn't have
* the first few lines of a valid ASCII STL file.
* @param path The path to the file to check
* @return Enumeration that represents either the type of file or a possible parsing error
*/
StlConstants::StlFileType DetermineStlFileType(const fs::path& path);

/**
* @brief Returns the number of triangles in the file according to the header. This
Expand All @@ -34,5 +49,13 @@ int32_t DetermineStlFileType(const fs::path& path);
* @return Number of triangle faces
*/
int32_t NumFacesFromHeader(const fs::path& path);

/**
* @brief A very basic function to convert a well behaved ASCII STL File into a binary STL file
* @param inputPath The input ASCII STL File
* @param outputPath The output Binary STL file
*/
void ConvertAsciiToBinaryStl(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath);

} // namespace StlUtilities
} // namespace nx::core
Loading