From d7190167f19a8d265d082f36686ad4a3ecb2e6b6 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Mon, 1 Jul 2024 11:22:27 -0400 Subject: [PATCH] FILTER: Add RemoveFlaggedEdges filter. Update other RemoveFlaggedXXX filters (#986) - Implemented RemoveFlaggedEdges filter with option to copy cell and vertex data - Added copying options to RemoveFlaggedTriangles and RemoveFlaggedVertices data - Added Unit test updates - Updated documentation for all filters - Added example pipelines for new/updated filters Signed-off-by: Michael Jackson Co-authored-by: nyoungbq --- src/Plugins/SimplnxCore/CMakeLists.txt | 2 + .../docs/Images/RemoveFlaggedEdges_1.png | Bin 0 -> 35451 bytes .../docs/Images/RemoveFlaggedEdges_2.png | Bin 0 -> 26596 bytes .../docs/RemoveFlaggedEdgesFilter.md | 35 +++ .../docs/RemoveFlaggedTrianglesFilter.md | 13 +- .../docs/RemoveFlaggedVerticesFilter.md | 18 +- ...move_flagged_triangles_example.d3dpipeline | 106 ++++++++ .../Filters/Algorithms/RemoveFlaggedEdges.cpp | 217 +++++++++++++++ .../Filters/Algorithms/RemoveFlaggedEdges.hpp | 73 +++++ .../Algorithms/RemoveFlaggedTriangles.cpp | 69 +++-- .../Algorithms/RemoveFlaggedTriangles.hpp | 27 ++ .../Filters/RemoveFlaggedEdgesFilter.cpp | 249 ++++++++++++++++++ .../Filters/RemoveFlaggedEdgesFilter.hpp | 113 ++++++++ .../Filters/RemoveFlaggedTrianglesFilter.cpp | 136 +++++++++- .../Filters/RemoveFlaggedTrianglesFilter.hpp | 8 + .../Filters/RemoveFlaggedVerticesFilter.cpp | 37 +-- src/Plugins/SimplnxCore/test/CMakeLists.txt | 4 +- .../test/RemoveFlaggedEdgesTest.cpp | 84 ++++++ .../test/RemoveFlaggedTrianglesTest.cpp | 69 +++-- .../test/RemoveFlaggedVerticesTest.cpp | 71 ++++- src/simplnx/Utilities/DataArrayUtilities.cpp | 97 ++++--- src/simplnx/Utilities/DataArrayUtilities.hpp | 226 +++++++++++----- .../simplnx/UnitTest/UnitTestCommon.hpp | 2 + 23 files changed, 1451 insertions(+), 205 deletions(-) create mode 100644 src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_1.png create mode 100644 src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_2.png create mode 100644 src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md create mode 100644 src/Plugins/SimplnxCore/pipelines/remove_flagged_triangles_example.d3dpipeline create mode 100644 src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.cpp create mode 100644 src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp create mode 100644 src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.cpp create mode 100644 src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp create mode 100644 src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp diff --git a/src/Plugins/SimplnxCore/CMakeLists.txt b/src/Plugins/SimplnxCore/CMakeLists.txt index 20d0bce4cf..7fe32ecea0 100644 --- a/src/Plugins/SimplnxCore/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/CMakeLists.txt @@ -105,6 +105,7 @@ set(FilterList RegularGridSampleSurfaceMeshFilter RemoveFlaggedFeaturesFilter RemoveFlaggedTrianglesFilter + RemoveFlaggedEdgesFilter RemoveFlaggedVerticesFilter RequireMinimumSizeFeaturesFilter RenameDataObjectFilter @@ -200,6 +201,7 @@ set(AlgorithmList RegularGridSampleSurfaceMesh RemoveFlaggedFeatures RemoveFlaggedTriangles + RemoveFlaggedEdges ReplaceElementAttributesWithNeighborValues ResampleImageGeom ResampleRectGridToImageGeom diff --git a/src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_1.png b/src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_1.png new file mode 100644 index 0000000000000000000000000000000000000000..04934eccef85f3a433e8bb44b48057c7fca6e34d GIT binary patch literal 35451 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 literal 0 HcmV?d00001 diff --git a/src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_2.png b/src/Plugins/SimplnxCore/docs/Images/RemoveFlaggedEdges_2.png new file mode 100644 index 0000000000000000000000000000000000000000..2a0ee46f8d1e2109609be27c7a8b9e21e59b67e8 GIT binary patch literal 26596 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 literal 0 HcmV?d00001 diff --git a/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md b/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md new file mode 100644 index 0000000000..8b5b2e04aa --- /dev/null +++ b/src/Plugins/SimplnxCore/docs/RemoveFlaggedEdgesFilter.md @@ -0,0 +1,35 @@ +# Remove Flagged Edges + +## Group (Subgroup) + +Surface Meshing (Misc) + +## Description + +This **Filter** removes **Edges** from the supplied **Edges Geometry** that are flagged by a (boolean|uint8) mask array as **true|1**. A new reduced **Edge Geometry** is created that contains all the remaining **Edges**. It is unknown until run time how many **Edges** will be removed from the **Geometry**. Therefore, this **Filter** requires that a new **EdgeGeom** be created to contain the reduced **Edge Geometry**. This new **Geometry** will *NOT* contain copies of any **Feature Attribute Matrix** or **Ensemble Attribute Matrix** from the original **Geometry**. The mask is expected to be over the edges themselves so it should be based on something from the **Edge Data Attribute Matrix**. + +## Data Handling + +For each of the vertex and edge data attribute matrices, the user can select to copy none, some or all of the associated data arrays into the newly created geometry. If the user wishes to not copy any of the data, just leave the choice to "Copy Selected XXX Data" [0] but do not populate the list with any selections. + +*Note:* Since it cannot be known before run time how many **Edges** will be removed, the new **Edge Geometry** and all associated **Edge** data to be copied will be initialized to have size 0. + +## Example Output + +- The next figure shows an edge geometry that has had a mask generated. Yellow parts are flagged as true. + +![Masked edge geometries for removal.](Images/RemoveFlaggedEdges_1.png) + +- The next figure shows the result of running the filter. + +![Resulting edge geometry](Images/RemoveFlaggedEdges_2.png) + +% Auto generated parameter table will be inserted here + +## 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/docs/RemoveFlaggedTrianglesFilter.md b/src/Plugins/SimplnxCore/docs/RemoveFlaggedTrianglesFilter.md index 158c1d20cb..a2eba12d37 100644 --- a/src/Plugins/SimplnxCore/docs/RemoveFlaggedTrianglesFilter.md +++ b/src/Plugins/SimplnxCore/docs/RemoveFlaggedTrianglesFilter.md @@ -6,14 +6,13 @@ 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**. +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**. The mask is expected to be over the triangles themselves so it should be based on something from the ***Face Data*** **Attribute Matrix**. -- 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**. +## Data Handling -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*. +For each of the vertex and edge data attribute matrices, the user can select to copy none, some or all of the associated data arrays into the newly created geometry. If the user wishes to not copy any of the data, just leave the choice to "Copy Selected XXX Data" [0] but do not populate the list with any selections. -*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. +*Note:* Since it cannot be known before run time how many **Vertices** will be removed, the new **Triangle Geometry** and all associated **Triangle** data to be copied will be initialized to have size 0. ## Example Output @@ -27,7 +26,9 @@ all associated **Vertex** data to be copied will be initialized to have size 0. % Auto generated parameter table will be inserted here -## Example Pipelines +## Example Pipeline + +*'remove_flagged_triangles_example'* in **SimplnxCore** ## License & Copyright diff --git a/src/Plugins/SimplnxCore/docs/RemoveFlaggedVerticesFilter.md b/src/Plugins/SimplnxCore/docs/RemoveFlaggedVerticesFilter.md index 01c39f1585..764e77b387 100644 --- a/src/Plugins/SimplnxCore/docs/RemoveFlaggedVerticesFilter.md +++ b/src/Plugins/SimplnxCore/docs/RemoveFlaggedVerticesFilter.md @@ -2,28 +2,16 @@ ## Group (Subgroup) -DREAM3D Review (Geometry) +Data Cleanup (Geometry) ## Description -This **Filter** removes **Vertices** from the supplied **Vertex Geometry** that are flagged as **TRUE** by a boolean mask array. -A new reduced **Vertex Geometry** is -created that contains all the remaining **Vertices**. It is unknown until run time how many **Vertices** will be removed -from the **Geometry**. Therefore, this **Filter** requires that a new **Data Container** be created to contain the -reduced **Vertex Geometry**. This new **Vertex Geometry** will contain copies of any **Feature** or **Ensemble** -**Attribute Matrices** from the original **Data Container**. 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 **Vertex Geometry**, -but all other copied objects (**Attribute Matrices** and **Attribute Arrays**) will retain the same names -as the original source. +This **Filter** removes **Vertices** from the supplied **Vertex Geometry** that are flagged as **TRUE** by a boolean mask array. A new reduced **Vertex Geometry** is created that contains all the remaining **Vertices**. It is unknown until run time how many **Vertices** will be removed from the **Geometry**. Therefore, this **Filter** requires that a new **Data Container** be created to contain the reduced **Vertex Geometry**. This new **Vertex Geometry** will contain copies of any **Feature** or **Ensemble** **Attribute Matrices** from the original **Data Container**. 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 **Vertex Geometry**, but all other copied objects (**Attribute Matrices** and **Attribute Arrays**) will retain the same names as the original source. -*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. Any **Feature** or **Ensemble** -information will retain the same dimensions and size. +*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. Any **Feature** or **Ensemble** information will retain the same dimensions and size. % Auto generated parameter table will be inserted here -## Example Pipelines - ## License & Copyright Please see the description file distributed with this plugin. diff --git a/src/Plugins/SimplnxCore/pipelines/remove_flagged_triangles_example.d3dpipeline b/src/Plugins/SimplnxCore/pipelines/remove_flagged_triangles_example.d3dpipeline new file mode 100644 index 0000000000..532ad120d2 --- /dev/null +++ b/src/Plugins/SimplnxCore/pipelines/remove_flagged_triangles_example.d3dpipeline @@ -0,0 +1,106 @@ +{ + "isDisabled": false, + "name": "remove_flagged_triangles_example.d3dpipeline", + "pinnedParams": [], + "pipeline": [ + { + "args": { + "face_attribute_matrix_name": "Face Data", + "face_normals_name": "Face Normals", + "output_triangle_geometry_path": "TriangleGeometry", + "scale_factor": 1.0, + "scale_output": false, + "stl_file_path": "Data/STL_Models/Example_Triangle_Geometry.stl", + "vertex_attribute_matrix_name": "Vertex Data" + }, + "comments": "", + "filter": { + "name": "nx::core::ReadStlFileFilter", + "uuid": "2f64bd45-9d28-4254-9e07-6aa7c6d3d015" + }, + "isDisabled": false + }, + { + "args": { + "created_region_ids_path": "TriangleGeometry/Face Data/Region Ids", + "input_triangle_geometry_path": "TriangleGeometry", + "num_triangles_name": "NumTriangles", + "triangle_attribute_matrix_name": "Cell Feature AM" + }, + "comments": "", + "filter": { + "name": "nx::core::LabelTriangleGeometryFilter", + "uuid": "7a7a2c6f-3b03-444d-8b8c-5976b0e5c82e" + }, + "isDisabled": false + }, + { + "args": { + "array_thresholds_object": { + "inverted": false, + "thresholds": [ + { + "array_path": "TriangleGeometry/Face Data/Region Ids", + "comparison": 1, + "inverted": false, + "type": "array", + "union": 0, + "value": 3.0 + } + ], + "type": "collection", + "union": 0 + }, + "created_mask_type": 1, + "custom_false_value": 0.0, + "custom_true_value": 1.0, + "output_data_array_name": "Mask", + "use_custom_false_value": false, + "use_custom_true_value": false + }, + "comments": "", + "filter": { + "name": "nx::core::MultiThresholdObjectsFilter", + "uuid": "4246245e-1011-4add-8436-0af6bed19228" + }, + "isDisabled": false + }, + { + "args": { + "input_triangle_geometry_path": "TriangleGeometry", + "mask_array_path": "TriangleGeometry/Face Data/Mask", + "output_triangle_geometry_path": "ExemplarReducedGeometry", + "triangle_data_handling_index": 0, + "triangle_data_selected_array_paths": [ + "TriangleGeometry/Face Data/Face Normals", + "TriangleGeometry/Face Data/Mask", + "TriangleGeometry/Face Data/Region Ids" + ], + "triangle_data_selected_attribute_matrix_path": "", + "vertex_data_handling_index": 0, + "vertex_data_selected_array_paths": [], + "vertex_data_selected_attribute_matrix_path": "" + }, + "comments": "", + "filter": { + "name": "nx::core::RemoveFlaggedTrianglesFilter", + "uuid": "38155c61-2709-4731-be95-43745bb3f8d8" + }, + "isDisabled": false + }, + { + "args": { + "export_file_path": "Data/Output/remove_flagged_elements_data/remove_flagged_triangles_data.dream3d", + "write_xdmf_file": true + }, + "comments": "", + "filter": { + "name": "nx::core::WriteDREAM3DFilter", + "uuid": "b3a95784-2ced-41ec-8d3d-0242ac130003" + }, + "isDisabled": false + } + ], + "version": 1, + "workflowParams": [] +} 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..031b366d0f --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.cpp @@ -0,0 +1,217 @@ +#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/DataGroupUtilities.hpp" +#include "simplnx/Utilities/ParallelAlgorithmUtilities.hpp" +#include "simplnx/Utilities/ParallelDataAlgorithm.hpp" + +using namespace nx::core; + +namespace +{ +constexpr usize k_NumVerts = 2; + +/** + * @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)); + + /** This section will copy any user defined Edge Data Arrays from the old to the reduced edge geometry **/ + if(m_InputValues->EdgeDataHandling == detail::k_CopySelectedEdgeArraysIdx) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedEdgeGeom.getEdgeAttributeMatrixRef(), m_InputValues->SelectedEdgeData, newEdgesIndexList, m_ShouldCancel, + m_MessageHandler); + } + else if(m_InputValues->EdgeDataHandling == detail::k_CopyAllEdgeArraysIdx) + { + std::vector ignorePaths; + auto getChildrenResult = GetAllChildArrayDataPaths(m_DataStructure, m_InputValues->EdgeAttributeMatrixPath, ignorePaths); + if(getChildrenResult.has_value()) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedEdgeGeom.getEdgeAttributeMatrixRef(), getChildrenResult.value(), newEdgesIndexList, m_ShouldCancel, m_MessageHandler); + } + } + + /** This section will copy any user defined Vertex Data Arrays from the old to the reduced Vertex geometry **/ + if(m_InputValues->VertexDataHandling == detail::k_CopySelectedVertexArraysIdx) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedEdgeGeom.getVertexAttributeMatrixRef(), m_InputValues->SelectedVertexData, vertexListIndices, m_ShouldCancel, + m_MessageHandler); + } + else if(m_InputValues->VertexDataHandling == detail::k_CopyAllVertexArraysIdx) + { + std::vector ignorePaths; + auto getChildrenResult = GetAllChildArrayDataPaths(m_DataStructure, m_InputValues->VertexAttributeMatrixPath, ignorePaths); + if(getChildrenResult.has_value()) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedEdgeGeom.getVertexAttributeMatrixRef(), getChildrenResult.value(), vertexListIndices, m_ShouldCancel, m_MessageHandler); + } + } + + 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..467f1aef25 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedEdges.hpp @@ -0,0 +1,73 @@ +#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/MultiArraySelectionParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" + +namespace nx::core +{ +namespace detail +{ +const std::string k_CopySelectedTriangleData("Copy Selected Edge Data"); +const std::string k_CopyAllEdgeData("Copy All Edge Data"); + +const nx::core::ChoicesParameter::Choices k_EdgeDataHandlingChoices = {k_CopySelectedTriangleData, k_CopyAllEdgeData}; +const nx::core::ChoicesParameter::ValueType k_CopySelectedEdgeArraysIdx = 0ULL; +const nx::core::ChoicesParameter::ValueType k_CopyAllEdgeArraysIdx = 1ULL; + +const std::string k_CopySelectedVertexData("Copy Selected Vertex Data"); +const std::string k_CopyAllVertexData("Copy All Vertex Data"); + +const nx::core::ChoicesParameter::Choices k_VertexDataHandlingChoices = {k_CopySelectedVertexData, k_CopyAllVertexData}; +const nx::core::ChoicesParameter::ValueType k_CopySelectedVertexArraysIdx = 0ULL; +const nx::core::ChoicesParameter::ValueType k_CopyAllVertexArraysIdx = 1ULL; +} // namespace detail + +struct SIMPLNXCORE_EXPORT RemoveFlaggedEdgesInputValues +{ + DataPath EdgeGeometry; + DataPath MaskArrayPath; + DataPath ReducedEdgeGeometry; + // These variables are associated with the Edge Data Handling + nx::core::ChoicesParameter::ValueType EdgeDataHandling; + MultiArraySelectionParameter::ValueType SelectedEdgeData; + DataPath EdgeAttributeMatrixPath; + // These variables are associated with the Vertex Data Handling + nx::core::ChoicesParameter::ValueType VertexDataHandling; + MultiArraySelectionParameter::ValueType SelectedVertexData; + DataPath VertexAttributeMatrixPath; +}; + +/** + * @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/Algorithms/RemoveFlaggedTriangles.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.cpp index 410bffc40b..1afe1f5100 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.cpp @@ -4,6 +4,7 @@ #include "simplnx/DataStructure/DataGroup.hpp" #include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" #include "simplnx/Utilities/DataArrayUtilities.hpp" +#include "simplnx/Utilities/DataGroupUtilities.hpp" #include "simplnx/Utilities/ParallelDataAlgorithm.hpp" using namespace nx::core; @@ -93,7 +94,7 @@ Result<> RemoveFlaggedTriangles::operator()() 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& reducedTriangle = m_DataStructure.getDataRefAs(m_InputValues->ReducedTriangleGeometry); + auto& reducedTriangleGeom = m_DataStructure.getDataRefAs(m_InputValues->ReducedTriangleGeometry); // Set up allocated masks usize size = originalTriangle.getNumberOfFaces(); @@ -120,41 +121,41 @@ Result<> RemoveFlaggedTriangles::operator()() } // flatten a list of the indices of vertices used by the triangles - std::vector VertexListIndices; // also used as a pseudo look up table in PopulateReducedGeometryTrianglesImpl + std::vector vertexListIndices; // also used as a pseudo look up table in PopulateReducedGeometryTrianglesImpl for(usize& index : newTrianglesIndexList) { usize vertIDs[3] = {0, 0, 0}; originalTriangle.getFacePointIds(index, vertIDs); - VertexListIndices.push_back(vertIDs[0]); - VertexListIndices.push_back(vertIDs[1]); - VertexListIndices.push_back(vertIDs[2]); + vertexListIndices.push_back(vertIDs[0]); + vertexListIndices.push_back(vertIDs[1]); + vertexListIndices.push_back(vertIDs[2]); } if(getCancel()) { return {}; } - if(VertexListIndices.empty()) + 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()); + 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(); - reducedTriangle.resizeVertexList(size); // resize accordingly - reducedTriangle.getVertexAttributeMatrix()->resizeTuples({size}); + size = vertexListIndices.size(); + reducedTriangleGeom.resizeVertexList(size); // resize accordingly + reducedTriangleGeom.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 = originalTriangle.getVertexCoordinate(VertexListIndices[i]); - reducedTriangle.setVertexCoordinate(i, coords); + coords = originalTriangle.getVertexCoordinate(vertexListIndices[i]); + reducedTriangleGeom.setVertexCoordinate(i, coords); } if(getCancel()) @@ -164,13 +165,47 @@ Result<> RemoveFlaggedTriangles::operator()() // Set up preprocessing conditions (allocation for parallelization) size = newTrianglesIndexList.size(); - reducedTriangle.resizeFaceList(size); // resize accordingly - reducedTriangle.getFaceAttributeMatrix()->resizeTuples({size}); + reducedTriangleGeom.resizeFaceList(size); // resize accordingly + reducedTriangleGeom.getFaceAttributeMatrix()->resizeTuples({size}); // parse triangles and reassign indexes to match new vertex list ParallelDataAlgorithm dataAlg; dataAlg.setRange(0, size); - dataAlg.execute(PopulateReducedGeometryTrianglesImpl(originalTriangle, reducedTriangle, newTrianglesIndexList, VertexListIndices)); + dataAlg.execute(PopulateReducedGeometryTrianglesImpl(originalTriangle, reducedTriangleGeom, newTrianglesIndexList, vertexListIndices)); + + /** This section will copy any user defined Triangle Data Arrays from the old to the reduced Triangle geometry **/ + if(m_InputValues->TriangleDataHandling == detail::k_CopySelectedTriangleArraysIdx) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedTriangleGeom.getFaceAttributeMatrixRef(), m_InputValues->SelectedTriangleData, newTrianglesIndexList, m_ShouldCancel, + m_MessageHandler); + } + else if(m_InputValues->TriangleDataHandling == detail::k_CopyAllTriangleArraysIdx) + { + std::vector ignorePaths; + auto getChildrenResult = GetAllChildArrayDataPaths(m_DataStructure, m_InputValues->TriangleAttributeMatrixPath, ignorePaths); + if(getChildrenResult.has_value()) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedTriangleGeom.getFaceAttributeMatrixRef(), getChildrenResult.value(), newTrianglesIndexList, m_ShouldCancel, + m_MessageHandler); + } + } + + /** This section will copy any user defined Vertex Data Arrays from the old to the reduced Vertex geometry **/ + if(m_InputValues->VertexDataHandling == detail::k_CopySelectedVertexArraysIdx) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedTriangleGeom.getVertexAttributeMatrixRef(), m_InputValues->SelectedVertexData, vertexListIndices, m_ShouldCancel, + m_MessageHandler); + } + else if(m_InputValues->VertexDataHandling == detail::k_CopyAllVertexArraysIdx) + { + std::vector ignorePaths; + auto getChildrenResult = GetAllChildArrayDataPaths(m_DataStructure, m_InputValues->VertexAttributeMatrixPath, ignorePaths); + if(getChildrenResult.has_value()) + { + TransferGeometryElementData::transferElementData(m_DataStructure, reducedTriangleGeom.getVertexAttributeMatrixRef(), getChildrenResult.value(), vertexListIndices, m_ShouldCancel, + m_MessageHandler); + } + } return {}; } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.hpp index 611551fd5b..2881586f11 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/RemoveFlaggedTriangles.hpp @@ -6,16 +6,43 @@ #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/MultiArraySelectionParameter.hpp" #include "simplnx/Parameters/StringParameter.hpp" namespace nx::core { +namespace detail +{ +const std::string k_CopySelectedTriangleData("Copy Selected Triangle Data"); +const std::string k_CopyAllTriangleData("Copy All Triangle Data"); + +const nx::core::ChoicesParameter::Choices k_TriangleDataHandlingChoices = {k_CopySelectedTriangleData, k_CopyAllTriangleData}; +const nx::core::ChoicesParameter::ValueType k_CopySelectedTriangleArraysIdx = 0ULL; +const nx::core::ChoicesParameter::ValueType k_CopyAllTriangleArraysIdx = 1ULL; + +const std::string k_CopySelectedVertexData("Copy Selected Vertex Data"); +const std::string k_CopyAllVertexData("Copy All Vertex Data"); + +const nx::core::ChoicesParameter::Choices k_VertexDataHandlingChoices = {k_CopySelectedVertexData, k_CopyAllVertexData}; +const nx::core::ChoicesParameter::ValueType k_CopySelectedVertexArraysIdx = 0ULL; +const nx::core::ChoicesParameter::ValueType k_CopyAllVertexArraysIdx = 1ULL; +} // namespace detail + struct SIMPLNXCORE_EXPORT RemoveFlaggedTrianglesInputValues { DataPath TriangleGeometry; DataPath MaskArrayPath; DataPath ReducedTriangleGeometry; + // These variables are associated with the Edge Data Handling + nx::core::ChoicesParameter::ValueType TriangleDataHandling; + MultiArraySelectionParameter::ValueType SelectedTriangleData; + DataPath TriangleAttributeMatrixPath; + // These variables are associated with the Vertex Data Handling + nx::core::ChoicesParameter::ValueType VertexDataHandling; + MultiArraySelectionParameter::ValueType SelectedVertexData; + DataPath VertexAttributeMatrixPath; }; /** 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..5db6497860 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.cpp @@ -0,0 +1,249 @@ +#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/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/DataGroupCreationParameter.hpp" +#include "simplnx/Parameters/GeometrySelectionParameter.hpp" +#include "simplnx/Parameters/StringParameter.hpp" +#include "simplnx/Utilities/DataGroupUtilities.hpp" +#include "simplnx/Utilities/SIMPLConversion.hpp" + +using namespace nx::core; + +namespace +{ + +} // namespace + +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_InputEdgeGeometryPath_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}})); + + params.insertSeparator(Parameters::Separator{"Output Geometry"}); + params.insert(std::make_unique(k_OutputEdgeGeometryPath_Key, "Created Geometry", "The name of the created Edge Geometry", DataPath({"ReducedGeometry"}))); + + // Vertex Data Handling + params.insertSeparator(Parameters::Separator{"Vertex Data Handling"}); + params.insertLinkableParameter(std::make_unique(k_VertexDataHandling_Key, "Vertex Data Handling", "How to handle Data that resides on the edges", + detail::k_CopySelectedVertexArraysIdx, detail::k_VertexDataHandlingChoices)); + params.insert( + std::make_unique(k_VertexDataSelectedAttributeMatrix_Key, "Vertex Data", "Vertex Attribute Matrix that will be copied to the reduced geometry", DataPath{})); + params.insert(std::make_unique(k_VertexDataSelectedArrays_Key, "Vertex Attribute Arrays to Copy", "Vertex DataPaths to copy", std::vector(), + MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes())); + + params.linkParameters(k_VertexDataHandling_Key, k_VertexDataSelectedAttributeMatrix_Key, detail::k_CopyAllVertexArraysIdx); + params.linkParameters(k_VertexDataHandling_Key, k_VertexDataSelectedArrays_Key, detail::k_CopySelectedVertexArraysIdx); + + // Edge Data Handling + params.insertSeparator(Parameters::Separator{"Edge Data Handling"}); + params.insertLinkableParameter(std::make_unique(k_EdgeDataHandling_Key, "Edge Data Handling", "How to handle Data that resides on the edges", detail::k_CopySelectedEdgeArraysIdx, + detail::k_EdgeDataHandlingChoices)); + 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", "Edge DataPaths to copy", std::vector(), + MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes())); + + params.linkParameters(k_EdgeDataHandling_Key, k_EdgeDataSelectedArrays_Key, detail::k_CopySelectedEdgeArraysIdx); + params.linkParameters(k_EdgeDataHandling_Key, k_EdgeDataSelectedAttributeMatrix_Key, detail::k_CopyAllEdgeArraysIdx); + + 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_InputEdgeGeometryPath_Key); + auto pReducedGeometryPathValue = filterArgs.value(k_OutputEdgeGeometryPath_Key); + auto pEdgeArrayHandling = filterArgs.value(k_EdgeDataHandling_Key); + auto selectedEdgeArrays = filterArgs.value(k_EdgeDataSelectedArrays_Key); + auto selectedEdgeAttrMatPath = filterArgs.value(k_EdgeDataSelectedAttributeMatrix_Key); + + auto pVertexArrayHandling = filterArgs.value(k_VertexDataHandling_Key); + auto selectedVertexArrays = filterArgs.value(k_VertexDataSelectedArrays_Key); + auto selectedVertexAttrMatPath = filterArgs.value(k_VertexDataSelectedAttributeMatrix_Key); + + PreflightResult preflightResult; + Result resultOutputActions; + std::vector preflightUpdatedValues; + + const auto* initialGeomPtr = dataStructure.getDataAs(pInitialGeometryPathValue); + + std::string reducedVertexAttributeMatrixName = (initialGeomPtr->getVertexAttributeMatrix() == nullptr ? "Vertex Data" : initialGeomPtr->getVertexAttributeMatrix()->getName()); + std::string reducedEdgeAttributeMatrixName = (initialGeomPtr->getEdgeAttributeMatrix() == nullptr ? "Edge Data" : initialGeomPtr->getEdgeAttributeMatrix()->getName()); + + DataPath reducedVertexAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(reducedVertexAttributeMatrixName); + DataPath reducedEdgeAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(reducedEdgeAttributeMatrixName); + + std::vector edgeDataShape = {initialGeomPtr->getNumberOfEdges()}; + std::vector vertexDataShape = {initialGeomPtr->getNumberOfVertices()}; + + if(initialGeomPtr->getGeomType() == IGeometry::Type::Edge) + { + auto createGeometryAction = + std::make_unique>(pReducedGeometryPathValue, initialGeomPtr->getNumberOfEdges(), initialGeomPtr->getNumberOfVertices(), reducedVertexAttributeMatrixName, + reducedEdgeAttributeMatrixName, initialGeomPtr->getVertices()->getName(), initialGeomPtr->getEdges()->getName()); + resultOutputActions.value().appendAction(std::move(createGeometryAction)); + } + + /** This section is for copying the Edge Data ***/ + // This _could_ be nullptr. We are going to hold off doing that check until inside each of the + // conditional blocks below. + { + const AttributeMatrix* srcEdgeAttrMatPtr = initialGeomPtr->getEdgeAttributeMatrix(); + if(pEdgeArrayHandling == detail::k_CopySelectedEdgeArraysIdx) + { + if(!selectedEdgeArrays.empty() && nullptr == srcEdgeAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have edge data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + TransferGeometryElementData::createDataArrayActions(dataStructure, srcEdgeAttrMatPtr, selectedEdgeArrays, reducedEdgeAttributeMatrixPath, resultOutputActions); + } + else if(pEdgeArrayHandling == detail::k_CopyAllEdgeArraysIdx) + { + if(nullptr == srcEdgeAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have edge data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + std::vector ignorePaths; + + auto getChildrenResult = GetAllChildArrayDataPaths(dataStructure, selectedEdgeAttrMatPath, ignorePaths); + if(getChildrenResult.has_value()) + { + selectedEdgeArrays = getChildrenResult.value(); + TransferGeometryElementData::createDataArrayActions(dataStructure, srcEdgeAttrMatPtr, selectedEdgeArrays, reducedEdgeAttributeMatrixPath, resultOutputActions); + } + } + } + + /** This section is for copying the Vertex Data ***/ + // This _could_ be nullptr. We are going to hold off doing that check until inside each of the + // conditional blocks below. + { + const AttributeMatrix* srcVertexAttrMatPtr = initialGeomPtr->getVertexAttributeMatrix(); + if(pVertexArrayHandling == detail::k_CopySelectedVertexArraysIdx) + { + if(!selectedVertexArrays.empty() && nullptr == srcVertexAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have Vertex data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + TransferGeometryElementData::createDataArrayActions(dataStructure, srcVertexAttrMatPtr, selectedVertexArrays, reducedVertexAttributeMatrixPath, resultOutputActions); + } + else if(pVertexArrayHandling == detail::k_CopyAllVertexArraysIdx) + { + if(nullptr == srcVertexAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have Vertex data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + std::vector ignorePaths; + + auto getChildrenResult = GetAllChildArrayDataPaths(dataStructure, selectedVertexAttrMatPath, ignorePaths); + if(getChildrenResult.has_value()) + { + selectedVertexArrays = getChildrenResult.value(); + TransferGeometryElementData::createDataArrayActions(dataStructure, srcVertexAttrMatPtr, selectedVertexArrays, reducedVertexAttributeMatrixPath, resultOutputActions); + } + } + } + + // 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_InputEdgeGeometryPath_Key); + inputValues.MaskArrayPath = filterArgs.value(k_MaskArrayPath_Key); + inputValues.ReducedEdgeGeometry = filterArgs.value(k_OutputEdgeGeometryPath_Key); + + inputValues.EdgeDataHandling = filterArgs.value(k_EdgeDataHandling_Key); + inputValues.EdgeAttributeMatrixPath = filterArgs.value(k_EdgeDataSelectedAttributeMatrix_Key); + inputValues.SelectedEdgeData = filterArgs.value(k_EdgeDataSelectedArrays_Key); + + inputValues.VertexDataHandling = filterArgs.value(k_VertexDataHandling_Key); + inputValues.VertexAttributeMatrixPath = filterArgs.value(k_VertexDataSelectedAttributeMatrix_Key); + inputValues.SelectedVertexData = filterArgs.value(k_VertexDataSelectedArrays_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_InputEdgeGeometryPath_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_OutputEdgeGeometryPath_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..10ad2b3322 --- /dev/null +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp @@ -0,0 +1,113 @@ +#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_InputEdgeGeometryPath_Key = "input_edge_geometry_path"; + static inline constexpr StringLiteral k_MaskArrayPath_Key = "mask_array_path"; + static inline constexpr StringLiteral k_OutputEdgeGeometryPath_Key = "output_edge_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"; + + static inline constexpr StringLiteral k_VertexDataHandling_Key = "vertex_data_handling_index"; + static inline constexpr StringLiteral k_VertexDataSelectedArrays_Key = "vertex_data_selected_array_paths"; + static inline constexpr StringLiteral k_VertexDataSelectedAttributeMatrix_Key = "vertex_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/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.cpp index c9764a4d7a..9cae37e861 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.cpp @@ -9,6 +9,7 @@ #include "simplnx/Parameters/DataGroupCreationParameter.hpp" #include "simplnx/Parameters/GeometrySelectionParameter.hpp" #include "simplnx/Parameters/StringParameter.hpp" +#include "simplnx/Utilities/DataGroupUtilities.hpp" #include "simplnx/Utilities/SIMPLConversion.hpp" using namespace nx::core; @@ -42,7 +43,7 @@ std::string RemoveFlaggedTrianglesFilter::humanName() const //------------------------------------------------------------------------------ std::vector RemoveFlaggedTrianglesFilter::defaultTags() const { - return {"Surface Meshing", "Cleanup"}; + return {"Surface Meshing", "Cleanup", "Remove", "Delete"}; } //------------------------------------------------------------------------------ @@ -52,13 +53,38 @@ Parameters RemoveFlaggedTrianglesFilter::parameters() const // 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, "Triangle|Quad Geometry", "The Triangle|Quad Geometry that will be processed.", DataPath(), - GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Triangle, IGeometry::Type::Quad})); + params.insert(std::make_unique(k_SelectedTriangleGeometryPath_Key, "Triangle Geometry", "The Triangle Geometry that will be processed.", DataPath(), + GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Triangle})); params.insert(std::make_unique(k_MaskArrayPath_Key, "Mask", "The DataArrayPath to the mask array that marks each face as either true (remove) or false(keep).", DataPath{}, ArraySelectionParameter::AllowedTypes{DataType::boolean, DataType::uint8}, ArraySelectionParameter::AllowedComponentShapes{{1}})); params.insertSeparator(Parameters::Separator{"Output Geometry"}); params.insert(std::make_unique(k_CreatedTriangleGeometryPath_Key, "Created Geometry", "The name of the created Triangle Geometry", DataPath({"ReducedGeometry"}))); + + // Vertex Data Handling + params.insertSeparator(Parameters::Separator{"Vertex Data Handling"}); + params.insertLinkableParameter(std::make_unique(k_VertexDataHandling_Key, "Vertex Data Handling", "How to handle Data that resides on the triangles", + detail::k_CopySelectedVertexArraysIdx, detail::k_VertexDataHandlingChoices)); + params.insert( + std::make_unique(k_VertexDataSelectedAttributeMatrix_Key, "Vertex Data", "Vertex Attribute Matrix that will be copied to the reduced geometry", DataPath{})); + params.insert(std::make_unique(k_VertexDataSelectedArrays_Key, "Vertex Attribute Arrays to Copy", "Vertex DataPaths to copy", std::vector(), + MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes())); + + params.linkParameters(k_VertexDataHandling_Key, k_VertexDataSelectedAttributeMatrix_Key, detail::k_CopyAllVertexArraysIdx); + params.linkParameters(k_VertexDataHandling_Key, k_VertexDataSelectedArrays_Key, detail::k_CopySelectedVertexArraysIdx); + + // Triangle Data Handling + params.insertSeparator(Parameters::Separator{"Triangle Data Handling"}); + params.insertLinkableParameter(std::make_unique(k_TriangleDataHandling_Key, "Triangle Data Handling", "How to handle Data that resides on the triangles", + detail::k_CopySelectedTriangleArraysIdx, detail::k_TriangleDataHandlingChoices)); + params.insert(std::make_unique(k_TriangleDataSelectedAttributeMatrix_Key, "Triangle Data", "Triangle Attribute Matrix that will be copied to the reduced geometry", + DataPath{})); + params.insert(std::make_unique(k_TriangleDataSelectedArrays_Key, "Triangle Attribute Arrays to Copy", "Triangle DataPaths to copy", std::vector(), + MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::DataArray}, GetAllNumericTypes())); + + params.linkParameters(k_TriangleDataHandling_Key, k_TriangleDataSelectedArrays_Key, detail::k_CopySelectedTriangleArraysIdx); + params.linkParameters(k_TriangleDataHandling_Key, k_TriangleDataSelectedAttributeMatrix_Key, detail::k_CopyAllTriangleArraysIdx); + return params; } @@ -74,31 +100,105 @@ IFilter::PreflightResult RemoveFlaggedTrianglesFilter::preflightImpl(const DataS { auto pInitialGeometryPathValue = filterArgs.value(k_SelectedTriangleGeometryPath_Key); auto pReducedGeometryPathValue = filterArgs.value(k_CreatedTriangleGeometryPath_Key); + auto pTriangleArrayHandling = filterArgs.value(k_TriangleDataHandling_Key); + auto selectedTriangleArrays = filterArgs.value(k_TriangleDataSelectedArrays_Key); + auto selectedTriangleAttrMatPath = filterArgs.value(k_TriangleDataSelectedAttributeMatrix_Key); + + auto pVertexArrayHandling = filterArgs.value(k_VertexDataHandling_Key); + auto selectedVertexArrays = filterArgs.value(k_VertexDataSelectedArrays_Key); + auto selectedVertexAttrMatPath = filterArgs.value(k_VertexDataSelectedAttributeMatrix_Key); PreflightResult preflightResult; Result resultOutputActions; std::vector preflightUpdatedValues; - const auto* initialGeom = dataStructure.getDataAs(pInitialGeometryPathValue); + const auto* initialGeomPtr = dataStructure.getDataAs(pInitialGeometryPathValue); + + std::string reducedVertexAttributeMatrixName = (initialGeomPtr->getVertexAttributeMatrix() == nullptr ? "Vertex Data" : initialGeomPtr->getVertexAttributeMatrix()->getName()); + std::string reducedFaceAttributeMatrixName = (initialGeomPtr->getEdgeAttributeMatrix() == nullptr ? "Face Data" : initialGeomPtr->getEdgeAttributeMatrix()->getName()); - if(initialGeom->getGeomType() == IGeometry::Type::Triangle) + DataPath reducedVertexAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(reducedVertexAttributeMatrixName); + DataPath reducedFaceAttributeMatrixPath = pReducedGeometryPathValue.createChildPath(reducedFaceAttributeMatrixName); + + std::vector triangleDataShape = {initialGeomPtr->getNumberOfFaces()}; + std::vector vertexDataShape = {initialGeomPtr->getNumberOfVertices()}; + + if(initialGeomPtr->getGeomType() == IGeometry::Type::Triangle) { - auto createGeometryAction = std::make_unique>( - pReducedGeometryPathValue, initialGeom->getNumberOfFaces(), initialGeom->getNumberOfVertices(), - (initialGeom->getVertexAttributeMatrix() == nullptr ? "VertexAM" : initialGeom->getVertexAttributeMatrix()->getName()), - (initialGeom->getFaceAttributeMatrix() == nullptr ? "FaceAM" : initialGeom->getFaceAttributeMatrix()->getName()), initialGeom->getVertices()->getName(), initialGeom->getFaces()->getName()); + auto createGeometryAction = + std::make_unique>(pReducedGeometryPathValue, initialGeomPtr->getNumberOfFaces(), initialGeomPtr->getNumberOfVertices(), reducedVertexAttributeMatrixName, + reducedFaceAttributeMatrixName, initialGeomPtr->getVertices()->getName(), initialGeomPtr->getFaces()->getName()); resultOutputActions.value().appendAction(std::move(createGeometryAction)); } - if(initialGeom->getGeomType() == IGeometry::Type::Quad) + if(initialGeomPtr->getGeomType() == IGeometry::Type::Quad) { - auto createGeometryAction = std::make_unique>( - pReducedGeometryPathValue, initialGeom->getNumberOfFaces(), initialGeom->getNumberOfVertices(), - (initialGeom->getVertexAttributeMatrix() == nullptr ? "VertexAM" : initialGeom->getVertexAttributeMatrix()->getName()), - (initialGeom->getFaceAttributeMatrix() == nullptr ? "FaceAM" : initialGeom->getFaceAttributeMatrix()->getName()), initialGeom->getVertices()->getName(), initialGeom->getFaces()->getName()); + auto createGeometryAction = + std::make_unique>(pReducedGeometryPathValue, initialGeomPtr->getNumberOfFaces(), initialGeomPtr->getNumberOfVertices(), reducedVertexAttributeMatrixName, + reducedFaceAttributeMatrixName, initialGeomPtr->getVertices()->getName(), initialGeomPtr->getFaces()->getName()); resultOutputActions.value().appendAction(std::move(createGeometryAction)); } + /** This section is for copying the Face Data ***/ + // This _could_ be nullptr. We are going to hold off doing that check until inside each of the + // conditional blocks below. + { + const AttributeMatrix* srcTriangleAttrMatPtr = initialGeomPtr->getFaceAttributeMatrix(); + if(pTriangleArrayHandling == detail::k_CopySelectedTriangleArraysIdx) + { + if(!selectedTriangleArrays.empty() && nullptr == srcTriangleAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have face data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + TransferGeometryElementData::createDataArrayActions(dataStructure, srcTriangleAttrMatPtr, selectedTriangleArrays, reducedFaceAttributeMatrixPath, resultOutputActions); + } + else if(pTriangleArrayHandling == detail::k_CopyAllTriangleArraysIdx) + { + if(nullptr == srcTriangleAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have face data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + std::vector ignorePaths; + + auto getChildrenResult = GetAllChildArrayDataPaths(dataStructure, selectedTriangleAttrMatPath, ignorePaths); + if(getChildrenResult.has_value()) + { + selectedTriangleArrays = getChildrenResult.value(); + TransferGeometryElementData::createDataArrayActions(dataStructure, srcTriangleAttrMatPtr, selectedTriangleArrays, reducedFaceAttributeMatrixPath, resultOutputActions); + } + } + } + + /** This section is for copying the Vertex Data ***/ + // This _could_ be nullptr. We are going to hold off doing that check until inside each of the + // conditional blocks below. + { + const AttributeMatrix* srcVertexAttrMatPtr = initialGeomPtr->getVertexAttributeMatrix(); + if(pVertexArrayHandling == detail::k_CopySelectedVertexArraysIdx) + { + if(!selectedVertexArrays.empty() && nullptr == srcVertexAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have Vertex data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + TransferGeometryElementData::createDataArrayActions(dataStructure, srcVertexAttrMatPtr, selectedVertexArrays, reducedVertexAttributeMatrixPath, resultOutputActions); + } + else if(pVertexArrayHandling == detail::k_CopyAllVertexArraysIdx) + { + if(nullptr == srcVertexAttrMatPtr) + { + return {MakeErrorResult(-5551, fmt::format("'{}' must have Vertex data attribute matrix", pInitialGeometryPathValue.toString()))}; + } + std::vector ignorePaths; + + auto getChildrenResult = GetAllChildArrayDataPaths(dataStructure, selectedVertexAttrMatPath, ignorePaths); + if(getChildrenResult.has_value()) + { + selectedVertexArrays = getChildrenResult.value(); + TransferGeometryElementData::createDataArrayActions(dataStructure, srcVertexAttrMatPtr, selectedVertexArrays, reducedVertexAttributeMatrixPath, resultOutputActions); + } + } + } + // Return both the resultOutputActions and the preflightUpdatedValues via std::move() return {std::move(resultOutputActions), std::move(preflightUpdatedValues)}; } @@ -113,6 +213,14 @@ Result<> RemoveFlaggedTrianglesFilter::executeImpl(DataStructure& dataStructure, inputValues.MaskArrayPath = filterArgs.value(k_MaskArrayPath_Key); inputValues.ReducedTriangleGeometry = filterArgs.value(k_CreatedTriangleGeometryPath_Key); + inputValues.TriangleDataHandling = filterArgs.value(k_TriangleDataHandling_Key); + inputValues.TriangleAttributeMatrixPath = filterArgs.value(k_TriangleDataSelectedAttributeMatrix_Key); + inputValues.SelectedTriangleData = filterArgs.value(k_TriangleDataSelectedArrays_Key); + + inputValues.VertexDataHandling = filterArgs.value(k_VertexDataHandling_Key); + inputValues.VertexAttributeMatrixPath = filterArgs.value(k_VertexDataSelectedAttributeMatrix_Key); + inputValues.SelectedVertexData = filterArgs.value(k_VertexDataSelectedArrays_Key); + return RemoveFlaggedTriangles(dataStructure, messageHandler, shouldCancel, &inputValues)(); } diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.hpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.hpp index fc38da088d..43403a06a8 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.hpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.hpp @@ -28,6 +28,14 @@ class SIMPLNXCORE_EXPORT RemoveFlaggedTrianglesFilter : public IFilter 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_TriangleDataHandling_Key = "triangle_data_handling_index"; + static inline constexpr StringLiteral k_TriangleDataSelectedArrays_Key = "triangle_data_selected_array_paths"; + static inline constexpr StringLiteral k_TriangleDataSelectedAttributeMatrix_Key = "triangle_data_selected_attribute_matrix_path"; + + static inline constexpr StringLiteral k_VertexDataHandling_Key = "vertex_data_handling_index"; + static inline constexpr StringLiteral k_VertexDataSelectedArrays_Key = "vertex_data_selected_array_paths"; + static inline constexpr StringLiteral k_VertexDataSelectedAttributeMatrix_Key = "vertex_data_selected_attribute_matrix_path"; + /** * @brief Reads SIMPL json and converts it complex Arguments. * @param json diff --git a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedVerticesFilter.cpp b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedVerticesFilter.cpp index 3cad8b388d..cda22ca636 100644 --- a/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedVerticesFilter.cpp +++ b/src/Plugins/SimplnxCore/src/SimplnxCore/Filters/RemoveFlaggedVerticesFilter.cpp @@ -30,7 +30,7 @@ struct RemoveFlaggedVerticesFunctor { // copy data to masked geometry template - void operator()(const IDataArray& sourceIDataArray, IDataArray& destIDataArray, const BoolArray& mask, size_t numVerticesToKeep) const + void operator()(const IDataArray& sourceIDataArray, IDataArray& destIDataArray, const std::unique_ptr& maskCompare, size_t numVerticesToKeep) const { using DataArrayType = DataArray; const auto& sourceDataArray = dynamic_cast(sourceIDataArray); @@ -42,7 +42,7 @@ struct RemoveFlaggedVerticesFunctor usize destTupleIndex = 0; for(usize inputIndex = 0; inputIndex < numInputTuples; inputIndex++) { - if(!mask[inputIndex]) + if(!maskCompare->isTrue(inputIndex)) { for(usize compIdx = 0; compIdx < nComps; compIdx++) { @@ -92,11 +92,13 @@ Parameters RemoveFlaggedVerticesFilter::parameters() const params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_SelectedVertexGeometryPath_Key, "Vertex Geometry", "Path to the target Vertex Geometry", DataPath(), GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Vertex})); - params.insert(std::make_unique(k_InputMaskPath_Key, "Flagged Vertex Array", "DataPath to the conditional array that will be used to decide which vertices are removed.", - DataPath(), ArraySelectionParameter::AllowedTypes{DataType::boolean}, ArraySelectionParameter::AllowedComponentShapes{{1}})); + params.insert(std::make_unique(k_InputMaskPath_Key, "Flagged Vertex Array (Mask)", + "The DataArrayPath to the mask array that marks each face as either true (remove) or false(keep).", DataPath{}, + ArraySelectionParameter::AllowedTypes{DataType::boolean, DataType::uint8}, ArraySelectionParameter::AllowedComponentShapes{{1}})); params.insertSeparator(Parameters::Separator{"Output Vertex Geometry"}); params.insert(std::make_unique(k_CreatedVertexGeometryPath_Key, "Reduced Vertex Geometry", "Created Vertex Geometry DataPath. This will be created during the filter.", DataPath())); + return params; } @@ -109,7 +111,6 @@ IFilter::PreflightResult RemoveFlaggedVerticesFilter::preflightImpl(const DataSt const std::atomic_bool& shouldCancel) const { auto vertexGeomPath = filterArgs.value(k_SelectedVertexGeometryPath_Key); - auto maskArrayPath = filterArgs.value(k_InputMaskPath_Key); auto reducedVertexPath = filterArgs.value(k_CreatedVertexGeometryPath_Key); nx::core::Result resultOutputActions; @@ -133,12 +134,6 @@ IFilter::PreflightResult RemoveFlaggedVerticesFilter::preflightImpl(const DataSt const std::string vertexAttrMatName = inputVertexGeomPtr->getVertexAttributeMatrixDataPath().getTargetName(); - const auto* maskArrayPtr = dataStructure.getDataAs(maskArrayPath); - if(maskArrayPtr != nullptr) - { - dataArrayPaths.push_back(maskArrayPath); - } - // Create vertex geometry const uint64 numVertices = inputVertexGeomPtr->getNumberOfVertices(); auto reduced = std::make_unique(reducedVertexPath, numVertices, vertexAttrMatName, CreateVertexGeometryAction::k_SharedVertexListName); @@ -222,15 +217,25 @@ Result<> RemoveFlaggedVerticesFilter::executeImpl(DataStructure& dataStructure, const VertexGeom& vertexGeom = dataStructure.getDataRefAs(vertexGeomPath); const std::string vertexDataName = vertexGeom.getVertexAttributeMatrixDataPath().getTargetName(); - auto& mask = dataStructure.getDataRefAs(maskArrayPath); + std::unique_ptr maskCompare; + try + { + maskCompare = InstantiateMaskCompare(dataStructure, 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) {}", maskArrayPath.toString()); + return MakeErrorResult(-54070, message); + } - const size_t numVerticesToKeep = std::count(mask.begin(), mask.end(), false); + const size_t numVerticesToKeep = maskCompare->getNumberOfTuples() - maskCompare->countTrueValues(); // We don't need component size since it must be 1 const size_t numberOfVertices = vertexGeom.getNumberOfVertices(); const std::vector tDims = {numVerticesToKeep}; // Resize the reduced vertex geometry object - VertexGeom& reducedVertexGeom = dataStructure.getDataRefAs(reducedVertexPath); + auto& reducedVertexGeom = dataStructure.getDataRefAs(reducedVertexPath); reducedVertexGeom.resizeVertexList(numVerticesToKeep); reducedVertexGeom.getVertexAttributeMatrix()->resizeTuples(tDims); @@ -241,7 +246,7 @@ Result<> RemoveFlaggedVerticesFilter::executeImpl(DataStructure& dataStructure, for(size_t inputVertexIndex = 0; inputVertexIndex < numberOfVertices; inputVertexIndex++) { // If the mask value == FALSE we are keeping that vertex. - if(!mask[inputVertexIndex]) + if(!maskCompare->isTrue(inputVertexIndex)) { reducedVertexGeom.setVertexCoordinate(keepIndex, vertexGeom.getVertexCoordinate(inputVertexIndex)); keepIndex++; @@ -263,7 +268,7 @@ Result<> RemoveFlaggedVerticesFilter::executeImpl(DataStructure& dataStructure, auto& dest = dataStructure.getDataRefAs(destinationPath); messageHandler(nx::core::IFilter::Message{nx::core::IFilter::Message::Type::Info, fmt::format("Copying source array '{}' to reduced geometry vertex data.", src.getName())}); - ExecuteDataFunction(RemoveFlaggedVerticesFunctor{}, src.getDataType(), src, dest, mask, numVerticesToKeep); + ExecuteDataFunction(RemoveFlaggedVerticesFunctor{}, src.getDataType(), src, dest, maskCompare, numVerticesToKeep); } return {}; diff --git a/src/Plugins/SimplnxCore/test/CMakeLists.txt b/src/Plugins/SimplnxCore/test/CMakeLists.txt index 71cda8568e..feeb0ebf07 100644 --- a/src/Plugins/SimplnxCore/test/CMakeLists.txt +++ b/src/Plugins/SimplnxCore/test/CMakeLists.txt @@ -106,6 +106,7 @@ set(${PLUGIN_NAME}UnitTest_SRCS RegularGridSampleSurfaceMeshTest.cpp RemoveFlaggedFeaturesTest.cpp RemoveFlaggedTrianglesTest.cpp + RemoveFlaggedEdgesTest.cpp RemoveFlaggedVerticesTest.cpp RequireMinimumSizeFeaturesTest.cpp RenameDataObjectTest.cpp @@ -232,12 +233,12 @@ if(EXISTS "${DREAM3D_DATA_DIR}" AND SIMPLNX_DOWNLOAD_TEST_FILES) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME initialize_data_test_files.tar.gz SHA512 f04fe76ef96add4b775111ae7fc95233a7748a25501d9f00a8b2a162c87782b8cd2813e6e46ba7892e721976693da06965e624335dbb28224c9c5b877a05aa49) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME 6_6_write_stl_file_test.tar.gz SHA512 05455dfb57724a14a3b2d92ad02ac335b5596ca4f353830ee60d84ffc858523136140328a1c95b6693331be9dc5c40a26e76f45ee54ab11e218f5efb6b2a4c38) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME label_triangle_geometry_test.tar.gz SHA512 9281fa954c842ca1881ba932d6d96b9c43601b466b6b7ae33721d1c886629ba68986a3ccaf774f5be577e8bfce145612f77a2b98af319e6aa35a3e0aeb607597) - download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME remove_flagged_triangles_test.tar.gz SHA512 cd5c6f3ea16a6d09e00e0c0bd0f941b27dca8a0beaeabb7262a2a6adaad83c829187c5d1aa433718123b628eaa839f016604c1134ced9f870723594b2df4be99) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME generate_color_table_test.tar.gz SHA512 b5683c758964eb723267400b14047f8adb0d5365ee9ca93d1a6940e9b6ad198cd4739c1ca799eb787b7706e668dbc16ab8243642034cdba5b71d64c27e682d3f) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME read_vtk_structured_points_test.tar.gz SHA512 e7a07a4e3901204c2562754cd71e0fdba1a46de2a5135bad2b6d66b40eefd0e63bed4dbe0ccd6ccadafb708ef63e20635d080aa3a35c172c4ced6986e0f75d5c) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME ReadSTLFileTest.tar.gz SHA512 975587206625ffa183160308934e767347de55a34a16272cf5c121114efa286b3c6939e3c6a397e8728fdefe1771bc024bd4c9b409afdff0b76f2f56fcb9eb69) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME DBSCAN_tests.tar.gz SHA512 fba3d6c02cde5eeeccad0153032e403c20826365c2362351b8e1048b3d385480e7fba828748196e03ffa95aff569420630b0506441d9da9893d25db40390fdf0) download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME scan_path_test_data.tar.gz SHA512 3dc7104df9b49fb571caec0dca8fbeb327b477aa7d830c47e014b018d207be03e2333c51d113eef9efbbe49a6be9711e215c582bae1ed55a4ad07e157bd55344) + download_test_data(DREAM3D_DATA_DIR ${DREAM3D_DATA_DIR} ARCHIVE_NAME remove_flagged_elements_data.tar.gz SHA512 6bd5b1d93a100bcf8e6d9aa88243cadf51c9f0dee85fc3c054f5fe489b6720991b41390cf4682862483dfee11595a2e384d8698f68c0245cf0521d275db450ee) endif() @@ -260,6 +261,7 @@ set(PREBUILT_PIPELINE_NAMES "${${PLUGIN_NAME}_SOURCE_DIR}/pipelines/Import_STL_Model.d3dpipeline" "${${PLUGIN_NAME}_SOURCE_DIR}/pipelines/Triangle_Face_Data_Demo.d3dpipeline" "${${PLUGIN_NAME}_SOURCE_DIR}/pipelines/Remove_Triangles.d3dpipeline" + "${${PLUGIN_NAME}_SOURCE_DIR}/pipelines/remove_flagged_triangles_example.d3dpipeline" # These are workflow files # "${${PLUGIN_NAME}_SOURCE_DIR}/pipelines/Import_ASCII_Data.d3dworkflow" diff --git a/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp b/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp new file mode 100644 index 0000000000..ccb68b9c7b --- /dev/null +++ b/src/Plugins/SimplnxCore/test/RemoveFlaggedEdgesTest.cpp @@ -0,0 +1,84 @@ +#include "SimplnxCore/Filters/RemoveFlaggedEdgesFilter.hpp" +#include "SimplnxCore/SimplnxCore_test_dirs.hpp" + +#include "simplnx/Parameters/ChoicesParameter.hpp" +#include "simplnx/UnitTest/UnitTestCommon.hpp" + +#include + +using namespace nx::core; + +namespace +{ +fs::path k_BaseDataFilePath = fs::path(fmt::format("{}/remove_flagged_elements_data/remove_flagged_edges_data.dream3d", nx::core::unit_test::k_TestFilesDir)); + +constexpr StringLiteral k_RegionIdsName = "Region Ids"; +constexpr StringLiteral k_SliceIdsName = "Slice Ids"; +constexpr StringLiteral k_VertexListName = "SharedVertexList"; +constexpr StringLiteral k_EdgeListName = "SharedEdgeList"; + +const DataPath k_EdgeGeomPath({"Edge Geometry"}); +const DataPath k_MaskPath = k_EdgeGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(Constants::k_Mask); +const DataPath k_RegionIdsPath = k_EdgeGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(k_RegionIdsName); +const DataPath k_SliceIdsPath = k_EdgeGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(k_SliceIdsName); +const DataPath k_ReducedGeomPath({"ReducedGeometry"}); +const DataPath k_ExemplarReducedGeomPath({"ExemplarReducedGeometry"}); +} // namespace + +TEST_CASE("SimplnxCore::RemoveFlaggedEdgesFilter: Test Algorithm", "[SimplnxCore][RemoveFlaggedEdgesFilter]") +{ + const UnitTest::TestFileSentinel testDataSentinel(unit_test::k_CMakeExecutable, unit_test::k_TestFilesDir, "remove_flagged_elements_data.tar.gz", "remove_flagged_elements_data"); + + // 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_InputEdgeGeometryPath_Key, std::make_any(::k_EdgeGeomPath)); + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_MaskArrayPath_Key, std::make_any(::k_MaskPath)); + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_OutputEdgeGeometryPath_Key, std::make_any(::k_ReducedGeomPath)); + + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_EdgeDataHandling_Key, std::make_any(0ULL)); + args.insertOrAssign(RemoveFlaggedEdgesFilter::k_EdgeDataSelectedArrays_Key, std::make_any>(std::vector{::k_SliceIdsPath, ::k_RegionIdsPath})); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(::k_RegionIdsName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(::k_RegionIdsName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(::k_SliceIdsName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Edge_Data).createChildPath(::k_SliceIdsName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(::k_VertexListName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(::k_VertexListName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(::k_EdgeListName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(::k_EdgeListName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } +} diff --git a/src/Plugins/SimplnxCore/test/RemoveFlaggedTrianglesTest.cpp b/src/Plugins/SimplnxCore/test/RemoveFlaggedTrianglesTest.cpp index 1bebe6e096..9ef2bc8362 100644 --- a/src/Plugins/SimplnxCore/test/RemoveFlaggedTrianglesTest.cpp +++ b/src/Plugins/SimplnxCore/test/RemoveFlaggedTrianglesTest.cpp @@ -1,38 +1,37 @@ -#include - #include "SimplnxCore/Filters/RemoveFlaggedTrianglesFilter.hpp" #include "SimplnxCore/SimplnxCore_test_dirs.hpp" -#include "simplnx/DataStructure/Geometry/TriangleGeom.hpp" + +#include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/UnitTest/UnitTestCommon.hpp" +#include + 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)); +fs::path k_BaseDataFilePath = fs::path(fmt::format("{}/remove_flagged_elements_data/remove_flagged_triangles_data.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"; +constexpr StringLiteral k_RegionIdsName = "Region Ids"; +constexpr StringLiteral k_FaceNormalsName = "Face Normals"; +constexpr StringLiteral k_VertexListName = "SharedVertexList"; +constexpr StringLiteral k_TriangleListName = "SharedTriList"; const DataPath k_TriangleGeomPath({"TriangleGeometry"}); const DataPath k_MaskPath = k_TriangleGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(Constants::k_Mask); +const DataPath k_RegionIdsPath = k_TriangleGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(k_RegionIdsName); +const DataPath k_FaceNormalsPath = k_TriangleGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(k_FaceNormalsName); const DataPath k_ReducedGeomPath({"ReducedGeometry"}); - -const DataPath k_VertexListPath = k_ReducedGeomPath.createChildPath("SharedVertexList"); -const DataPath k_TriangleListPath = k_ReducedGeomPath.createChildPath("SharedTriList"); -} // namespace +const DataPath k_ExemplarReducedGeomPath({"ExemplarReducedGeometry"}); } // namespace -TEST_CASE("SimplnxCore::RemoveFlaggedTrianglesFilter: Valid Filter Execution", "[SimplnxCore][RemoveFlaggedTrianglesFilter]") +TEST_CASE("SimplnxCore::RemoveFlaggedTrianglesFilter: Test Algorithm", "[SimplnxCore][RemoveFlaggedTrianglesFilter]") { - const UnitTest::TestFileSentinel testDataSentinel(unit_test::k_CMakeExecutable, unit_test::k_TestFilesDir, "remove_flagged_triangles_test.tar.gz", "remove_flagged_triangles_test.dream3d"); + const UnitTest::TestFileSentinel testDataSentinel(unit_test::k_CMakeExecutable, unit_test::k_TestFilesDir, "remove_flagged_elements_data.tar.gz", "remove_flagged_elements_data"); // Load DataStructure containing the base geometry and an exemplar cleaned geometry DataStructure dataStructure = UnitTest::LoadDataStructure(k_BaseDataFilePath); + { // Instantiate the filter and an Arguments Object RemoveFlaggedTrianglesFilter filter; @@ -43,23 +42,43 @@ TEST_CASE("SimplnxCore::RemoveFlaggedTrianglesFilter: Valid Filter Execution", " args.insertOrAssign(RemoveFlaggedTrianglesFilter::k_MaskArrayPath_Key, std::make_any(::k_MaskPath)); args.insertOrAssign(RemoveFlaggedTrianglesFilter::k_CreatedTriangleGeometryPath_Key, std::make_any(::k_ReducedGeomPath)); + args.insertOrAssign(RemoveFlaggedTrianglesFilter::k_TriangleDataHandling_Key, std::make_any(0ULL)); + args.insertOrAssign(RemoveFlaggedTrianglesFilter::k_TriangleDataSelectedArrays_Key, std::make_any>(std::vector{::k_FaceNormalsPath, ::k_RegionIdsPath})); + // 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); + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(::k_RegionIdsName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(::k_RegionIdsName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(::k_FaceNormalsName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Face_Data).createChildPath(::k_FaceNormalsName); - UnitTest::CompareDataArrays(dataStructure.getDataRefAs(::k_TriangleListPath), exemplarDataStructure.getDataRefAs(::k_TriangleListPath)); - UnitTest::CompareDataArrays(dataStructure.getDataRefAs(::k_VertexListPath), exemplarDataStructure.getDataRefAs(::k_VertexListPath)); + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(::k_VertexListName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(::k_VertexListName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(::k_TriangleListName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(::k_TriangleListName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } } diff --git a/src/Plugins/SimplnxCore/test/RemoveFlaggedVerticesTest.cpp b/src/Plugins/SimplnxCore/test/RemoveFlaggedVerticesTest.cpp index c62e03452f..45adf089bd 100644 --- a/src/Plugins/SimplnxCore/test/RemoveFlaggedVerticesTest.cpp +++ b/src/Plugins/SimplnxCore/test/RemoveFlaggedVerticesTest.cpp @@ -3,12 +3,27 @@ #include "simplnx/DataStructure/AttributeMatrix.hpp" #include "simplnx/UnitTest/UnitTestCommon.hpp" -#include "simplnx/Utilities/Parsing/HDF5/Writers/FileWriter.hpp" #include using namespace nx::core; +namespace +{ +fs::path k_BaseDataFilePath = fs::path(fmt::format("{}/remove_flagged_elements_data/remove_flagged_vertices_data.dream3d", nx::core::unit_test::k_TestFilesDir)); + +constexpr StringLiteral k_CopyTestName = "copy_test"; +constexpr StringLiteral k_DataName = "data"; +constexpr StringLiteral k_VertexListName = "SharedVertexList"; + +const DataPath k_VertexGeomPath({"VertexGeometry"}); +const DataPath k_MaskPath = k_VertexGeomPath.createChildPath(Constants::k_Vertex_Data).createChildPath(Constants::k_Mask); +const DataPath k_ReducedGeomPath({"ReducedGeometry"}); +const DataPath k_ExemplarReducedGeomPath({"ExemplarReducedGeometry"}); + +const DataPath k_VertexListPath = k_ReducedGeomPath.createChildPath("SharedVertexList"); +} // namespace + TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Instantiate", "[SimplnxCore][RemoveFlaggedVerticesFilter]") { RemoveFlaggedVerticesFilter filter; @@ -24,7 +39,7 @@ TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Instantiate", "[SimplnxCore REQUIRE(preflightResult.outputActions.invalid()); } -TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Test Algorithm", "[SimplnxCore][RemoveFlaggedVerticesFilter]") +TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: From Scratch", "[SimplnxCore][RemoveFlaggedVerticesFilter]") { RemoveFlaggedVerticesFilter filter; DataStructure dataStructure; @@ -84,10 +99,10 @@ TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Test Algorithm", "[SimplnxC size_t reducedTupleCount = reducedVertexGeom->getNumberOfVertices(); REQUIRE(reducedTupleCount == 75); - Int32Array& reducedFeatureIds = dataStructure.getDataRefAs(reducedVertexAMPath.createChildPath(Constants::k_FeatureIds)); + auto& reducedFeatureIds = dataStructure.getDataRefAs(reducedVertexAMPath.createChildPath(Constants::k_FeatureIds)); REQUIRE((reducedFeatureIds.getNumberOfTuples() == 75)); - Int32Array& reducedSlipVectors = dataStructure.getDataRefAs(reducedVertexAMPath.createChildPath(Constants::k_SlipVector)); + auto& reducedSlipVectors = dataStructure.getDataRefAs(reducedVertexAMPath.createChildPath(Constants::k_SlipVector)); REQUIRE((reducedSlipVectors.getNumberOfTuples() == 75)); for(size_t i = 0; i < reducedTupleCount; i++) @@ -110,3 +125,51 @@ TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Test Algorithm", "[SimplnxC auto resultH5 = HDF5::DataStructureWriter::WriteFile(dataStructure, fileWriter); SIMPLNX_RESULT_REQUIRE_VALID(resultH5); } + +TEST_CASE("SimplnxCore::RemoveFlaggedVerticesFilter: Test Algorithm", "[SimplnxCore][RemoveFlaggedVerticesFilter]") +{ + const UnitTest::TestFileSentinel testDataSentinel(unit_test::k_CMakeExecutable, unit_test::k_TestFilesDir, "remove_flagged_elements_data.tar.gz", "remove_flagged_elements_data"); + + // Load DataStructure containing the base geometry and an exemplar cleaned geometry + DataStructure dataStructure = UnitTest::LoadDataStructure(k_BaseDataFilePath); + + { + // Instantiate the filter and an Arguments Object + RemoveFlaggedVerticesFilter filter; + Arguments args; + + // Create default Parameters for the filter. + args.insertOrAssign(RemoveFlaggedVerticesFilter::k_SelectedVertexGeometryPath_Key, std::make_any(::k_VertexGeomPath)); + args.insertOrAssign(RemoveFlaggedVerticesFilter::k_InputMaskPath_Key, std::make_any(::k_MaskPath)); + args.insertOrAssign(RemoveFlaggedVerticesFilter::k_CreatedVertexGeometryPath_Key, std::make_any(::k_ReducedGeomPath)); + + // Preflight the filter and check result + auto preflightResult = filter.preflight(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(preflightResult.outputActions); + + // Execute the filter and check the result + auto executeResult = filter.execute(dataStructure, args); + SIMPLNX_RESULT_REQUIRE_VALID(executeResult.result); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Vertex_Data).createChildPath(::k_DataName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Vertex_Data).createChildPath(::k_DataName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(Constants::k_Vertex_Data).createChildPath(::k_CopyTestName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(Constants::k_Vertex_Data).createChildPath(::k_CopyTestName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } + + { + DataPath generated = ::k_ReducedGeomPath.createChildPath(::k_VertexListName); + DataPath exemplar = ::k_ExemplarReducedGeomPath.createChildPath(::k_VertexListName); + + UnitTest::CompareDataArrays(dataStructure.getDataRefAs(generated), dataStructure.getDataRefAs(exemplar)); + } +} diff --git a/src/simplnx/Utilities/DataArrayUtilities.cpp b/src/simplnx/Utilities/DataArrayUtilities.cpp index dfabfe6f33..74345055e9 100644 --- a/src/simplnx/Utilities/DataArrayUtilities.cpp +++ b/src/simplnx/Utilities/DataArrayUtilities.cpp @@ -24,8 +24,8 @@ struct InitializeNeighborListFunctor template void operator()(INeighborList* iNeighborList) { - auto* neighborList = dynamic_cast*>(iNeighborList); - neighborList->setList(neighborList->getNumberOfTuples() - 1, typename NeighborList::SharedVectorType(new typename NeighborList::VectorType)); + auto* neighborListPtr = dynamic_cast*>(iNeighborList); + neighborListPtr->setList(neighborListPtr->getNumberOfTuples() - 1, typename NeighborList::SharedVectorType(new typename NeighborList::VectorType)); } }; } // namespace @@ -124,8 +124,8 @@ bool CheckArraysAreSameType(const DataStructure& dataStructure, const std::vecto std::set types; for(const auto& dataPath : dataArrayPaths) { - const auto* dataArray = dataStructure.getDataAs(dataPath); - types.insert(dataArray->getDataType()); + const auto* dataArrayPtr = dataStructure.getDataAs(dataPath); + types.insert(dataArrayPtr->getDataType()); } return types.size() == 1; } @@ -153,11 +153,11 @@ bool CheckMemoryRequirement(DataStructure& dataStructure, uint64 requiredMemory, return true; } - Preferences* preferences = Application::GetOrCreateInstance()->getPreferences(); + Preferences* preferencesPtr = Application::GetOrCreateInstance()->getPreferences(); const uint64 memoryUsage = dataStructure.memoryUsage() + requiredMemory; - const uint64 largeDataStructureSize = preferences->largeDataStructureSize(); - std::string largeDataFormat = preferences->largeDataFormat(); + const uint64 largeDataStructureSize = preferencesPtr->largeDataStructureSize(); + std::string largeDataFormat = preferencesPtr->largeDataFormat(); if(memoryUsage >= largeDataStructureSize) { @@ -184,51 +184,51 @@ Result<> ConditionalReplaceValueInArray(const std::string& valueAsStr, DataObjec //----------------------------------------------------------------------------- Result<> ResizeAndReplaceDataArray(DataStructure& dataStructure, const DataPath& dataPath, std::vector& tupleShape, IDataAction::Mode mode) { - auto* inputDataArray = dataStructure.getDataAs(dataPath); + auto* inputDataArrayPtr = dataStructure.getDataAs(dataPath); - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } - if(TemplateHelpers::CanDynamicCast()(inputDataArray)) + if(TemplateHelpers::CanDynamicCast()(inputDataArrayPtr)) { - return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArray); + return ReplaceArray(dataStructure, dataPath, tupleShape, mode, *inputDataArrayPtr); } return MakeErrorResult(-401, fmt::format("The input array at DataPath '{}' was of an unsupported type", dataPath.toString())); @@ -237,13 +237,13 @@ Result<> ResizeAndReplaceDataArray(DataStructure& dataStructure, const DataPath& //----------------------------------------------------------------------------- Result<> ValidateNumFeaturesInArray(const DataStructure& dataStructure, const DataPath& arrayPath, const Int32Array& featureIds) { - const auto* featureArray = dataStructure.getDataAs(arrayPath); - if(featureArray == nullptr) + const auto* featureArrayPtr = dataStructure.getDataAs(arrayPath); + if(featureArrayPtr == nullptr) { return MakeErrorResult(-5550, fmt::format("Could not find the input array path '{}' for validating number of features", arrayPath.toString())); } Result<> results = {}; - const usize numFeatures = featureArray->getNumberOfTuples(); + const usize numFeatures = featureArrayPtr->getNumberOfTuples(); for(const int32& featureId : featureIds) { @@ -267,8 +267,8 @@ Result<> ValidateNumFeaturesInArray(const DataStructure& dataStructure, const Da //----------------------------------------------------------------------------- void InitializeNeighborList(DataStructure& dataStructure, const DataPath& neighborListPath) { - auto* neighborList = dataStructure.getDataAs(neighborListPath); - ExecuteNeighborFunction(InitializeNeighborListFunctor{}, neighborList->getDataType(), neighborList); + auto* neighborListPtr = dataStructure.getDataAs(neighborListPath); + ExecuteNeighborFunction(InitializeNeighborListFunctor{}, neighborListPtr->getDataType(), neighborListPtr); } //----------------------------------------------------------------------------- @@ -327,4 +327,31 @@ bool ConvertIDataArray(const std::shared_ptr& dataArray, const std:: return false; } } + +namespace TransferGeometryElementData +{ +void transferElementData(DataStructure& m_DataStructure, AttributeMatrix& destCellDataAM, const std::vector& sourceDataPaths, const std::vector& newEdgesIndexList, + const std::atomic_bool& m_ShouldCancel, const IFilter::MessageHandler& m_MessageHandler) +{ + // 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; + for(const auto& edgeDataArrayPath : sourceDataPaths) + { + if(m_ShouldCancel) + { + return; + } + + const auto& oldDataArray = m_DataStructure.getDataRefAs(edgeDataArrayPath); + const std::string srcName = oldDataArray.getName(); + + auto& newDataArray = dynamic_cast(destCellDataAM.at(srcName)); + m_MessageHandler(fmt::format("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. +} +} // namespace TransferGeometryElementData + } // namespace nx::core diff --git a/src/simplnx/Utilities/DataArrayUtilities.hpp b/src/simplnx/Utilities/DataArrayUtilities.hpp index d128c909b7..a490c59942 100644 --- a/src/simplnx/Utilities/DataArrayUtilities.hpp +++ b/src/simplnx/Utilities/DataArrayUtilities.hpp @@ -11,9 +11,12 @@ #include "simplnx/DataStructure/IO/Generic/DataIOCollection.hpp" #include "simplnx/DataStructure/NeighborList.hpp" #include "simplnx/DataStructure/StringArray.hpp" +#include "simplnx/Filter/Actions/CreateArrayAction.hpp" #include "simplnx/Filter/Output.hpp" +#include "simplnx/Parameters/MultiArraySelectionParameter.hpp" #include "simplnx/Utilities/MemoryUtilities.hpp" #include "simplnx/Utilities/ParallelAlgorithmUtilities.hpp" +#include "simplnx/Utilities/ParallelTaskAlgorithm.hpp" #include "simplnx/Utilities/TemplateHelpers.hpp" #include "simplnx/simplnx_export.hpp" @@ -258,7 +261,6 @@ SIMPLNX_EXPORT Result<> CheckValueConvertsToArrayType(const std::string& value, template void ReplaceValue(DataArray& inputArrayPtr, const DataArray* condArrayPtr, T replaceValue, bool invertMask = false) { - T replaceVal = static_cast(replaceValue); usize numTuples = inputArrayPtr.getNumberOfTuples(); const DataArray& conditionalArray = *condArrayPtr; @@ -365,10 +367,10 @@ std::shared_ptr> CreateDataStore(const typename IDataStore: } case IDataAction::Mode::Execute: { uint64 dataSize = CalculateDataSize(tupleShape, componentShape); - auto* preferences = Application::GetOrCreateInstance()->getPreferences(); - if(preferences->forceOocData()) + auto* preferencesPtr = Application::GetOrCreateInstance()->getPreferences(); + if(preferencesPtr->forceOocData()) { - dataFormat = preferences->largeDataFormat(); + dataFormat = preferencesPtr->largeDataFormat(); } auto ioCollection = Application::GetOrCreateInstance()->getIOCollection(); ioCollection->checkStoreDataFormat(dataSize, dataFormat); @@ -399,16 +401,16 @@ Result<> CreateArray(DataStructure& dataStructure, const std::vector& tup std::optional dataObjectId; - DataObject* parentObject = nullptr; + DataObject* parentObjectPtr = nullptr; if(parentPath.getLength() != 0) { - parentObject = dataStructure.getData(parentPath); - if(parentObject == nullptr) + parentObjectPtr = dataStructure.getData(parentPath); + if(parentObjectPtr == nullptr) { return MakeErrorResult(-260, fmt::format("CreateArray: Parent object '{}' does not exist", parentPath.toString())); } - dataObjectId = parentObject->getId(); + dataObjectId = parentObjectPtr->getId(); } if(tupleShape.empty()) @@ -450,15 +452,15 @@ Result<> CreateArray(DataStructure& dataStructure, const std::vector& tup return MakeErrorResult(-264, fmt::format("CreateArray: Cannot create Data Array at path '{}' because it already exists. Choose a different name.", path.toString())); } - if(parentObject->getDataObjectType() == DataObject::Type::AttributeMatrix) + if(parentObjectPtr->getDataObjectType() == DataObject::Type::AttributeMatrix) { - auto* attrMatrix = dynamic_cast(parentObject); - std::string amShape = fmt::format("Attribute Matrix Tuple Dims: {}", fmt::join(attrMatrix->getShape(), " x ")); + auto* attrMatrixPtr = dynamic_cast(parentObjectPtr); + std::string amShape = fmt::format("Attribute Matrix Tuple Dims: {}", fmt::join(attrMatrixPtr->getShape(), " x ")); std::string arrayShape = fmt::format("Data Array Tuple Shape: {}", fmt::join(store->getTupleShape(), " x ")); return MakeErrorResult(-265, fmt::format("CreateArray: Unable to create Data Array '{}' inside Attribute matrix '{}'. Mismatch of tuple dimensions. The created Data Array must have the same tuple " "dimensions or the same total number of tuples.\n{}\n{}", - name, dataStructure.getDataPathsForId(parentObject->getId()).front().toString(), amShape, arrayShape)); + name, dataStructure.getDataPathsForId(parentObjectPtr->getId()).front().toString(), amShape, arrayShape)); } else { @@ -527,13 +529,13 @@ Result<> CreateNeighbors(DataStructure& dataStructure, usize numTuples, const Da if(parentPath.getLength() != 0) { - auto* parentObject = dataStructure.getData(parentPath); - if(parentObject == nullptr) + auto* parentObjectPtr = dataStructure.getData(parentPath); + if(parentObjectPtr == nullptr) { return MakeErrorResult(-5801, fmt::format("{}Parent object \"{}\" does not exist", prefix, parentPath.toString())); } - dataObjectId = parentObject->getId(); + dataObjectId = parentObjectPtr->getId(); } const usize last = path.getLength() - 1; @@ -560,12 +562,12 @@ template DataArray* ArrayFromPath(DataStructure& dataStructure, const DataPath& path) { using DataArrayType = DataArray; - DataObject* object = dataStructure.getData(path); - if(object == nullptr) + DataObject* objectPtr = dataStructure.getData(path); + if(objectPtr == nullptr) { throw std::runtime_error(fmt::format("DataArray does not exist at DataPath: '{}'", path.toString())); } - auto* dataArray = dynamic_cast(object); + auto* dataArray = dynamic_cast(objectPtr); if(dataArray == nullptr) { throw std::runtime_error(fmt::format("DataPath does not point to a DataArray. DataPath: '{}'", path.toString())); @@ -583,13 +585,13 @@ DataArray* ArrayFromPath(DataStructure& dataStructure, const DataPath& path) template DataArray& ArrayRefFromPath(DataStructure& dataStructure, const DataPath& path) { - DataObject* object = dataStructure.getData(path); - auto* dataArray = dynamic_cast*>(object); - if(dataArray == nullptr) + DataObject* objectPtr = dataStructure.getData(path); + auto* dataArrayPtr = dynamic_cast*>(objectPtr); + if(dataArrayPtr == nullptr) { throw std::runtime_error("Can't obtain DataArray"); } - return *dataArray; + return *dataArrayPtr; } /** @@ -604,8 +606,8 @@ DataArray& ArrayRefFromPath(DataStructure& dataStructure, const DataPath& pat template Result<> ImportFromBinaryFile(const fs::path& binaryFilePath, DataArray& outputDataArray, usize startByte = 0, usize defaultBufferSize = 1000000) { - FILE* inputFile = std::fopen(binaryFilePath.string().c_str(), "rb"); - if(inputFile == nullptr) + FILE* inputFilePtr = std::fopen(binaryFilePath.string().c_str(), "rb"); + if(inputFilePtr == nullptr) { return MakeErrorResult(-1000, fmt::format("Unable to open the specified file. '{}'", binaryFilePath.string())); } @@ -613,7 +615,7 @@ Result<> ImportFromBinaryFile(const fs::path& binaryFilePath, DataArray& outp // Skip some bytes if needed if(startByte > 0) { - FSEEK64(inputFile, static_cast(startByte), SEEK_SET); + FSEEK64(inputFilePtr, static_cast(startByte), SEEK_SET); } const usize numElements = outputDataArray.getSize(); @@ -624,14 +626,14 @@ Result<> ImportFromBinaryFile(const fs::path& binaryFilePath, DataArray& outp usize elementCounter = 0; while(elementCounter < numElements) { - usize elements_read = std::fread(buffer.data(), sizeof(T), chunkSize, inputFile); + usize elementsRead = std::fread(buffer.data(), sizeof(T), chunkSize, inputFilePtr); - for(usize i = 0; i < elements_read; i++) + for(usize i = 0; i < elementsRead; i++) { outputDataArray[i + elementCounter] = buffer[i]; } - elementCounter += elements_read; + elementCounter += elementsRead; usize elementsLeft = numElements - elementCounter; @@ -641,7 +643,7 @@ Result<> ImportFromBinaryFile(const fs::path& binaryFilePath, DataArray& outp } } - std::fclose(inputFile); + std::fclose(inputFilePtr); return {}; } @@ -667,28 +669,28 @@ DataArray* ImportFromBinaryFile(const std::string& filename, const std::strin if(!fs::exists(filename)) { - std::cout << "File Does Not Exist:'" << filename << "'" << std::endl; + std::cout << "File Does Not Exist:'" << filename << "'\n"; return nullptr; } std::shared_ptr dataStore = std::shared_ptr(new DataStoreType({tupleShape}, componentShape, static_cast(0))); - ArrayType* dataArray = ArrayType::Create(dataStructure, name, dataStore, parentId); + ArrayType* dataArrayPtr = ArrayType::Create(dataStructure, name, dataStore, parentId); const usize fileSize = fs::file_size(filename); - const usize numBytesToRead = dataArray->getSize() * sizeof(T); + const usize numBytesToRead = dataArrayPtr->getSize() * sizeof(T); if(numBytesToRead != fileSize) { - std::cout << "FileSize '" << fileSize << "' and Allocated Size '" << numBytesToRead << "' do not match" << std::endl; + std::cout << "FileSize '" << fileSize << "' and Allocated Size '" << numBytesToRead << "' do not match\n"; return nullptr; } - Result<> result = ImportFromBinaryFile(fs::path(filename), *dataArray); + Result<> result = ImportFromBinaryFile(fs::path(filename), *dataArrayPtr); if(result.invalid()) { return nullptr; } - return dataArray; + return dataArrayPtr; } /** @@ -772,12 +774,12 @@ SIMPLNX_EXPORT Result<> ValidateNumFeaturesInArray(const DataStructure& dataStru template Result<> ResizeDataArray(DataStructure& dataStructure, const DataPath& arrayPath, const std::vector& newShape) { - auto* dataArray = dataStructure.getDataAs>(arrayPath); - if(dataArray == nullptr) + auto* dataArrayPtr = dataStructure.getDataAs>(arrayPath); + if(dataArrayPtr == nullptr) { return MakeErrorResult(-4830, fmt::format("Could not find array path '{}' in the given data structure", arrayPath.toString())); } - if(dataArray->getTupleShape() == newShape) // array does not need to be reshaped + if(dataArrayPtr->getTupleShape() == newShape) // array does not need to be reshaped { return {}; } @@ -789,7 +791,7 @@ Result<> ResizeDataArray(DataStructure& dataStructure, const DataPath& arrayPath } // the array's parent is not in an Attribute Matrix so we can safely reshape to the new tuple shape - dataArray->template getIDataStoreRefAs>().resizeTuples(newShape); + dataArrayPtr->template getIDataStoreRefAs>().resizeTuples(newShape); return {}; } @@ -988,7 +990,7 @@ class CopyTupleUsingIndexList { if(newDataStore.copyFrom(i, oldDataStore, oldIndexI, 1).invalid()) { - std::cout << fmt::format("Array copy failed: Source Array Name: {} Source Tuple Index: {}\nDest Array Name: {} Dest. Tuple Index {}\n", m_OldCellArray.getName(), oldIndexI, i) << std::endl; + std::cout << fmt::format("Array copy failed: Source Array Name: {} Source Tuple Index: {}\nDest Array Name: {} Dest. Tuple Index {}\n\n", m_OldCellArray.getName(), oldIndexI, i); break; } } @@ -1103,19 +1105,19 @@ class AppendArray const usize offset = m_TupleOffset * m_DestCellArray->getNumberOfComponents(); if(m_ArrayType == IArray::ArrayType::NeighborListArray) { - using NeighborListT = NeighborList; - auto* destArray = dynamic_cast(m_DestCellArray); + using NeighborListType = NeighborList; + auto* destArrayPtr = dynamic_cast(m_DestCellArray); // Make sure the destination array is allocated AND each tuple list is initialized so we can use the [] operator to copy over the data - if(destArray->getValues().empty() || destArray->getList(0) == nullptr) + if(destArrayPtr->getValues().empty() || destArrayPtr->getList(0) == nullptr) { - destArray->addEntry(destArray->getNumberOfTuples() - 1, 0); + destArrayPtr->addEntry(destArrayPtr->getNumberOfTuples() - 1, 0); } - AppendData(*dynamic_cast(m_InputCellArray), *destArray, offset); + AppendData(*dynamic_cast(m_InputCellArray), *destArrayPtr, offset); } if(m_ArrayType == IArray::ArrayType::DataArray) { - using DataArrayT = DataArray; - AppendData(*dynamic_cast(m_InputCellArray), *dynamic_cast(m_DestCellArray), offset); + using DataArrayType = DataArray; + AppendData(*dynamic_cast(m_InputCellArray), *dynamic_cast(m_DestCellArray), offset); } if(m_ArrayType == IArray::ArrayType::StringArray) { @@ -1169,9 +1171,9 @@ class CombineArrays } if(m_ArrayType == IArray::ArrayType::DataArray) { - using DataArrayT = DataArray; - AppendData(*dynamic_cast(m_InputCellArray1), *dynamic_cast(m_DestCellArray), 0); - AppendData(*dynamic_cast(m_InputCellArray2), *dynamic_cast(m_DestCellArray), m_InputCellArray1->getSize()); + using DataArrayType = DataArray; + AppendData(*dynamic_cast(m_InputCellArray1), *dynamic_cast(m_DestCellArray), 0); + AppendData(*dynamic_cast(m_InputCellArray2), *dynamic_cast(m_DestCellArray), m_InputCellArray1->getSize()); } if(m_ArrayType == IArray::ArrayType::StringArray) { @@ -1231,11 +1233,11 @@ class CopyUsingIndexList } else if(m_ArrayType == IArray::ArrayType::DataArray) { - using DataArrayT = DataArray; - auto* destArray = dynamic_cast(m_DestCellArray); + using DataArrayType = DataArray; + auto* destArray = dynamic_cast(m_DestCellArray); if(oldIndexI >= 0) { - copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArray, i, oldIndexI, 1); + copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArray, i, oldIndexI, 1); } else { @@ -1359,21 +1361,21 @@ class MapRectGridDataToImageData if(m_ArrayType == IArray::ArrayType::NeighborListArray) { using NeighborListT = NeighborList; - auto* destArray = dynamic_cast(m_DestCellArray); + auto* destArrayPtr = dynamic_cast(m_DestCellArray); // Make sure the destination array is allocated AND each tuple list is initialized so we can use the [] operator to copy over the data - destArray->setList(imageIndex, typename NeighborListT::SharedVectorType(new typename NeighborListT::VectorType)); + destArrayPtr->setList(imageIndex, typename NeighborListT::SharedVectorType(new typename NeighborListT::VectorType)); if(rectGridIndex >= 0) { - copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArray, imageIndex, rectGridIndex, 1); + copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArrayPtr, imageIndex, rectGridIndex, 1); } } else if(m_ArrayType == IArray::ArrayType::DataArray) { - using DataArrayT = DataArray; - auto* destArray = dynamic_cast(m_DestCellArray); + using DataArrayType = DataArray; + auto* destArray = dynamic_cast(m_DestCellArray); if(rectGridIndex >= 0) { - copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArray, imageIndex, rectGridIndex, 1); + copySucceeded = CopyData(*dynamic_cast(m_InputCellArray), *destArray, imageIndex, rectGridIndex, 1); } else { @@ -1394,9 +1396,8 @@ class MapRectGridDataToImageData } if(!copySucceeded) { - std::cout << fmt::format("Array copy failed: Source Array Name: {} Source Tuple Index: {}\nDest Array Name: {} Dest. Tuple Index {}\n", m_InputCellArray->getName(), rectGridIndex, - imageIndex) - << std::endl; + std::cout << fmt::format("Array copy failed: Source Array Name: {} Source Tuple Index: {}\nDest Array Name: {} Dest. Tuple Index {}\n\n", m_InputCellArray->getName(), rectGridIndex, + imageIndex); break; } @@ -1426,9 +1427,9 @@ class MapRectGridDataToImageData */ inline void RunAppendBoolAppend(IArray& destCellArray, const IArray& inputCellArray, usize tupleOffset) { - using DataArrayT = DataArray; + using DataArrayType = DataArray; const usize offset = tupleOffset * destCellArray.getNumberOfComponents(); - AppendData(*dynamic_cast(&inputCellArray), *dynamic_cast(&destCellArray), offset); + AppendData(*dynamic_cast(&inputCellArray), *dynamic_cast(&destCellArray), offset); } /** @@ -1437,9 +1438,9 @@ inline void RunAppendBoolAppend(IArray& destCellArray, const IArray& inputCellAr */ inline void RunCombineBoolAppend(const IArray& inputCellArray1, const IArray& inputCellArray2, IArray& destCellArray) { - using DataArrayT = DataArray; - AppendData(*dynamic_cast(&inputCellArray1), *dynamic_cast(&destCellArray), 0); - AppendData(*dynamic_cast(&inputCellArray2), *dynamic_cast(&destCellArray), inputCellArray1.getSize()); + using DataArrayType = DataArray; + AppendData(*dynamic_cast(&inputCellArray1), *dynamic_cast(&destCellArray), 0); + AppendData(*dynamic_cast(&inputCellArray2), *dynamic_cast(&destCellArray), inputCellArray1.getSize()); } /** @@ -1448,8 +1449,8 @@ inline void RunCombineBoolAppend(const IArray& inputCellArray1, const IArray& in */ inline void RunBoolCopyUsingIndexList(IArray& destCellArray, const IArray& inputCellArray, const nonstd::span& newToOldIndices) { - using DataArrayT = DataArray; - CopyUsingIndexList(*dynamic_cast(&destCellArray), *dynamic_cast(&inputCellArray), newToOldIndices); + using DataArrayType = DataArray; + CopyUsingIndexList(*dynamic_cast(&destCellArray), *dynamic_cast(&inputCellArray), newToOldIndices); } /** @@ -1459,9 +1460,9 @@ inline void RunBoolCopyUsingIndexList(IArray& destCellArray, const IArray& input inline void RunBoolMapRectToImage(IArray& destCellArray, const IArray& inputCellArray, const FloatVec3& origin, const SizeVec3& imageGeoDims, const std::vector& imageGeoSpacing, const SizeVec3& rectGridDims, const Float32Array* xGridValues, const Float32Array* yGridValues, const Float32Array* zGridValues) { - using DataArrayT = DataArray; - MapRectGridDataToImageData(*dynamic_cast(&destCellArray), *dynamic_cast(&inputCellArray), origin, imageGeoDims, imageGeoSpacing, rectGridDims, - xGridValues, yGridValues, zGridValues); + using DataArrayType = DataArray; + MapRectGridDataToImageData(*dynamic_cast(&destCellArray), *dynamic_cast(&inputCellArray), origin, imageGeoDims, imageGeoSpacing, rectGridDims, + xGridValues, yGridValues, zGridValues); } template @@ -1553,4 +1554,85 @@ void RunParallelMapRectToImage(IArray& destArray, ParallelRunnerT&& runner, Args } // namespace CopyFromArray +namespace TransferGeometryElementData +{ + +/** + * @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 + { + 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; +}; + +/** + * + * @param m_DataStructure + * @param destCellDataAM The destination Attribute Matrix + * @param sourceDataPaths The source data array paths that are to be copied + * @param newEdgesIndexList The index mapping + * @param m_ShouldCancel Should the algorithm be canceled + * @param m_MessageHandler The message handler to use for messages. + */ +SIMPLNX_EXPORT void transferElementData(DataStructure& m_DataStructure, AttributeMatrix& destCellDataAM, const std::vector& sourceDataPaths, const std::vector& newEdgesIndexList, + const std::atomic_bool& m_ShouldCancel, const IFilter::MessageHandler& m_MessageHandler); + +template +void createDataArrayActions(const DataStructure& dataStructure, const AttributeMatrix* sourceAttrMatPtr, const MultiArraySelectionParameter::ValueType& selectedArrayPaths, + const DataPath& reducedGeometryPathAttrMatPath, Result& resultOutputActions) +{ + // Now loop over each array in selectedEdgeArrays and create the corresponding arrays + // in the destination geometry's attribute matrix + for(const auto& dataPath : selectedArrayPaths) + { + const auto& srcArray = dataStructure.getDataRefAs(dataPath); + DataType dataType = srcArray.getDataType(); + IDataStore::ShapeType componentShape = srcArray.getIDataStoreRef().getComponentShape(); + DataPath dataArrayPath = reducedGeometryPathAttrMatPath.createChildPath(srcArray.getName()); + resultOutputActions.value().appendAction(std::make_unique(dataType, sourceAttrMatPtr->getShape(), std::move(componentShape), dataArrayPath)); + } +} + +} // namespace TransferGeometryElementData } // namespace nx::core diff --git a/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp b/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp index 47622dd073..57fe21ffac 100644 --- a/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp +++ b/test/UnitTestCommon/include/simplnx/UnitTest/UnitTestCommon.hpp @@ -76,6 +76,8 @@ inline constexpr StringLiteral k_FaceData("FaceData"); inline constexpr StringLiteral k_Face_Data("Face Data"); inline constexpr StringLiteral k_FaceFeatureData("FaceFeatureData"); inline constexpr StringLiteral k_VertexData("VertexData"); +inline constexpr StringLiteral k_Vertex_Data("Vertex Data"); +inline constexpr StringLiteral k_Edge_Data("Edge Data"); inline constexpr StringLiteral k_GBCD_Name("GBCD"); inline constexpr StringLiteral k_Centroids("Centroids");