diff --git a/src/Plugins/SimplnxCore/CMakeLists.txt b/src/Plugins/SimplnxCore/CMakeLists.txt index 46a7905760..3f2acf5918 100644 --- a/src/Plugins/SimplnxCore/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/CMakeLists.txt @@ -102,6 +102,7 @@ set(FilterList RegularGridSampleSurfaceMeshFilter RemoveFlaggedFeaturesFilter RemoveFlaggedTrianglesFilter + RemoveFlaggedEdgesFilter RemoveFlaggedVerticesFilter RequireMinimumSizeFeaturesFilter RenameDataObjectFilter @@ -194,6 +195,7 @@ set(AlgorithmList RegularGridSampleSurfaceMesh RemoveFlaggedFeatures RemoveFlaggedTriangles + RemoveFlaggedEdges ReplaceElementAttributesWithNeighborValues ResampleImageGeom ResampleRectGridToImageGeom diff --git a/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md b/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md new file mode 100644 index 0000000000..158c1d20cb --- /dev/null +++ b/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md @@ -0,0 +1,38 @@ +# Remove Flagged Triangles + +## Group (Subgroup) + +Surface Meshing (Misc) + +## Description + +This **Filter** removes **Triangles** from the supplied **Triangle Geometry** that are flagged by a boolean mask array as **true**. A new reduced **Geometry** is created that contains all the remaining **Triangles**. It is unknown until run time how many **Triangles** will be removed from the **Geometry**. Therefore, this **Filter** requires that a new **TriangleGeom** be created to contain the reduced **Triangle Geometry**. This new **Geometry** will *NOT* contain copies of any **Feature Attribute Matrix** or **Ensemble Attribute Matrix** from the original **Geometry**. + +- Additionally, all **Vertex** data will be copied, with tuples *removed* for any **Vertices** removed by the **Filter**. The user must supply a name for the reduced **Geometry**. + +The mask is expected to be over the triangles themselves so it should be based on something from the ***Face Data*** **Attribute Matrix**, generally we suggest basing the mask on the created **Region Ids** array from the *Label Triangle Geometry Filter*. + +*Note:* Since it cannot be known before run time how many **Vertices** will be removed, the new **Vertex Geometry** and +all associated **Vertex** data to be copied will be initialized to have size 0. + +## Example Output + +- The next figure shows a triangle geometry that has had a mask generated. Yellow parts are flagged as true. + +![Masked triangle geometries for removal.](Images/RemoveFlaggedTriangles_1.png) + +- The next figure shows the result of running the filter. + +![Resulting triangle geometry](Images/RemoveFlaggedTriangles_2.png) + +% Auto generated parameter table will be inserted here + +## Example Pipelines + +## License & Copyright + +Please see the description file distributed with this plugin. + +## DREAM3D-NX Help + +If you need help, need to file a bug report or want to request a new feature, please head over to the [DREAM3DNX-Issues](https://github.com/BlueQuartzSoftware/DREAM3DNX-Issues) GitHub site where the community of DREAM3D-NX users can help answer your questions. diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.cpp new file mode 100644 index 0000000000..e93b9c8ca9 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.cpp @@ -0,0 +1,261 @@ +#include "RemoveFlaggedEdges.hpp" + +#include "simplnx/DataStructure/DataArray.hpp" +#include "simplnx/DataStructure/DataGroup.hpp" +#include "simplnx/DataStructure/Geometry/EdgeGeom.hpp" +#include "simplnx/Utilities/DataArrayUtilities.hpp" +#include "simplnx/Utilities/ParallelAlgorithmUtilities.hpp" +#include "simplnx/Utilities/ParallelDataAlgorithm.hpp" +#include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" + +using namespace nx::core; + +namespace +{ +constexpr usize k_NumVerts = 2; + +/** + * @brief + * @tparam T + */ +template +class CopyCellDataArray +{ +public: + CopyCellDataArray(const IDataArray& oldCellArray, IDataArray& newCellArray, const std::vector& newEdgesIndex, const std::atomic_bool& shouldCancel) + : m_OldCellArray(dynamic_cast&>(oldCellArray)) + , m_NewCellArray(dynamic_cast&>(newCellArray)) + , m_NewEdgesIndex(newEdgesIndex) + , m_ShouldCancel(shouldCancel) + { + } + + ~CopyCellDataArray() = default; + + CopyCellDataArray(const CopyCellDataArray&) = default; + CopyCellDataArray(CopyCellDataArray&&) noexcept = default; + CopyCellDataArray& operator=(const CopyCellDataArray&) = delete; + CopyCellDataArray& operator=(CopyCellDataArray&&) noexcept = delete; + + void operator()() const + { + convert(); + } + +protected: + void convert() const + { + size_t numComps = m_OldCellArray.getNumberOfComponents(); + const auto& oldCellData = m_OldCellArray.getDataStoreRef(); + + auto& dataStore = m_NewCellArray.getDataStoreRef(); + std::fill(dataStore.begin(), dataStore.end(), static_cast(-1)); + + uint64 destTupleIndex = 0; + for(const auto& srcIndex : m_NewEdgesIndex) + { + for(size_t compIndex = 0; compIndex < numComps; compIndex++) + { + dataStore.setValue(destTupleIndex * numComps + compIndex, oldCellData.getValue(srcIndex * numComps + compIndex)); + } + destTupleIndex++; + } + } + +private: + const DataArray& m_OldCellArray; + DataArray& m_NewCellArray; + const std::vector& m_NewEdgesIndex; + const std::atomic_bool& m_ShouldCancel; +}; + +/** + * @brief The PopulateReducedGeometryEdgesImpl pulls the vertices associated with a triangle then locates the indices in + * the new VertexList then assigns that "new" triangle to Reduced Geometry + */ +class PopulateReducedGeometryEdgesImpl +{ +public: + PopulateReducedGeometryEdgesImpl(const EdgeGeom& originalTriangle, EdgeGeom& reducedTriangle, const std::vector& newEdgesIndex, const std::vector& newVerticesIndex) + : m_OriginalTriangle(originalTriangle) + , m_ReducedEgeGeom(reducedTriangle) + , m_NewEdgesIndex(newEdgesIndex) + , m_NewVerticesIndex(newVerticesIndex) + { + } + ~PopulateReducedGeometryEdgesImpl() = default; + + PopulateReducedGeometryEdgesImpl(const PopulateReducedGeometryEdgesImpl&) = default; // Copy Constructor Not Implemented + PopulateReducedGeometryEdgesImpl(PopulateReducedGeometryEdgesImpl&&) = delete; // Move Constructor Not Implemented + PopulateReducedGeometryEdgesImpl& operator=(const PopulateReducedGeometryEdgesImpl&) = delete; // Copy Assignment Not Implemented + PopulateReducedGeometryEdgesImpl& operator=(PopulateReducedGeometryEdgesImpl&&) = delete; // Move Assignment Not Implemented + + void generate(usize start, usize end) const + { + for(usize index = start; index < end; index++) + { + // pull old vertices + usize oldVertexIndices[k_NumVerts] = {0, 0}; + m_OriginalTriangle.getEdgePointIds(m_NewEdgesIndex[index], oldVertexIndices); + + // locate new vertex index for each vertex index + usize newVertexIndices[k_NumVerts] = {0, 0}; + for(usize vertIndex = 0; vertIndex < k_NumVerts; vertIndex++) + { + const auto& itr = lower_bound(m_NewVerticesIndex.cbegin(), m_NewVerticesIndex.cend(), oldVertexIndices[vertIndex]); // find first instance of value as iterator + usize indexOfTarget = std::distance(m_NewVerticesIndex.cbegin(), itr); + newVertexIndices[vertIndex] = indexOfTarget; + } + + // set the triangle in reduced + m_ReducedEgeGeom.setEdgePointIds(index, newVertexIndices); + } + } + + void operator()(const Range& range) const + { + generate(range.min(), range.max()); + } + +private: + const EdgeGeom& m_OriginalTriangle; + EdgeGeom& m_ReducedEgeGeom; + const std::vector& m_NewEdgesIndex; + const std::vector& m_NewVerticesIndex; +}; +} // namespace + +// ----------------------------------------------------------------------------- +RemoveFlaggedEdges::RemoveFlaggedEdges(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, RemoveFlaggedEdgesInputValues* inputValues) +: m_DataStructure(dataStructure) +, m_InputValues(inputValues) +, m_ShouldCancel(shouldCancel) +, m_MessageHandler(mesgHandler) +{ +} + +// ----------------------------------------------------------------------------- +const std::atomic_bool& RemoveFlaggedEdges::getCancel() +{ + return m_ShouldCancel; +} + +// ----------------------------------------------------------------------------- +Result<> RemoveFlaggedEdges::operator()() +{ + // Remove Edges from reduced according to removeEdgesIndex + const auto& originalEdgeGeom = m_DataStructure.getDataRefAs(m_InputValues->EdgeGeometry); + std::unique_ptr maskCompare; + try + { + maskCompare = InstantiateMaskCompare(m_DataStructure, m_InputValues->MaskArrayPath); + } catch(const std::out_of_range& exception) + { + // This really should NOT be happening as the path was verified during preflight BUT we may be calling this from + // somewhere else that is NOT going through the normal nx::core::IFilter API of Preflight and Execute + std::string message = fmt::format("Mask Array DataPath does not exist or is not of the correct type (Bool | UInt8) {}", m_InputValues->MaskArrayPath.toString()); + return MakeErrorResult(-54070, message); + } + auto& reducedEdgeGeom = m_DataStructure.getDataRefAs(m_InputValues->ReducedEdgeGeometry); + + // Set up allocated masks + usize size = originalEdgeGeom.getNumberOfEdges(); + std::vector newEdgesIndexList; + newEdgesIndexList.reserve(size); + + // parse mask Edges list and load a list of indices for Edges to keep + for(usize index = 0; index < size; index++) + { + if(!maskCompare->isTrue(index)) + { + newEdgesIndexList.push_back(index); + } + } + newEdgesIndexList.shrink_to_fit(); + + if(getCancel()) + { + return {}; + } + if(newEdgesIndexList.empty()) + { + return MakeErrorResult(-67880, "Re-evaluate mask conditions - with current configuration all Edges will be stripped!"); + } + + // flatten a list of the indices of vertices used by the Edges + std::vector vertexListIndices; // also used as a pseudo look up table in PopulateReducedGeometryEdgesImpl + usize vertIDs[k_NumVerts] = {0, 0}; + for(usize& index : newEdgesIndexList) + { + + originalEdgeGeom.getEdgePointIds(index, vertIDs); + vertexListIndices.push_back(vertIDs[0]); + vertexListIndices.push_back(vertIDs[1]); + } + if(getCancel()) + { + return {}; + } + + if(vertexListIndices.empty()) + { + return MakeErrorResult(-67881, "Re-evaluate mask conditions - with current configuration all vertices will be dumped!"); + } + + // clear duplicate values out of vector + std::sort(vertexListIndices.begin(), vertexListIndices.end()); // orders ascending !!!!! Basis for later search !!!!! + auto dupes = std::unique(vertexListIndices.begin(), vertexListIndices.end()); + vertexListIndices.erase(dupes, vertexListIndices.end()); + + // define new sizing + size = vertexListIndices.size(); + reducedEdgeGeom.resizeVertexList(size); // resize accordingly + reducedEdgeGeom.getVertexAttributeMatrix()->resizeTuples({size}); + + // load reduced Geometry Vertex list according to used vertices + Point3Df coords = {0.0f, 0.0f, 0.0f}; + for(usize i = 0; i < size; i++) + { + coords = originalEdgeGeom.getVertexCoordinate(vertexListIndices[i]); + reducedEdgeGeom.setVertexCoordinate(i, coords); + } + + if(getCancel()) + { + return {}; + } + + // Set up preprocessing conditions (allocation for parallelization) + size = newEdgesIndexList.size(); + reducedEdgeGeom.resizeEdgeList(size); // resize accordingly + reducedEdgeGeom.getEdgeAttributeMatrix()->resizeTuples({size}); + + // parse Edges and reassign indexes to match new vertex list + ParallelDataAlgorithm dataAlg; + dataAlg.setRange(0, size); + dataAlg.execute(PopulateReducedGeometryEdgesImpl(originalEdgeGeom, reducedEdgeGeom, newEdgesIndexList, vertexListIndices)); + + // The actual cropping of the dataStructure arrays is done in parallel where parallel here + // refers to the cropping of each DataArray being done on a separate thread. + ParallelTaskAlgorithm taskRunner; + const auto& srcCellDataAM = originalEdgeGeom.getEdgeAttributeMatrixRef(); + auto& destCellDataAM = reducedEdgeGeom.getEdgeAttributeMatrixRef(); + for(const auto& [dataId, oldDataObject] : srcCellDataAM) + { + if(m_ShouldCancel) + { + return {}; + } + + const auto& oldDataArray = dynamic_cast(*oldDataObject); + const std::string srcName = oldDataArray.getName(); + + auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); + + m_MessageHandler(fmt::format("Reducing Edge Geometry || Copying Data Array {}", srcName)); + ExecuteParallelFunction(oldDataArray.getDataType(), taskRunner, oldDataArray, newDataArray, newEdgesIndexList, m_ShouldCancel); + } + taskRunner.wait(); // This will spill over if the number of DataArrays to process does not divide evenly by the number of threads. + + return {}; +} diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp new file mode 100644 index 0000000000..61233bd85b --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/DataStructure/DataStructure.hpp" +#include "simplnx/Filter/IFilter.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/ChoicesParameter.hpp" +#include "simplnx/Parameters/DataGroupSelectionParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" + +namespace +{ + +const std::string k_Ignore("Ignore Edge Data"); +const std::string k_CopyAll("Copy All Edge Data"); +const std::string k_CopySelected("Copy Selected Edge Data"); + +const nx::core::ChoicesParameter::Choices k_ArrayHandlingChoices = {k_Ignore, k_CopySelected, k_CopyAll}; + +const nx::core::ChoicesParameter::ValueType k_IgnoreArraysIdx = 0ULL; +const nx::core::ChoicesParameter::ValueType k_CopySelectedArraysIdx = 1ULL; +const nx::core::ChoicesParameter::ValueType k_CopyAllArraysIdx = 2ULL; + +} // namespace + +namespace nx::core +{ +struct SIMPLNXCORE_EXPORT RemoveFlaggedEdgesInputValues +{ + DataPath EdgeGeometry; + DataPath MaskArrayPath; + DataPath ReducedEdgeGeometry; +}; + +/** + * @class ConditionalSetValueFilter + + */ +class SIMPLNXCORE_EXPORT RemoveFlaggedEdges +{ +public: + RemoveFlaggedEdges(DataStructure& dataStructure, const IFilter::MessageHandler& mesgHandler, const std::atomic_bool& shouldCancel, RemoveFlaggedEdgesInputValues* inputValues); + ~RemoveFlaggedEdges() noexcept = default; + + RemoveFlaggedEdges(const RemoveFlaggedEdges&) = delete; + RemoveFlaggedEdges(RemoveFlaggedEdges&&) noexcept = delete; + RemoveFlaggedEdges& operator=(const RemoveFlaggedEdges&) = delete; + RemoveFlaggedEdges& operator=(RemoveFlaggedEdges&&) noexcept = delete; + + Result<> operator()(); + + const std::atomic_bool& getCancel(); + +private: + DataStructure& m_DataStructure; + const RemoveFlaggedEdgesInputValues* m_InputValues = nullptr; + const std::atomic_bool& m_ShouldCancel; + const IFilter::MessageHandler& m_MessageHandler; +}; +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.cpp new file mode 100644 index 0000000000..0cc1e930ca --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.cpp @@ -0,0 +1,201 @@ +#include "RemoveFlaggedEdgesFilter.hpp" + +#include "SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp" + +#include "simplnx/DataStructure/DataPath.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" +#include "simplnx/Filter/Actions/CreateAttributeMatrixAction.hpp" +#include "simplnx/Filter/Actions/CreateGeometry1DAction.hpp" +#include "simplnx/Filter/Actions/EmptyAction.hpp" +#include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" +#include "simplnx/Utilities/SIMPLConversion.hpp" + +using namespace nx::core; + +namespace nx::core +{ +//------------------------------------------------------------------------------ +std::string RemoveFlaggedEdgesFilter::name() const +{ + return FilterTraits::name.str(); +} + +//------------------------------------------------------------------------------ +std::string RemoveFlaggedEdgesFilter::className() const +{ + return FilterTraits::className; +} + +//------------------------------------------------------------------------------ +Uuid RemoveFlaggedEdgesFilter::uuid() const +{ + return FilterTraits::uuid; +} + +//------------------------------------------------------------------------------ +std::string RemoveFlaggedEdgesFilter::humanName() const +{ + return "Remove Flagged Edges"; +} + +//------------------------------------------------------------------------------ +std::vector RemoveFlaggedEdgesFilter::defaultTags() const +{ + return {"Surface Meshing", "Cleanup", "Edge Geometry", "Remove", "Delete"}; +} + +//------------------------------------------------------------------------------ +Parameters RemoveFlaggedEdgesFilter::parameters() const +{ + Parameters params; + + // Create the parameter descriptors that are needed for this filter + params.insertSeparator(Parameters::Separator{"Input Data Objects"}); + params.insert(std::make_unique(k_SelectedTriangleGeometryPath_Key, "Edge Geometry", "The Edge Geometry that will be processed.", DataPath(), + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Edge})); + params.insert(std::make_unique(k_MaskArrayPath_Key, "Mask", "The DataArrayPath to the mask array that marks each edge as either true (remove) or false(keep).", DataPath{}, + ArraySelectionParameter::AllowedTypes{DataType::boolean, DataType::uint8}, ArraySelectionParameter::AllowedComponentShapes{{1}})); + + // Edge Data Handling + params.insertLinkableParameter( + std::make_unique(k_EdgeDataHandling_Key, "Edge Data Handling", "How to handle Data that resides on the edges", k_IgnoreArraysIdx, k_ArrayHandlingChoices)); + params.insert( + std::make_unique(k_EdgeDataSelectedAttributeMatrix_Key, "Edge Data", "Edge Attribute Matrix that will be copied to the reduced geometry", DataPath{})); + params.insert(std::make_unique(k_EdgeDataSelectedArrays_Key, "Edge Attribute Arrays to Copy", "DataPaths to copy", std::vector(), + MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes())); + + params.linkParameters(k_EdgeDataHandling_Key, k_EdgeDataSelectedArrays_Key, k_CopySelectedArraysIdx); + params.linkParameters(k_EdgeDataHandling_Key, k_EdgeDataSelectedAttributeMatrix_Key, k_CopyAllArraysIdx); + + // Vertex Data Handling + + params.insertSeparator(Parameters::Separator{"Output Geometry"}); + params.insert(std::make_unique(k_CreatedTriangleGeometryPath_Key, "Created Geometry", "The name of the created Edge Geometry", DataPath({"ReducedGeometry"}))); + + return params; +} + +//------------------------------------------------------------------------------ +IFilter::UniquePointer RemoveFlaggedEdgesFilter::clone() const +{ + return std::make_unique(); +} + +//------------------------------------------------------------------------------ +IFilter::PreflightResult RemoveFlaggedEdgesFilter::preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + auto pInitialGeometryPathValue = filterArgs.value(k_SelectedTriangleGeometryPath_Key); + auto pReducedGeometryPathValue = filterArgs.value(k_CreatedTriangleGeometryPath_Key); + auto pArrayHandling = filterArgs.value(k_EdgeDataHandling_Key); + + PreflightResult preflightResult; + Result resultOutputActions; + std::vector preflightUpdatedValues; + + const auto* initialGeom = dataStructure.getDataAs(pInitialGeometryPathValue); + + if(initialGeom->getGeomType() == IGeometry::Type::Edge) + { + auto createGeometryAction = std::make_unique>( + pReducedGeometryPathValue, initialGeom->getNumberOfEdges(), initialGeom->getNumberOfVertices(), + (initialGeom->getVertexAttributeMatrix() == nullptr ? "VertexAM" : initialGeom->getVertexAttributeMatrix()->getName()), + (initialGeom->getEdgeAttributeMatrix() == nullptr ? "FaceAM" : initialGeom->getEdgeAttributeMatrix()->getName()), initialGeom->getVertices()->getName(), initialGeom->getEdges()->getName()); + resultOutputActions.value().appendAction(std::move(createGeometryAction)); + } + + std::vector ignorePaths; // already copied over so skip these when collecting child paths to finish copying over later + + std::vector dataArrayShape = {initialGeom->getNumberOfEdges()}; // The DataArray shape goes slowest to fastest (ZYX) + + // This section gets the cell attribute matrix for the input Edge Geometry and + // then creates new arrays from each array that is in that attribute matrix. We + // also push this attribute matrix into the `ignorePaths` variable since we do + // not need to manually copy these arrays to the destination image geometry + if(pArrayHandling == k_CopyAllArraysIdx) + { + // Get the name of the Cell Attribute Matrix, so we can use that in the CreateImageGeometryAction + const AttributeMatrix* selectedCellData = initialGeom->getEdgeAttributeMatrix(); + if(selectedCellData == nullptr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have cell data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + std::string cellDataName = selectedCellData->getName(); + ignorePaths.push_back(pInitialGeometryPathValue.createChildPath(cellDataName)); + + // Now loop over each array in the source image geometry's cell attribute matrix and create the corresponding arrays + // in the destination geometry's attribute matrix + DataPath newCellAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(cellDataName); + for(const auto& [identifier, object] : *selectedCellData) + { + const auto& srcArray = dynamic_cast(*object); + DataType dataType = srcArray.getDataType(); + IDataStore::ShapeType componentShape = srcArray.getIDataStoreRef().getComponentShape(); + DataPath dataArrayPath = newCellAttributeMatrixPath.createChildPath(srcArray.getName()); + resultOutputActions.value().appendAction(std::make_unique(dataType, dataArrayShape, std::move(componentShape), dataArrayPath)); + } + } + if(pArrayHandling == k_CopySelectedArraysIdx) + { + const AttributeMatrix* selectedCellData = initialGeom->getEdgeAttributeMatrix(); + std::string cellDataName = selectedCellData->getName(); + + auto selectedEdgeArrays = filterArgs.value(k_EdgeDataSelectedArrays_Key); + // Now loop over each array in selectedEdgeArrays and create the corresponding arrays + // in the destination geometry's attribute matrix + DataPath newCellAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(cellDataName); + for(const auto& dataPath : selectedEdgeArrays) + { + const auto& srcArray = dataStructure.getDataRefAs(dataPath); + DataType dataType = srcArray.getDataType(); + IDataStore::ShapeType componentShape = srcArray.getIDataStoreRef().getComponentShape(); + DataPath dataArrayPath = newCellAttributeMatrixPath.createChildPath(srcArray.getName()); + resultOutputActions.value().appendAction(std::make_unique(dataType, dataArrayShape, std::move(componentShape), dataArrayPath)); + } + } + + // Return both the resultOutputActions and the preflightUpdatedValues via std::move() + return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; +} + +//------------------------------------------------------------------------------ +Result<> RemoveFlaggedEdgesFilter::executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const +{ + RemoveFlaggedEdgesInputValues inputValues; + + inputValues.EdgeGeometry = filterArgs.value(k_SelectedTriangleGeometryPath_Key); + inputValues.MaskArrayPath = filterArgs.value(k_MaskArrayPath_Key); + inputValues.ReducedEdgeGeometry = filterArgs.value(k_CreatedTriangleGeometryPath_Key); + + return RemoveFlaggedEdges(dataStructure, messageHandler, shouldCancel, &inputValues)(); +} + +namespace +{ +namespace SIMPL +{ +constexpr StringLiteral k_TriangleGeometryKey = "EdgeGeometry"; +constexpr StringLiteral k_MaskArrayPathKey = "MaskArrayPath"; +constexpr StringLiteral k_ReducedTriangleGeometryKey = "ReducedEdgeGeometry"; +} // namespace SIMPL +} // namespace + +Result RemoveFlaggedEdgesFilter::FromSIMPLJson(const nlohmann::json& json) +{ + Arguments args = RemoveFlaggedEdgesFilter().getDefaultArguments(); + + std::vector> results; + + results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_TriangleGeometryKey, k_SelectedTriangleGeometryPath_Key)); + results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_MaskArrayPathKey, k_MaskArrayPath_Key)); + results.push_back(SIMPLConversion::ConvertParameter(args, json, SIMPL::k_ReducedTriangleGeometryKey, k_CreatedTriangleGeometryPath_Key)); + + Result<> conversionResult = MergeResults(std::move(results)); + + return ConvertResultTo(std::move(conversionResult), std::move(args)); +} +} // namespace nx::core diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp new file mode 100644 index 0000000000..c1988ebcac --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "SimplnxCore/SimplnxCore_export.hpp" + +#include "simplnx/Filter/FilterTraits.hpp" +#include "simplnx/Filter/IFilter.hpp" + +namespace nx::core +{ +/** + * @class RemoveFlaggedEdgesFilter + * @brief This filter will .... + */ +class SIMPLNXCORE_EXPORT RemoveFlaggedEdgesFilter : public IFilter +{ +public: + RemoveFlaggedEdgesFilter() = default; + ~RemoveFlaggedEdgesFilter() noexcept override = default; + + RemoveFlaggedEdgesFilter(const RemoveFlaggedEdgesFilter&) = delete; + RemoveFlaggedEdgesFilter(RemoveFlaggedEdgesFilter&&) noexcept = delete; + + RemoveFlaggedEdgesFilter& operator=(const RemoveFlaggedEdgesFilter&) = delete; + RemoveFlaggedEdgesFilter& operator=(RemoveFlaggedEdgesFilter&&) noexcept = delete; + + // Parameter Keys + static inline constexpr StringLiteral k_SelectedTriangleGeometryPath_Key = "input_triangle_geometry_path"; + static inline constexpr StringLiteral k_MaskArrayPath_Key = "mask_array_path"; + static inline constexpr StringLiteral k_CreatedTriangleGeometryPath_Key = "output_triangle_geometry_path"; + static inline constexpr StringLiteral k_EdgeDataHandling_Key = "edge_data_handling_index"; + static inline constexpr StringLiteral k_EdgeDataSelectedArrays_Key = "edge_data_selected_array_paths"; + static inline constexpr StringLiteral k_EdgeDataSelectedAttributeMatrix_Key = "edge_data_selected_attribute_matrix_path"; + + /** + * @brief Reads SIMPL json and converts it complex Arguments. + * @param json + * @return Result + */ + static Result FromSIMPLJson(const nlohmann::json& json); + + /** + * @brief Returns the name of the filter. + * @return + */ + std::string name() const override; + + /** + * @brief Returns the C++ classname of this filter. + * @return + */ + std::string className() const override; + + /** + * @brief Returns the uuid of the filter. + * @return + */ + Uuid uuid() const override; + + /** + * @brief Returns the human readable name of the filter. + * @return + */ + std::string humanName() const override; + + /** + * @brief Returns the default tags for this filter. + * @return + */ + std::vector defaultTags() const override; + + /** + * @brief Returns the parameters of the filter (i.e. its inputs) + * @return + */ + Parameters parameters() const override; + + /** + * @brief Returns a copy of the filter. + * @return + */ + UniquePointer clone() const override; + +protected: + /** + * @brief Takes in a DataStructure and checks that the filter can be run on it with the given arguments. + * Returns any warnings/errors. Also returns the changes that would be applied to the DataStructure. + * Some parts of the actions may not be completely filled out if all the required information is not available at preflight time. + * @param ds The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + PreflightResult preflightImpl(const DataStructure& dataStructure, const Arguments& filterArgs, const MessageHandler& messageHandler, const std::atomic_bool& shouldCancel) const override; + + /** + * @brief Applies the filter's algorithm to the DataStructure with the given arguments. Returns any warnings/errors. + * On failure, there is no guarantee that the DataStructure is in a correct state. + * @param ds The input DataStructure instance + * @param filterArgs These are the input values for each parameter that is required for the filter + * @param messageHandler The MessageHandler object + * @return Returns a Result object with error or warning values if any of those occurred during execution of this function + */ + Result<> executeImpl(DataStructure& dataStructure, const Arguments& filterArgs, const PipelineFilter* pipelineNode, const MessageHandler& messageHandler, + const std::atomic_bool& shouldCancel) const override; +}; +} // namespace nx::core + +SIMPLNX_DEF_FILTER_TRAITS(nx::core, RemoveFlaggedEdgesFilter, "48155f61-2709-4731-be95-43745bb3f8d8"); diff --git a/src/Plugins/SimplnxCore/test/CMakeLists.txt b/src/Plugins/SimplnxCore/test/CMakeLists.txt index 575cc834d4..dd8cb31f0b 100644 --- a/src/Plugins/SimplnxCore/test/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/test/CMakeLists.txt @@ -103,6 +103,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS RegularGridSampleSurfaceMeshTest.cpp RemoveFlaggedFeaturesTest.cpp RemoveFlaggedTrianglesTest.cpp + RemoveFlaggedEdgesTest.cpp RemoveFlaggedVerticesTest.cpp RequireMinimumSizeFeaturesTest.cpp RenameDataObjectTest.cpp diff --git a/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp b/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp new file mode 100644 index 0000000000..8755f804ea --- /dev/null +++ b/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp @@ -0,0 +1,65 @@ +#include + +#include "SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp" +#include "SimplnxCore/SimplnxCore_test_dirs.hpp" +#include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +using namespace nx::core; + +namespace +{ +namespace +{ +fs::path k_ExemplarDataFilePath = fs::path(fmt::format("{}/remove_flagged_triangles_test/remove_flagged_triangles_test.dream3d", nx::core::unit_test::k_TestFilesDir)); +fs::path k_BaseDataFilePath = fs::path(fmt::format("{}/remove_flagged_triangles_test/data_to_generate_test/masked_triangle_geometry.dream3d", nx::core::unit_test::k_TestFilesDir)); + +static constexpr StringLiteral k_CreatedAMName = "Cell Feature AM"; +static constexpr StringLiteral k_NumTrianglesName = "NumTriangles"; +static constexpr StringLiteral k_RegionIdsName = "Region Ids"; + +const DataPath k_TriangleGeomPath({"TriangleGeometry"}); +const DataPath k_MaskPath = k_TriangleGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(Constants::k_Mask); +const DataPath k_ReducedGeomPath({"ReducedGeometry"}); + +const DataPath k_VertexListPath = k_ReducedGeomPath.createChildPath("SharedVertexList"); +const DataPath k_TriangleListPath = k_ReducedGeomPath.createChildPath("SharedTriList"); +} // namespace +} // namespace + +TEST_CASE("SimplnxCore::RemoveFlaggedEdgesFilter: Valid Filter Execution", "[SimplnxCore][RemoveFlaggedEdgesFilter]") +{ + const UnitTest::TestFileSentinel testDataSentinel(unit_test::k_CMakeExecutable, unit_test::k_TestFilesDir, "remove_flagged_triangles_test.tar.gz", "remove_flagged_triangles_test.dream3d"); + + // Load DataStructure containing the base geometry and an exemplar cleaned geometry + DataStructure dataStructure = UnitTest::LoadDataStructure(k_BaseDataFilePath); + { + // Instantiate the filter and an Arguments Object + RemoveFlaggedEdgesFilter filter; + Arguments args; + + // Create default Parameters for the filter. + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_SelectedTriangleGeometryPath_Key, std::make_any(::k_TriangleGeomPath)); + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_MaskArrayPath_Key, std::make_any(::k_MaskPath)); + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_CreatedTriangleGeometryPath_Key, std::make_any(::k_ReducedGeomPath)); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + // This is in here because the exemplar face attribute matrix is not sized correctly. This will + // correct that value allowing the test to proceed normally. + auto& exemplarContourTriGeom = dataStructure.getDataRefAs(k_TriangleGeomPath); + exemplarContourTriGeom.getVertexAttributeMatrix()->resizeTuples({144}); + exemplarContourTriGeom.getFaceAttributeMatrix()->resizeTuples({276}); + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + } + + DataStructure exemplarDataStructure = UnitTest::LoadDataStructure(k_ExemplarDataFilePath); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(::k_TriangleListPath), exemplarDataStructure.getDataRefAs(::k_TriangleListPath)); + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(::k_VertexListPath), exemplarDataStructure.getDataRefAs(::k_VertexListPath)); +}