From bb45a57153eb55e5a94009649b27bbde53afaf23 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 8 Jan 2026 18:05:25 -0800 Subject: [PATCH 1/3] Update with orientation fix --- src/axom/quest/io/STEPReader.cpp | 47 ++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 9aaafd4590..857349b941 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -123,7 +123,7 @@ class StepFileProcessor return bbox; } - /// Helper class to convert faces of CAD mesh to valid NURBSPatch instances + /// Helper class to convert the face geometry of CAD mesh to valid NURBSPatch instances /// The constructor converts the surface to a clamped (non-periodic) representation, if necessary class PatchProcessor { @@ -142,8 +142,8 @@ class StepFileProcessor const opencascade::handle& getSurface() const { return m_surface; } - /// Returns a representation of the surface as an axom::primal::NURBSPatch - NPatch nurbsPatch() const + /// Returns a representation of the surface geometry as an axom::primal::NURBSPatch + NPatch nurbsPatchGeometry() const { // Check if the surface is periodic in u or v const bool isUPeriodic = m_surface->IsUPeriodic(); @@ -679,7 +679,13 @@ class StepFileProcessor PatchProcessor patchProcessor(bsplineSurface, m_verbose); - patches[patchIndex] = patchProcessor.nurbsPatch(); + patches[patchIndex] = patchProcessor.nurbsPatchGeometry(); + + // If the face is flipped in opencascade, we need to flip the primal primitive too + if(face.Orientation() == TopAbs_REVERSED) + { + patches[patchIndex].reverseOrientation_u(); + } PatchData& patchData = m_patchData[patchIndex]; patchData.patchIndex = patchIndex; @@ -931,23 +937,10 @@ class StepFileProcessor } } - // Ensure that the trimming curves form ccw loops - if(patch.isTrimmed()) + // If the face is flipped, then the trimming curves all need to be reversed too + if(TopoDS::Face(faceExp.Current()).Orientation() == TopAbs_Orientation::TopAbs_REVERSED) { - auto area_field = [](PointType x) -> VectorType { - return primal::Vector({-0.5 * x[1], 0.5 * x[0]}); - }; - - constexpr int n_quad_pts = 20; - auto area = - primal::evaluate_vector_line_integral(patch.getTrimmingCurves(), area_field, n_quad_pts); - - // Signed areas should be positive - if(area < 0) - { - patch.reverseOrientation_u(); - patch.reverseTrimmingCurves(); - } + patch.reverseTrimmingCurves(); } } } @@ -1146,6 +1139,8 @@ class PatchTriangulator { TopoDS_Face face = TopoDS::Face(faceExp.Current()); + const bool isReversed = (face.Orientation() == TopAbs_Orientation::TopAbs_REVERSED); + // Create a triangulation of this patch TopLoc_Location loc; opencascade::handle triangulation = BRep_Tool::Triangulation(face, loc); @@ -1166,6 +1161,11 @@ class PatchTriangulator int n1, n2, n3; triangle.Get(n1, n2, n3); + if(isReversed) + { + std::swap(n1, n3); + } + gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); @@ -1199,6 +1199,8 @@ class PatchTriangulator { TopoDS_Face face = TopoDS::Face(faceExp.Current()); + const bool isReversed = (face.Orientation() == TopAbs_Orientation::TopAbs_REVERSED); + // Get the underlying surface of the face opencascade::handle surface = BRep_Tool::Surface(face); @@ -1236,6 +1238,11 @@ class PatchTriangulator int n1, n2, n3; triangle.Get(n1, n2, n3); + if(isReversed) + { + std::swap(n1, n3); + } + gp_Pnt p1 = triangulation->Node(n1).Transformed(trsf); gp_Pnt p2 = triangulation->Node(n2).Transformed(trsf); gp_Pnt p3 = triangulation->Node(n3).Transformed(trsf); From 3535194ba02e94d402b17a57de842fb1cdc3cad1 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 8 Jan 2026 18:08:39 -0800 Subject: [PATCH 2/3] merge develop --- ...tor-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake | 150 +++++++ ...or-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake | 152 +++++++ .../quest/detail/clipping/Plane3DClipper.cpp | 412 ++++++++++++++++++ .../quest/detail/clipping/Plane3DClipper.hpp | 111 +++++ .../quest/detail/clipping/SphereClipper.cpp | 286 ++++++++++++ .../quest/detail/clipping/SphereClipper.hpp | 88 ++++ src/axom/slic/tests/slic_scoped_abort.cpp | 48 ++ src/tools/svg2contours/pyproject.toml | 9 + 8 files changed, 1256 insertions(+) create mode 100644 host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake create mode 100644 host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake create mode 100644 src/axom/quest/detail/clipping/Plane3DClipper.cpp create mode 100644 src/axom/quest/detail/clipping/Plane3DClipper.hpp create mode 100644 src/axom/quest/detail/clipping/SphereClipper.cpp create mode 100644 src/axom/quest/detail/clipping/SphereClipper.hpp create mode 100644 src/axom/slic/tests/slic_scoped_abort.cpp create mode 100644 src/tools/svg2contours/pyproject.toml diff --git a/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake new file mode 100644 index 0000000000..6628791d32 --- /dev/null +++ b/host-configs/rzvector-toss_4_x86_64_ib-gcc@13.3.1_cuda.cmake @@ -0,0 +1,150 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/blt-develop-y4lowmbigfrmwmadjiome5skenhjmppo;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/c2c-1.8.0-yjnetcavimy5hqnzfpgo5yd3sgp6d7em;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/caliper-git.527e1ae3a16487e425707e928e05c43d372e144f_master-32xfra57ycz6e4swxzzlcftvtyiigxq3;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/conduit-0.9.5-5okkjejyikqzdsuolsem6nrgh5wbxtji;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/mfem-4.9.0-ncjsyulbxhtq4ecbwkp2ymvi37w5yblp;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/raja-2025.09.0-v7pmqpv2eujl7gxfidt2lnzsk4rjzc66;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/umpire-2025.09.0-dm4xnkv4qzfnlb5dydf3n674me36dptt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/adiak-0.4.0-ecormlgrlf7kstyjdei2byoghuailmzh;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/libunwind-1.8.1-dy7qlg4lcvlr2m6vn3roajjiwfkgscv2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/hdf5-1.8.23-3shxsbxuy2tsvr4s7zzj7d54o4wowfeu;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/parmetis-4.0.3-4zgk2ygmcjgnexs4haf52gwzwxdgehrv;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/hypre-2.27.0-2xbyhidzio7nypdsprobvu4ubndyjder;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/camp-2025.09.2-hggt5bhm3lmgyb72vjl3of42hglqacc5;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/fmt-11.0.2-usm3fj6qxr75ga5p2ydlhearxsbtpckd;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/metis-5.1.0-nbxqo5m7iy3uyjd32skqzpa4d5cev2dt;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/gcc-runtime-13.3.1-uv6f6pcmzth37hskwtrhtbmbujh53k6y;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/usr/tce;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/gcc-13.3.1/cppcheck-2.18.0-d2tmb7ls2d5vgh6vhcyohecwcu6msopb;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/doxygen-1.13.2;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/axom-develop-qm4vtbew3gwpn2im4xpqiaukfgo4ot5f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/axom-develop-qm4vtbew3gwpn2im4xpqiaukfgo4ot5f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/axom-develop-qm4vtbew3gwpn2im4xpqiaukfgo4ot5f/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1/axom-develop-qm4vtbew3gwpn2im4xpqiaukfgo4ot5f/lib64;;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: gcc@13.3.1/hpiynx6mdjb57v77us3h6yyzxymdbr6d +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/gcc/gcc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/gcc/g++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/gcc/gfortran" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/gcc/gcc-13.3.1/bin/gcc" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/gcc/gcc-13.3.1/bin/g++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/gcc/gcc-13.3.1/bin/gfortran" CACHE PATH "") + +endif() + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-gcc-13.3.1/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +set(MPIEXEC_EXECUTABLE "/usr/bin/srun" CACHE PATH "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# Cuda +#------------------------------------------------ + +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") + +set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") + +set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") + +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") + +set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") + +set(CMAKE_CUDA_FLAGS "" CACHE STRING "") + +set(ENABLE_CUDA ON CACHE BOOL "") + +set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") + +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) + +# nvcc does not like gtest's 'pthreads' flag + +set(gtest_disable_pthreads ON CACHE BOOL "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/gcc-13.3.1" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-5okkjejyikqzdsuolsem6nrgh5wbxtji" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-yjnetcavimy5hqnzfpgo5yd3sgp6d7em" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-ncjsyulbxhtq4ecbwkp2ymvi37w5yblp" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-3shxsbxuy2tsvr4s7zzj7d54o4wowfeu" CACHE PATH "") + +set(LUA_DIR "/usr" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-2025.09.0-v7pmqpv2eujl7gxfidt2lnzsk4rjzc66" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.09.0-dm4xnkv4qzfnlb5dydf3n674me36dptt" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-ecormlgrlf7kstyjdei2byoghuailmzh" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.527e1ae3a16487e425707e928e05c43d372e144f_master-32xfra57ycz6e4swxzzlcftvtyiigxq3" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-2025.09.2-hggt5bhm3lmgyb72vjl3of42hglqacc5" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/clang/clang-19.1.3/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/sphinx-build" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-d2tmb7ls2d5vgh6vhcyohecwcu6msopb/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/doxygen-1.13.2/bin/doxygen" CACHE PATH "") + + diff --git a/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake new file mode 100644 index 0000000000..abd6a2d6a9 --- /dev/null +++ b/host-configs/rzvector-toss_4_x86_64_ib-llvm@19.1.3_cuda.cmake @@ -0,0 +1,152 @@ +#------------------------------------------------------------------------------ +# !!!! This is a generated file, edit at own risk !!!! +#------------------------------------------------------------------------------ +# CMake executable path: /usr/tce/bin/cmake +#------------------------------------------------------------------------------ + +set(CMAKE_PREFIX_PATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/blt-develop-uphkp6ropgb55g3wisq57xwuovrrqrpa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/c2c-1.8.0-cdecs2lvvxzf6yp7hvmrhmabnu3uis2y;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/caliper-git.527e1ae3a16487e425707e928e05c43d372e144f_master-fb23m3s7hcjmjecywju2hnsdscwtbuq4;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/conduit-0.9.5-zmxmjvii4djhglvt6yvnapnfyuwm433l;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/mfem-4.9.0-7pp5u3ofxhzq6z24d5o7bf6n5gzi43pa;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/raja-2025.09.0-7zyyq5hg5zscsbuyusyrmyhmsyoe22hj;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/umpire-2025.09.0-jn7bzeenwte5b3vxugtnxq3d6ptvtjnm;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/adiak-0.4.0-5ng2vyz32c3qzwdhdymu4hay3r43axob;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/libunwind-1.8.1-6wx2v2yhfwv726wgc6iou4lbgodmaidc;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/hdf5-1.8.23-vg64z33f2srhqgxrdf4rd2jqul325f37;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/parmetis-4.0.3-tb6nliqnpfrqyfi26dkoshlf6n7ok6l2;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/hypre-2.27.0-7hnlmejgmu62ovco5qxfhmu4kdl3v35g;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/camp-2025.09.2-5tkrmojkdzp4fhu67dcxi6nix4wfwzn6;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/fmt-11.0.2-klyhttb7ma3ny2ksm6ywvoorqr6nrhie;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/metis-5.1.0-2pu2cesnzoxhc53feelz6xnfos4rz43a;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/gcc-runtime-13.3.1-uv6f6pcmzth37hskwtrhtbmbujh53k6y;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5;/usr/tce;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/gcc-13.3.1/cppcheck-2.18.0-d2tmb7ls2d5vgh6vhcyohecwcu6msopb;/usr/tce/packages/cuda/cuda-12.9.1;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/doxygen-1.13.2;/usr/tce/packages/gcc/gcc-13.3.1;/usr/tce/packages/clang/clang-19.1.3;/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3;/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37/view/python-3.13.5" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH "ON" CACHE STRING "") + +set(CMAKE_BUILD_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/axom-develop-7hsiqp53ocgbsewdhmckiryccyu6s5ct/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/axom-develop-7hsiqp53ocgbsewdhmckiryccyu6s5ct/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_INSTALL_RPATH "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/axom-develop-7hsiqp53ocgbsewdhmckiryccyu6s5ct/lib;/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3/axom-develop-7hsiqp53ocgbsewdhmckiryccyu6s5ct/lib64;;/usr/tce/backend/installations/linux-rhel8-x86_64/gcc-13.3.1/llvm-19.1.3-gy2lu5xbi4csr2k47emlajzfs5mlsd4g/lib/x86_64-unknown-linux-gnu;/opt/rh/gcc-toolset-13/root/usr/lib/gcc/x86_64-redhat-linux/13;/usr/tce/packages/clang/clang-19.1.3/lib;/collab/usr/global/tools/tce4/packages/gcc/gcc-13.3.1/lib/gcc/x86_64-redhat-linux/13" CACHE STRING "") + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "") + +#------------------------------------------------------------------------------ +# Compilers +#------------------------------------------------------------------------------ +# Compiler Spec: llvm@19.1.3/bmskmshy3mzlmo37b2pxkt76vqcffxvp +#------------------------------------------------------------------------------ +if(DEFINED ENV{SPACK_CC}) + + set(CMAKE_C_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/clang/clang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/clang/clang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/none-none/compiler-wrapper-1.0-hxzxhboumtniaqs7qivgliluv4h2lbxt/libexec/spack/gcc/gfortran" CACHE PATH "") + +else() + + set(CMAKE_C_COMPILER "/usr/tce/packages/clang/clang-19.1.3-magic/bin/clang" CACHE PATH "") + + set(CMAKE_CXX_COMPILER "/usr/tce/packages/clang/clang-19.1.3-magic/bin/clang++" CACHE PATH "") + + set(CMAKE_Fortran_COMPILER "/usr/tce/packages/gcc/gcc-13.3.1/bin/gfortran" CACHE PATH "") + +endif() + +set(CMAKE_Fortran_FLAGS "-fPIC" CACHE STRING "") + +set(ENABLE_FORTRAN ON CACHE BOOL "") + +set(BLT_EXE_LINKER_FLAGS " -Xlinker -rpath -Xlinker /usr/tce/packages/clang/clang-19.1.3-magic/lib" CACHE STRING "Adds a missing libstdc++ rpath") + +#------------------------------------------------------------------------------ +# MPI +#------------------------------------------------------------------------------ + +set(MPI_C_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3/bin/mpicc" CACHE PATH "") + +set(MPI_CXX_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3/bin/mpicxx" CACHE PATH "") + +set(MPI_Fortran_COMPILER "/usr/tce/packages/mvapich2/mvapich2-2.3.7-clang-19.1.3/bin/mpif90" CACHE PATH "") + +set(MPIEXEC_NUMPROC_FLAG "-n" CACHE STRING "") + +set(ENABLE_MPI ON CACHE BOOL "") + +set(MPIEXEC_EXECUTABLE "/usr/bin/srun" CACHE PATH "") + +#------------------------------------------------------------------------------ +# Hardware +#------------------------------------------------------------------------------ + +#------------------------------------------------ +# Cuda +#------------------------------------------------ + +set(CUDAToolkit_ROOT "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") + +set(CMAKE_CUDA_COMPILER "${CUDAToolkit_ROOT}/bin/nvcc" CACHE PATH "") + +set(CMAKE_CUDA_HOST_COMPILER "${CMAKE_CXX_COMPILER}" CACHE PATH "") + +set(CUDA_TOOLKIT_ROOT_DIR "/usr/tce/packages/cuda/cuda-12.9.1" CACHE PATH "") + +set(CMAKE_CUDA_ARCHITECTURES "90" CACHE STRING "") + +set(CMAKE_CUDA_FLAGS "" CACHE STRING "") + +set(ENABLE_CUDA ON CACHE BOOL "") + +set(CMAKE_CUDA_SEPARABLE_COMPILATION ON CACHE BOOL "") + +set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -restrict --expt-extended-lambda " CACHE STRING "" FORCE) + +# nvcc does not like gtest's 'pthreads' flag + +set(gtest_disable_pthreads ON CACHE BOOL "") + +#------------------------------------------------ +# Hardware Specifics +#------------------------------------------------ + +set(ENABLE_OPENMP ON CACHE BOOL "") + +set(ENABLE_GTEST_DEATH_TESTS ON CACHE BOOL "") + +#------------------------------------------------------------------------------ +# TPLs +#------------------------------------------------------------------------------ + +set(TPL_ROOT "/usr/WS1/axom/libs/toss_4_x86_64_ib/2025_12_23_21_12_34/llvm-19.1.3" CACHE PATH "") + +set(CONDUIT_DIR "${TPL_ROOT}/conduit-0.9.5-zmxmjvii4djhglvt6yvnapnfyuwm433l" CACHE PATH "") + +set(C2C_DIR "${TPL_ROOT}/c2c-1.8.0-cdecs2lvvxzf6yp7hvmrhmabnu3uis2y" CACHE PATH "") + +set(MFEM_DIR "${TPL_ROOT}/mfem-4.9.0-7pp5u3ofxhzq6z24d5o7bf6n5gzi43pa" CACHE PATH "") + +set(HDF5_DIR "${TPL_ROOT}/hdf5-1.8.23-vg64z33f2srhqgxrdf4rd2jqul325f37" CACHE PATH "") + +set(LUA_DIR "/usr" CACHE PATH "") + +set(RAJA_DIR "${TPL_ROOT}/raja-2025.09.0-7zyyq5hg5zscsbuyusyrmyhmsyoe22hj" CACHE PATH "") + +set(UMPIRE_DIR "${TPL_ROOT}/umpire-2025.09.0-jn7bzeenwte5b3vxugtnxq3d6ptvtjnm" CACHE PATH "") + +# OPENCASCADE not built + +set(ADIAK_DIR "${TPL_ROOT}/adiak-0.4.0-5ng2vyz32c3qzwdhdymu4hay3r43axob" CACHE PATH "") + +set(CALIPER_DIR "${TPL_ROOT}/caliper-git.527e1ae3a16487e425707e928e05c43d372e144f_master-fb23m3s7hcjmjecywju2hnsdscwtbuq4" CACHE PATH "") + +set(CAMP_DIR "${TPL_ROOT}/camp-2025.09.2-5tkrmojkdzp4fhu67dcxi6nix4wfwzn6" CACHE PATH "") + +# scr not built + +#------------------------------------------------------------------------------ +# Devtools +#------------------------------------------------------------------------------ + +set(DEVTOOLS_ROOT "/collab/usr/gapps/axom/devtools/toss_4_x86_64_ib/2025_09_25_11_33_37" CACHE PATH "") + +set(CLANGFORMAT_EXECUTABLE "/usr/tce/packages/clang/clang-19.1.3/bin/clang-format" CACHE PATH "") + +set(Python_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/python3" CACHE PATH "") + +set(JSONSCHEMA_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/jsonschema" CACHE PATH "") + +set(ENABLE_DOCS ON CACHE BOOL "") + +set(SPHINX_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/python-3.13.5/bin/sphinx-build" CACHE PATH "") + +set(SHROUD_EXECUTABLE "/collab/usr/gapps/shroud/public/toss_4_x86_64_ib/shroud-0.14.0/bin/shroud" CACHE PATH "") + +set(CPPCHECK_EXECUTABLE "${DEVTOOLS_ROOT}/gcc-13.3.1/cppcheck-2.18.0-d2tmb7ls2d5vgh6vhcyohecwcu6msopb/bin/cppcheck" CACHE PATH "") + +set(DOXYGEN_EXECUTABLE "${DEVTOOLS_ROOT}/._view/qoeiu74kfjckl7xkcpsbmskffr2fq2tm/doxygen-1.13.2/bin/doxygen" CACHE PATH "") + + diff --git a/src/axom/quest/detail/clipping/Plane3DClipper.cpp b/src/axom/quest/detail/clipping/Plane3DClipper.cpp new file mode 100644 index 0000000000..d3b87c26fb --- /dev/null +++ b/src/axom/quest/detail/clipping/Plane3DClipper.cpp @@ -0,0 +1,412 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/config.hpp" + +#include "axom/quest/detail/clipping/Plane3DClipper.hpp" + +namespace axom +{ +namespace quest +{ +namespace experimental +{ + +Plane3DClipper::Plane3DClipper(const klee::Geometry& kGeom, const std::string& name) + : MeshClipperStrategy(kGeom) + , m_name(name.empty() ? std::string("Plane3D") : name) +{ + extractClipperInfo(); +} + +bool Plane3DClipper::labelCellsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::Array& labels) +{ + int allocId = shapeMesh.getAllocatorID(); + auto cellCount = shapeMesh.getCellCount(); + if(labels.size() < cellCount || labels.getAllocatorID() != shapeMesh.getAllocatorID()) + { + labels = axom::Array(ArrayOptions::Uninitialized(), cellCount, cellCount, allocId); + } + + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + labelCellsInOutImpl(shapeMesh, labels.view()); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + labelCellsInOutImpl(shapeMesh, labels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + labelCellsInOutImpl>(shapeMesh, labels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + labelCellsInOutImpl>(shapeMesh, labels.view()); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +bool Plane3DClipper::labelTetsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::Array& tetLabels) +{ + int allocId = shapeMesh.getAllocatorID(); + const auto cellCount = cellIds.size(); + const auto tetCount = cellCount * NUM_TETS_PER_HEX; + if(tetLabels.size() < tetCount || tetLabels.getAllocatorID() != allocId) + { + tetLabels = axom::Array(ArrayOptions::Uninitialized(), tetCount, tetCount, allocId); + } + + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + labelTetsInOutImpl(shapeMesh, cellIds, tetLabels.view()); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + labelTetsInOutImpl(shapeMesh, cellIds, tetLabels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + labelTetsInOutImpl>(shapeMesh, cellIds, tetLabels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + labelTetsInOutImpl>(shapeMesh, cellIds, tetLabels.view()); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +bool Plane3DClipper::specializedClipCells(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + conduit::Node& statistics) +{ + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + specializedClipCellsImpl(shapeMesh, ovlap, statistics); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + specializedClipCellsImpl(shapeMesh, ovlap, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + specializedClipCellsImpl>(shapeMesh, ovlap, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + specializedClipCellsImpl>(shapeMesh, ovlap, statistics); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +bool Plane3DClipper::specializedClipCells(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& cellIds, + conduit::Node& statistics) +{ + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + specializedClipCellsImpl(shapeMesh, ovlap, cellIds, statistics); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + specializedClipCellsImpl(shapeMesh, ovlap, cellIds, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + specializedClipCellsImpl>(shapeMesh, ovlap, cellIds, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + specializedClipCellsImpl>(shapeMesh, ovlap, cellIds, statistics); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +bool Plane3DClipper::specializedClipTets(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& tetIds, + conduit::Node& statistics) +{ + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + specializedClipTetsImpl(shapeMesh, ovlap, tetIds, statistics); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + specializedClipTetsImpl(shapeMesh, ovlap, tetIds, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + specializedClipTetsImpl>(shapeMesh, ovlap, tetIds, statistics); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + specializedClipTetsImpl>(shapeMesh, ovlap, tetIds, statistics); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +template +void Plane3DClipper::labelCellsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView labels) +{ + int allocId = shapeMesh.getAllocatorID(); + auto cellCount = shapeMesh.getCellCount(); + auto vertCount = shapeMesh.getVertexCount(); + auto cellVolumes = shapeMesh.getCellVolumes(); + constexpr double EPS = 1e-10; + + const auto& vertCoords = shapeMesh.getVertexCoords3D(); + const auto& vX = vertCoords[0]; + const auto& vY = vertCoords[1]; + const auto& vZ = vertCoords[2]; + + /* + Compute whether vertices are inside shape. + */ + axom::Array vertIsInside {ArrayOptions::Uninitialized(), vertCount, vertCount, allocId}; + auto vertIsInsideView = vertIsInside.view(); + SLIC_ASSERT(axom::execution_space::usesAllocId(vX.getAllocatorID())); + SLIC_ASSERT(axom::execution_space::usesAllocId(vY.getAllocatorID())); + SLIC_ASSERT(axom::execution_space::usesAllocId(vZ.getAllocatorID())); + SLIC_ASSERT(axom::execution_space::usesAllocId(vertIsInsideView.getAllocatorID())); + + auto plane = m_plane; + axom::for_all( + vertCount, + AXOM_LAMBDA(axom::IndexType vertId) { + primal::Point3D vert {vX[vertId], vY[vertId], vZ[vertId]}; + double signedDist = plane.signedDistance(vert); + vertIsInsideView[vertId] = signedDist > 0; + }); + + /* + * Label cell by whether it has vertices inside, outside or both. + */ + axom::ArrayView connView = shapeMesh.getCellNodeConnectivity(); + SLIC_ASSERT(connView.shape()[1] == NUM_VERTS_PER_CELL_3D); + + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType cellId) { + if(axom::utilities::isNearlyEqual(cellVolumes[cellId], 0.0, EPS)) + { + labels[cellId] = LabelType::LABEL_OUT; + return; + } + auto cellVertIds = connView[cellId]; + bool hasIn = vertIsInsideView[cellVertIds[0]]; + bool hasOut = !hasIn; + for(int vi = 0; vi < NUM_VERTS_PER_CELL_3D; ++vi) + { + int vertId = cellVertIds[vi]; + bool isIn = vertIsInsideView[vertId]; + hasIn |= isIn; + hasOut |= !isIn; + } + labels[cellId] = !hasOut ? LabelType::LABEL_IN + : !hasIn ? LabelType::LABEL_OUT + : LabelType::LABEL_ON; + }); + + return; +} + +template +void Plane3DClipper::labelTetsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::ArrayView tetLabels) +{ + auto cellCount = cellIds.size(); + auto meshTets = shapeMesh.getCellsAsTets(); + auto meshTetVolumes = shapeMesh.getTetVolumes(); + + auto plane = m_plane; + + constexpr double EPS = 1e-10; + + /* + * Label tet by whether it has vertices inside, outside or both. + * Degenerate tets as outside, because they contribute no volume. + */ + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType ci) { + axom::IndexType cellId = cellIds[ci]; + + const TetrahedronType* tetsForCell = &meshTets[cellId * NUM_TETS_PER_HEX]; + const double* tetVolumesForCell = &meshTetVolumes[cellId * NUM_TETS_PER_HEX]; + + for(IndexType ti = 0; ti < NUM_TETS_PER_HEX; ++ti) + { + const auto& tet = tetsForCell[ti]; + LabelType& tetLabel = tetLabels[ci * NUM_TETS_PER_HEX + ti]; + + if(axom::utilities::isNearlyEqual(tetVolumesForCell[ti], 0.0, EPS)) + { + tetLabel = LabelType::LABEL_OUT; + continue; + } + + bool hasIn = false; + bool hasOut = false; + for(int vi = 0; vi < TetrahedronType::NUM_VERTS; ++vi) + { + const auto& vert = tet[vi]; + double signedDist = plane.signedDistance(vert); + hasIn |= signedDist > 0; + hasOut |= signedDist < 0; + } + tetLabel = !hasOut ? LabelType::LABEL_IN + : !hasIn ? LabelType::LABEL_OUT + : LabelType::LABEL_ON; + } + }); + + return; +} + +template +void Plane3DClipper::specializedClipCellsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + conduit::Node& statistics) +{ + axom::IndexType cellCount = shapeMesh.getCellCount(); + axom::Array cellIds(cellCount, cellCount, shapeMesh.getAllocatorID()); + auto cellIdsView = cellIds.view(); + axom::for_all(cellCount, AXOM_LAMBDA(axom::IndexType i) { cellIdsView[i] = i; }); + specializedClipCellsImpl(shapeMesh, ovlap, cellIds, statistics); +} + +template +void Plane3DClipper::specializedClipCellsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& cellIds, + conduit::Node& statistics) +{ + using ATOMIC_POL = typename axom::execution_space::atomic_policy; + constexpr double EPS = 1e-10; + + int allocId = shapeMesh.getAllocatorID(); + + auto cellsAsTets = shapeMesh.getCellsAsTets(); + + auto plane = m_plane; + + axom::ReduceSum missSum {0}; + + axom::for_all( + cellIds.size(), + AXOM_LAMBDA(axom::IndexType i) { + axom::IndexType cellId = cellIds[i]; + const TetrahedronType* tetsInHex = cellsAsTets.data() + cellId * NUM_TETS_PER_HEX; + double vol = 0.0; + for(int ti = 0; ti < NUM_TETS_PER_HEX; ++ti) + { + const auto& tet = tetsInHex[ti]; + primal::Polyhedron overlap = primal::clip(tet, plane, EPS); + if(overlap.numVertices() >= 4) + { + auto volume = overlap.volume(); + vol += volume; + } + else + { + missSum += 1; + } + } + ovlap[cellId] = vol; + }); + + statistics["clipsOn"].set_int64(cellIds.size() * NUM_TETS_PER_HEX); + statistics["clipsSum"].set_int64(cellIds.size() * NUM_TETS_PER_HEX); + statistics["missSum"].set_int64(missSum.get()); +} + +template +void Plane3DClipper::specializedClipTetsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& tetIds, + conduit::Node& statistics) +{ + constexpr double EPS = 1e-10; + using ATOMIC_POL = typename axom::execution_space::atomic_policy; + + int allocId = shapeMesh.getAllocatorID(); + + auto meshTets = shapeMesh.getCellsAsTets(); + IndexType tetCount = tetIds.size(); + auto plane = m_plane; + + axom::for_all( + tetCount, + AXOM_LAMBDA(axom::IndexType ti) { + axom::IndexType tetId = tetIds[ti]; + axom::IndexType cellId = tetId / NUM_TETS_PER_HEX; + const auto& tet = meshTets[tetId]; + primal::Polyhedron overlap = primal::clip(tet, plane, EPS); + double vol = overlap.volume(); + RAJA::atomicAdd(ovlap.data() + cellId, vol); + }); + + // Because the tet screening is perfect, all tets in tetIds are on the plane. + statistics["onSum"].set_int64(tetCount); + statistics["clipsSum"].set_int64(tetCount); +} + +void Plane3DClipper::extractClipperInfo() +{ + const auto normal = m_info.fetch_existing("normal").as_double_array(); + const double offset = m_info.fetch_existing("offset").as_double(); + Vector3DType nVec; + for(int d = 0; d < 3; ++d) + { + nVec[d] = normal[d]; + } + m_plane = Plane3DType(nVec, offset); +} + +} // namespace experimental +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/detail/clipping/Plane3DClipper.hpp b/src/axom/quest/detail/clipping/Plane3DClipper.hpp new file mode 100644 index 0000000000..b7406f33c2 --- /dev/null +++ b/src/axom/quest/detail/clipping/Plane3DClipper.hpp @@ -0,0 +1,111 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_PLANE3DCLIPPER_HPP +#define AXOM_QUEST_PLANE3DCLIPPER_HPP + +#include "axom/klee/Geometry.hpp" +#include "axom/quest/MeshClipperStrategy.hpp" + +namespace axom +{ +namespace quest +{ +namespace experimental +{ + +/*! + * @brief Geometry clipping operations for plane geometries. +*/ +class Plane3DClipper : public MeshClipperStrategy +{ +public: + /*! + * @brief Constructor. + * + * @param [in] kGeom Describes the shape to place + * into the mesh. + * @param [in] name To override the default strategy name + * + * Clipping operations for a semi-infinite half-space + * on the positive normal direction of a plane. + * + * @internal Because this class provides screening via the + * labelCellsInOut and labelTetsInOut methods, the + * specializedClipCells methods below are not essential. They are + * implemented only to avoid crashing when the MeshClipper's screen + * level is overridden (for performance comparisons). The + * specializedClipTets method is much faster and is the one used for + * the default screen level. If the screen level override is remove, + * the non-essential methods can also be removed. + */ + Plane3DClipper(const klee::Geometry& kGeom, const std::string& name = ""); + + virtual ~Plane3DClipper() = default; + + const std::string& name() const override { return m_name; } + + bool labelCellsInOut(quest::experimental::ShapeMesh& shappeMesh, + axom::Array& label) override; + + bool labelTetsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::Array& tetLabels) override; + + bool specializedClipCells(quest::experimental::ShapeMesh& shappeMesh, + axom::ArrayView ovlap, + conduit::Node& statistics) override; + + bool specializedClipCells(quest::experimental::ShapeMesh& shappeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& cellIds, + conduit::Node& statistics) override; + + bool specializedClipTets(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& tetIds, + conduit::Node& statistics) override; + +#if !defined(__CUDACC__) +private: +#endif + std::string m_name; + + axom::primal::Plane m_plane; + + template + void labelCellsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView label); + + template + void labelTetsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::ArrayView label); + + template + void specializedClipCellsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + conduit::Node& statistics); + + template + void specializedClipCellsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& cellIds, + conduit::Node& statistics); + + template + void specializedClipTetsImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView ovlap, + const axom::ArrayView& tetIds, + conduit::Node& statistics); + + void extractClipperInfo(); +}; + +} // namespace experimental +} // namespace quest +} // namespace axom + +#endif // AXOM_QUEST_PLANE3DCLIPPER_HPP diff --git a/src/axom/quest/detail/clipping/SphereClipper.cpp b/src/axom/quest/detail/clipping/SphereClipper.cpp new file mode 100644 index 0000000000..0640753da0 --- /dev/null +++ b/src/axom/quest/detail/clipping/SphereClipper.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "axom/config.hpp" + +#include "axom/quest/Discretize.hpp" +#include "axom/quest/detail/clipping/SphereClipper.hpp" + +namespace axom +{ +namespace quest +{ +namespace experimental +{ + +SphereClipper::SphereClipper(const klee::Geometry& kGeom, const std::string& name) + : MeshClipperStrategy(kGeom) + , m_name(name.empty() ? std::string("Sphere") : name) + , m_transformer(m_extTrans) +{ + extractClipperInfo(); + + transformSphere(); +} + +bool SphereClipper::labelCellsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::Array& labels) +{ + SLIC_ERROR_IF(shapeMesh.dimension() != 3, "SphereClipper requires a 3D mesh."); + + int allocId = shapeMesh.getAllocatorID(); + auto cellCount = shapeMesh.getCellCount(); + if(labels.size() < cellCount || labels.getAllocatorID() != shapeMesh.getAllocatorID()) + { + labels = axom::Array(ArrayOptions::Uninitialized(), cellCount, cellCount, allocId); + } + + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + labelCellsInOutImpl(shapeMesh, labels.view()); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + labelCellsInOutImpl(shapeMesh, labels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + labelCellsInOutImpl>(shapeMesh, labels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + labelCellsInOutImpl>(shapeMesh, labels.view()); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +template +void SphereClipper::labelCellsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView labels) +{ + auto cellCount = shapeMesh.getCellCount(); + auto cellsAsHexes = shapeMesh.getCellsAsHexes(); + auto cellVolumes = shapeMesh.getCellVolumes(); + constexpr double EPS = 1e-10; + auto sphere = m_sphere; + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType cellId) { + LabelType& cellLabel = labels[cellId]; + if(axom::utilities::isNearlyEqual(cellVolumes[cellId], 0.0, EPS)) + { + cellLabel = LabelType::LABEL_OUT; + return; + } + const auto& hex = cellsAsHexes[cellId]; + cellLabel = polyhedronToLabel(hex, sphere); + // Note: cellLabel may be set to LABEL_ON if polyhedronToLabel + // cannot efficiently determine whether the hex is IN or OUT. + // See MeshClipperStrategy::labelCellsInOut(). + }); + return; +} + +bool SphereClipper::labelTetsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::Array& tetLabels) +{ + SLIC_ERROR_IF(shapeMesh.dimension() != 3, "SphereClipper requires a 3D mesh."); + + const axom::IndexType cellCount = cellIds.size(); + const int allocId = shapeMesh.getAllocatorID(); + + if(tetLabels.size() < cellCount * NUM_TETS_PER_HEX || + tetLabels.getAllocatorID() != shapeMesh.getAllocatorID()) + { + tetLabels = axom::Array(ArrayOptions::Uninitialized(), + cellCount * NUM_TETS_PER_HEX, + cellCount * NUM_TETS_PER_HEX, + allocId); + } + + switch(shapeMesh.getRuntimePolicy()) + { + case axom::runtime_policy::Policy::seq: + labelTetsInOutImpl(shapeMesh, cellIds, tetLabels.view()); + break; +#if defined(AXOM_RUNTIME_POLICY_USE_OPENMP) + case axom::runtime_policy::Policy::omp: + labelTetsInOutImpl(shapeMesh, cellIds, tetLabels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_CUDA) + case axom::runtime_policy::Policy::cuda: + labelTetsInOutImpl>(shapeMesh, cellIds, tetLabels.view()); + break; +#endif +#if defined(AXOM_RUNTIME_POLICY_USE_HIP) + case axom::runtime_policy::Policy::hip: + labelTetsInOutImpl>(shapeMesh, cellIds, tetLabels.view()); + break; +#endif + default: + SLIC_ERROR("Axom Internal error: Unhandled execution policy."); + } + return true; +} + +template +void SphereClipper::labelTetsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::ArrayView tetLabels) +{ + const axom::IndexType cellCount = cellIds.size(); + auto meshHexes = shapeMesh.getCellsAsHexes(); + auto tetVolumes = shapeMesh.getTetVolumes(); + constexpr double EPS = 1e-10; + auto sphere = m_sphere; + + axom::for_all( + cellCount, + AXOM_LAMBDA(axom::IndexType ci) { + axom::IndexType cellId = cellIds[ci]; + const HexahedronType& hex = meshHexes[cellId]; + + TetrahedronType cellTets[NUM_TETS_PER_HEX]; + ShapeMesh::hexToTets(hex, cellTets); + + for(IndexType ti = 0; ti < NUM_TETS_PER_HEX; ++ti) + { + LabelType& tetLabel = tetLabels[ci * NUM_TETS_PER_HEX + ti]; + const axom::IndexType tetId = cellId * NUM_TETS_PER_HEX + ti; + if(axom::utilities::isNearlyEqual(tetVolumes[tetId], 0.0, EPS)) + { + tetLabel = LabelType::LABEL_OUT; + continue; + } + const TetrahedronType& tet = cellTets[ti]; + tetLabel = polyhedronToLabel(tet, sphere); + // Note: cellLabel may be set to LABEL_ON if polyhedronToLabel + // cannot efficiently determine whether the tet is IN or OUT. + // See MeshClipperStrategy::labelTetsInOut(). + } + }); + return; +} + +template +AXOM_HOST_DEVICE inline MeshClipperStrategy::LabelType SphereClipper::polyhedronToLabel( + const Polyhedron& verts, + const SphereType& sphere) const +{ + /* + If bounding box of polyhedron is more than the radius distance + from center, it is LABEL_OUT. (Comparing vertices for this check + can miss intersections by edges and facets, so we compare bounding + box.) + + Otherwise, polyhedron is labeled either LABEL_ON or LABEL_IN. + Sphere is convex, so polyhedron is IN only if all vertices are inside. + + Some polyhedra may be LABEL_ON even though they are actually LABEL_OUT, + but this is a conservative error. The clip function will compute the + correct overlap volume. The purpose of labeling is bypass the + clip function where we can do it efficiently. + */ + BoundingBox3DType bb(verts[0]); + auto vertCount = Polyhedron::numVertices(); + for(int i = 1; i < vertCount; ++i) + { + bb.addPoint(verts[i]); + } + + const double sqRad = sphere.getRadius() * sphere.getRadius(); + + double sqDistToBb = primal::squared_distance(sphere.getCenter(), bb); + + if(sqDistToBb >= sqRad) + { + return LabelType::LABEL_OUT; + } + + for(int i = 0; i < vertCount; ++i) + { + const auto& vert = verts[i]; + double sqDistToVert = axom::primal::squared_distance(sphere.getCenter(), vert); + if(sqDistToVert > sqRad) + { + return LabelType::LABEL_ON; + } + } + return LabelType::LABEL_IN; +} + +bool SphereClipper::getGeometryAsOcts(quest::experimental::ShapeMesh& shapeMesh, + axom::Array>& octs) +{ + AXOM_ANNOTATE_SCOPE("SphereClipper::getGeometryAsOcts"); + int octCount = 0; + axom::quest::discretize(m_sphereBeforeTrans, m_levelOfRefinement, octs, octCount); + + auto octsView = octs.view(); + auto transformer = m_transformer; + int allocId = shapeMesh.getAllocatorID(); + axom::for_all( + octCount, + AXOM_LAMBDA(axom::IndexType iOct) { + OctahedronType& oct = octsView[iOct]; + for(int iVert = 0; iVert < OctType::NUM_VERTS; ++iVert) + { + Point3DType& ptCoords = oct[iVert]; + transformer.transform(ptCoords.array()); + } + }); + + // The disretize method uses host data. Place into proper space if needed. + if(octs.getAllocatorID() != allocId) + { + octs = axom::Array>(octs, allocId); + } + + SLIC_INFO(axom::fmt::format("SphereClipper '{}' {}-level refined got {} geometry octs.", + name(), + m_levelOfRefinement, + octs.size())); + return true; +} + +void SphereClipper::extractClipperInfo() +{ + const auto c = m_info.fetch_existing("center").as_double_array(); + const double radius = m_info.fetch_existing("radius").as_double(); + Point3DType center; + for(int d = 0; d < 3; ++d) + { + center[d] = c[d]; + } + m_sphereBeforeTrans = SphereType(center, radius); + m_levelOfRefinement = m_info.fetch_existing("levelOfRefinement").to_int32(); +} + +// Include external transformations in m_sphere. +void SphereClipper::transformSphere() +{ + const auto& centerBeforeTrans = m_sphereBeforeTrans.getCenter(); + const double radiusBeforeTrans = m_sphereBeforeTrans.getRadius(); + Point3DType surfacePtBeforeTrans {centerBeforeTrans.array() + + Point3DType::NumericArray {radiusBeforeTrans, 0, 0}}; + + auto center = m_transformer.getTransformed(centerBeforeTrans); + Point3DType surfacePoint = m_transformer.getTransformed(surfacePtBeforeTrans); + const double radius = Vector3DType(center, surfacePoint).norm(); + m_sphere = SphereType(center, radius); +} + +} // namespace experimental +} // end namespace quest +} // end namespace axom diff --git a/src/axom/quest/detail/clipping/SphereClipper.hpp b/src/axom/quest/detail/clipping/SphereClipper.hpp new file mode 100644 index 0000000000..4f067e59f3 --- /dev/null +++ b/src/axom/quest/detail/clipping/SphereClipper.hpp @@ -0,0 +1,88 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#ifndef AXOM_QUEST_SPHERECLIPPER_HPP +#define AXOM_QUEST_SPHERECLIPPER_HPP + +#include "axom/klee/Geometry.hpp" +#include "axom/quest/MeshClipperStrategy.hpp" +#include "axom/primal/geometry/CoordinateTransformer.hpp" + +namespace axom +{ +namespace quest +{ +namespace experimental +{ + +/*! + * @brief Geometry clipping operations for sphere geometries. + */ +class SphereClipper : public MeshClipperStrategy +{ +public: + /*! + * @brief Constructor. + * + * @param [in] kGeom Describes the shape to place + * into the mesh. + * @param [in] name To override the default strategy name + */ + SphereClipper(const klee::Geometry& kGeom, const std::string& name = ""); + + virtual ~SphereClipper() = default; + + const std::string& name() const override { return m_name; } + + bool labelCellsInOut(quest::experimental::ShapeMesh& shappeMesh, + axom::Array& label) override; + + bool labelTetsInOut(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::Array& tetLabels) override; + + bool getGeometryAsOcts(quest::experimental::ShapeMesh& shappeMesh, + axom::Array>& octs) override; + +#if !defined(__CUDACC__) +private: +#endif + std::string m_name; + + //!@brief Sphere before external transformations. + SphereType m_sphereBeforeTrans; + + //!@brief Sphere after external transformations. + SphereType m_sphere; + + //!@brief External transformations. + axom::primal::experimental::CoordinateTransformer m_transformer; + + int m_levelOfRefinement; + + template + void labelCellsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView label); + + template + void labelTetsInOutImpl(quest::experimental::ShapeMesh& shapeMesh, + axom::ArrayView cellIds, + axom::ArrayView tetLabels); + + //!@brief Compute LabelType for a polyhedron (hex or tet in our case). + template + AXOM_HOST_DEVICE inline MeshClipperStrategy::LabelType polyhedronToLabel(const Polyhedron& verts, + const SphereType& sphere) const; + + void extractClipperInfo(); + + void transformSphere(); +}; + +} // namespace experimental +} // namespace quest +} // namespace axom + +#endif // AXOM_QUEST_SPHERECLIPPER_HPP diff --git a/src/axom/slic/tests/slic_scoped_abort.cpp b/src/axom/slic/tests/slic_scoped_abort.cpp new file mode 100644 index 0000000000..ee3d63ca8f --- /dev/null +++ b/src/axom/slic/tests/slic_scoped_abort.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "axom/slic/interface/slic.hpp" + +namespace +{ +void custom_abort_function() { } +} // namespace + +TEST(slic_scoped_abort, throws_on_error) +{ + axom::slic::initialize(); + + axom::slic::ScopedAbortToThrow abort_guard; + EXPECT_THROW({ SLIC_ERROR("testing abort-to-throw"); }, axom::slic::SlicAbortException); + + axom::slic::finalize(); +} + +TEST(slic_scoped_abort, restores_state_on_stack_unwind) +{ + axom::slic::initialize(); + + axom::slic::disableAbortOnError(); + axom::slic::setAbortFunction(custom_abort_function); + + const bool prev_abort_on_error = axom::slic::isAbortOnErrorsEnabled(); + const auto prev_abort_function = axom::slic::getAbortFunction(); + + try + { + axom::slic::ScopedAbortToThrow abort_guard; + SLIC_ERROR("testing state restoration"); + FAIL() << "Expected SlicAbortException"; + } + catch(const axom::slic::SlicAbortException&) + { } + + EXPECT_EQ(prev_abort_on_error, axom::slic::isAbortOnErrorsEnabled()); + EXPECT_EQ(prev_abort_function, axom::slic::getAbortFunction()); + + axom::slic::finalize(); +} diff --git a/src/tools/svg2contours/pyproject.toml b/src/tools/svg2contours/pyproject.toml new file mode 100644 index 0000000000..0d47d7f075 --- /dev/null +++ b/src/tools/svg2contours/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "svg2contours" +version = "0.1.0" +description = "Environment for running svg2contours script" +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "svgpathtools>=1.7.2", +] From 7d1f75625513d7ecd4f7ad9dbd1c303edc72281f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 9 Jan 2026 15:04:49 -0800 Subject: [PATCH 3/3] Add the extra flag for trimmed surfaces --- src/axom/quest/io/STEPReader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/axom/quest/io/STEPReader.cpp b/src/axom/quest/io/STEPReader.cpp index 857349b941..264dc35c1c 100644 --- a/src/axom/quest/io/STEPReader.cpp +++ b/src/axom/quest/io/STEPReader.cpp @@ -938,7 +938,8 @@ class StepFileProcessor } // If the face is flipped, then the trimming curves all need to be reversed too - if(TopoDS::Face(faceExp.Current()).Orientation() == TopAbs_Orientation::TopAbs_REVERSED) + if(patch.isTrimmed() && + TopoDS::Face(faceExp.Current()).Orientation() == TopAbs_Orientation::TopAbs_REVERSED) { patch.reverseTrimmingCurves(); }