Skip to content

Commit

Permalink
ENH: WriteSTLFiles-Write files by Part Number
Browse files Browse the repository at this point in the history
- Allow user to select an Int32 Array to number the output STL files. This is a single
component array at the Triangle/Face level. This will match up with the Combine STL files
filter that generates the Part Number Array

Signed-off-by: Michael Jackson <[email protected]>
  • Loading branch information
imikejackson committed Jul 22, 2024
1 parent 250f1ad commit 8407197
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ Result<> SingleWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType
return result;
}

/**
* @brief
* @param path
* @param numTriangles
* @param header
* @param triangles
* @param vertices
* @param featureIds
* @param featureId
* @return
*/
Result<> MultiWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType numTriangles, const std::string&& header, const IGeometry::MeshIndexArrayType& triangles, const Float32Array& vertices,
const Int32Array& featureIds, const int32 featureId)
{
Expand Down Expand Up @@ -168,6 +179,7 @@ Result<> MultiWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType n
nonstd::span<uint16> attrByteCountPtr(reinterpret_cast<uint16*>(data.data() + 48), 2);
attrByteCountPtr[0] = 0;

const usize numComps = featureIds.getNumberOfComponents();
// Loop over all the triangles for this spin
for(IGeometry::MeshIndexType triangle = 0; triangle < numTriangles; ++triangle)
{
Expand All @@ -176,13 +188,12 @@ Result<> MultiWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType n
IGeometry::MeshIndexType nId1 = triangles[triangle * 3 + 1];
IGeometry::MeshIndexType nId2 = triangles[triangle * 3 + 2];

if(featureIds[triangle * 2] == featureId)
if(featureIds[triangle * numComps] == featureId)
{
// winding = 0; // 0 = Write it using forward spin
}
else if(featureIds[triangle * 2 + 1] == featureId)
else if(numComps > 1 && featureIds[triangle * numComps + 1] == featureId)
{
// winding = 1; // Write it using backward spin
// Switch the 2 node indices
IGeometry::MeshIndexType temp = nId1;
nId1 = nId2;
Expand Down Expand Up @@ -222,7 +233,7 @@ Result<> MultiWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType n
{
fclose(filePtr);
return {MakeWarningVoidResult(
-27873, fmt::format("Error Writing STL File '{}'. Not enough elements written for Feature Id {}. Wrote {} of 50. No file written.", path.filename().string(), featureId, totalWritten))};
-27873, fmt::format("Error Writing STL File '{}': Not enough bytes written for triangle {}. Only {} bytes written of 50 bytes", path.filename().string(), triCount, totalWritten))};
}
triCount++;
}
Expand Down Expand Up @@ -262,7 +273,7 @@ Result<> WriteStlFile::operator()()

auto groupingType = static_cast<GroupingType>(m_InputValues->GroupingType);

if(groupingType == GroupingType::None)
if(groupingType == GroupingType::SingleFile)
{
auto atomicFileResult = AtomicFile::Create(m_InputValues->OutputStlFile);
if(atomicFileResult.invalid())
Expand Down Expand Up @@ -309,75 +320,109 @@ Result<> WriteStlFile::operator()()
// Store a list of Atomic Files, so we can clean up or finish depending on the outcome of all the writes
std::vector<Result<AtomicFile>> fileList;

{ // Scope to cut overhead and ensure file lock is released on windows
if(groupingType == GroupingType::Features)
{
const auto& featureIds = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->FeatureIdsPath);
// Store all the unique Spins
if(groupingType == GroupingType::Features)

// Faster and more memory efficient since we don't need phases
std::unordered_set<int32> uniqueGrainIds(featureIds.cbegin(), featureIds.cend());

fileList.reserve(uniqueGrainIds.size());

usize fileIndex = 0;
for(const auto featureId : uniqueGrainIds)
{
// Faster and more memory efficient since we don't need phases
std::unordered_set<int32> uniqueGrainIds(featureIds.cbegin(), featureIds.cend());
// Generate the output file
fileList.push_back(AtomicFile::Create(m_InputValues->OutputStlDirectory / fmt::format("{}Feature_{}.stl", m_InputValues->OutputStlPrefix, featureId)));
if(fileList[fileIndex].invalid())
{
return ConvertResult(std::move(fileList[fileIndex]));
}

fileList.reserve(uniqueGrainIds.size());
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Feature Id {}", featureId));

usize fileIndex = 0;
for(const auto featureId : uniqueGrainIds)
auto result = ::MultiWriteOutStl(fileList[fileIndex].value().tempFilePath(), nTriangles, {"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId)}, triangles, vertices,
featureIds, featureId);
// if valid Loop over all the triangles for this spin
if(result.invalid())
{
// Generate the output file
fileList.push_back(AtomicFile::Create(m_InputValues->OutputStlDirectory / fmt::format("{}Feature_{}.stl", m_InputValues->OutputStlPrefix, featureId)));
if(fileList[fileIndex].invalid())
{
return ConvertResult(std::move(fileList[fileIndex]));
}

m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Feature Id {}", featureId));

auto result = ::MultiWriteOutStl(fileList[fileIndex].value().tempFilePath(), nTriangles, {"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId)}, triangles, vertices,
featureIds, featureId);
// if valid Loop over all the triangles for this spin
if(result.invalid())
{
return result;
}

fileIndex++;
return result;
}

fileIndex++;
}
if(groupingType == GroupingType::FeaturesAndPhases)
}
if(groupingType == GroupingType::FeaturesAndPhases)
{
const auto& featureIds = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->FeatureIdsPath);

std::map<int32, int32> uniqueGrainIdToPhase;

const auto& featurePhases = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->FeaturePhasesPath);
for(IGeometry::MeshIndexType i = 0; i < nTriangles; i++)
{
std::map<int32, int32> uniqueGrainIdToPhase;
uniqueGrainIdToPhase.emplace(featureIds[i * 2], featurePhases[i * 2]);
uniqueGrainIdToPhase.emplace(featureIds[i * 2 + 1], featurePhases[i * 2 + 1]);
}

// Loop over the unique feature Ids
usize fileIndex = 0;
for(const auto& [featureId, value] : uniqueGrainIdToPhase)
{
// Generate the output file
fileList.push_back(AtomicFile::Create(m_InputValues->OutputStlDirectory / fmt::format("{}Ensemble_{}_Feature_{}.stl", m_InputValues->OutputStlPrefix, value, featureId)));

const auto& featurePhases = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->FeaturePhasesPath);
for(IGeometry::MeshIndexType i = 0; i < nTriangles; i++)
if(fileList[fileIndex].invalid())
{
uniqueGrainIdToPhase.emplace(featureIds[i * 2], featurePhases[i * 2]);
uniqueGrainIdToPhase.emplace(featureIds[i * 2 + 1], featurePhases[i * 2 + 1]);
return ConvertResult(std::move(fileList[fileIndex]));
}

// Loop over the unique feature Ids
usize fileIndex = 0;
for(const auto& [featureId, value] : uniqueGrainIdToPhase)
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Feature Id {}", featureId));

auto result =
::MultiWriteOutStl(fileList[fileIndex].value().tempFilePath(), nTriangles,
{"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId) + " Phase " + StringUtilities::number(value)}, triangles, vertices, featureIds, featureId);
// if valid loop over all the triangles for this spin
if(result.invalid())
{
// Generate the output file
fileList.push_back(AtomicFile::Create(m_InputValues->OutputStlDirectory / fmt::format("{}Ensemble_{}_Feature_{}.stl", m_InputValues->OutputStlPrefix, value, featureId)));

if(fileList[fileIndex].invalid())
{
return ConvertResult(std::move(fileList[fileIndex]));
}

m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Feature Id {}", featureId));

auto result =
::MultiWriteOutStl(fileList[fileIndex].value().tempFilePath(), nTriangles,
{"DREAM3D Generated For Feature ID " + StringUtilities::number(featureId) + " Phase " + StringUtilities::number(value)}, triangles, vertices, featureIds, featureId);
// if valid loop over all the triangles for this spin
if(result.invalid())
{
return result;
}

fileIndex++;
return result;
}

fileIndex++;
}
}

// Group Triangles by Part Number which is a single component Int32 Array
if(groupingType == GroupingType::PartNumber)
{
const auto& partNumbers = m_DataStructure.getDataRefAs<Int32Array>(m_InputValues->PartNumberPath);
// Faster and more memory efficient since we don't need phases
// Build up a list of the unique Part Numbers
std::unordered_set<int32> uniquePartNumbers(partNumbers.cbegin(), partNumbers.cend());
fileList.reserve(uniquePartNumbers.size()); // Reserved enough file names

// Loop over each Part Number and write a file
usize fileIndex = 0;
for(const auto currentPartNumber : uniquePartNumbers)
{
// Generate the output file
fileList.push_back(AtomicFile::Create(m_InputValues->OutputStlDirectory / fmt::format("{}{}.stl", m_InputValues->OutputStlPrefix, currentPartNumber)));
if(fileList[fileIndex].invalid())
{
return ConvertResult(std::move(fileList[fileIndex]));
}

m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Writing STL for Part Number {}", currentPartNumber));

auto result = ::MultiWriteOutStl(fileList[fileIndex].value().tempFilePath(), nTriangles, {"DREAM3D Generated For Part Number " + StringUtilities::number(currentPartNumber)}, triangles, vertices,
partNumbers, currentPartNumber);
// If Result is invalid, report an error
if(result.invalid())
{
return result;
}

fileIndex++;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ enum class GroupingType : ChoicesParameter::ValueType
{
Features,
FeaturesAndPhases,
None
SingleFile,
PartNumber
};

struct SIMPLNXCORE_EXPORT WriteStlFileInputValues
Expand All @@ -29,6 +30,7 @@ struct SIMPLNXCORE_EXPORT WriteStlFileInputValues
DataPath FeatureIdsPath;
DataPath FeaturePhasesPath;
DataPath TriangleGeomPath;
DataPath PartNumberPath;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ Parameters WriteStlFileFilter::parameters() const
// Create the parameter descriptors that are needed for this filter
params.insertSeparator(Parameters::Separator{"Input Parameter(s)"});
params.insertLinkableParameter(std::make_unique<ChoicesParameter>(k_GroupingType_Key, "File Grouping Type", "How to partition the stl files", to_underlying(GroupingType::Features),
ChoicesParameter::Choices{"Features", "Phases and Features", "None [Single File]"})); // sequence dependent DO NOT REORDER
ChoicesParameter::Choices{"Features", "Phases and Features", "Single File", "Part Index"})); // sequence dependent DO NOT REORDER
params.insert(std::make_unique<FileSystemPathParameter>(k_OutputStlDirectory_Key, "Output STL Directory", "Directory to dump the STL file(s) to", fs::path(),
FileSystemPathParameter::ExtensionsType{}, FileSystemPathParameter::PathType::OutputDir, true));
params.insert(
std::make_unique<StringParameter>(k_OutputStlPrefix_Key, "STL File Prefix", "The prefix name of created files (other values will be appended later - including the .stl extension)", "Triangle"));
params.insert(std::make_unique<StringParameter>(k_OutputStlPrefix_Key, "Output STL File Prefix",
"The prefix name of created files (other values will be appended later - including the .stl extension)", "Triangle"));

params.insert(std::make_unique<FileSystemPathParameter>(k_OutputStlFile_Key, "Output STL File", "STL File to dump the Triangle Geometry to", fs::path(),
FileSystemPathParameter::ExtensionsType{".stl"}, FileSystemPathParameter::PathType::OutputFile, false));
Expand All @@ -74,7 +74,8 @@ Parameters WriteStlFileFilter::parameters() const
ArraySelectionParameter::AllowedTypes{DataType::int32}, ArraySelectionParameter::AllowedComponentShapes{{2}}));
params.insert(std::make_unique<ArraySelectionParameter>(k_FeaturePhasesPath_Key, "Feature Phases", "The feature phases array to further order/index files by", DataPath{},
ArraySelectionParameter::AllowedTypes{DataType::int32}, ArraySelectionParameter::AllowedComponentShapes{{1}}));

params.insert(std::make_unique<ArraySelectionParameter>(k_PartNumberPath_Key, "Part Numbers", "The Part Numbers to order/index files by", DataPath{},
ArraySelectionParameter::AllowedTypes{DataType::int32}, ArraySelectionParameter::AllowedComponentShapes{{1}}));
// link params -- GroupingType enum is stored in the algorithm header [WriteStlFile.hpp]
//------------ Group by Features -------------
params.linkParameters(k_GroupingType_Key, k_OutputStlDirectory_Key, to_underlying(GroupingType::Features));
Expand All @@ -88,7 +89,12 @@ Parameters WriteStlFileFilter::parameters() const
params.linkParameters(k_GroupingType_Key, k_FeaturePhasesPath_Key, to_underlying(GroupingType::FeaturesAndPhases));

//--------------- Single File ----------------
params.linkParameters(k_GroupingType_Key, k_OutputStlFile_Key, to_underlying(GroupingType::None));
params.linkParameters(k_GroupingType_Key, k_OutputStlFile_Key, to_underlying(GroupingType::SingleFile));

//--------------- Part Number ----------------
params.linkParameters(k_GroupingType_Key, k_OutputStlDirectory_Key, to_underlying(GroupingType::PartNumber));
params.linkParameters(k_GroupingType_Key, k_OutputStlPrefix_Key, to_underlying(GroupingType::PartNumber));
params.linkParameters(k_GroupingType_Key, k_PartNumberPath_Key, to_underlying(GroupingType::PartNumber));

return params;
}
Expand All @@ -108,6 +114,7 @@ IFilter::PreflightResult WriteStlFileFilter::preflightImpl(const DataStructure&
auto pTriangleGeomPathValue = filterArgs.value<DataPath>(k_TriangleGeomPath_Key);
auto pFeatureIdsPathValue = filterArgs.value<DataPath>(k_FeatureIdsPath_Key);
auto pFeaturePhasesPathValue = filterArgs.value<DataPath>(k_FeaturePhasesPath_Key);
auto pPartNumberPathValue = filterArgs.value<DataPath>(k_PartNumberPath_Key);

PreflightResult preflightResult;
nx::core::Result<OutputActions> resultOutputActions;
Expand All @@ -125,21 +132,29 @@ IFilter::PreflightResult WriteStlFileFilter::preflightImpl(const DataStructure&
-27871, fmt::format("The number of triangles is {}, but the STL specification only supports triangle counts up to {}", triangleGeom->getNumberOfFaces(), std::numeric_limits<int32>::max()));
}

if(pGroupingTypeValue == GroupingType::Features || pGroupingTypeValue == GroupingType::FeaturesAndPhases)
{
if(auto* featureIds = dataStructure.getDataAs<Int32Array>(pFeatureIdsPathValue); featureIds == nullptr)
{
return MakePreflightErrorResult(-27873, fmt::format("Feature Ids Array doesn't exist at: {}", pFeatureIdsPathValue.toString()));
}
}

if(pGroupingTypeValue == GroupingType::FeaturesAndPhases)
{
if(auto* featurePhases = dataStructure.getDataAs<Int32Array>(pFeaturePhasesPathValue); featurePhases == nullptr)
{
return MakePreflightErrorResult(-27872, fmt::format("Feature Phases Array doesn't exist at: {}", pFeaturePhasesPathValue.toString()));
}
}
if(pGroupingTypeValue != GroupingType::None)

if(pGroupingTypeValue == GroupingType::PartNumber)
{
if(auto* featureIds = dataStructure.getDataAs<Int32Array>(pFeatureIdsPathValue); featureIds == nullptr)
if(auto* featureIds = dataStructure.getDataAs<Int32Array>(pPartNumberPathValue); featureIds == nullptr)
{
return MakePreflightErrorResult(-27873, fmt::format("Feature Ids Array doesn't exist at: {}", pFeatureIdsPathValue.toString()));
return MakePreflightErrorResult(-27874, fmt::format("Part Number Array doesn't exist at: {}", pPartNumberPathValue.toString()));
}
}

// Return both the resultOutputActions and the preflightUpdatedValues via std::move()
return {std::move(resultOutputActions), std::move(preflightUpdatedValues)};
}
Expand All @@ -157,7 +172,7 @@ Result<> WriteStlFileFilter::executeImpl(DataStructure& dataStructure, const Arg
inputValues.FeatureIdsPath = filterArgs.value<DataPath>(k_FeatureIdsPath_Key);
inputValues.FeaturePhasesPath = filterArgs.value<DataPath>(k_FeaturePhasesPath_Key);
inputValues.TriangleGeomPath = filterArgs.value<DataPath>(k_TriangleGeomPath_Key);

inputValues.PartNumberPath = filterArgs.value<DataPath>(k_PartNumberPath_Key);
return WriteStlFile(dataStructure, messageHandler, shouldCancel, &inputValues)();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SIMPLNXCORE_EXPORT WriteStlFileFilter : public IFilter
static inline constexpr StringLiteral k_OutputStlPrefix_Key = "output_stl_prefix";
static inline constexpr StringLiteral k_FeatureIdsPath_Key = "feature_ids_path";
static inline constexpr StringLiteral k_FeaturePhasesPath_Key = "feature_phases_path";
static inline constexpr StringLiteral k_PartNumberPath_Key = "part_number_path";
static inline constexpr StringLiteral k_TriangleGeomPath_Key = "input_triangle_geometry_path";

/**
Expand Down

0 comments on commit 8407197

Please sign in to comment.