]> SALOME platform Git repositories - tools/medcoupling.git/commitdiff
Salome HOME
Reference version of Shape Recognition
authorEl Hadi Moussi <moussi@phimeca.com>
Mon, 5 Aug 2024 09:34:26 +0000 (11:34 +0200)
committerAnthony Geay <anthony.geay@edf.fr>
Fri, 30 Aug 2024 12:13:21 +0000 (14:13 +0200)
43 files changed:
CMakeLists.txt
resources/CMakeLists.txt
resources/ShapeRecognCone.med [new file with mode: 0644]
resources/ShapeRecognCylinder.med [new file with mode: 0755]
resources/ShapeRecognPlane.med [new file with mode: 0755]
resources/ShapeRecognSphere.med [new file with mode: 0755]
resources/ShapeRecognTorus.med [new file with mode: 0755]
src/CMakeLists.txt
src/ShapeRecogn/Areas.cxx [new file with mode: 0644]
src/ShapeRecogn/Areas.hxx [new file with mode: 0644]
src/ShapeRecogn/AreasBuilder.cxx [new file with mode: 0644]
src/ShapeRecogn/AreasBuilder.hxx [new file with mode: 0644]
src/ShapeRecogn/CMakeLists.txt [new file with mode: 0644]
src/ShapeRecogn/MathOps.cxx [new file with mode: 0644]
src/ShapeRecogn/MathOps.hxx [new file with mode: 0644]
src/ShapeRecogn/Nodes.cxx [new file with mode: 0644]
src/ShapeRecogn/Nodes.hxx [new file with mode: 0644]
src/ShapeRecogn/NodesBuilder.cxx [new file with mode: 0644]
src/ShapeRecogn/NodesBuilder.hxx [new file with mode: 0644]
src/ShapeRecogn/PrimitiveType.hxx [new file with mode: 0644]
src/ShapeRecogn/README.md [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMesh.cxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMesh.hxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMeshBuilder.cxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecognMeshBuilder.hxx [new file with mode: 0644]
src/ShapeRecogn/ShapeRecongConstants.hxx [new file with mode: 0644]
src/ShapeRecogn/Swig/CMakeLists.txt [new file with mode: 0644]
src/ShapeRecogn/Swig/ShapeRecogn.i [new file with mode: 0644]
src/ShapeRecogn/Test/CMakeLists.txt [new file with mode: 0644]
src/ShapeRecogn/Test/CTestTestfileInstall.cmake [new file with mode: 0644]
src/ShapeRecogn/Test/ConeTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/ConeTest.hxx [new file with mode: 0644]
src/ShapeRecogn/Test/CylinderTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/CylinderTest.hxx [new file with mode: 0644]
src/ShapeRecogn/Test/MathOpsTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/MathOpsTest.hxx [new file with mode: 0644]
src/ShapeRecogn/Test/PlaneTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/PlaneTest.hxx [new file with mode: 0644]
src/ShapeRecogn/Test/SphereTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/SphereTest.hxx [new file with mode: 0644]
src/ShapeRecogn/Test/TestShapeRecogn.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/TorusTest.cxx [new file with mode: 0644]
src/ShapeRecogn/Test/TorusTest.hxx [new file with mode: 0644]

index 840bac26a2b47fdd6e418b7d6f276d73ab192f5a..c080f174a5dd9163e6afe3ed4cdd8bd50c3a1796 100644 (file)
@@ -88,6 +88,7 @@ OPTION(MEDCOUPLING_MICROMED "Build MED without MED file dependency." OFF)
 OPTION(MEDCOUPLING_ENABLE_PYTHON "Build PYTHON bindings." ON)
 OPTION(MEDCOUPLING_ENABLE_PARTITIONER "Build MEDPartitioner." ON)
 OPTION(MEDCOUPLING_ENABLE_RENUMBER "Build Renumber." ON)
+OPTION(MEDCOUPLING_ENABLE_SHAPERECOGN "Build ShapeRecogn" OFF)
 OPTION(MEDCOUPLING_WITH_FILE_EXAMPLES "Install examples of files containing meshes and fields of different formats." ON)
 OPTION(MEDCOUPLING_USE_MPI "(Use MPI containers) - For MED this triggers the build of ParaMEDMEM." OFF)
 OPTION(MEDCOUPLING_BUILD_TESTS "Build MEDCoupling C++ tests." ON)
@@ -201,6 +202,21 @@ IF(MEDCOUPLING_ENABLE_RENUMBER)
   SALOME_LOG_OPTIONAL_PACKAGE(Boost MEDCOUPLING_ENABLE_RENUMBER)
 ENDIF(MEDCOUPLING_ENABLE_RENUMBER)
 
+IF(MEDCOUPLING_ENABLE_SHAPERECOGN)
+  FIND_PACKAGE(BLAS REQUIRED)
+  FIND_PACKAGE(LAPACK REQUIRED)
+  FIND_LIBRARY(LAPACKE_LIB NAMES lapacke REQUIRED)
+  SET(LAPACK_LIBRARIES ${LAPACKE_LIB} ${LAPACK_LIBRARIES} ${BLAS_LIBRARIES})
+  FIND_PATH(LAPACKE_INCLUDE_DIRS NAMES lapacke.h HINTS ${LAPACK_LIBRARIES})
+  IF(LAPACK_FOUND)
+    MESSAGE(STATUS "Lapacke libraries: ${LAPACK_LIBRARIES}")
+    MESSAGE(STATUS "Lapacke include dirs: ${LAPACKE_INCLUDE_DIRS}")
+  ELSE()
+    MESSAGE(FATAL_ERROR "Error in Lapacke detection ! lapacke not found !") 
+  ENDIF(LAPACK_FOUND)
+  SALOME_LOG_OPTIONAL_PACKAGE(Lapack MEDCOUPLING_ENABLE_SHAPERECOGN)
+ENDIF(MEDCOUPLING_ENABLE_SHAPERECOGN)
+
 IF(MEDCOUPLING_ENABLE_PYTHON)
   FIND_PACKAGE(SalomePythonInterp)
   FIND_PACKAGE(SalomePythonLibs)
@@ -339,6 +355,10 @@ IF(MEDCOUPLING_USE_MPI)
     ENDIF()
   ENDIF()
 ENDIF()
+  
+IF(MEDCOUPLING_ENABLE_SHAPERECOGN)
+  LIST(APPEND _${PROJECT_NAME}_exposed_targets shaperecogn)
+ENDIF(MEDCOUPLING_ENABLE_SHAPERECOGN)
 
 # Add all targets to the build-tree export set
 
index 704221ca6974f8a04d6e439aa3e7c98c6f9816b2..cf6ec248139e68536fa9e8e788c500e482bea9e0 100644 (file)
@@ -94,6 +94,11 @@ SET(MED_other_FILES
   test_MED_MAIL.sauv
   castem17_result_xdr.sauv
   castem17_result_ascii.sauv
+  ShapeRecognPlane.med
+  ShapeRecognCylinder.med
+  ShapeRecognCone.med
+  ShapeRecognSphere.med
+  ShapeRecognTorus.med
   )
 
 SET(MED_RESOURCES_FILES ${MED_test_fig_files})
diff --git a/resources/ShapeRecognCone.med b/resources/ShapeRecognCone.med
new file mode 100644 (file)
index 0000000..da32d3e
Binary files /dev/null and b/resources/ShapeRecognCone.med differ
diff --git a/resources/ShapeRecognCylinder.med b/resources/ShapeRecognCylinder.med
new file mode 100755 (executable)
index 0000000..f028da1
Binary files /dev/null and b/resources/ShapeRecognCylinder.med differ
diff --git a/resources/ShapeRecognPlane.med b/resources/ShapeRecognPlane.med
new file mode 100755 (executable)
index 0000000..c06ecfb
Binary files /dev/null and b/resources/ShapeRecognPlane.med differ
diff --git a/resources/ShapeRecognSphere.med b/resources/ShapeRecognSphere.med
new file mode 100755 (executable)
index 0000000..4d7e700
Binary files /dev/null and b/resources/ShapeRecognSphere.med differ
diff --git a/resources/ShapeRecognTorus.med b/resources/ShapeRecognTorus.med
new file mode 100755 (executable)
index 0000000..7623db5
Binary files /dev/null and b/resources/ShapeRecognTorus.med differ
index 14229c770a3a8b09212ed2dcd6f232d3a5146c6e..e4d70409cf116a871c2cede9001e84d4f41f2b64 100644 (file)
@@ -82,6 +82,10 @@ IF(MEDCOUPLING_USE_MPI)
   ENDIF(MEDCOUPLING_BUILD_TESTS)
 ENDIF(MEDCOUPLING_USE_MPI)
 
+IF(MEDCOUPLING_ENABLE_SHAPERECOGN)
+  ADD_SUBDIRECTORY(ShapeRecogn)
+ENDIF(MEDCOUPLING_ENABLE_SHAPERECOGN)
+
 # Application tests
 configure_file(CTestTestfileInstall.cmake.in "CTestTestfileST.cmake" @ONLY)
 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CTestTestfileST.cmake DESTINATION ${MEDCOUPLING_INSTALL_TESTS} RENAME CTestTestfile.cmake)
diff --git a/src/ShapeRecogn/Areas.cxx b/src/ShapeRecogn/Areas.cxx
new file mode 100644 (file)
index 0000000..f8441cb
--- /dev/null
@@ -0,0 +1,485 @@
+#include "Areas.hxx"
+#include "MathOps.hxx"
+
+#include <random>
+#include <cblas.h>
+
+using namespace MEDCoupling;
+
+Areas::Areas(const Nodes *nodes) : nodes(nodes), areaIdByNodes(nodes->getNbNodes(), -1)
+{
+}
+
+mcIdType Areas::addArea(PrimitiveType primitive)
+{
+    Area area;
+    area.primitive = primitive;
+    areas.push_back(area);
+    return areas.size() - 1;
+}
+
+void Areas::cleanArea(mcIdType areaId)
+{
+    cleanArea(areaId, -1);
+}
+
+void Areas::cancelArea(mcIdType areaId, PrimitiveType primitive)
+{
+    cleanArea(areaId, -2);
+    Area &area = areas[areaId];
+    area.primitive = primitive;
+}
+
+void Areas::removeArea(mcIdType areaId)
+{
+    for (size_t nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+    {
+        if (areaIdByNodes[nodeId] == areaId)
+            areaIdByNodes[nodeId] = -1;
+        else if (areaIdByNodes[nodeId] > areaId)
+            areaIdByNodes[nodeId] -= 1;
+    }
+    areas.erase(areas.begin() + areaId);
+}
+
+void Areas::addNode(mcIdType areaId, mcIdType nodeId)
+{
+    removeNode(nodeId);
+    areaIdByNodes[nodeId] = areaId;
+    Area &area = areas[areaId];
+    area.nodeIds.push_back(nodeId);
+    size_t nbNodes = area.nodeIds.size();
+    area.k1 = ((double)(nbNodes - 1) * area.k1 + nodes->getK1(nodeId)) / (double)nbNodes;
+    area.k2 = ((double)(nbNodes - 1) * area.k2 + nodes->getK2(nodeId)) / (double)nbNodes;
+    area.adimK1 = ((double)(nbNodes - 1) * area.adimK1 + nodes->getAdimK1(nodeId)) / (double)nbNodes;
+    area.adimK2 = ((double)(nbNodes - 1) * area.adimK2 + nodes->getAdimK2(nodeId)) / (double)nbNodes;
+    area.adimKdiff0 = ((double)(nbNodes - 1) * area.adimKdiff0 + nodes->getAdimKdiff0(nodeId)) / (double)nbNodes;
+}
+
+void Areas::cleanInvalidNodeAreas()
+{
+    for (mcIdType nodeId = 0; nodeId < (mcIdType)areaIdByNodes.size(); ++nodeId)
+    {
+        if (areaIdByNodes[nodeId] <= -2)
+            areaIdByNodes[nodeId] = -1;
+    }
+}
+
+mcIdType Areas::getAreaId(mcIdType nodeId) const
+{
+    return areaIdByNodes[nodeId];
+}
+
+bool Areas::isEmpty(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds.empty();
+}
+
+size_t Areas::getNumberOfAreas() const
+{
+    return areas.size();
+}
+
+size_t Areas::getNumberOfNodes(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds.size();
+}
+
+bool Areas::isNodeCompatible(mcIdType areaId, mcIdType nodeId) const
+{
+    PrimitiveType areaType = getPrimitiveType(areaId);
+    PrimitiveType nodeType = nodes->getPrimitiveType(nodeId);
+    return (
+        (areaType != PrimitiveType::Cylinder ||
+         nodeType != PrimitiveType::Plane) &&
+        (areaType != PrimitiveType::Sphere ||
+         nodeType != PrimitiveType::Plane));
+}
+
+PrimitiveType Areas::getPrimitiveType(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    if (area.primitive != PrimitiveType::Unknown)
+        return area.primitive;
+    else if (area.nodeIds.empty())
+        return PrimitiveType::Unknown;
+    else
+        return nodes->getPrimitiveType(area.nodeIds[0]);
+}
+
+std::string Areas::getPrimitiveTypeName(mcIdType areaId) const
+{
+    return convertPrimitiveToString(getPrimitiveType(areaId));
+}
+
+int Areas::getPrimitiveTypeInt(mcIdType areaId) const
+{
+    return convertPrimitiveToInt(getPrimitiveType(areaId));
+}
+
+const std::vector<mcIdType> &Areas::getNodeIds(mcIdType areaId) const
+{
+    return areas[areaId].nodeIds;
+}
+
+double Areas::getAdimK1(mcIdType areaId) const
+{
+
+    return areas[areaId].adimK1;
+}
+
+double Areas::getAdimK2(mcIdType areaId) const
+{
+    return areas[areaId].adimK2;
+}
+
+double Areas::getAdimKdiff0(mcIdType areaId) const
+{
+    return areas[areaId].adimKdiff0;
+}
+
+void Areas::computeProperties(mcIdType areaId)
+{
+    switch (getPrimitiveType(areaId))
+    {
+    case PrimitiveType::Plane:
+        computePlaneProperties(areaId);
+        break;
+    case PrimitiveType::Sphere:
+        computeSphereProperties(areaId);
+        break;
+    case PrimitiveType::Cylinder:
+        computeCylinderProperties(areaId);
+        break;
+    case PrimitiveType::Cone:
+        computeConeProperties(areaId);
+        break;
+    case PrimitiveType::Torus:
+        computeTorusProperties(areaId);
+        break;
+    case PrimitiveType::Unknown:
+    default:
+        break;
+    }
+}
+
+double Areas::getMinorRadius(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    return area.minorRadius;
+}
+
+double Areas::getRadius(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    return area.radius;
+}
+
+double Areas::getAngle(mcIdType areaId) const
+{
+    return areas[areaId].angle;
+}
+
+const std::array<double, 3> &Areas::getNormal(mcIdType areaId) const
+{
+    return areas[areaId].normal;
+}
+
+const std::array<double, 3> &Areas::getCenter(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    return area.center;
+}
+
+const std::array<double, 3> &Areas::getAxis(mcIdType areaId) const
+{
+    return areas[areaId].axis;
+}
+
+const std::array<double, 3> &Areas::getAxisPoint(mcIdType areaId) const
+{
+    return areas[areaId].axisPoint;
+}
+
+const std::array<double, 3> &Areas::getApex(mcIdType areaId) const
+{
+    return areas[areaId].apex;
+}
+
+std::array<double, 3> Areas::getAffinePoint(mcIdType areaId) const
+{
+    const Area &area = areas[areaId];
+    if (area.nodeIds.empty())
+        return {0.0, 0.0, 0.0};
+    else
+        return nodes->getCoordinates(area.nodeIds[0]);
+}
+
+void Areas::cleanArea(mcIdType areaId, mcIdType newAreaId = -1)
+{
+    Area &area = areas[areaId];
+    for (mcIdType nodeId : area.nodeIds)
+        areaIdByNodes[nodeId] = newAreaId;
+    area.primitive = PrimitiveType::Unknown;
+    area.k1 = 0.0;
+    area.k2 = 0.0;
+    area.adimK1 = 0.0;
+    area.adimK2 = 0.0;
+    area.adimKdiff0 = 0.0;
+    area.minorRadius = 0.0;
+    area.radius = 0.0;
+    area.angle = 0.0;
+    area.normal = {0.0, 0.0, 0.0};
+    area.center = {0.0, 0.0, 0.0};
+    area.axis = {0.0, 0.0, 0.0};
+    area.axisPoint = {0.0, 0.0, 0.0};
+    area.apex = {0.0, 0.0, 0.0};
+    area.nodeIds.clear();
+}
+
+void Areas::removeNode(mcIdType nodeId)
+{
+    mcIdType areaId = areaIdByNodes[nodeId];
+    if (areaId > -1)
+    {
+        Area &area = areas[areaId];
+        area.nodeIds.erase(
+            std::remove(
+                area.nodeIds.begin(),
+                area.nodeIds.end(),
+                nodeId),
+            area.nodeIds.end());
+        areaIdByNodes[nodeId] = -1;
+        // TODO: Update the parameters of the area ?
+    }
+}
+
+void Areas::computePlaneProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    const std::vector<double> &normals = nodes->getNormals();
+    mcIdType nbNodes = area.nodeIds.size();
+    area.normal = {0.0, 0.0, 0.0};
+    for (mcIdType nodeId : area.nodeIds)
+    {
+        for (size_t i = 0; i < 3; ++i)
+            area.normal[i] += normals[3 * nodeId + i];
+    }
+    for (size_t i = 0; i < 3; ++i)
+        area.normal[i] /= (double)nbNodes;
+}
+
+void Areas::computeSphereProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    const std::vector<double> &normals = nodes->getNormals();
+    area.radius = (2 / (area.k1 + area.k2));
+    std::array<double, 3> center{0.0, 0.0, 0.0};
+    if (!area.nodeIds.empty())
+    {
+        size_t nbNodes = area.nodeIds.size();
+        for (mcIdType nodeId : area.nodeIds)
+        {
+            std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+            for (size_t i = 0; i < 3; ++i)
+                center[i] += nodeCoords[i] - area.radius * normals[3 * nodeId + i];
+        }
+        for (size_t i = 0; i < 3; ++i)
+            center[i] /= (double)nbNodes;
+    }
+    area.center = center;
+    area.radius = fabs(area.radius);
+}
+
+void Areas::computeCylinderProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t nbNodes = area.nodeIds.size();
+    // Project the nodes to the central axis of the cylinder
+    std::vector<double> projectedNodes(3 * nbNodes, 0.0);
+    area.radius = 0;
+    area.axisPoint.fill(0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+        double approxRadius = 1.0 / nodes->getKdiff0(nodeId);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            projectedNodes[3 * i + j] =
+                nodeCoords[j] - approxRadius * nodes->getNormals()[3 * nodeId + j];
+            area.axisPoint[j] += projectedNodes[3 * i + j];
+        }
+        area.radius += approxRadius;
+    }
+    // Axis point is the mean of the projected nodes
+    for (size_t i = 0; i < 3; ++i)
+        area.axisPoint[i] /= (double)nbNodes;
+    // Radius of the cylinder is the mean of the approximate radius of each node
+    area.radius = fabs(area.radius / (double)nbNodes);
+    // Compute the axis of the cylinder
+    area.axis = MathOps::computePCAFirstAxis(projectedNodes);
+}
+
+void Areas::computeConeProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t nbNodes = area.nodeIds.size();
+    // Project the nodes to the central axis of the cone
+    std::vector<double> projectedNodes(3 * nbNodes, 0.0);
+    std::vector<double> radiusNodes(nbNodes, 0.0);
+    area.axisPoint.fill(0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        std::array<double, 3> nodeCoords = nodes->getCoordinates(nodeId);
+        radiusNodes[i] = 1.0 / nodes->getKdiff0(nodeId);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            projectedNodes[3 * i + j] =
+                nodeCoords[j] - radiusNodes[i] * nodes->getNormals()[3 * nodeId + j];
+            area.axisPoint[j] += projectedNodes[3 * i + j];
+        }
+    }
+    // Axis point is the mean of the projected nodes
+    for (size_t i = 0; i < 3; ++i)
+        area.axisPoint[i] /= (double)nbNodes;
+    // Compute the axis of the cone
+    area.axis = MathOps::computePCAFirstAxis(projectedNodes);
+    double normAxis = MathOps::computeNorm(area.axis);
+    for (size_t i = 0; i < 3; ++i)
+        area.axis[i] /= normAxis;
+    // Compute the angle of the cone
+    const std::vector<double> &weakDirections = nodes->getWeakDirections();
+    std::vector<double> weakDirectionNodes(3 * nbNodes, 0.0);
+    for (size_t i = 0; i < nbNodes; ++i)
+    {
+        mcIdType nodeId = area.nodeIds[i];
+        for (size_t j = 0; j < 3; ++j)
+            weakDirectionNodes[3 * i + j] = weakDirections[3 * nodeId + j];
+    }
+    std::vector<double> angles = MathOps::computeAngles(
+        weakDirectionNodes, area.axis);
+    // Correct the angles > pi/2 with the supplementary angle
+    for (size_t i = 0; i < angles.size(); ++i)
+    {
+        if (angles[i] > M_PI_2)
+            angles[i] = M_PI - angles[i];
+    }
+    area.angle = MathOps::mean(angles);
+    // Compute the radius of the cone which is the mean of the approximate radius of each node
+    area.radius = MathOps::mean(radiusNodes);
+    // Select extrem nodes
+    double q1 = MathOps::computeQuantile(radiusNodes, 0.1);
+    double q2 = MathOps::computeQuantile(radiusNodes, 0.9);
+    std::vector<mcIdType> q1_indices;
+    for (mcIdType idx = 0; idx < (mcIdType)radiusNodes.size(); ++idx)
+    {
+        if (radiusNodes[idx] < q1)
+            q1_indices.push_back(idx);
+    }
+    std::vector<mcIdType> q2_indices;
+    for (mcIdType idx = 0; idx < (mcIdType)radiusNodes.size(); ++idx)
+    {
+        if (radiusNodes[idx] > q2)
+            q2_indices.push_back(idx);
+    }
+    std::random_device rd;
+    std::mt19937 g(rd());
+    std::shuffle(q2_indices.begin(), q2_indices.end(), g);
+    // Compute the height of the cone
+    // std::vector<double> heights(q1_indices.size(), 0.0);
+    // std::vector<double> distancesToApex(q1_indices.size(), 0.0);
+    std::array<double, 3> p{0.0, 0.0, 0.0};
+    size_t min_q_size = std::min<size_t>(q1_indices.size(), q2_indices.size());
+    for (size_t i = 0; i < min_q_size; ++i)
+    {
+        for (size_t j = 0; j < 3; ++j)
+            p[j] = (projectedNodes[3 * q1_indices[i] + j] - projectedNodes[3 * q2_indices[i] + j]);
+        double height = MathOps::computeNorm(p);
+        double distanceToApex = height * radiusNodes[q1_indices[i]] / (radiusNodes[q2_indices[i]] - radiusNodes[q1_indices[i]]);
+        double orientationAxis = MathOps::dot(p, area.axis);
+        for (size_t j = 0; j < 3; ++j)
+        {
+            area.apex[j] += projectedNodes[3 * q1_indices[i] + j];
+            if (orientationAxis >= 0)
+                area.apex[j] += distanceToApex * area.axis[j];
+            else
+                area.apex[j] -= distanceToApex * area.axis[j];
+        }
+    }
+    for (size_t j = 0; j < 3; ++j)
+        area.apex[j] /= (double)min_q_size;
+}
+
+void Areas::computeTorusProperties(mcIdType areaId)
+{
+    Area &area = areas[areaId];
+    size_t n = area.nodeIds.size();
+    if (n == 0)
+        return;
+    std::vector<double> areaNodesK1(n, 0.0);
+    std::vector<double> areaNodesK2(n, 0.0);
+    for (size_t i = 0; i < n; ++i)
+    {
+        areaNodesK1[i] = nodes->getK1(area.nodeIds[i]);
+        areaNodesK2[i] = nodes->getK2(area.nodeIds[i]);
+    }
+    double var1 = MathOps::computeVariance(areaNodesK1);
+    double var2 = MathOps::computeVariance(areaNodesK2);
+    double minorCurvature;
+    if (var1 > var2)
+        minorCurvature = MathOps::mean(areaNodesK2);
+    else
+        minorCurvature = MathOps::mean(areaNodesK1);
+    area.minorRadius = 1.0 / minorCurvature;
+    std::vector<double> majorRadiusNodes(3 * n, 0.0);
+    for (size_t i = 0; i < n; ++i)
+    {
+        std::array<double, 3> coords = nodes->getCoordinates(area.nodeIds[i]);
+        std::array<double, 3> normal = nodes->getNormal(area.nodeIds[i]);
+        for (size_t j = 0; j < 3; ++j)
+            majorRadiusNodes[3 * i + j] = coords[j] - normal[j] * area.minorRadius;
+    }
+    std::array<double, 3> meanMajorRadiusNodes = MathOps::meanCoordinates(majorRadiusNodes);
+    for (size_t i = 0; i < n; ++i)
+    {
+        for (size_t j = 0; j < 3; ++j)
+            majorRadiusNodes[3 * i + j] -= meanMajorRadiusNodes[j];
+    }
+    std::array<double, 3> normal = MathOps::computePCAThirdAxis(majorRadiusNodes);
+    std::array<double, 6> base2d = MathOps::computeBaseFromNormal(normal);
+    std::vector<double> projectedMajorRadiusNodes(2 * n, 0.0);
+    cblas_dgemm(
+        CBLAS_LAYOUT::CblasRowMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        CBLAS_TRANSPOSE::CblasTrans,
+        (int)n, 2, 3,
+        1.0,
+        majorRadiusNodes.data(), 3,
+        base2d.data(), 3,
+        0.0, projectedMajorRadiusNodes.data(), 2);
+    std::vector<double> A(3 * n, 1.0);
+    std::vector<double> B(n, 0.0);
+    for (size_t i = 0; i < n; ++i)
+    {
+        for (size_t j = 0; j < 2; ++j)
+            A[3 * i + j] = projectedMajorRadiusNodes[2 * i + j];
+        B[i] = pow(projectedMajorRadiusNodes[2 * i], 2) +
+               pow(projectedMajorRadiusNodes[2 * i + 1], 2);
+    }
+    std::vector<double> fit = MathOps::lstsqRow(A, B);
+    double a = fit[0];
+    double b = fit[1];
+    double c = fit[2];
+    double xc = a / 2.0;
+    double yc = b / 2.0;
+    area.radius = sqrt(4.0 * c + pow(a, 2) + pow(b, 2)) / 2.0;
+    for (size_t i = 0; i < 3; ++i)
+        area.center[i] = xc * base2d[i] + yc * base2d[3 + i] + meanMajorRadiusNodes[i];
+}
+
+const std::vector<mcIdType> &Areas::getAreaIdByNodes() const
+{
+    return areaIdByNodes;
+}
diff --git a/src/ShapeRecogn/Areas.hxx b/src/ShapeRecogn/Areas.hxx
new file mode 100644 (file)
index 0000000..b1b6f6b
--- /dev/null
@@ -0,0 +1,108 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __AREAS_HXX__
+#define __AREAS_HXX__
+
+#include "PrimitiveType.hxx"
+#include "Nodes.hxx"
+
+#include <array>
+
+namespace MEDCoupling
+{
+
+    struct Area
+    {
+        PrimitiveType primitive = PrimitiveType::Unknown;
+        double k1 = 0.0;
+        double k2 = 0.0;
+        double adimK1 = 0.0;
+        double adimK2 = 0.0;
+        double adimKdiff0 = 0.0;
+        double minorRadius = 0.0;
+        double radius = 0.0;
+        double angle = 0.0;
+        std::array<double, 3> normal{0.0, 0.0, 0.0};
+        std::array<double, 3> center{0.0, 0.0, 0.0};
+        std::array<double, 3> axis{0.0, 0.0, 0.0};
+        std::array<double, 3> axisPoint{0.0, 0.0, 0.0};
+        std::array<double, 3> apex{0.0, 0.0, 0.0};
+        std::vector<mcIdType> nodeIds;
+    };
+
+    class Areas
+    {
+    public:
+        Areas(const Nodes *nodes);
+
+        mcIdType addArea(PrimitiveType primitive = PrimitiveType::Unknown);
+        void cleanArea(mcIdType areaId);
+        void cancelArea(mcIdType areaId, PrimitiveType primitive = PrimitiveType::Unknown);
+        void removeArea(mcIdType areaId);
+        void addNode(mcIdType areaId, mcIdType nodeId);
+        void cleanInvalidNodeAreas();
+
+        mcIdType getAreaId(mcIdType nodeId) const;
+        const std::vector<mcIdType> &getAreaIdByNodes() const;
+
+        bool isEmpty(mcIdType areaId) const;
+        size_t getNumberOfAreas() const;
+        size_t getNumberOfNodes(mcIdType areaId) const;
+
+        bool isNodeCompatible(mcIdType areaId, mcIdType nodeId) const;
+
+        PrimitiveType getPrimitiveType(mcIdType areaId) const;
+        std::string getPrimitiveTypeName(mcIdType areaId) const;
+        int getPrimitiveTypeInt(mcIdType areaId) const;
+
+        const std::vector<mcIdType> &getNodeIds(mcIdType areaId) const;
+
+        double getAdimK1(mcIdType areaId) const;
+        double getAdimK2(mcIdType areaId) const;
+        double getAdimKdiff0(mcIdType areaId) const;
+
+        void computeProperties(mcIdType areaId);
+        double getMinorRadius(mcIdType areaId) const;
+        double getRadius(mcIdType areaId) const;
+        double getAngle(mcIdType areaId) const;
+        const std::array<double, 3> &getNormal(mcIdType areaId) const;
+        const std::array<double, 3> &getCenter(mcIdType areaId) const;
+        const std::array<double, 3> &getAxis(mcIdType areaId) const;
+        const std::array<double, 3> &getAxisPoint(mcIdType areaId) const;
+        const std::array<double, 3> &getApex(mcIdType areaId) const;
+        std::array<double, 3> getAffinePoint(mcIdType areaId) const;
+
+    private:
+        void cleanArea(mcIdType areaId, mcIdType newAreaId);
+        void removeNode(mcIdType nodeId);
+
+        void computePlaneProperties(mcIdType areaId);
+        void computeSphereProperties(mcIdType areaId);
+        void computeCylinderProperties(mcIdType areaId);
+        void computeConeProperties(mcIdType areaId);
+        void computeTorusProperties(mcIdType areaId);
+
+        std::vector<Area> areas;
+        const Nodes *nodes;
+        std::vector<mcIdType> areaIdByNodes;
+    };
+};
+
+#endif // __AREAS_HXX__
diff --git a/src/ShapeRecogn/AreasBuilder.cxx b/src/ShapeRecogn/AreasBuilder.cxx
new file mode 100644 (file)
index 0000000..947a54e
--- /dev/null
@@ -0,0 +1,371 @@
+#include "AreasBuilder.hxx"
+#include "MathOps.hxx"
+#include "ShapeRecongConstants.hxx"
+
+#include <unordered_set>
+
+using namespace MEDCoupling;
+
+AreasBuilder::AreasBuilder(const Nodes *nodes) : nodes(nodes), areas(new Areas(nodes))
+{
+    size_t nbNodes = nodes->getNbNodes();
+    threshold = std::max<size_t>(THRESHOLD_MIN_NB_NODES, nbNodes / THRESHOLD_MAX_NB_AREAS);
+}
+
+void AreasBuilder::build()
+{
+    explore();
+    expand();
+    rebuild();
+}
+
+void AreasBuilder::explore()
+{
+    exploreAreas();
+    areas->cleanInvalidNodeAreas();
+    filterHighPass();
+}
+
+void AreasBuilder::expand()
+{
+    expandAreas();
+    filterHighPass();
+}
+
+void AreasBuilder::rebuild()
+{
+    rebuildInvalidAreas();
+    filterHighPass();
+    expandAreasByType(PrimitiveType::Cone);
+    filterHighPass();
+}
+
+Areas *AreasBuilder::getAreas() const
+{
+    return areas;
+}
+
+void AreasBuilder::exploreAreas()
+{
+    // int nbNodesExplored = 0;
+    std::vector<bool> exploredNodeIds(nodes->getNbNodes(), false);
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    mcIdType areaId = -1;
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+    {
+        if (!exploredNodeIds[nodeId] &&
+            nodes->getPrimitiveType(nodeId) != PrimitiveType::Unknown)
+        {
+            exploredNodeIds[nodeId] = true;
+            if (areaId != -1 && areas->getNumberOfNodes(areaId) < threshold)
+                areas->cancelArea(areaId, nodes->getPrimitiveType(nodeId));
+            else
+                areaId = areas->addArea(nodes->getPrimitiveType(nodeId));
+            areas->addNode(areaId, nodeId);
+            const std::vector<mcIdType> neighbors = nodes->getNeighbors(nodeId);
+            for (mcIdType neighborId : neighbors)
+            {
+                if (nodes->getPrimitiveType(neighborId) == areas->getPrimitiveType(areaId) &&
+                    areas->getAreaId(neighborId) <= -1)
+                    nodesToExplore.insert(neighborId);
+            }
+            // Explore all the neighbors matching the area
+            while (!nodesToExplore.empty())
+            {
+                mcIdType neighborId = *nodesToExplore.begin();
+                nodesToExplore.erase(neighborId);
+                if (doesItMatch(areaId, neighborId))
+                {
+                    exploredNodeIds[neighborId] = true;
+                    areas->addNode(areaId, neighborId);
+                    const std::vector<mcIdType> neighborsOfNeighbor = nodes->getNeighbors(neighborId);
+                    for (mcIdType neighborIdOfNeighbor : neighborsOfNeighbor)
+                    {
+                        if (!exploredNodeIds[neighborIdOfNeighbor] &&
+                            areas->getAreaId(neighborIdOfNeighbor) <= -1 &&
+                            // Already in doesItMatch but avoid useless insertion
+                            nodes->getPrimitiveType(neighborIdOfNeighbor) == areas->getPrimitiveType(areaId))
+                            nodesToExplore.insert(neighborIdOfNeighbor);
+                    }
+                }
+            }
+        }
+        // if (!exploredNodeIds[nodeId])
+        //     nbNodesExplored += 1;
+        exploredNodeIds[nodeId] = true;
+    }
+}
+
+void AreasBuilder::expandAreas()
+{
+    // Expand by topological order
+    expandAreasByType(PrimitiveType::Plane);
+    expandAreasByType(PrimitiveType::Sphere);
+    expandAreasByType(PrimitiveType::Cylinder);
+    expandAreasByType(PrimitiveType::Cone);
+    expandAreasByType(PrimitiveType::Torus);
+}
+
+void AreasBuilder::expandAreasByType(PrimitiveType primitive)
+{
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    for (mcIdType areaId = 0; areaId < (mcIdType)areas->getNumberOfAreas(); ++areaId)
+    {
+        if (areas->getPrimitiveType(areaId) == primitive)
+        {
+            std::vector<bool> exploredNodeIds(nodes->getNbNodes(), false);
+            areas->computeProperties(areaId);
+            const std::vector<mcIdType> &nodeIds = areas->getNodeIds(areaId);
+            for (mcIdType nodeId : nodeIds)
+            {
+                exploredNodeIds[nodeId] = true;
+                nodesToExplore.insert(nodeId);
+            }
+            while (!nodesToExplore.empty())
+            {
+                mcIdType nodeId = *nodesToExplore.begin();
+                nodesToExplore.erase(nodeId);
+                if (doesItBelong(areaId, nodeId))
+                {
+                    // TODO: Is the properties need to be updated after adding a node ?
+                    // It gives bad results for the cone and the cylinder
+                    // mcIdType oldAreaId = areas->getAreaId(nodeId);
+                    areas->addNode(areaId, nodeId);
+                    // areas->computeProperties(areaId);
+                    // areas->computeProperties(oldAreaId);
+                    const std::vector<mcIdType> neighborIds = nodes->getNeighbors(nodeId);
+                    for (mcIdType neighborId : neighborIds)
+                    {
+                        if (!exploredNodeIds[neighborId])
+                            nodesToExplore.insert(neighborId);
+                    }
+                }
+                exploredNodeIds[nodeId] = true;
+            }
+        }
+    }
+}
+
+void AreasBuilder::rebuildInvalidAreas()
+{
+    std::vector<mcIdType> exploredNodeIds(nodes->getNbNodes(), false);
+    std::vector<bool> isIinvalidNodes(nodes->getNbNodes(), false);
+    std::unordered_set<mcIdType> nodesToExplore;
+    // Reserve a set with the size of nodes to avoid reallocation for each insert/erase
+    // TODO: Improve the size ? Nb of Nodes is too much ?
+    nodesToExplore.reserve(nodes->getNbNodes());
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+        isIinvalidNodes[nodeId] = isInvalidCylinderNode(nodeId);
+    for (mcIdType nodeId = 0; nodeId < nodes->getNbNodes(); ++nodeId)
+    {
+        if (isIinvalidNodes[nodeId] && !exploredNodeIds[nodeId])
+        {
+            mcIdType areaId = areas->addArea(PrimitiveType::Cone);
+            areas->addNode(areaId, nodeId);
+            const std::vector<mcIdType> neighbors = nodes->getNeighbors(nodeId);
+            for (mcIdType neighborId : neighbors)
+                nodesToExplore.insert(neighborId);
+            while (!nodesToExplore.empty())
+            {
+                mcIdType neighborId = *nodesToExplore.begin();
+                nodesToExplore.erase(neighborId);
+                if (
+                    (areas->getAreaId(neighborId) == -1 ||
+                     isIinvalidNodes[neighborId]))
+                {
+                    exploredNodeIds[neighborId] = true;
+                    areas->addNode(areaId, neighborId);
+                    const std::vector<mcIdType> neighborsOfNeighbor = nodes->getNeighbors(neighborId);
+                    for (mcIdType neighborIdOfNeighbor : neighborsOfNeighbor)
+                    {
+                        if (!exploredNodeIds[neighborIdOfNeighbor])
+                            nodesToExplore.insert(neighborIdOfNeighbor);
+                    }
+                }
+            }
+        }
+    }
+}
+
+void AreasBuilder::filterHighPass()
+{
+    mcIdType nbAreas = areas->getNumberOfAreas();
+    for (mcIdType areaId = (nbAreas - 1); areaId >= 0; --areaId)
+    {
+        if (areas->getNumberOfNodes(areaId) < threshold)
+            areas->removeArea(areaId);
+    }
+}
+
+bool AreasBuilder::doesItMatch(mcIdType areaId, mcIdType nodeId) const
+{
+    PrimitiveType areaType = areas->getPrimitiveType(areaId);
+    PrimitiveType neighborType = nodes->getPrimitiveType(nodeId);
+    bool isMatching = false;
+    if (areaType == neighborType)
+    {
+        switch (areaType)
+        {
+        case PrimitiveType::Plane:
+        case PrimitiveType::Torus:
+            isMatching = true;
+            break;
+        case PrimitiveType::Sphere:
+        {
+            double kmoy = fabs(
+                (areas->getAdimK1(areaId) + areas->getAdimK2(areaId)) / 2.0);
+            double nodeKmoy = fabs(
+                (nodes->getAdimK1(nodeId) + nodes->getAdimK2(nodeId)) / 2.0);
+            isMatching = fabs((nodeKmoy - kmoy)) < TOL_MATCH_SPHERE;
+        }
+        break;
+        case PrimitiveType::Cylinder:
+            isMatching = fabs(areas->getAdimKdiff0(areaId) -
+                              nodes->getAdimKdiff0(nodeId)) < TOL_MATCH_CYLINDER;
+            break;
+        case PrimitiveType::Cone:
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+    }
+    return isMatching;
+}
+
+bool AreasBuilder::doesItBelong(mcIdType areaId, mcIdType nodeId) const
+{
+    bool isClose = false;
+    if (areas->isNodeCompatible(areaId, nodeId))
+    {
+        PrimitiveType areaType = areas->getPrimitiveType(areaId);
+        switch (areaType)
+        {
+        case PrimitiveType::Plane:
+        {
+            isClose = distanceToPlane(
+                          nodes->getCoordinates(nodeId),
+                          areas->getAffinePoint(areaId),
+                          areas->getNormal(areaId)) < DELTA_PLANE;
+        }
+        break;
+        case PrimitiveType::Sphere:
+        {
+            double distanceToCenter = distanceToSphere(
+                nodes->getCoordinates(nodeId),
+                areas->getCenter(areaId));
+            double radius = areas->getRadius(areaId);
+            isClose = fabs((distanceToCenter - radius) / radius) < DELTA_SPHERE;
+        }
+        break;
+        case PrimitiveType::Cylinder:
+        {
+            double distance = distanceToCylinder(
+                nodes->getCoordinates(nodeId),
+                areas->getAxis(areaId),
+                areas->getAxisPoint(areaId));
+            double radius = areas->getRadius(areaId);
+            isClose = fabs((distance - radius) / radius) < DELTA_CYLINDER;
+        }
+        break;
+        case PrimitiveType::Cone:
+        {
+            double radius = areas->getRadius(areaId);
+            isClose = distanceToCone(
+                          nodes->getCoordinates(nodeId),
+                          areas->getAxis(areaId),
+                          areas->getApex(areaId),
+                          areas->getAngle(areaId)) /
+                          fabs(radius) <
+                      DELTA_CONE;
+        }
+        break;
+        case PrimitiveType::Torus:
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+    }
+    return isClose;
+}
+
+bool AreasBuilder::isInvalidCylinderNode(mcIdType nodeId) const
+{
+    mcIdType areaId = areas->getAreaId(nodeId);
+    if (areaId != -1 &&
+        nodes->getPrimitiveType(nodeId) == PrimitiveType::Cylinder &&
+        areas->getPrimitiveType(areaId) == PrimitiveType::Cylinder)
+    {
+        double angle = MathOps::computeAngle(
+            nodes->getWeakDirection(nodeId),
+            areas->getAxis(areaId));
+        return angle >= THETA_MAX_CYLINDER && angle <= (M_PI - THETA_MAX_CYLINDER);
+    }
+    return false;
+}
+
+double AreasBuilder::distanceToPlane(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &point,
+    const std::array<double, 3> &normal)
+{
+    std::array<double, 3> vec{0.0, 0.0, 0.0};
+    for (size_t i = 0; i < nodeCoords.size(); ++i)
+        vec[i] = nodeCoords[i] - point[i];
+    return fabs(MathOps::dot(vec, normal)) / MathOps::computeNorm(normal);
+}
+
+double AreasBuilder::distanceToSphere(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &center)
+{
+    return MathOps::computeNorm(
+        std::array<double, 3>{
+            nodeCoords[0] - center[0],
+            nodeCoords[1] - center[1],
+            nodeCoords[2] - center[2]});
+}
+
+double AreasBuilder::distanceToCylinder(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &axis,
+    const std::array<double, 3> &axisPoint)
+{
+
+    std::array<double, 3> pa = {
+        axisPoint[0] - nodeCoords[0],
+        axisPoint[1] - nodeCoords[1],
+        axisPoint[2] - nodeCoords[2]};
+    double innerProduct = MathOps::dot(pa, axis);
+    return MathOps::computeNorm(
+        std::array<double, 3>({pa[0] - innerProduct * axis[0],
+                               pa[1] - innerProduct * axis[1],
+                               pa[2] - innerProduct * axis[2]}));
+}
+
+double AreasBuilder::distanceToCone(
+    const std::array<double, 3> &nodeCoords,
+    const std::array<double, 3> &axis,
+    const std::array<double, 3> &apex,
+    double angle)
+{
+    std::array<double, 3> ps{
+        apex[0] - nodeCoords[0],
+        apex[1] - nodeCoords[1],
+        apex[2] - nodeCoords[2]};
+    std::array<double, 3> v(axis);
+    if (MathOps::dot(axis, ps) <= 0)
+    {
+        for (size_t i = 0; i < 3; ++i)
+            v[i] *= -1;
+    }
+    double a = MathOps::computeNorm(
+        MathOps::cross(ps, v));
+    double b = MathOps::dot(ps, v);
+    return fabs(a * cos(angle) - b * sin(angle));
+}
diff --git a/src/ShapeRecogn/AreasBuilder.hxx b/src/ShapeRecogn/AreasBuilder.hxx
new file mode 100644 (file)
index 0000000..9742af6
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __AREASBUILDER_HXX__
+#define __AREASBUILDER_HXX__
+
+#include "Nodes.hxx"
+#include "Areas.hxx"
+
+namespace MEDCoupling
+{
+    class AreasBuilder
+    {
+    public:
+        AreasBuilder(const Nodes *nodes);
+
+        void build();
+
+        Areas *getAreas() const;
+
+    private:
+        void explore();
+        void expand();
+        void rebuild();
+        void exploreAreas();
+        void expandAreas();
+        void expandAreasByType(PrimitiveType primitive);
+        void rebuildInvalidAreas();
+        void filterHighPass();
+        bool doesItMatch(mcIdType areaId, mcIdType nodeId) const;
+        bool doesItBelong(mcIdType areaId, mcIdType nodeId) const;
+        bool isInvalidCylinderNode(mcIdType nodeId) const;
+
+        static double distanceToPlane(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &point,
+            const std::array<double, 3> &normal);
+        static double distanceToSphere(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &center);
+        static double distanceToCylinder(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &axis,
+            const std::array<double, 3> &axisPoint);
+        static double distanceToCone(
+            const std::array<double, 3> &nodeCoords,
+            const std::array<double, 3> &axis,
+            const std::array<double, 3> &apex,
+            double angle);
+
+        const Nodes *nodes;
+        Areas *areas;
+
+        size_t threshold = 5;
+    };
+};
+
+#endif // __AREASBUILDER_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/CMakeLists.txt b/src/ShapeRecogn/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1f63ae1
--- /dev/null
@@ -0,0 +1,63 @@
+# Copyright (C) 2012-2024  CEA, EDF
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+ADD_DEFINITIONS(${HDF5_DEFINITIONS} ${MEDFILE_DEFINITIONS})
+
+IF (NOT DEFINED MSVC)
+  ADD_DEFINITIONS(-Wsign-compare -Wconversion)
+ENDIF()
+
+IF(MEDCOUPLING_ENABLE_PYTHON)
+  ADD_SUBDIRECTORY(Swig)
+ENDIF(MEDCOUPLING_ENABLE_PYTHON)
+
+IF(MEDCOUPLING_BUILD_TESTS)
+  ADD_SUBDIRECTORY(Test)
+ENDIF(MEDCOUPLING_BUILD_TESTS)
+
+INCLUDE_DIRECTORIES(
+  ${MEDFILE_INCLUDE_DIRS}
+  ${HDF5_INCLUDE_DIRS}
+  ${LAPACKE_INCLUDE_DIRS}
+  ${CMAKE_CURRENT_SOURCE_DIR}/../MEDLoader
+  ${CMAKE_CURRENT_SOURCE_DIR}/../MEDCoupling
+  ${CMAKE_CURRENT_SOURCE_DIR}/../INTERP_KERNEL
+  ${CMAKE_CURRENT_SOURCE_DIR}/../INTERP_KERNEL/Bases
+)
+
+SET(shaperecogn_SOURCES
+  MathOps.cxx
+  Nodes.cxx
+  NodesBuilder.cxx
+  Areas.cxx
+  AreasBuilder.cxx
+  ShapeRecognMeshBuilder.cxx
+  ShapeRecognMesh.cxx
+)
+
+ADD_LIBRARY(shaperecogn ${shaperecogn_SOURCES})
+SET_TARGET_PROPERTIES(shaperecogn PROPERTIES COMPILE_FLAGS "")
+TARGET_LINK_LIBRARIES(shaperecogn medcouplingcpp medloader ${MEDFILE_C_LIBRARIES} ${HDF5_LIBRARIES} ${LAPACK_LIBRARIES})
+INSTALL(TARGETS shaperecogn EXPORT ${PROJECT_NAME}TargetGroup DESTINATION ${MEDCOUPLING_INSTALL_LIBS})
+
+FILE(GLOB shaperecogn_HEADERS_HXX "${CMAKE_CURRENT_SOURCE_DIR}/*.hxx")
+INSTALL(FILES ${shaperecogn_HEADERS_HXX} DESTINATION ${MEDCOUPLING_INSTALL_HEADERS})
+
+# To allow usage as SWIG dependencies:
+SET(shaperecogn_HEADERS_HXX PARENT_SCOPE)
diff --git a/src/ShapeRecogn/MathOps.cxx b/src/ShapeRecogn/MathOps.cxx
new file mode 100644 (file)
index 0000000..8f1705a
--- /dev/null
@@ -0,0 +1,331 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "MathOps.hxx"
+
+#include <algorithm>
+#include <lapacke.h>
+#include <cblas.h>
+#include <iostream>
+#include <cfloat>
+
+using namespace MEDCoupling;
+
+std::vector<double> MathOps::lstsq(
+    std::vector<double> &a, const std::vector<double> &b)
+{
+    int m = (int)b.size();
+    int n = 3;
+    int nrhs = 1;
+    return lstsq(a, b, m, n, nrhs);
+}
+
+std::vector<double> MathOps::lstsq(
+    std::vector<double> &a,
+    const std::vector<double> &b,
+    int m, int n, int nrhs)
+{
+    int ldb = std::max<int>(m, n);
+    int lds = std::min<int>(m, n);
+    std::vector<double> x(ldb, 0.0);
+    for (size_t i = 0; i < b.size(); ++i)
+        x[i] = b[i];
+    std::vector<double> s(lds, 0.0);
+    double rcond = DBL_EPSILON * (double)ldb; // same value as numpy.linalg.lstsq
+    int rank = 0;
+    int info = LAPACKE_dgelsd(
+        LAPACK_COL_MAJOR,
+        m, n, nrhs,
+        a.data(), m,
+        x.data(), ldb,
+        s.data(),
+        rcond,
+        &rank);
+    return x;
+}
+
+std::vector<double> MathOps::lstsqRow(
+    std::vector<double> &a,
+    const std::vector<double> &b)
+{
+    int m = b.size();
+    int n = 3;
+    int nrhs = 1;
+    int ldb = std::max<int>(m, n);
+    int lds = std::min<int>(m, n);
+    std::vector<double> x(ldb, 0.0);
+    for (size_t i = 0; i < b.size(); ++i)
+        x[i] = b[i];
+    std::vector<double> s(lds, 0.0);
+    double rcond = DBL_EPSILON * (double)ldb; // same value as numpy.linalg.lstsq
+    int rank = 0;
+    int info = LAPACKE_dgelsd(
+        LAPACK_ROW_MAJOR,
+        m, n, nrhs,
+        a.data(), n,
+        x.data(), nrhs,
+        s.data(),
+        rcond,
+        &rank);
+    return x;
+}
+std::array<double, 3> MathOps::cross(const std::array<double, 3> &a, const std::array<double, 3> &b)
+{
+    std::array<double, 3> c{0.0, 0.0, 0.0};
+    c[0] = a[1] * b[2] - a[2] * b[1];
+    c[1] = a[2] * b[0] - a[0] * b[2];
+    c[2] = a[0] * b[1] - a[1] * b[0];
+    return c;
+}
+
+std::array<double, 3> MathOps::normalize(const std::array<double, 3> &a)
+{
+    std::array<double, 3> an{0.0, 0.0, 0.0};
+    double n = computeNorm(a);
+    for (size_t i = 0; i < 3; i++)
+        an[i] = a[i] / n;
+    return an;
+}
+
+double MathOps::computeNorm(const std::array<double, 3> &a)
+{
+    double n = 0;
+    for (size_t i = 0; i < 3; i++)
+        n += a[i] * a[i];
+    return sqrt(n);
+}
+
+std::vector<double> MathOps::computeNorm(
+    const std::vector<double> &a)
+{
+    size_t s = a.size() / 3;
+    std::vector<double> n(s, 0.0);
+    for (size_t i = 0; i < s; i++)
+    {
+        for (size_t j = 0; j < 3; j++)
+            n[i] += a[3 * i + j] * a[3 * i + j];
+        n[i] = sqrt(n[i]);
+    }
+    return n;
+}
+
+double MathOps::dot(const std::array<double, 3> &a, const std::array<double, 3> &b)
+{
+    double d = 0.0;
+    for (size_t i = 0; i < 3; i++)
+        d += a[i] * b[i];
+    return d;
+}
+
+std::vector<double> MathOps::dot(const std::vector<double> &a, const std::array<double, 3> &b)
+{
+    size_t nbNodes = a.size() / 3;
+    std::vector<double> d(nbNodes, 0.0);
+    cblas_dgemv(
+        CBLAS_LAYOUT::CblasRowMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        (int)nbNodes, 3, 1.0,
+        a.data(), 3,
+        b.data(), 1,
+        0.0, d.data(), 1);
+    return d;
+}
+
+double MathOps::mean(const std::vector<double> &values)
+{
+    double mean = 0.0;
+    for (double value : values)
+        mean += value;
+    return mean / values.size();
+}
+
+std::array<double, 3> MathOps::meanCoordinates(const std::vector<double> &coordinates)
+{
+    std::array<double, 3> coordsMean{0.0, 0.0, 0.0};
+    size_t nbNodes = coordinates.size() / 3;
+    for (size_t nodeId = 0; nodeId < nbNodes; ++nodeId)
+    {
+        coordsMean[0] += coordinates[3 * nodeId];
+        coordsMean[1] += coordinates[3 * nodeId + 1];
+        coordsMean[2] += coordinates[3 * nodeId + 2];
+    }
+    coordsMean[0] /= nbNodes;
+    coordsMean[1] /= nbNodes;
+    coordsMean[2] /= nbNodes;
+    return coordsMean;
+}
+
+std::array<double, 9> MathOps::computeCov(
+    const std::vector<double> &coordinates)
+{
+    std::array<double, 9> covMatrix;
+    covMatrix.fill(0);
+    size_t nbNodes = coordinates.size() / 3;
+    // Center the coordinates
+    std::vector<double> coordsCentered(coordinates);
+    std::array<double, 3> coordsMean = meanCoordinates(coordinates);
+    for (size_t nodeId = 0; nodeId < nbNodes; ++nodeId)
+    {
+        coordsCentered[3 * nodeId] -= coordsMean[0];
+        coordsCentered[3 * nodeId + 1] -= coordsMean[1];
+        coordsCentered[3 * nodeId + 2] -= coordsMean[2];
+    }
+    cblas_dgemm(
+        CBLAS_LAYOUT::CblasColMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        CBLAS_TRANSPOSE::CblasTrans,
+        3, 3, (int)nbNodes, 1.0,
+        coordsCentered.data(), 3,
+        coordsCentered.data(), 3,
+        0.0, covMatrix.data(), 3);
+    for (size_t i = 0; i < 9; ++i)
+    {
+        covMatrix[i] /= (double)nbNodes - 1;
+    }
+    return covMatrix;
+}
+
+std::array<double, 9> MathOps::computePCA(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> covMatrix = computeCov(coordinates);
+    std::array<double, 3> eigenValues{0.0, 0.0, 0.0};
+    LAPACKE_dsyevd(
+        LAPACK_COL_MAJOR,
+        'V',
+        'U',
+        3,
+        covMatrix.data(),
+        3,
+        eigenValues.data());
+    return covMatrix;
+}
+
+std::array<double, 3> MathOps::computePCAFirstAxis(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> pca = computePCA(coordinates);
+    // The eignvalues are in ascending order so the first axis correspond to the last vector
+    return {pca[6], pca[7], pca[8]};
+}
+
+std::array<double, 3> MathOps::computePCAThirdAxis(const std::vector<double> &coordinates)
+{
+    std::array<double, 9> pca = computePCA(coordinates);
+    // The eignvalues are in ascending order so the third axis correspond to the first vector
+    return {pca[0], pca[1], pca[2]};
+}
+
+double MathOps::computeQuantile(const std::vector<double> &values, double q)
+{
+    std::vector<double> sortedValues(values);
+    std::sort(sortedValues.begin(), sortedValues.end());
+    double pos = q * (sortedValues.size() - 1);
+    size_t index = static_cast<size_t>(pos);
+    if (pos == index)
+        return sortedValues[index];
+    else
+    {
+        double frac = pos - index;
+        return sortedValues[index] * (1 - frac) + sortedValues[index + 1] * frac;
+    }
+}
+
+double MathOps::computeAngle(const std::array<double, 3> &direction, std::array<double, 3> axis)
+{
+    double angle = dot(direction, axis);
+    double normAxis = computeNorm(axis);
+    double normDirection = computeNorm(direction);
+    angle /= normDirection * normAxis;
+    if (fabs(angle) >= 1)
+        return 0;
+    else
+        return acos(angle);
+}
+
+std::vector<double> MathOps::computeAngles(
+    const std::vector<double> &directions, std::array<double, 3> axis)
+{
+    size_t nbDirections = directions.size() / 3;
+    std::vector<double> angles(nbDirections, 0.0);
+    cblas_dgemv(
+        CBLAS_LAYOUT::CblasRowMajor,
+        CBLAS_TRANSPOSE::CblasNoTrans,
+        nbDirections, 3, 1.0,
+        directions.data(), 3,
+        axis.data(), 1,
+        0.0, angles.data(), 1);
+    double normAxis = computeNorm(axis);
+    std::vector<double> normDirections = computeNorm(directions);
+    for (size_t i = 0; i < nbDirections; ++i)
+    {
+        angles[i] /= normAxis * normDirections[i];
+        if (fabs(angles[i]) >= 1.0)
+            angles[i] = 0.0;
+        angles[i] = acos(angles[i]);
+    }
+    return angles;
+}
+
+double MathOps::computeOrientedAngle(const std::array<double, 3> &normal, const std::array<double, 3> &vector1, const std::array<double, 3> &vector2)
+{
+    double angle = computeAngle(vector1, vector2);
+    if (dot(cross(vector1, vector2), normal) >= 0.0)
+        return angle;
+    else
+        return -angle;
+}
+
+double MathOps::computeVariance(std::vector<double> values)
+{
+    size_t n = values.size();
+    double m = mean(values);
+    double d2 = 0;
+    for (size_t i = 0; i < n; ++i)
+        d2 += pow(values[i] - m, 2);
+    return d2 / (double)n;
+}
+
+std::array<double, 6> MathOps::computeBaseFromNormal(std::array<double, 3> normal)
+{
+    std::array<double, 3> n_normal = normalize(normal);
+    std::array<double, 3> s;
+    std::array<double, 1> u;
+    std::array<double, 9> v;
+    LAPACKE_dgesdd(
+        LAPACK_COL_MAJOR,
+        'A', 1, 3, n_normal.data(), 1,
+        s.data(),
+        u.data(), 1,
+        v.data(), 3);
+    std::array<double, 3> v1 = {v[3], v[4], v[5]};
+    std::array<double, 3> v2 = {v[6], v[7], v[8]};
+    std::array<double, 3> u1 = normalize(v1);
+    std::array<double, 3> u2;
+    u2.fill(0.0);
+    double innerProd = dot(u1, v2);
+    for (size_t i = 0; i < 3; ++i)
+        u2[i] = v2[i] - innerProd * u1[i];
+    u2 = normalize(u2);
+    double sign = dot(cross(u1, u2), normal);
+    if (sign < 0)
+    {
+        for (size_t i = 0; i < 3; ++i)
+            u1[i] *= -1;
+    }
+    return {u1[0], u1[1], u1[2], u2[0], u2[1], u2[2]};
+}
diff --git a/src/ShapeRecogn/MathOps.hxx b/src/ShapeRecogn/MathOps.hxx
new file mode 100644 (file)
index 0000000..e511845
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __MATHOPS_HXX__
+#define __MATHOPS_HXX__
+
+#include <vector>
+#include <array>
+
+namespace MEDCoupling
+{
+    class MathOps
+    {
+    public:
+        static std::vector<double> lstsq(std::vector<double> &a, const std::vector<double> &b);
+        static std::vector<double> lstsq(
+            std::vector<double> &a,
+            const std::vector<double> &b,
+            int m,
+            int n = 3,
+            int nrhs = 1);
+        static std::vector<double> lstsqRow(
+            std::vector<double> &a,
+            const std::vector<double> &b);
+        static std::array<double, 3> cross(const std::array<double, 3> &a, const std::array<double, 3> &b);
+        static std::array<double, 3> normalize(const std::array<double, 3> &a);
+        static double computeNorm(const std::array<double, 3> &a);
+        static std::vector<double> computeNorm(const std::vector<double> &a);
+        static double dot(const std::array<double, 3> &a, const std::array<double, 3> &b);
+        static std::vector<double> dot(const std::vector<double> &a, const std::array<double, 3> &b);
+        static double mean(const std::vector<double> &values);
+        static std::array<double, 3> meanCoordinates(const std::vector<double> &coordinates);
+        static std::array<double, 9> computeCov(const std::vector<double> &coordinates);
+        static std::array<double, 9> computePCA(const std::vector<double> &coordinates);
+        static std::array<double, 3> computePCAFirstAxis(const std::vector<double> &coordinates);
+        static std::array<double, 3> computePCAThirdAxis(const std::vector<double> &coordinates);
+        static double computeQuantile(
+            const std::vector<double> &values,
+            double q);
+        static double computeAngle(
+            const std::array<double, 3> &direction,
+            std::array<double, 3> axis);
+        static std::vector<double> computeAngles(
+            const std::vector<double> &directions,
+            std::array<double, 3> axis);
+        static double computeOrientedAngle(
+            const std::array<double, 3> &normal,
+            const std::array<double, 3> &vector1,
+            const std::array<double, 3> &vector2);
+        static double computeVariance(std::vector<double> values);
+        static std::array<double, 6> computeBaseFromNormal(std::array<double, 3> normal);
+    };
+};
+
+#endif //__MATHOPS_HXX__
diff --git a/src/ShapeRecogn/Nodes.cxx b/src/ShapeRecogn/Nodes.cxx
new file mode 100644 (file)
index 0000000..615971a
--- /dev/null
@@ -0,0 +1,125 @@
+#include "Nodes.hxx"
+
+using namespace MEDCoupling;
+
+Nodes::Nodes(
+    const MEDCouplingUMesh *mesh,
+    const DataArrayInt64 *neighbors,
+    const DataArrayInt64 *neighborsIdx)
+    : mesh(mesh), coords(mesh->getCoords()), neighbors(neighbors), neighborsIdx(neighborsIdx)
+{
+}
+
+Nodes::~Nodes()
+{
+    mesh->decrRef();
+    neighbors->decrRef();
+    neighborsIdx->decrRef();
+}
+
+mcIdType Nodes::getNbNodes() const
+{
+    return coords->getNumberOfTuples();
+}
+
+const std::vector<double> &Nodes::getK1() const
+{
+    return k1;
+}
+
+const std::vector<double> &Nodes::getK2() const
+{
+    return k2;
+}
+
+const std::vector<PrimitiveType> &Nodes::getPrimitiveType() const
+{
+    return primitives;
+}
+
+const std::vector<double> &Nodes::getNormals() const
+{
+    return normals;
+}
+
+const std::vector<double> &Nodes::getWeakDirections() const
+{
+    return weakDirections;
+}
+
+const std::vector<double> &Nodes::getMainDirections() const
+{
+    return mainDirections;
+}
+
+std::array<double, 3> Nodes::getNormal(mcIdType nodeId) const
+{
+    return {normals[3 * nodeId], normals[3 * nodeId + 1], normals[3 * nodeId + 2]};
+}
+
+double Nodes::getK1(mcIdType nodeId) const
+{
+    return k1[nodeId];
+}
+
+double Nodes::getK2(mcIdType nodeId) const
+{
+    return k2[nodeId];
+}
+
+double Nodes::getKdiff0(mcIdType nodeId) const
+{
+    return fabs(k1[nodeId]) > fabs(k2[nodeId]) ? k1[nodeId] : k2[nodeId];
+}
+
+double Nodes::getAdimK1(mcIdType nodeId) const
+{
+    return adimK1[nodeId];
+}
+
+double Nodes::getAdimK2(mcIdType nodeId) const
+{
+    return adimK2[nodeId];
+}
+
+double Nodes::getAdimKdiff0(mcIdType nodeId) const
+{
+    return fabs(adimK1[nodeId]) > fabs(adimK2[nodeId]) ? adimK1[nodeId] : adimK2[nodeId];
+}
+
+const std::vector<mcIdType> Nodes::getNeighbors(mcIdType nodeId) const
+{
+    mcIdType start = neighborsIdx->getIJ(nodeId, 0);
+    mcIdType nbNeighbors = neighborsIdx->getIJ(nodeId + 1, 0) - start;
+
+    std::vector<mcIdType> neighborNodes(nbNeighbors, 0);
+    for (mcIdType i = 0; i < nbNeighbors; i++)
+        neighborNodes[i] = neighbors->getIJ(start + i, 0);
+    return neighborNodes;
+}
+
+PrimitiveType Nodes::getPrimitiveType(mcIdType nodeId) const
+{
+    return primitives[nodeId];
+}
+
+std::array<double, 3> Nodes::getWeakDirection(mcIdType nodeId) const
+{
+    return {weakDirections[3 * nodeId],
+            weakDirections[3 * nodeId + 1],
+            weakDirections[3 * nodeId + 2]};
+}
+
+std::array<double, 3> Nodes::getMainDirection(mcIdType nodeId) const
+{
+    return {mainDirections[3 * nodeId],
+            mainDirections[3 * nodeId + 1],
+            mainDirections[3 * nodeId + 2]};
+}
+
+std::array<double, 3> Nodes::getCoordinates(mcIdType nodeId) const
+{
+    std::array<double, 3> nodeCoords;
+    coords->getTuple(nodeId, nodeCoords.data());
+    return nodeCoords;
+}
diff --git a/src/ShapeRecogn/Nodes.hxx b/src/ShapeRecogn/Nodes.hxx
new file mode 100644 (file)
index 0000000..1d47d30
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __NODES_HXX__
+#define __NODES_HXX__
+
+#include <vector>
+#include "MEDCouplingUMesh.hxx"
+#include "PrimitiveType.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes
+    {
+    public:
+        friend class NodesBuilder;
+        Nodes(const MEDCouplingUMesh *mesh,
+              const DataArrayInt64 *neighbors,
+              const DataArrayInt64 *neighborsIdx);
+        ~Nodes();
+
+        mcIdType getNbNodes() const;
+        const std::vector<double> &getK1() const;
+        const std::vector<double> &getK2() const;
+        const std::vector<PrimitiveType> &getPrimitiveType() const;
+        const std::vector<double> &getNormals() const;
+        const std::vector<double> &getWeakDirections() const;
+        const std::vector<double> &getMainDirections() const;
+
+        std::array<double, 3> getNormal(mcIdType nodeId) const;
+        double getK1(mcIdType nodeId) const;
+        double getK2(mcIdType nodeId) const;
+        double getKdiff0(mcIdType nodeId) const;
+        double getAdimK1(mcIdType nodeId) const;
+        double getAdimK2(mcIdType nodeId) const;
+        double getAdimKdiff0(mcIdType nodeId) const;
+        const std::vector<mcIdType> getNeighbors(mcIdType nodeId) const;
+        PrimitiveType getPrimitiveType(mcIdType nodeId) const;
+        std::array<double, 3> getWeakDirection(mcIdType nodeId) const;
+        std::array<double, 3> getMainDirection(mcIdType nodeId) const;
+        std::array<double, 3> getCoordinates(mcIdType nodeId) const;
+
+    private:
+        const MEDCouplingUMesh *mesh;
+        const DataArrayDouble *coords;
+        // normals 3 * nbNodes
+        std::vector<double> normals;
+        // neighbors
+        const DataArrayInt64 *neighbors;
+        const DataArrayInt64 *neighborsIdx;
+        // curvature
+        std::vector<double> k1;
+        std::vector<double> k2;
+        std::vector<double> adimK1;
+        std::vector<double> adimK2;
+        std::vector<double> weakDirections;
+        std::vector<double> mainDirections;
+        std::vector<PrimitiveType> primitives;
+    };
+};
+
+#endif //__NODES_HXX__
diff --git a/src/ShapeRecogn/NodesBuilder.cxx b/src/ShapeRecogn/NodesBuilder.cxx
new file mode 100644 (file)
index 0000000..1302d32
--- /dev/null
@@ -0,0 +1,311 @@
+
+#include "NodesBuilder.hxx"
+#include "Nodes.hxx"
+#include "MathOps.hxx"
+#include "ShapeRecongConstants.hxx"
+
+using namespace MEDCoupling;
+
+NodesBuilder::NodesBuilder(
+    const MEDCouplingUMesh *mesh) : mesh(mesh)
+{
+}
+
+Nodes *NodesBuilder::build()
+{
+    DataArrayInt64 *neighbors;
+    DataArrayInt64 *neighborsIdx;
+    mesh->computeNeighborsOfNodes(neighbors, neighborsIdx);
+    nodes = new Nodes(mesh, neighbors, neighborsIdx);
+    computeNormals();
+    computeCurvatures();
+    return nodes;
+}
+
+void NodesBuilder::computeNormals()
+{
+    mcIdType nbNodes = mesh->getNumberOfNodes();
+    mcIdType nbCells = mesh->getNumberOfCells();
+    std::array<double, 3> cellNormal;
+    cellNormal.fill(0.0);
+    // product of normal AreaByCell
+    std::vector<double> prodNormalAreaByCell(3 * nbCells, 0.0);
+    for (int cellId = 0; cellId < nbCells; cellId++)
+    {
+        std::vector<mcIdType> nodeIds;
+        mesh->getNodeIdsOfCell(cellId, nodeIds);
+        computeCellNormal(nodeIds, cellNormal);
+        prodNormalAreaByCell[3 * cellId + 0] = cellNormal[0] / 2.0;
+        prodNormalAreaByCell[3 * cellId + 1] = cellNormal[1] / 2.0;
+        prodNormalAreaByCell[3 * cellId + 2] = cellNormal[2] / 2.0;
+    }
+    //
+    nodes->normals.resize(3 * nbNodes, 1.0);
+    DataArrayInt64 *revNodal = DataArrayInt64::New();
+    DataArrayInt64 *revNodalIdx = DataArrayInt64::New();
+    mesh->getReverseNodalConnectivity(revNodal, revNodalIdx);
+    for (size_t nodeId = 0; nodeId < (size_t)nbNodes; nodeId++)
+    {
+        int nbCells = revNodalIdx->getIJ(nodeId + 1, 0) -
+                      revNodalIdx->getIJ(nodeId, 0);
+        std::vector<mcIdType> cellIds(nbCells, 0);
+        int start = revNodalIdx->getIJ(nodeId, 0);
+        for (size_t i = 0; i < cellIds.size(); ++i)
+            cellIds[i] = revNodal->getIJ(start + i, 0);
+        double normal = 0.0;
+        for (size_t i = 0; i < 3; i++)
+        {
+            nodes->normals[3 * nodeId + i] = 0.0;
+            for (mcIdType j = 0; j < nbCells; j++)
+            {
+                nodes->normals[3 * nodeId + i] +=
+                    prodNormalAreaByCell[3 * cellIds[j] + i];
+            }
+            nodes->normals[3 * nodeId + i] /= (double)nbCells;
+            normal += pow(nodes->normals[3 * nodeId + i], 2);
+        }
+        for (size_t i = 0; i < 3; i++)
+            nodes->normals[3 * nodeId + i] /= sqrt(normal);
+    }
+    revNodal->decrRef();
+    revNodalIdx->decrRef();
+}
+
+void NodesBuilder::computeCurvatures(double tol)
+{
+    mcIdType nbNodes = mesh->getNumberOfNodes();
+    nodes->k1.resize(nbNodes);
+    nodes->k2.resize(nbNodes);
+    nodes->adimK1.resize(nbNodes);
+    nodes->adimK2.resize(nbNodes);
+    nodes->primitives.resize(nbNodes);
+    nodes->weakDirections.resize(3 * nbNodes);
+    nodes->mainDirections.resize(3 * nbNodes);
+    for (mcIdType nodeId = 0; nodeId < nbNodes; nodeId++)
+        computeCurvatures(nodeId, tol);
+}
+
+void NodesBuilder::computeCurvatures(mcIdType nodeId, double tol)
+{
+    std::array<double, 3> normal = nodes->getNormal(nodeId);
+    std::vector<mcIdType> neighborIds = nodes->getNeighbors(nodeId);
+    double theta0 = 0.0;
+    double k1 = 0.0;
+    double k2 = 0.0;
+    double adimK1 = 0.0;
+    double adimK2 = 0.0;
+    PrimitiveType primitive = PrimitiveType::Unknown;
+    std::array<double, 3> mainDirection{0.0, 0.0, 0.0};
+    std::array<double, 3> weakDirection{0.0, 0.0, 0.0};
+    if (neighborIds.size() > 0)
+    {
+        std::vector<double> discreteCurvatures = computeDiscreteCurvatures(nodeId, neighborIds);
+        std::vector<double> tangents = computeTangentDirections(nodeId, neighborIds);
+        mcIdType maxCurvatureId = std::distance(
+            discreteCurvatures.begin(),
+            std::max_element(discreteCurvatures.begin(), discreteCurvatures.end()));
+        std::array<double, 3> e1{
+            tangents[3 * maxCurvatureId],
+            tangents[3 * maxCurvatureId + 1],
+            tangents[3 * maxCurvatureId + 2]};
+        std::array<double, 3> e2 = MathOps::normalize(MathOps::cross(e1, normal));
+        std::vector<double> coeffs = computeNormalCurvatureCoefficients(
+            discreteCurvatures, tangents, normal, e1);
+        double a = coeffs[0], b = coeffs[1], c = coeffs[2];
+        double h = (a + c) / 2.0;
+        double kg = a * c - pow(b, 2) / 4.0;
+        k1 = h + sqrt(fabs(pow(h, 2) - kg));
+        k2 = h - sqrt(fabs(pow(h, 2) - kg));
+        if (fabs(k1 - k2) >= tol)
+            theta0 = 0.5 * asin(b / (k1 - k2));
+        std::array<double, 3> direction1{0.0, 0.0, 0.0};
+        std::array<double, 3> direction2{0.0, 0.0, 0.0};
+        for (size_t i = 0; i < 3; ++i)
+        {
+            direction1[i] = cos(theta0) * e1[i] + sin(theta0) * e2[i];
+            direction2[i] = -sin(theta0) * e1[i] + cos(theta0) * e2[i];
+        }
+        double averageDistance = computeAverageDistance(nodeId, neighborIds);
+        double adimK1 = k1 * averageDistance;
+        double adimK2 = k2 * averageDistance;
+        double adimKdiff0, adimKis0;
+        if (fabs(k1) > fabs(k2))
+        {
+            adimKdiff0 = adimK1;
+            adimKis0 = adimK2;
+            mainDirection = direction1;
+            weakDirection = direction2;
+        }
+        else
+        {
+            adimKdiff0 = adimK2;
+            adimKis0 = adimK1;
+            mainDirection = direction2;
+            weakDirection = direction1;
+        }
+        primitive = findPrimitiveType(adimK1, adimK2, adimKdiff0, adimKis0);
+    }
+    // Populate nodes
+    nodes->k1[nodeId] = k1;
+    nodes->k2[nodeId] = k2;
+    nodes->adimK1[nodeId] = adimK1;
+    nodes->adimK2[nodeId] = adimK2;
+    for (size_t i = 0; i < 3; ++i)
+    {
+        nodes->weakDirections[3 * nodeId + i] = weakDirection[i];
+        nodes->mainDirections[3 * nodeId + i] = mainDirection[i];
+    }
+    nodes->primitives[nodeId] = primitive;
+}
+
+PrimitiveType NodesBuilder::findPrimitiveType(double k1, double k2, double kdiff0, double kis0) const
+{
+    if ((fabs(k1 - k2) < EPSILON_PRIMITIVE) && (fabs((k1 + k2) / 2) < EPSILON_PRIMITIVE))
+        return PrimitiveType::Plane;
+    else if ((fabs(k1 - k2) < EPSILON_PRIMITIVE) && (fabs((k1 + k2) / 2) > EPSILON_PRIMITIVE))
+        return PrimitiveType::Sphere;
+    else if ((fabs(kdiff0) > EPSILON_PRIMITIVE) && (fabs(kis0) < EPSILON_PRIMITIVE))
+        return PrimitiveType::Cylinder;
+    else if ((fabs(k1) > EPSILON_PRIMITIVE) && (fabs(k2) > EPSILON_PRIMITIVE))
+        return PrimitiveType::Torus;
+    else
+        return PrimitiveType::Unknown;
+}
+
+PrimitiveType NodesBuilder::findPrimitiveType2(double k1, double k2, double kdiff0, double kis0) const
+{
+    double epsilon2 = pow(EPSILON_PRIMITIVE, 2);
+    double diffCurvature = fabs(k1 - k2);
+    double gaussianCurvature = k1 * k2;
+    double meanCurvature = (k1 + k2) / 2.0;
+    if (fabs(k1) < EPSILON_PRIMITIVE &&
+        fabs(k2) < EPSILON_PRIMITIVE &&
+        gaussianCurvature < epsilon2 &&
+        meanCurvature < EPSILON_PRIMITIVE)
+        return PrimitiveType::Plane;
+    else if (diffCurvature < EPSILON_PRIMITIVE && k1 > EPSILON_PRIMITIVE && k2 > EPSILON_PRIMITIVE)
+        return PrimitiveType::Sphere;
+    else if (
+        (fabs(k1) > EPSILON_PRIMITIVE && fabs(k2) < EPSILON_PRIMITIVE) ||
+        (fabs(k1) < EPSILON_PRIMITIVE && fabs(k2) > EPSILON_PRIMITIVE))
+        return PrimitiveType::Cylinder;
+    else if (
+        std::signbit(k1) != std::signbit(k2) ||
+        (fabs(k1) < EPSILON_PRIMITIVE && fabs(k2) < EPSILON_PRIMITIVE))
+        return PrimitiveType::Torus;
+    else
+        return PrimitiveType::Unknown;
+}
+
+std::vector<double> NodesBuilder::computeNormalCurvatureCoefficients(
+    const std::vector<double> &discreteCurvatures,
+    const std::vector<double> &tangents,
+    const std::array<double, 3> &normal,
+    const std::array<double, 3> &e1) const
+{
+    size_t nbNeighbors = discreteCurvatures.size();
+    std::vector<double> a(3 * nbNeighbors, 0.0);
+    for (size_t i = 0; i < nbNeighbors; ++i)
+    {
+        std::array<double, 3> tangent{tangents[3 * i], tangents[3 * i + 1], tangents[3 * i + 2]};
+        double theta = MathOps::computeOrientedAngle(normal, tangent, e1);
+        double cos_theta = cos(theta);
+        double sin_theta = sin(theta);
+        a[i] = pow(cos_theta, 2);
+        a[nbNeighbors + i] = cos_theta * sin_theta;
+        a[2 * nbNeighbors + i] = pow(sin_theta, 2);
+    }
+    return MathOps::lstsq(a, discreteCurvatures);
+}
+
+void NodesBuilder::computeCellNormal(
+    const std::vector<mcIdType> &nodeIds,
+    std::array<double, 3> &cellNormal) const
+{
+    std::vector<double> point1;
+    std::vector<double> point2;
+    std::vector<double> point3;
+    mesh->getCoordinatesOfNode(nodeIds[0], point1);
+    mesh->getCoordinatesOfNode(nodeIds[1], point2);
+    mesh->getCoordinatesOfNode(nodeIds[2], point3);
+    std::array<double, 3> a;
+    a.fill(3);
+    std::array<double, 3> b;
+    b.fill(3);
+    for (int i = 0; i < 3; i++)
+    {
+        a[i] = point2[i] - point1[i];
+        b[i] = point3[i] - point1[i];
+    }
+    cellNormal[0] = a[1] * b[2] - a[2] * b[1];
+    cellNormal[1] = a[2] * b[0] - a[0] * b[2];
+    cellNormal[2] = a[0] * b[1] - a[1] * b[0];
+}
+
+double NodesBuilder::computeAverageDistance(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const
+{
+    double distance = 0.0;
+    std::array<double, 3>
+        nodeCoords = nodes->getCoordinates(nodeId);
+    for (size_t i = 0; i < neighborIds.size(); ++i)
+    {
+        std::array<double, 3> neighborCoords = nodes->getCoordinates(neighborIds[i]);
+        double distanceToNeighbor = 0.0;
+        for (size_t j = 0; j < 3; ++j)
+            distanceToNeighbor += pow(neighborCoords[j] - nodeCoords[j], 2);
+        distance += sqrt(distanceToNeighbor);
+    }
+    return distance / (double)neighborIds.size();
+}
+
+std::vector<double> NodesBuilder::computeDiscreteCurvatures(
+    mcIdType nodeId,
+    const std::vector<mcIdType> &neighborIds) const
+{
+    std::vector<double> discreteCurvatures(neighborIds.size(), 0.0);
+    for (size_t i = 0; i < neighborIds.size(); ++i)
+        discreteCurvatures[i] = computeDiscreteCurvature(nodeId, neighborIds[i]);
+    return discreteCurvatures;
+}
+
+double NodesBuilder::computeDiscreteCurvature(
+    mcIdType nodeId,
+    mcIdType neighborId) const
+{
+    double curvature = 0.0;
+    double n = 0.0;
+    for (size_t i = 0; i < 3; i++)
+    {
+        double ni = nodes->coords->getIJ(neighborId, i) - nodes->coords->getIJ(nodeId, i);
+        curvature += ni * (nodes->normals[3 * neighborId + i] - nodes->normals[3 * nodeId + i]);
+        n += ni * ni;
+    }
+    return curvature / n;
+}
+
+std::vector<double> NodesBuilder::computeTangentDirections(
+    mcIdType nodeId,
+    const std::vector<mcIdType> &neighborIds) const
+{
+    size_t nbNeighbors = neighborIds.size();
+    std::vector<double> tangent(3 * nbNeighbors, 0.0);
+    for (size_t i = 0; i < nbNeighbors; ++i)
+    {
+        mcIdType neighborId = neighborIds[i];
+        double s = 0.0;
+        for (size_t j = 0; j < 3; j++)
+        {
+            tangent[3 * i + j] = nodes->coords->getIJ(neighborId, j) - nodes->coords->getIJ(nodeId, j);
+            s += tangent[3 * i + j] * nodes->normals[3 * nodeId + j];
+        }
+        double n = 0.0;
+        for (size_t j = 0; j < 3; j++)
+        {
+            tangent[3 * i + j] -= s * nodes->normals[3 * nodeId + j];
+            n += tangent[3 * i + j] * tangent[3 * i + j];
+        }
+        for (size_t j = 0; j < 3; j++)
+            tangent[3 * i + j] /= sqrt(n);
+    }
+    return tangent;
+}
diff --git a/src/ShapeRecogn/NodesBuilder.hxx b/src/ShapeRecogn/NodesBuilder.hxx
new file mode 100644 (file)
index 0000000..1901180
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __NODECURVATURECALCULATOR_HXX__
+#define __NODECURVATURECALCULATOR_HXX__
+
+#include <string>
+#include <vector>
+#include <array>
+#include "MEDCouplingUMesh.hxx"
+#include "PrimitiveType.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes;
+
+    class NodesBuilder
+    {
+    public:
+        NodesBuilder(const MEDCouplingUMesh *);
+
+        Nodes *build();
+
+    private:
+        void computeNormals();
+        void computeCurvatures(double tol = 0.000001);
+        void computeCurvatures(mcIdType nodeId, double tol);
+        PrimitiveType findPrimitiveType(double k1, double k2, double kdiff0, double kis0) const;
+        PrimitiveType findPrimitiveType2(double k1, double k2, double kdiff0, double kis0) const;
+        std::vector<double> computeNormalCurvatureCoefficients(
+            const std::vector<double> &discreteCurvatures,
+            const std::vector<double> &tangents,
+            const std::array<double, 3> &normal,
+            const std::array<double, 3> &e1) const;
+        void computeCellNormal(const std::vector<mcIdType> &nodeIds, std::array<double, 3> &cellNormal) const;
+        double computeAverageDistance(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const;
+        std::vector<double> computeDiscreteCurvatures(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const;
+        double computeDiscreteCurvature(mcIdType nodeId, mcIdType neighborId) const;
+        std::vector<double> computeTangentDirections(mcIdType nodeId, const std::vector<mcIdType> &neighborIds) const;
+
+        const MEDCouplingUMesh *mesh;
+        Nodes *nodes;
+    };
+};
+
+#endif //__NODECURVATURECALCULATOR_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/PrimitiveType.hxx b/src/ShapeRecogn/PrimitiveType.hxx
new file mode 100644 (file)
index 0000000..b131da8
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __PRIMITIVETYPE_HXX__
+#define __PRIMITIVETYPE_HXX__
+
+#include <string>
+namespace MEDCoupling
+{
+    enum PrimitiveType
+    {
+        Plane = 0,
+        Sphere = 1,
+        Cylinder = 2,
+        Cone = 3,
+        Torus = 4,
+        Unknown = 5
+    };
+
+    inline std::string convertPrimitiveToString(PrimitiveType type)
+    {
+        std::string typeName = "";
+        switch (type)
+        {
+        case PrimitiveType::Plane:
+            typeName = "Plane";
+            break;
+        case PrimitiveType::Sphere:
+            typeName = "Sphere";
+            break;
+        case PrimitiveType::Cylinder:
+            typeName = "Cylinder";
+            break;
+        case PrimitiveType::Cone:
+            typeName = "Cone";
+            break;
+        case PrimitiveType::Torus:
+            typeName = "Torus";
+            break;
+        case PrimitiveType::Unknown:
+            typeName = "Unknown";
+            break;
+        default:
+            break;
+        }
+        return typeName;
+    };
+
+    inline int convertPrimitiveToInt(PrimitiveType type)
+    {
+        int typeInt = 5;
+        switch (type)
+        {
+        case PrimitiveType::Plane:
+            typeInt = 0;
+            break;
+        case PrimitiveType::Sphere:
+            typeInt = 1;
+            break;
+        case PrimitiveType::Cylinder:
+            typeInt = 2;
+            break;
+        case PrimitiveType::Cone:
+            typeInt = 3;
+            break;
+        case PrimitiveType::Torus:
+            typeInt = 4;
+            break;
+        case PrimitiveType::Unknown:
+        default:
+            break;
+        }
+        return typeInt;
+    };
+};
+
+#endif // __PRIMITIVETYPE_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/README.md b/src/ShapeRecogn/README.md
new file mode 100644 (file)
index 0000000..61b9eb4
--- /dev/null
@@ -0,0 +1,106 @@
+# ShapeRecogn
+A tool leveraging the MEDCoupling library for recognizing canonical shapes in 3D mesh files.
+
+## Table Of Contents
+
+1. [Quickstart](#quickstart)
+2. [Installation](#installation)
+3. [Description](#description)
+
+## Quickstart
+
+### With output file
+
+```python
+>> import ShapeRecogn as sr
+>>
+>> shape_recogn_builder = sr.ShapeRecognMeshBuilder("resources/ShapeRecognCone.med")
+>> shape_recogn = shape_recogn_builder.recognize()
+>> shape_recogn.save("ShapeRecognCone_areas.med")
+```
+
+### Without output file
+
+```python
+>> import ShapeRecogn as sr
+>>
+>> shape_recogn_builder = sr.ShapeRecognMeshBuilder("resources/ShapeRecognCone.med")
+>> shape_recogn = shape_recogn_builder.recognize()
+>> radius_field = shape_recogn.getRadius()
+>> radius_field
+MEDCouplingFieldDouble C++ instance at 0x55f3418c6700. Name : "Radius (Area)".
+Nature of field : NoNature.
+P1 spatial discretization.
+
+Mesh info : MEDCouplingUMesh C++ instance at 0x55f34171a2d0. Name : "Mesh_1". Mesh dimension : 2. Space dimension : 3.
+
+Array info : DataArrayDouble C++ instance at 0x55f3418b40e0. Number of tuples : 957. Number of components : 1.
+[9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, 9.8948383131651862, ... ]
+```
+
+## Installation
+
+### Prerequisites
+
+**LAPACKE Library**: ShapeRecogn requires the [LAPACKE](https://www.netlib.org/lapack/lapacke.html) library for numerical computations. Ensure it is installed on your system.
+
+### CMAKE configuration
+
+Run CMake to configure the build. Make sure to enable ShapeRecogn by setting the `-DMEDCOUPLING_ENABLE_SHAPERECOGN=ON` option.
+
+## Description
+
+ShapeRecogn is a tool that leverages the MEDCoupling library to recognize
+canonical shapes from 3D meshes using triangle cells.
+
+The tool is based on the thesis work of Roseline Bénière *[Reconstruction d’un modèle B-Rep à partir d’un maillage 3D](https://theses.fr/2012MON20034)*, and it recognizes five canonical shapes:
+ - Plane (0)
+ - Sphere (1)
+ - Cylinder (2)
+ - Cone (3)
+ - Torus (4)
+ - Unknown (5) (When the algorithm failed the recognition)
+
+The recognized shapes are divided into areas within the mesh.
+The tool also generates an output file with detailed fields describing the areas and their properties. This makes it easier to analyze or further process the mesh data.
+ - Area Id: To distinguish the areas
+ - Primitive Type (Area): One of the canonical shape with the id describe above
+ - Normal (Area):
+    * Normal of a plane area
+ - Minor Radius (Area)
+    * Minor radius of a torus area
+ - Radius (Area)
+    * Radius of a sphere area
+    * Radius of a cylinder area
+    * Radius of the base of a cone area
+    * Major radius of a torus area
+ - Angle (Area)
+    * Angle between the central axis and the slant of a cone area
+ - Center (Area)
+    * Center of a sphere area
+    * Center of a torus area
+ - Axis (Area)
+    * Central axis of a cylinder area
+    * Central axis of a cone area
+ - Apex (Area)
+    * Apex of a cone area
+
+Some intermediate computation values concerning the nodes are also available:
+ - K1 (Node) and K2 (Node): the *[principal curvatures](https://en.wikipedia.org/wiki/Principal_curvature)*
+ - Primitive Type (Node) : One of the canonical shape with the id describe above. The primitive type is deduced usint K1 and K2 values.
+ - Normal (Node): Normal of the nodes using neighbor nodes
+
+Each field can be retrieved as a `MEDCouplingDoubleField` using the following methods of the `ShapeRecognMesh` class:
+ - `getAreaId()`
+ - `getAreaPrimitiveType()`
+ - `getAreaNormal()`
+ - `getMinorRadius()`
+ - `getRadius()`
+ - `getAngle()`
+ - `getCenter()`
+ - `getAxis()`
+ - `getApex()`
+ - `getNodeK1()`
+ - `getNodeK2()`
+ - `getNodePrimitiveType()`
+ - `getNodeNormal()`
diff --git a/src/ShapeRecogn/ShapeRecognMesh.cxx b/src/ShapeRecogn/ShapeRecognMesh.cxx
new file mode 100644 (file)
index 0000000..4c8ee5f
--- /dev/null
@@ -0,0 +1,155 @@
+#include "ShapeRecognMesh.hxx"
+
+#include "MEDLoader.hxx"
+
+using namespace MEDCoupling;
+
+ShapeRecognMesh::ShapeRecognMesh()
+    : nodeK1(0), nodeK2(0), nodePrimitiveType(0),
+      nodeNormal(0), areaId(0), areaPrimitiveType(0),
+      areaNormal(0), minorRadius(0), radius(0),
+      angle(0), center(0), axis(0), apex(0)
+{
+}
+ShapeRecognMesh::~ShapeRecognMesh()
+{
+    nodeK1->decrRef();
+    nodeK2->decrRef();
+    nodePrimitiveType->decrRef();
+    nodeNormal->decrRef();
+    areaId->decrRef();
+    areaPrimitiveType->decrRef();
+    areaNormal->decrRef();
+    minorRadius->decrRef();
+    radius->decrRef();
+    angle->decrRef();
+    center->decrRef();
+    axis->decrRef();
+    apex->decrRef();
+}
+
+std::size_t ShapeRecognMesh::getHeapMemorySizeWithoutChildren() const
+{
+    return 0;
+}
+
+std::vector<const BigMemoryObject *> ShapeRecognMesh::getDirectChildrenWithNull() const
+{
+    std::vector<const BigMemoryObject *> ret;
+    ret.push_back(nodeK1);
+    ret.push_back(nodeK2);
+    ret.push_back(nodePrimitiveType);
+    ret.push_back(nodeNormal);
+    ret.push_back(areaId);
+    ret.push_back(areaPrimitiveType);
+    ret.push_back(areaNormal);
+    ret.push_back(minorRadius);
+    ret.push_back(radius);
+    ret.push_back(angle);
+    ret.push_back(center);
+    ret.push_back(axis);
+    ret.push_back(apex);
+    return ret;
+}
+
+ShapeRecognMesh *ShapeRecognMesh::New()
+{
+    return new ShapeRecognMesh;
+}
+
+void ShapeRecognMesh::save(const std::string &outputFile, bool writeFromScratch) const
+{
+    // Nodes
+    // - k1
+    WriteField(outputFile, nodeK1, writeFromScratch);
+    // - k2
+    WriteField(outputFile, nodeK2, false);
+    // - primitive types
+    WriteField(outputFile, nodePrimitiveType, false);
+    // - Normal
+    WriteField(outputFile, nodeNormal, false);
+    // Areas
+    // - Area Id
+    WriteField(outputFile, areaId, false);
+    // - Primitive Types
+    WriteField(outputFile, areaPrimitiveType, false);
+    // - Normal
+    WriteField(outputFile, areaNormal, false);
+    // - Minor Radius
+    WriteField(outputFile, minorRadius, false);
+    // - Radius
+    WriteField(outputFile, radius, false);
+    // - Angle
+    WriteField(outputFile, angle, false);
+    // - Center
+    WriteField(outputFile, center, false);
+    // - Axis
+    WriteField(outputFile, axis, false);
+    // - Apex
+    WriteField(outputFile, apex, false);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getNodeK1() const
+{
+    return nodeK1;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getNodeK2() const
+{
+    return nodeK2;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getNodePrimitiveType() const
+{
+    return nodePrimitiveType;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getNodeNormal() const
+{
+    return nodeNormal;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getAreaId() const
+{
+    return areaId;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getAreaPrimitiveType() const
+{
+    return areaPrimitiveType;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getAreaNormal() const
+{
+    return areaNormal;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getMinorRadius() const
+{
+    return minorRadius;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getRadius() const
+{
+    return radius;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getAngle() const
+{
+    return angle;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getCenter() const
+{
+    return center;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getAxis() const
+{
+    return axis;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMesh::getApex() const
+{
+    return apex;
+}
diff --git a/src/ShapeRecogn/ShapeRecognMesh.hxx b/src/ShapeRecogn/ShapeRecognMesh.hxx
new file mode 100644 (file)
index 0000000..02983e3
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SHAPERECOGNMESH_HXX__
+#define __SHAPERECOGNMESH_HXX__
+
+#include <string>
+
+#include "MEDCouplingUMesh.hxx"
+#include "MEDCouplingFieldDouble.hxx"
+#include "MEDCouplingRefCountObject.hxx"
+
+namespace MEDCoupling
+{
+    class ShapeRecognMesh : public RefCountObject
+    {
+        friend class ShapeRecognMeshBuilder;
+
+    public:
+        static ShapeRecognMesh *New();
+        std::size_t getHeapMemorySizeWithoutChildren() const;
+        std::vector<const BigMemoryObject *> getDirectChildrenWithNull() const;
+
+        void save(const std::string &outputFile, bool writeFromScratch = true) const;
+
+        // Node properties
+        MEDCoupling::MEDCouplingFieldDouble *getNodeK1() const;
+        MEDCoupling::MEDCouplingFieldDouble *getNodeK2() const;
+        MEDCoupling::MEDCouplingFieldDouble *getNodePrimitiveType() const;
+        MEDCoupling::MEDCouplingFieldDouble *getNodeNormal() const;
+
+        // Area properties
+        MEDCoupling::MEDCouplingFieldDouble *getAreaId() const;
+        MEDCoupling::MEDCouplingFieldDouble *getAreaPrimitiveType() const;
+        MEDCoupling::MEDCouplingFieldDouble *getAreaNormal() const;
+        MEDCoupling::MEDCouplingFieldDouble *getMinorRadius() const;
+        MEDCoupling::MEDCouplingFieldDouble *getRadius() const;
+        MEDCoupling::MEDCouplingFieldDouble *getAngle() const;
+        MEDCoupling::MEDCouplingFieldDouble *getCenter() const;
+        MEDCoupling::MEDCouplingFieldDouble *getAxis() const;
+        MEDCoupling::MEDCouplingFieldDouble *getApex() const;
+
+    protected:
+        ShapeRecognMesh();
+        ~ShapeRecognMesh();
+
+    private:
+        MEDCoupling::MEDCouplingFieldDouble *nodeK1;
+        MEDCoupling::MEDCouplingFieldDouble *nodeK2;
+        MEDCoupling::MEDCouplingFieldDouble *nodePrimitiveType;
+        MEDCoupling::MEDCouplingFieldDouble *nodeNormal;
+        MEDCoupling::MEDCouplingFieldDouble *areaId;
+        MEDCoupling::MEDCouplingFieldDouble *areaPrimitiveType;
+        MEDCoupling::MEDCouplingFieldDouble *areaNormal;
+        MEDCoupling::MEDCouplingFieldDouble *minorRadius;
+        MEDCoupling::MEDCouplingFieldDouble *radius;
+        MEDCoupling::MEDCouplingFieldDouble *angle;
+        MEDCoupling::MEDCouplingFieldDouble *center;
+        MEDCoupling::MEDCouplingFieldDouble *axis;
+        MEDCoupling::MEDCouplingFieldDouble *apex;
+    };
+};
+
+#endif // __SHAPERECOGNMESH_HXX__
diff --git a/src/ShapeRecogn/ShapeRecognMeshBuilder.cxx b/src/ShapeRecogn/ShapeRecognMeshBuilder.cxx
new file mode 100644 (file)
index 0000000..f0a2b05
--- /dev/null
@@ -0,0 +1,250 @@
+#include "ShapeRecognMeshBuilder.hxx"
+
+#include "NodesBuilder.hxx"
+#include "AreasBuilder.hxx"
+#include "MEDLoader.hxx"
+#include "ShapeRecognMesh.hxx"
+#include "MEDCouplingFieldDouble.hxx"
+
+using namespace MEDCoupling;
+
+ShapeRecognMeshBuilder::ShapeRecognMeshBuilder(const std::string &fileName, int meshDimRelToMax)
+{
+    mesh = ReadUMeshFromFile(fileName, meshDimRelToMax);
+    if (mesh->getMeshDimension() != 2)
+        throw INTERP_KERNEL::Exception("Expect a mesh with a dimension equal to 2");
+    if (mesh->getNumberOfCellsWithType(INTERP_KERNEL::NORM_TRI3) != mesh->getNumberOfCells())
+        throw INTERP_KERNEL::Exception("Expect a mesh containing exclusively triangular cells");
+}
+
+ShapeRecognMeshBuilder::~ShapeRecognMeshBuilder()
+{
+    if (areas != nullptr)
+        delete areas;
+    if (nodes != nullptr)
+        delete nodes;
+    mesh->decrRef();
+}
+
+ShapeRecognMesh *ShapeRecognMeshBuilder::recognize()
+{
+    mesh->incrRef();
+    NodesBuilder nodesBuilder(mesh);
+    nodes = nodesBuilder.build();
+    AreasBuilder areasBuilder(nodes);
+    areasBuilder.build();
+    areas = areasBuilder.getAreas();
+    MCAuto<ShapeRecognMesh> recognMesh = ShapeRecognMesh::New();
+    recognMesh->nodeK1 = buildNodeK1();
+    recognMesh->nodeK2 = buildNodeK2();
+    recognMesh->nodePrimitiveType = buildNodePrimitiveType();
+    recognMesh->nodeNormal = buildNodeNormal();
+    recognMesh->areaId = buildAreaId();
+    recognMesh->areaPrimitiveType = buildAreaPrimitiveType();
+    recognMesh->areaNormal = buildAreaNormal();
+    recognMesh->minorRadius = buildMinorRadius();
+    recognMesh->radius = buildRadius();
+    recognMesh->angle = buildAngle();
+    recognMesh->center = buildCenter();
+    recognMesh->axis = buildAxis();
+    recognMesh->apex = buildApex();
+    return recognMesh.retn();
+}
+
+const Nodes *ShapeRecognMeshBuilder::getNodes() const
+{
+    return nodes;
+}
+
+const Areas *ShapeRecognMeshBuilder::getAreas() const
+{
+    return areas;
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildNodeK1() const
+{
+    if (nodes == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    return buildField("K1 (Node)", 1, nodes->getK1());
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildNodeK2() const
+{
+    if (nodes == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    return buildField("K2 (Node)", 1, nodes->getK2());
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildNodePrimitiveType() const
+{
+    if (nodes == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    return buildField("Primitive Type (Node)", 1, nodes->getPrimitiveType());
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildNodeNormal() const
+{
+    if (nodes == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    return buildField("Normal (Node)", 3, nodes->getNormals());
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildAreaId() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    return buildField("Area Id", 1, areas->getAreaIdByNodes());
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildAreaPrimitiveType() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildAreaArray([](Areas *areas, mcIdType areaId) -> double
+                                    { return (double)areas->getPrimitiveType(areaId); });
+    return buildField("Primitive Type (Area)", 1, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildAreaNormal() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildArea3DArray([](Areas *areas, mcIdType areaId) -> const std::array<double, 3> &
+                                      { return areas->getNormal(areaId); });
+    return buildField("Normal (Area)", 3, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildMinorRadius() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildAreaArray([](Areas *areas, mcIdType areaId) -> double
+                                    { return areas->getMinorRadius(areaId); });
+    return buildField("Minor Radius (Area)", 1, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildRadius() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildAreaArray([](Areas *areas, mcIdType areaId) -> double
+                                    { return areas->getRadius(areaId); });
+    return buildField("Radius (Area)", 1, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildAngle() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildAreaArray([](Areas *areas, mcIdType areaId) -> double
+                                    { return areas->getAngle(areaId); });
+    return buildField("Angle (Area)", 1, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildCenter() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildArea3DArray([](Areas *areas, mcIdType areaId) -> const std::array<double, 3> &
+                                      { return areas->getCenter(areaId); });
+    return buildField("Center (Area)", 3, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildAxis() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildArea3DArray([](Areas *areas, mcIdType areaId) -> const std::array<double, 3> &
+                                      { return areas->getAxis(areaId); });
+    return buildField("Axis (Area)", 3, values);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildApex() const
+{
+    if (areas == nullptr)
+        throw INTERP_KERNEL::Exception("recognize must be called before building any fields");
+    double *values = buildArea3DArray([](Areas *areas, mcIdType areaId) -> const std::array<double, 3> &
+                                      { return areas->getApex(areaId); });
+    return buildField("Apex (Area)", 3, values);
+}
+
+template <typename T>
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildField(
+    const std::string &name,
+    size_t nbOfCompo,
+    const std::vector<T> &values) const
+{
+    DataArrayDouble *data = DataArrayDouble::New();
+    data->setName(name);
+    data->alloc(nodes->getNbNodes(), nbOfCompo);
+    std::copy(values.begin(), values.end(), data->getPointer());
+    data->declareAsNew();
+    return buildField(name, nbOfCompo, data);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildField(
+    const std::string &name,
+    size_t nbOfCompo,
+    double *values) const
+{
+    DataArrayDouble *data = DataArrayDouble::New();
+    data->setName(name);
+    data->useArray(
+        values,
+        true,
+        MEDCoupling::DeallocType::CPP_DEALLOC,
+        nodes->getNbNodes(),
+        nbOfCompo);
+    return buildField(name, nbOfCompo, data);
+}
+
+MEDCouplingFieldDouble *ShapeRecognMeshBuilder::buildField(
+    const std::string &name,
+    size_t nbOfCompo,
+    DataArrayDouble *data) const
+{
+    MEDCouplingFieldDouble *field = MEDCouplingFieldDouble::New(ON_NODES);
+    field->setName(name);
+    field->setMesh(mesh);
+    if (nbOfCompo == 3)
+    {
+        data->setInfoOnComponent(0, "X");
+        data->setInfoOnComponent(1, "Y");
+        data->setInfoOnComponent(2, "Z");
+    }
+    field->setArray(data);
+    data->decrRef();
+    return field;
+}
+
+double *ShapeRecognMeshBuilder::buildArea3DArray(
+    const std::array<double, 3> &(*areaFunc)(Areas *, mcIdType)) const
+{
+    double *values = new double[3 * nodes->getNbNodes()];
+    const std::vector<mcIdType> &areaIdByNodes = areas->getAreaIdByNodes();
+    for (size_t nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+    {
+        mcIdType areaId = areaIdByNodes[nodeId];
+        if (areaId != -1)
+        {
+            const std::array<double, 3> &areaValues = areaFunc(areas, areaId);
+            values[3 * nodeId] = areaValues[0];
+            values[3 * nodeId + 1] = areaValues[1];
+            values[3 * nodeId + 2] = areaValues[2];
+        }
+    }
+    return values;
+}
+
+double *ShapeRecognMeshBuilder::buildAreaArray(double (*areaFunc)(Areas *, mcIdType)) const
+{
+    const std::vector<mcIdType> &areaIdByNodes = areas->getAreaIdByNodes();
+    double *values = new double[nodes->getNbNodes()];
+    for (size_t nodeId = 0; nodeId < areaIdByNodes.size(); ++nodeId)
+    {
+        mcIdType areaId = areaIdByNodes[nodeId];
+        if (areaId != -1)
+            values[nodeId] = areaFunc(areas, areaId);
+    }
+    return values;
+}
diff --git a/src/ShapeRecogn/ShapeRecognMeshBuilder.hxx b/src/ShapeRecogn/ShapeRecognMeshBuilder.hxx
new file mode 100644 (file)
index 0000000..eee7c15
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SHAPERECOGNMESHBUILDER_HXX__
+#define __SHAPERECOGNMESHBUILDER_HXX__
+
+#include <string>
+
+#include "MEDCouplingUMesh.hxx"
+#include "MEDCouplingFieldDouble.hxx"
+
+namespace MEDCoupling
+{
+    class Nodes;
+    class Areas;
+    class ShapeRecognMesh;
+
+    class ShapeRecognMeshBuilder
+    {
+    public:
+        ShapeRecognMeshBuilder(const std::string &fileName, int meshDimRelToMax = 0);
+        ~ShapeRecognMeshBuilder();
+
+        const Nodes *getNodes() const;
+        const Areas *getAreas() const;
+
+        ShapeRecognMesh *recognize();
+
+    private:
+        // Node properties
+        MEDCoupling::MEDCouplingFieldDouble *buildNodeK1() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildNodeK2() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildNodePrimitiveType() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildNodeNormal() const;
+
+        // Area properties
+        MEDCoupling::MEDCouplingFieldDouble *buildAreaId() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildAreaPrimitiveType() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildAreaNormal() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildMinorRadius() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildRadius() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildAngle() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildCenter() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildAxis() const;
+        MEDCoupling::MEDCouplingFieldDouble *buildApex() const;
+
+        template <typename T>
+        MEDCouplingFieldDouble *buildField(
+            const std::string &name,
+            size_t nbOfCompo,
+            const std::vector<T> &values) const;
+        MEDCouplingFieldDouble *buildField(
+            const std::string &name,
+            size_t nbOfCompo,
+            double *values) const;
+        MEDCouplingFieldDouble *buildField(
+            const std::string &name,
+            size_t nbOfCompo,
+            DataArrayDouble *values) const;
+        double *buildArea3DArray(const std::array<double, 3> &(*areaFunc)(Areas *, mcIdType)) const;
+        double *buildAreaArray(double (*areaFunc)(Areas *, mcIdType)) const;
+
+        const MEDCouplingUMesh *mesh;
+        Nodes *nodes = nullptr;
+        Areas *areas = nullptr;
+    };
+};
+
+#endif // __SHAPERECOGNMESHBUILDER_HXX__
diff --git a/src/ShapeRecogn/ShapeRecongConstants.hxx b/src/ShapeRecogn/ShapeRecongConstants.hxx
new file mode 100644 (file)
index 0000000..801df4d
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SHAPERECOGNCONSTANTS_HXX__
+#define __SHAPERECOGNCONSTANTS_HXX__
+
+namespace MEDCoupling
+{
+    // Nodes
+    constexpr double EPSILON_PRIMITIVE = 0.005;
+    // Areas
+    // - Match
+    constexpr double TOL_MATCH_CYLINDER = 0.05;
+    constexpr double TOL_MATCH_SPHERE = 0.05;
+    // - Relative distance
+    constexpr double DELTA_PLANE = 0.05;
+    constexpr double DELTA_SPHERE = 0.05;
+    constexpr double DELTA_CYLINDER = 0.05;
+    constexpr double DELTA_CONE = 0.05;
+    // - Invalid Zones
+    constexpr double THETA_MAX_CYLINDER = 0.05;
+    // - Thresholds
+    constexpr int THRESHOLD_MIN_NB_NODES = 5;
+    constexpr int THRESHOLD_MAX_NB_AREAS = 500;
+};
+
+#endif //__SHAPERECOGNCONSTANTS_HXX__
\ No newline at end of file
diff --git a/src/ShapeRecogn/Swig/CMakeLists.txt b/src/ShapeRecogn/Swig/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8e839d1
--- /dev/null
@@ -0,0 +1,83 @@
+# Copyright (C) 2012-2024  CEA, EDF
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+FIND_PACKAGE(SWIG REQUIRED)
+INCLUDE(${SWIG_USE_FILE})
+
+ADD_DEFINITIONS(${PYTHON_DEFINITIONS})
+
+SET_SOURCE_FILES_PROPERTIES(ShapeRecogn.i PROPERTIES CPLUSPLUS ON)
+IF ("${PYTHON_VERSION_MAJOR}" STREQUAL "3")
+  SET_SOURCE_FILES_PROPERTIES(ShapeRecogn.i PROPERTIES SWIG_FLAGS "-py3")
+ELSE()
+  SET_SOURCE_FILES_PROPERTIES(ShapeRecogn.i PROPERTIES SWIG_DEFINITIONS "-shadow")
+ENDIF()
+
+SET(SWIG_MODULE_ShapeRecogn_EXTRA_FLAGS "")
+IF(MEDCOUPLING_USE_64BIT_IDS)
+  STRING(APPEND SWIG_MODULE_ShapeRecogn_EXTRA_FLAGS "-DMEDCOUPLING_USE_64BIT_IDS")
+ENDIF(MEDCOUPLING_USE_64BIT_IDS)
+
+SET (ShapeRecogn_SWIG_DPYS_FILES
+  ShapeRecogn.i
+)
+
+INCLUDE_DIRECTORIES(
+  ${PYTHON_INCLUDE_DIRS}
+  ${MEDFILE_INCLUDE_DIRS}
+  ${HDF5_INCLUDE_DIRS}
+  ${CMAKE_CURRENT_SOURCE_DIR}
+  ${CMAKE_CURRENT_SOURCE_DIR}/..
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../MEDLoader
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../MEDCoupling_Swig
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../MEDCoupling
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../INTERP_KERNEL
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../INTERP_KERNEL/Bases
+)
+
+SET (SWIG_MODULE_ShapeRecogn_EXTRA_DEPS ${ShapeRecogn_SWIG_DPYS_FILES}
+    ${shaperecogn_HEADERS_HXX}
+    ${medcoupling_HEADERS_HXX} ${medcoupling_HEADERS_TXX}
+    ${interpkernel_HEADERS_HXX} ${interpkernel_HEADERS_TXX})
+
+IF(WIN32)
+  SET_PROPERTY(SOURCE ShapeRecogn.i PROPERTY COMPILE_DEFINITIONS WIN32)
+ENDIF()
+
+IF(${CMAKE_VERSION} VERSION_LESS "3.8.0")   
+  SWIG_ADD_MODULE(ShapeRecogn python ShapeRecogn.i)
+ELSE()
+  SWIG_ADD_LIBRARY(ShapeRecogn LANGUAGE python SOURCES ShapeRecogn.i)
+ENDIF()
+
+SWIG_LINK_LIBRARIES(ShapeRecogn ${PYTHON_LIBRARIES} ${PLATFORM_LIBS} shaperecogn medloader medcouplingcpp)
+SWIG_CHECK_GENERATION(ShapeRecogn)
+IF(WIN32)
+  SET_TARGET_PROPERTIES(_ShapeRecogn PROPERTIES DEBUG_OUTPUT_NAME _ShapeRecogn_d)
+  # To increase the size of the .obj file on Windows because ShapeRecognPYTHON_wrap.cxx, generated by SWIG, is too big
+  TARGET_COMPILE_OPTIONS(_ShapeRecogn PRIVATE /bigobj) 
+ENDIF(WIN32)
+
+INSTALL(TARGETS _ShapeRecogn DESTINATION ${MEDCOUPLING_INSTALL_PYTHON})
+INSTALL(FILES ShapeRecogn.i DESTINATION ${MEDCOUPLING_INSTALL_HEADERS})
+
+SALOME_INSTALL_SCRIPTS(
+  ${CMAKE_CURRENT_BINARY_DIR}/ShapeRecogn.py 
+  ${MEDCOUPLING_INSTALL_PYTHON} 
+  EXTRA_DPYS "${SWIG_MODULE_ShapeRecogn_REAL_NAME}")
diff --git a/src/ShapeRecogn/Swig/ShapeRecogn.i b/src/ShapeRecogn/Swig/ShapeRecogn.i
new file mode 100644 (file)
index 0000000..58893b0
--- /dev/null
@@ -0,0 +1,15 @@
+%module ShapeRecogn
+
+%include "std_string.i"
+%include "MEDCouplingCommon.i"
+
+%{
+#include "ShapeRecognMesh.hxx"
+#include "ShapeRecognMeshBuilder.hxx"
+using namespace MEDCoupling;
+%}
+
+%ignore getAreas() const;
+%ignore getNodes() const;
+%include "ShapeRecognMesh.hxx"
+%include "ShapeRecognMeshBuilder.hxx"
diff --git a/src/ShapeRecogn/Test/CMakeLists.txt b/src/ShapeRecogn/Test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2dc29d5
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright (C) 2012-2024  CEA, EDF
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+INCLUDE_DIRECTORIES(
+  ${CPPUNIT_INCLUDE_DIRS}
+  ${HDF5_INCLUDE_DIRS}
+  ${MEDFILE_INCLUDE_DIRS}
+  ${CMAKE_CURRENT_SOURCE_DIR}/..
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../MEDLoader
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../MEDCoupling
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../INTERP_KERNEL
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../INTERP_KERNEL/Bases
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../INTERP_KERNELTest # For common CppUnitTest.hxx file and TestIKUtils.hxx
+  )
+
+SET(TestShapeRecogn_SOURCES
+  TestShapeRecogn.cxx
+  MathOpsTest.cxx
+  PlaneTest.cxx
+  CylinderTest.cxx
+  ConeTest.cxx
+  SphereTest.cxx
+  TorusTest.cxx
+)
+
+SALOME_ACCUMULATE_ENVIRONMENT(MEDCOUPLING_RESOURCE_DIR "${CMAKE_BINARY_DIR}/resources")
+SALOME_GENERATE_TESTS_ENVIRONMENT(tests_env)
+
+ADD_EXECUTABLE(TestShapeRecogn ${TestShapeRecogn_SOURCES})
+TARGET_LINK_LIBRARIES(TestShapeRecogn shaperecogn InterpKernelTestUtils ${CPPUNIT_LIBRARIES} ${PLATFORM_LIBS})
+
+INSTALL(TARGETS TestShapeRecogn DESTINATION ${MEDCOUPLING_INSTALL_BINS})
+
+SET(BASE_TESTS TestShapeRecogn)
+
+FOREACH(test ${BASE_TESTS})
+  ADD_TEST(NAME ${test}
+           COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/MCTestLauncher.py ${CMAKE_CURRENT_BINARY_DIR}/${test})
+  SET_TESTS_PROPERTIES(${test} PROPERTIES ENVIRONMENT "${tests_env}")
+ENDFOREACH()
+
+# Application tests
+
+SET(TEST_INSTALL_DIRECTORY ${MEDCOUPLING_INSTALL_TESTS}/ShapeRecogn)
+INSTALL(TARGETS TestShapeRecogn DESTINATION ${TEST_INSTALL_DIRECTORY})
+
+INSTALL(FILES CTestTestfileInstall.cmake
+        DESTINATION ${TEST_INSTALL_DIRECTORY}
+        RENAME CTestTestfile.cmake)
diff --git a/src/ShapeRecogn/Test/CTestTestfileInstall.cmake b/src/ShapeRecogn/Test/CTestTestfileInstall.cmake
new file mode 100644 (file)
index 0000000..ab32ede
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) 2015-2024  CEA, EDF
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#
+# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+#
+
+SET(TEST_NAMES
+  TestShapeRecogn
+)
+
+FOREACH(tfile ${TEST_NAMES})
+  SET(TEST_NAME ${COMPONENT_NAME}_${tfile})
+  ADD_TEST(${TEST_NAME} python3 MCTestLauncher.py ${tfile})
+  SET_TESTS_PROPERTIES(${TEST_NAME} PROPERTIES
+    LABELS "${COMPONENT_NAME}"
+    TIMEOUT ${TIMEOUT}
+    )
+ENDFOREACH()
diff --git a/src/ShapeRecogn/Test/ConeTest.cxx b/src/ShapeRecogn/Test/ConeTest.cxx
new file mode 100644 (file)
index 0000000..d25f4a7
--- /dev/null
@@ -0,0 +1,324 @@
+#include "ConeTest.hxx"
+
+#include "ShapeRecognMeshBuilder.hxx"
+#include "Areas.hxx"
+#include "MathOps.hxx"
+#include "TestInterpKernelUtils.hxx" // getResourceFile()
+
+using namespace MEDCoupling;
+
+void ConeTest::setUp()
+{
+    std::string file = INTERP_TEST::getResourceFile("ShapeRecognCone.med", 3);
+    srMesh = new ShapeRecognMeshBuilder(file);
+    srMesh->recognize();
+    areas = srMesh->getAreas();
+}
+
+void ConeTest::tearDown()
+{
+    if (srMesh != 0)
+        delete srMesh;
+    areas = 0;
+}
+
+void ConeTest::testNumberOfAreas()
+{
+    CPPUNIT_ASSERT_EQUAL(3, (int)areas->getNumberOfAreas());
+}
+
+void ConeTest::testComputePlaneProperties()
+{
+    const Nodes *nodes = srMesh->getNodes();
+    Areas areas(nodes);
+    mcIdType areaId = areas.addArea();
+    std::vector<mcIdType> nodeIds{560, 561, 562, 563, 564};
+    // Check the coordinates
+    std::vector<double> coordsRef{
+        -0.13274028,
+        -1.19035036,
+        7.0,
+        0.62579096,
+        -0.97231524,
+        7.0,
+        -0.75942852,
+        -0.99273623,
+        7.0,
+        0.76454326,
+        0.85588873,
+        7.0,
+        1.12546475,
+        -0.04924272,
+        7.0,
+    };
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+    {
+        mcIdType nodeId = nodeIds[i];
+        areas.addNode(areaId, nodeId);
+        std::array<double, 3> coords = nodes->getCoordinates(nodeId);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i], coords[0], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 1], coords[1], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 2], coords[2], 1E-6);
+    }
+    areas.computeProperties(areaId);
+    // Check normal
+    std::array<double, 3> normal = areas.getNormal(areaId);
+    std::array<double, 3> normalRef{0.0, 0.0, 1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(normalRef[i], normal[i], 1E-6);
+}
+
+void ConeTest::testComputeCylinderProperties()
+{
+    const Nodes *nodes = srMesh->getNodes();
+    Areas areas(nodes);
+    mcIdType areaId = areas.addArea();
+    // check coordinates
+    std::vector<double> coordsRef{
+        8.58402821e+00,
+        -2.10248053e-15,
+        1.41597179e+00,
+        7.87604232e+00,
+        -1.92907400e-15,
+        2.12395768e+00,
+        7.16805642e+00,
+        -1.75566747e-15,
+        2.83194358e+00,
+    };
+    // Add nodes
+    std::vector<mcIdType> nodeIds{26, 27, 28};
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+    {
+        mcIdType nodeId = nodeIds[i];
+        areas.addNode(areaId, nodeId);
+        std::array<double, 3> coords = nodes->getCoordinates(nodeId);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i], coords[0], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 1], coords[1], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 2], coords[2], 1E-6);
+    }
+    areas.computeProperties(areaId);
+    // Check radius
+    double radius = areas.getRadius(areaId);
+    double radiusRef = 11.016809459292505;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(radiusRef, radius, 1E-6);
+    // Check axis
+    std::array<double, 3> axis = areas.getAxis(areaId);
+    std::array<double, 3> axisRef{-2.85185021e-02, 2.47000552e-05, 9.99593264e-01};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisRef[i], axis[i], 1E-6);
+    // Check axis point
+    std::array<double, 3> axisPoint = areas.getAxisPoint(areaId);
+    std::array<double, 3> axisPointRef{9.55163389e-02, -2.27195412e-05, -5.67562579e+00};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisPointRef[i], axisPoint[i], 1E-6);
+}
+
+void ConeTest::testComputeConeProperties()
+{
+    std::srand(0);
+    const Nodes *nodes = srMesh->getNodes();
+    Areas areas(nodes);
+    mcIdType areaId = areas.addArea(PrimitiveType::Cone);
+    std::vector<double> coordsRef{
+        8.58402821e+00,
+        -2.10248053e-15,
+        1.41597179e+00,
+        7.87604232e+00,
+        -1.92907400e-15,
+        2.12395768e+00,
+        7.16805642e+00,
+        -1.75566747e-15,
+        2.83194358e+00,
+        6.46007053e+00,
+        -1.58226094e-15,
+        3.53992947e+00};
+    // Add nodes
+    std::vector<mcIdType> nodeIds{26, 27, 28, 29};
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+    {
+        mcIdType nodeId = nodeIds[i];
+        areas.addNode(areaId, nodeId);
+        std::array<double, 3> coords = nodes->getCoordinates(nodeId);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i], coords[0], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 1], coords[1], 1E-6);
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(coordsRef[3 * i + 2], coords[2], 1E-6);
+    }
+    areas.computeProperties(areaId);
+    // Radius
+    double radiusRef = 10.546213172989718;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(radiusRef, areas.getRadius(areaId), 1E-6);
+    // Angle
+    double angleRef = 0.7567719849399294;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(angleRef, areas.getAngle(areaId), 1E-6);
+    // Axis
+    std::array<double, 3> axisRef{-2.99093478e-02, 1.48776702e-05, 9.99552615e-01};
+    std::array<double, 3> axis = areas.getAxis(areaId);
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisRef[i], axis[i], 1E-6);
+    // Axis Point
+    std::array<double, 3> axisPointRef{
+        7.43242419e-02, -1.70396559e-05, -4.98890939e+00};
+    std::array<double, 3> axisPoint = areas.getAxisPoint(areaId);
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisPointRef[i], axisPoint[i], 1E-6);
+    // Apex
+    std::array<double, 3> apexRef{
+        -3.87870047e-01, 1.98282729e-04, 1.03928162e+01};
+    std::array<double, 3> apex = areas.getApex(areaId);
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(apexRef[i], apex[i], 1E-6);
+}
+
+void ConeTest::testFirstArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Plane, areas->getPrimitiveType(0));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef{
+        549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559,
+        560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570,
+        571};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(0);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // normal
+    std::array<double, 3> normal = areas->getNormal(0);
+    std::array<double, 3> normalRef{0.0, 0.0, 1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(normalRef[i], normal[i], 1E-6);
+}
+
+void ConeTest::testSecondArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Plane, areas->getPrimitiveType(0));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef = {
+        572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583,
+        584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595,
+        596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607,
+        608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619,
+        620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631,
+        632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643,
+        644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655,
+        656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667,
+        668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679,
+        680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691,
+        692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703,
+        704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715,
+        716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727,
+        728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739,
+        740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751,
+        752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763,
+        764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775,
+        776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787,
+        788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799,
+        800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811,
+        812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823,
+        824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835,
+        836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847,
+        848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859,
+        860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871,
+        872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883,
+        884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895,
+        896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907,
+        908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919,
+        920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931,
+        932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943,
+        944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955,
+        956};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(1);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // normal
+    std::array<double, 3> normal = areas->getNormal(1);
+    std::array<double, 3> normalRef = {0.0, 0.0, -1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(normalRef[i], normal[i], 1E-6);
+}
+
+void ConeTest::testThirdArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Cone, areas->getPrimitiveType(2));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef{
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+        16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+        30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
+        44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+        58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
+        72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85,
+        86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
+        100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+        111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
+        122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
+        133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+        144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
+        155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
+        166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176,
+        177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187,
+        188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198,
+        199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+        210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220,
+        221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231,
+        232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242,
+        243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253,
+        254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+        265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275,
+        276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286,
+        287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297,
+        298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308,
+        309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319,
+        320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330,
+        331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341,
+        342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352,
+        353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363,
+        364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
+        375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385,
+        386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396,
+        397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407,
+        408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418,
+        419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429,
+        430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440,
+        441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451,
+        452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462,
+        463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473,
+        474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484,
+        485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
+        496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506,
+        507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517,
+        518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528,
+        529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539,
+        540, 541, 542, 543, 544, 545, 546, 547, 548};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(2);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // radius
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(10.0, areas->getRadius(2), 5E-1);
+    // angle
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(M_PI_4, areas->getAngle(2), 1E-2);
+    // axis
+    std::array<double, 3> axis = areas->getAxis(2);
+    std::array<double, 3> axisRef{0.0, 0.0, 1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisRef[i], axis[i], 1E-2);
+    // // axis point
+    // std::array<double, 3> axisPoint = areas->getAxisPoint(2);
+    // std::array<double, 3> axisPointRef{
+    //     -6.50039462e-03, -5.76665576e-05, -3.99198765e+00};
+    // for (size_t i = 0; i < 3; ++i)
+    //     CPPUNIT_ASSERT_DOUBLES_EQUAL(
+    //         axisPointRef[i], axisPoint[i], 1E-2);
+    // apex
+    std::array<double, 3> apex = areas->getApex(2);
+    std::array<double, 3> apexRef{0.0, 0.0, 10.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(apexRef[i], apex[i], 1E-1);
+}
diff --git a/src/ShapeRecogn/Test/ConeTest.hxx b/src/ShapeRecogn/Test/ConeTest.hxx
new file mode 100644 (file)
index 0000000..20b1bef
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __CONETEST_HXX__
+#define __CONETEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class ShapeRecognMeshBuilder;
+    class Areas;
+
+    class ConeTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(ConeTest);
+        CPPUNIT_TEST(testNumberOfAreas);
+        CPPUNIT_TEST(testComputePlaneProperties);
+        CPPUNIT_TEST(testComputeCylinderProperties);
+        CPPUNIT_TEST(testComputeConeProperties);
+        CPPUNIT_TEST(testFirstArea);
+        CPPUNIT_TEST(testSecondArea);
+        CPPUNIT_TEST(testThirdArea);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        void setUp() override;
+        void tearDown() override;
+
+        void testComputePlaneProperties();
+        void testComputeCylinderProperties();
+        void testComputeConeProperties();
+
+        void testNumberOfAreas();
+        void testFirstArea();
+        void testSecondArea();
+        void testThirdArea();
+
+    private:
+        ShapeRecognMeshBuilder *srMesh = 0;
+        const Areas *areas;
+    };
+};
+
+#endif // __CONETEST_HXX__
diff --git a/src/ShapeRecogn/Test/CylinderTest.cxx b/src/ShapeRecogn/Test/CylinderTest.cxx
new file mode 100644 (file)
index 0000000..47f6ac3
--- /dev/null
@@ -0,0 +1,136 @@
+#include "CylinderTest.hxx"
+
+#include "ShapeRecognMeshBuilder.hxx"
+#include "Areas.hxx"
+#include "MathOps.hxx"
+#include "TestInterpKernelUtils.hxx" // getResourceFile()
+
+using namespace MEDCoupling;
+
+void CylinderTest::setUp()
+{
+    std::string file = INTERP_TEST::getResourceFile("ShapeRecognCylinder.med", 3);
+    srMesh = new ShapeRecognMeshBuilder(file);
+    srMesh->recognize();
+    areas = srMesh->getAreas();
+}
+
+void CylinderTest::tearDown()
+{
+    if (srMesh != 0)
+        delete srMesh;
+    areas = 0;
+}
+
+void CylinderTest::testNumberOfAreas()
+{
+    CPPUNIT_ASSERT_EQUAL(3, (int)areas->getNumberOfAreas());
+}
+
+void CylinderTest::testFirstArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Cylinder, areas->getPrimitiveType(0));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef{
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+        17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+        32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
+        47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+        62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,
+        77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
+        92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+        105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115,
+        116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126,
+        127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137,
+        138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148,
+        149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+        160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
+        171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181,
+        182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192,
+        193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
+        204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214,
+        215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225,
+        226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236,
+        237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247,
+        248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258,
+        259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269,
+        270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280,
+        281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291,
+        292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302,
+        303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313,
+        314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
+        325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335,
+        336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346,
+        347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357,
+        358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368,
+        369};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(0);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // radius
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(4.993494657779537, areas->getRadius(0), 1E-2);
+    // axis
+    std::array<double, 3> axis = areas->getAxis(0);
+    std::array<double, 3> axisRef{7.66631075e-04, -1.59966800e-04, 9.99999693e-01};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(axisRef[i], axis[i], 1E-4);
+    // axis point
+    std::array<double, 3> axisPoint = areas->getAxisPoint(0);
+    std::array<double, 3> axisPointRef{3.78927537e-03, -2.03409415e-03, 5.03355802e+00};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(
+            axisPointRef[i], axisPoint[i], 1E-2);
+}
+
+void CylinderTest::testSecondArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Plane, areas->getPrimitiveType(1));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef{
+        370, 371, 372, 373, 374, 375, 376, 377, 378, 379,
+        380, 381, 382, 383, 384, 385, 386, 387, 388, 389,
+        390, 391, 392, 393, 394, 395, 396, 397, 398, 399,
+        400, 401, 402, 403, 404, 405, 406, 407, 408, 409,
+        410, 411, 412, 413, 414, 415, 416, 417, 418, 419,
+        420, 421, 422, 423, 424, 425, 426, 427, 428, 429,
+        430, 431, 432, 433, 434, 435, 436, 437, 438, 439,
+        440, 441, 442};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(1);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // normal
+    std::array<double, 3> normal = areas->getNormal(1);
+    std::array<double, 3> normalRef{0.0, 0.0, 1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(normalRef[i], normal[i], 1E-6);
+}
+
+void CylinderTest::testThirdArea()
+{
+    // primitive type
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Plane, areas->getPrimitiveType(2));
+    // node ids
+    std::vector<mcIdType> nodeIdsRef{
+        443, 444, 445, 446, 447, 448, 449, 450, 451, 452,
+        453, 454, 455, 456, 457, 458, 459, 460, 461, 462,
+        463, 464, 465, 466, 467, 468, 469, 470, 471, 472,
+        473, 474, 475, 476, 477, 478, 479, 480, 481, 482,
+        483, 484, 485, 486, 487, 488, 489, 490, 491, 492,
+        493, 494, 495, 496, 497, 498, 499};
+    std::vector<mcIdType> nodeIds = areas->getNodeIds(2);
+    std::sort(nodeIds.begin(), nodeIds.end());
+    CPPUNIT_ASSERT_EQUAL(nodeIdsRef.size(), nodeIds.size());
+    for (size_t i = 0; i < nodeIds.size(); ++i)
+        CPPUNIT_ASSERT_EQUAL(nodeIdsRef[i], nodeIds[i]);
+    // normal
+    std::array<double, 3> normal = areas->getNormal(2);
+    std::array<double, 3> normalRef{0.0, 0.0, -1.0};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(normalRef[i], normal[i], 1E-6);
+}
diff --git a/src/ShapeRecogn/Test/CylinderTest.hxx b/src/ShapeRecogn/Test/CylinderTest.hxx
new file mode 100644 (file)
index 0000000..a35ac14
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __CYLINDERTEST_HXX__
+#define __CYLINDERTEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class ShapeRecognMeshBuilder;
+    class Areas;
+
+    class CylinderTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(CylinderTest);
+        CPPUNIT_TEST(testNumberOfAreas);
+        CPPUNIT_TEST(testFirstArea);
+        CPPUNIT_TEST(testSecondArea);
+        CPPUNIT_TEST(testThirdArea);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        void setUp() override;
+        void tearDown() override;
+
+        void testNumberOfAreas();
+        void testFirstArea();
+        void testSecondArea();
+        void testThirdArea();
+
+    private:
+        ShapeRecognMeshBuilder *srMesh = 0;
+        const Areas *areas;
+    };
+};
+
+#endif // __CYLINDERTEST_HXX__
diff --git a/src/ShapeRecogn/Test/MathOpsTest.cxx b/src/ShapeRecogn/Test/MathOpsTest.cxx
new file mode 100644 (file)
index 0000000..033f992
--- /dev/null
@@ -0,0 +1,97 @@
+#include "MathOpsTest.hxx"
+#include "MathOps.hxx"
+
+using namespace MEDCoupling;
+
+void MathOpsTest::testLstsq()
+{
+    std::vector<double> a = {
+        0.69473263, 0.83318004, 0.60822673, 0.59243878, 0.82872553,
+        0.84048546, 0.95698819, 0.02171218, 0.27683381, 0.20628928,
+        0.80895323, 0.4207767, 0.37468575, 0.86258204, 0.42571846};
+    std::vector<double> b = {
+        0.91167508, 0.95878824, 0.7234827, 0.51753917, 0.18603306};
+    std::vector<double> x = MathOps::lstsq(a, b);
+    std::array<double, 3> xRef = {0.35719095, 0.5134345, 0.26039343};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(xRef[i], x[i], 1E-6);
+}
+
+void MathOpsTest::testLstsq2()
+{
+    std::vector<double> a = {
+        0.4564562, 0.3517006,
+        0.28928215, 0.72309086,
+        0.05944836, 0.56024464};
+    std::vector<double> b = {0.98902712, 0.46791812};
+    std::vector<double> x = MathOps::lstsq(a, b);
+    std::array<double, 3> xRef = {2.10752524, 0.2636243, -0.82807416};
+    for (size_t i = 0; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(xRef[i], x[i], 1E-6);
+}
+
+void MathOpsTest::testLstsqBig()
+{
+    std::vector<double> a(5000000 * 3, 1.0);
+    std::vector<double> b(5000000, 1.0);
+    std::vector<double> x = MathOps::lstsq(a, b);
+    std::array<double, 3> xRef = {1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0};
+    for (size_t i = 2; i < 3; ++i)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(xRef[i], x[i], 1E-6);
+}
+
+void MathOpsTest::testComputeCov()
+{
+    std::vector<double> coordinates{
+        1, 2, -3, -8, 6, -3, 9, 2, -1, -10, -11, -120};
+    std::array<double, 9>
+        covMatrix = MathOps::computeCov(coordinates);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(76.666666, covMatrix[0], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(26.666666, covMatrix[1], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(319.333333, covMatrix[2], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(26.666666, covMatrix[3], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(54.916666, covMatrix[4], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(420.75, covMatrix[5], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(319.333333, covMatrix[6], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(420.75, covMatrix[7], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(3462.25, covMatrix[8], 1E-6);
+}
+
+void MathOpsTest::testComputePCAFirstAxis()
+{
+    std::vector<double> coordinates{
+        1, 2, -3, -8, 6, -3, 9, 2, -1, -10, -11, -120};
+    std::array<double, 3>
+        axis = MathOps::computePCAFirstAxis(coordinates);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.09198798, axis[0], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.11994164, axis[1], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.9885101, axis[2], 1E-6);
+}
+
+void MathOpsTest::testComputeAngles()
+{
+    std::vector<double> directions{
+        1, 2, -3, -8, 6, -3, 9, 2, -1, -10, -11, -120};
+    std::array<double, 3> axis{1, 2, 3};
+    std::vector<double> angles = MathOps::computeAngles(
+        directions, axis);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.86054803, angles[0], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.69914333, angles[1], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.27845478, angles[2], 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(2.61880376, angles[3], 1E-6);
+}
+
+void MathOpsTest::testComputeBaseFromNormal()
+{
+    std::array<double, 3> normal = {1.0, 2.0, 3.0};
+    std::array<double, 6> base = MathOps::computeBaseFromNormal(normal);
+    std::array<double, 6> baseRef = {
+        -0.53452248, 0.77454192, -0.33818712,
+        -0.80178373, -0.33818712, 0.49271932};
+    for (size_t i = 0; i < 6; ++i)
+    {
+        std::ostringstream message;
+        message << "Mismatch at index " << i;
+        CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(message.str().c_str(), baseRef[i], base[i], 1E-6);
+    }
+}
diff --git a/src/ShapeRecogn/Test/MathOpsTest.hxx b/src/ShapeRecogn/Test/MathOpsTest.hxx
new file mode 100644 (file)
index 0000000..2988a95
--- /dev/null
@@ -0,0 +1,51 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __MATHOPSTEST_HXX__
+#define __MATHOPSTEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class MathOpsTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(MathOpsTest);
+        CPPUNIT_TEST(testLstsq);
+        CPPUNIT_TEST(testLstsq2);
+        CPPUNIT_TEST(testLstsqBig);
+        CPPUNIT_TEST(testComputeCov);
+        CPPUNIT_TEST(testComputePCAFirstAxis);
+        CPPUNIT_TEST(testComputeAngles);
+        CPPUNIT_TEST(testComputeBaseFromNormal);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        static void testLstsq();
+        static void testLstsq2();
+        static void testLstsqBig();
+        static void testComputeCov();
+        static void testComputePCAFirstAxis();
+        static void testComputeAngles();
+        static void testComputeBaseFromNormal();
+    };
+};
+
+#endif // __MATHOPSTEST_HXX__
diff --git a/src/ShapeRecogn/Test/PlaneTest.cxx b/src/ShapeRecogn/Test/PlaneTest.cxx
new file mode 100644 (file)
index 0000000..6f47f16
--- /dev/null
@@ -0,0 +1,44 @@
+#include "PlaneTest.hxx"
+
+#include "ShapeRecognMeshBuilder.hxx"
+#include "Areas.hxx"
+#include "MathOps.hxx"
+#include "TestInterpKernelUtils.hxx" // getResourceFile()
+
+using namespace MEDCoupling;
+
+void PlaneTest::setUp()
+{
+    std::string file = INTERP_TEST::getResourceFile("ShapeRecognPlane.med", 3);
+    srMesh = new ShapeRecognMeshBuilder(file);
+    srMesh->recognize();
+    areas = srMesh->getAreas();
+}
+
+void PlaneTest::tearDown()
+{
+    if (srMesh != 0)
+        delete srMesh;
+    areas = 0;
+}
+
+void PlaneTest::testArea()
+{
+    CPPUNIT_ASSERT_EQUAL(36, (int)areas->getNumberOfNodes(0));
+    CPPUNIT_ASSERT_EQUAL(1, (int)areas->getNumberOfAreas());
+    // Normal
+    std::array<double, 3> normal = areas->getNormal(0);
+    std::array<double, 3> normalRef = {0.781525, 0.310606, -0.541056};
+    std::array<double, 3> affinePoint = areas->getAffinePoint(0);
+    double proportion0 = normal[0] / normalRef[0];
+    double proportion1 = normal[1] / normalRef[1];
+    double proportion2 = normal[2] / normalRef[2];
+    double proportion3 = MathOps::dot(normal, affinePoint) / MathOps::dot(normalRef, affinePoint);
+    // Check proportions between the normal vectors of the two planes
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(proportion0, proportion1, 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(proportion1, proportion2, 1E-6);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(proportion2, proportion3, 1E-6);
+    // Check the angle
+    double angle = MathOps::computeAngle(normal, normalRef);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, angle, 1E-6);
+}
\ No newline at end of file
diff --git a/src/ShapeRecogn/Test/PlaneTest.hxx b/src/ShapeRecogn/Test/PlaneTest.hxx
new file mode 100644 (file)
index 0000000..af6eb47
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __PLANETEST_HXX__
+#define __PLANETEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class ShapeRecognMeshBuilder;
+    class Areas;
+
+    class PlaneTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(PlaneTest);
+        CPPUNIT_TEST(testArea);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        void setUp() override;
+        void tearDown() override;
+
+        void testArea();
+
+    private:
+        ShapeRecognMeshBuilder *srMesh = 0;
+        const Areas *areas;
+    };
+};
+
+#endif // __PLANETEST_HXX__
diff --git a/src/ShapeRecogn/Test/SphereTest.cxx b/src/ShapeRecogn/Test/SphereTest.cxx
new file mode 100644 (file)
index 0000000..4d7f64e
--- /dev/null
@@ -0,0 +1,36 @@
+#include "SphereTest.hxx"
+
+#include "ShapeRecognMeshBuilder.hxx"
+#include "Areas.hxx"
+#include "MathOps.hxx"
+#include "TestInterpKernelUtils.hxx" // getResourceFile()
+
+using namespace MEDCoupling;
+
+void SphereTest::setUp()
+{
+    std::string file = INTERP_TEST::getResourceFile("ShapeRecognSphere.med", 3);
+    srMesh = new ShapeRecognMeshBuilder(file);
+    srMesh->recognize();
+    areas = srMesh->getAreas();
+}
+
+void SphereTest::tearDown()
+{
+    if (srMesh != 0)
+        delete srMesh;
+    areas = 0;
+}
+
+void SphereTest::testArea()
+{
+    CPPUNIT_ASSERT_EQUAL(1, (int)areas->getNumberOfAreas());
+    // 8 double nodes so 147 - 6 nodes
+    CPPUNIT_ASSERT_EQUAL(141, (int)areas->getNumberOfNodes(0));
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Sphere, areas->getPrimitiveType(0));
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.0, areas->getRadius(0), 1E-2);
+    std::array<double, 3> centerRef = {5.3, -6.7, -9.02};
+    std::array<double, 3> center = areas->getCenter(0);
+    for (size_t j = 0; j < 3; ++j)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(centerRef[j], center[j], 1E-2);
+}
diff --git a/src/ShapeRecogn/Test/SphereTest.hxx b/src/ShapeRecogn/Test/SphereTest.hxx
new file mode 100644 (file)
index 0000000..6500def
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __SPHERETEST_HXX__
+#define __SPHERETEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class ShapeRecognMeshBuilder;
+    class Areas;
+
+    class SphereTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(SphereTest);
+        CPPUNIT_TEST(testArea);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        void setUp() override;
+        void tearDown() override;
+
+        void testArea();
+
+    private:
+        ShapeRecognMeshBuilder *srMesh = 0;
+        const Areas *areas;
+    };
+};
+
+#endif // __SPHERETEST_HXX__
diff --git a/src/ShapeRecogn/Test/TestShapeRecogn.cxx b/src/ShapeRecogn/Test/TestShapeRecogn.cxx
new file mode 100644 (file)
index 0000000..b7cd2af
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#include "MathOpsTest.hxx"
+#include "PlaneTest.hxx"
+#include "CylinderTest.hxx"
+#include "ConeTest.hxx"
+#include "SphereTest.hxx"
+#include "TorusTest.hxx"
+
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::MathOpsTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::PlaneTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::CylinderTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::ConeTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::SphereTest);
+CPPUNIT_TEST_SUITE_REGISTRATION(MEDCoupling::TorusTest);
+
+#include "BasicMainTest.hxx"
diff --git a/src/ShapeRecogn/Test/TorusTest.cxx b/src/ShapeRecogn/Test/TorusTest.cxx
new file mode 100644 (file)
index 0000000..6e24d10
--- /dev/null
@@ -0,0 +1,38 @@
+#include "TorusTest.hxx"
+
+#include "ShapeRecognMeshBuilder.hxx"
+#include "Areas.hxx"
+#include "MathOps.hxx"
+#include "TestInterpKernelUtils.hxx" // getResourceFile()
+
+using namespace MEDCoupling;
+
+void TorusTest::setUp()
+{
+    std::string file = INTERP_TEST::getResourceFile("ShapeRecognTorus.med", 3);
+    srMesh = new ShapeRecognMeshBuilder(file);
+    srMesh->recognize();
+    areas = srMesh->getAreas();
+}
+
+void TorusTest::tearDown()
+{
+    if (srMesh != 0)
+        delete srMesh;
+    areas = 0;
+}
+
+void TorusTest::testArea()
+{
+    CPPUNIT_ASSERT_EQUAL(275, (int)srMesh->getNodes()->getNbNodes());
+    CPPUNIT_ASSERT_EQUAL(1, (int)areas->getNumberOfAreas());
+    CPPUNIT_ASSERT_EQUAL(PrimitiveType::Torus, areas->getPrimitiveType(0));
+    // Some nodes are unknown
+    CPPUNIT_ASSERT_EQUAL(272, (int)areas->getNumberOfNodes(0));
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.843297, areas->getMinorRadius(0), 1E-2);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1.156428, areas->getRadius(0), 1E-1);
+    std::array<double, 3> centerRef = {7.687022, -3.726887, -9.02};
+    std::array<double, 3> center = areas->getCenter(0);
+    for (size_t j = 0; j < 3; ++j)
+        CPPUNIT_ASSERT_DOUBLES_EQUAL(centerRef[j], center[j], 1E-2);
+}
diff --git a/src/ShapeRecogn/Test/TorusTest.hxx b/src/ShapeRecogn/Test/TorusTest.hxx
new file mode 100644 (file)
index 0000000..07ae86d
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright (C) 2007-2024  CEA, EDF
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+//
+// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
+//
+
+#ifndef __TORUSTEST_HXX__
+#define __TORUSTEST_HXX__
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+namespace MEDCoupling
+{
+    class ShapeRecognMeshBuilder;
+    class Areas;
+
+    class TorusTest : public CppUnit::TestFixture
+    {
+        CPPUNIT_TEST_SUITE(TorusTest);
+        CPPUNIT_TEST(testArea);
+        CPPUNIT_TEST_SUITE_END();
+
+    public:
+        void setUp() override;
+        void tearDown() override;
+
+        void testArea();
+
+    private:
+        ShapeRecognMeshBuilder *srMesh = 0;
+        const Areas *areas;
+    };
+};
+
+#endif // __TORUSTEST_HXX__