Skip to content

Commit

Permalink
BUG: ReadStlFile-Fix error logic when reading an ASCI STL File.
Browse files Browse the repository at this point in the history
Add an experimental function to convert from ASCI file to a binary STL file. This
could be used in the actual filter to add support for reading ASCII STL files.

Signed-off-by: Michael Jackson <[email protected]>
  • Loading branch information
imikejackson committed Jan 31, 2024
1 parent 0ff43b9 commit ce87e64
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 35 deletions.
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

0 comments on commit ce87e64

Please sign in to comment.