From 8407197b6d9a7ca61b58aa2478c35f3fc0587b48 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Mon, 22 Jul 2024 15:25:08 -0400 Subject: [PATCH] ENH: WriteSTLFiles-Write files by Part Number - 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 --- .../Filters/Algorithms/WriteStlFile.cpp | 165 +++++++++++------- .../Filters/Algorithms/WriteStlFile.hpp | 4 +- .../Filters/WriteStlFileFilter.cpp | 35 ++-- .../Filters/WriteStlFileFilter.hpp | 1 + 4 files changed, 134 insertions(+), 71 deletions(-) diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp index f82ae19a79..3378c56dbd 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.cpp @@ -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) { @@ -168,6 +179,7 @@ Result<> MultiWriteOutStl(const fs::path& path, const IGeometry::MeshIndexType n nonstd::span attrByteCountPtr(reinterpret_cast(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) { @@ -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; @@ -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++; } @@ -262,7 +273,7 @@ Result<> WriteStlFile::operator()() auto groupingType = static_cast(m_InputValues->GroupingType); - if(groupingType == GroupingType::None) + if(groupingType == GroupingType::SingleFile) { auto atomicFileResult = AtomicFile::Create(m_InputValues->OutputStlFile); if(atomicFileResult.invalid()) @@ -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> fileList; - { // Scope to cut overhead and ensure file lock is released on windows + if(groupingType == GroupingType::Features) + { const auto& featureIds = m_DataStructure.getDataRefAs(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 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 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(m_InputValues->FeatureIdsPath); + + std::map uniqueGrainIdToPhase; + + const auto& featurePhases = m_DataStructure.getDataRefAs(m_InputValues->FeaturePhasesPath); + for(IGeometry::MeshIndexType i = 0; i < nTriangles; i++) { - std::map 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(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(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 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++; } } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.hpp index 72d464155a..f55ead9c3d 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/WriteStlFile.hpp @@ -17,7 +17,8 @@ enum class GroupingType : ChoicesParameter::ValueType { Features, FeaturesAndPhases, - None + SingleFile, + PartNumber }; struct SIMPLNXCORE_EXPORT WriteStlFileInputValues @@ -29,6 +30,7 @@ struct SIMPLNXCORE_EXPORT WriteStlFileInputValues DataPath FeatureIdsPath; DataPath FeaturePhasesPath; DataPath TriangleGeomPath; + DataPath PartNumberPath; }; /** diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.cpp index 7d6a88f036..f4ad6934ef 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.cpp @@ -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(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(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(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(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(k_OutputStlFile_Key, "Output STL File", "STL File to dump the Triangle Geometry to", fs::path(), FileSystemPathParameter::ExtensionsType{".stl"}, FileSystemPathParameter::PathType::OutputFile, false)); @@ -74,7 +74,8 @@ Parameters WriteStlFileFilter::parameters() const ArraySelectionParameter::AllowedTypes{DataType::int32}, ArraySelectionParameter::AllowedComponentShapes{{2}})); params.insert(std::make_unique(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(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)); @@ -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; } @@ -108,6 +114,7 @@ IFilter::PreflightResult WriteStlFileFilter::preflightImpl(const DataStructure& auto pTriangleGeomPathValue = filterArgs.value(k_TriangleGeomPath_Key); auto pFeatureIdsPathValue = filterArgs.value(k_FeatureIdsPath_Key); auto pFeaturePhasesPathValue = filterArgs.value(k_FeaturePhasesPath_Key); + auto pPartNumberPathValue = filterArgs.value(k_PartNumberPath_Key); PreflightResult preflightResult; nx::core::Result resultOutputActions; @@ -125,6 +132,14 @@ 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::max())); } + if(pGroupingTypeValue == GroupingType::Features || pGroupingTypeValue == GroupingType::FeaturesAndPhases) + { + if(auto* featureIds = dataStructure.getDataAs(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(pFeaturePhasesPathValue); featurePhases == nullptr) @@ -132,14 +147,14 @@ IFilter::PreflightResult WriteStlFileFilter::preflightImpl(const DataStructure& 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(pFeatureIdsPathValue); featureIds == nullptr) + if(auto* featureIds = dataStructure.getDataAs(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)}; } @@ -157,7 +172,7 @@ Result<> WriteStlFileFilter::executeImpl(DataStructure& dataStructure, const Arg inputValues.FeatureIdsPath = filterArgs.value(k_FeatureIdsPath_Key); inputValues.FeaturePhasesPath = filterArgs.value(k_FeaturePhasesPath_Key); inputValues.TriangleGeomPath = filterArgs.value(k_TriangleGeomPath_Key); - + inputValues.PartNumberPath = filterArgs.value(k_PartNumberPath_Key); return WriteStlFile(dataStructure, messageHandler, shouldCancel, &inputValues)(); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.hpp index 847301ff88..d2c9290fe4 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/WriteStlFileFilter.hpp @@ -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"; /**