From 65e4c6115ab083c3f25d1f2c79172f62e6489d20 Mon Sep 17 00:00:00 2001 From: Tobias Reiter <44025882+tobre1@users.noreply.github.com> Date: Sun, 21 May 2023 10:59:47 +0200 Subject: [PATCH] Extrude 2D level sets in 3D domain (#96) * LS extrude dimension * Create python binding * Added comments * Use custom boundary conditions * Remove lsExtrude from python wrapping --- .gitignore | 3 +- Python/pyWrap.cpp | 34 ++++++++ Tests/Extrude/CMakeLists.txt | 10 +++ Tests/Extrude/Extrude.cpp | 77 +++++++++++++++++++ include/lsExtrude.hpp | 145 +++++++++++++++++++++++++++++++++++ 5 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 Tests/Extrude/CMakeLists.txt create mode 100644 Tests/Extrude/Extrude.cpp create mode 100644 include/lsExtrude.hpp diff --git a/.gitignore b/.gitignore index c182d408..6e2974ee 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ _generate/ *.py[cod] *.egg-info *env* -.mypy_cache/ \ No newline at end of file +.mypy_cache/ +.eggs/ \ No newline at end of file diff --git a/Python/pyWrap.cpp b/Python/pyWrap.cpp index 512f4d81..3f8c1e93 100644 --- a/Python/pyWrap.cpp +++ b/Python/pyWrap.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -509,6 +510,39 @@ PYBIND11_MODULE(VIENNALS_MODULE_NAME, module) { .def("setWidth", &lsExpand::setWidth, "Set the width to expand to.") .def("apply", &lsExpand::apply, "Perform expansion."); + // lsExtrude + // Does not work in current implementation, because one can not import both 2D + // and 3D ViennaLS libraries in Python in the same file + // pybind11::class_, lsSmartPointer>>(module, + // "lsExtrude") + // // constructors + // .def(pybind11::init(&lsSmartPointer>::New<>)) + // .def( + // pybind11::init(&lsSmartPointer>::New< + // lsSmartPointer> &, + // lsSmartPointer> &, std::array, const int, + // std::array, 3>>)) + // // methods + // .def("setInputLevelSet", &lsExtrude::setInputLevelSet, + // "Set 2D input Level Set") + // .def("setOutputLevelSet", &lsExtrude::setOutputLevelSet, + // "Set 3D output Level Set") + // .def("setExtent", &lsExtrude::setExtent, + // "Set the extent in the extruded dimension") + // .def("setExtrudeDimension", &lsExtrude::setExtrudeDimension, + // "Set the dimension which should be extruded") + // .def("setBoundaryConditions", + // pybind11::overload_cast, + // 3>>( + // &lsExtrude::setBoundaryConditions), + // "Set the boundary conditions in the 3D extruded domain.") + // .def("setBoundaryConditions", + // pybind11::overload_cast *>( + // &lsExtrude::setBoundaryConditions), + // "Set the boundary conditions in the 3D extruded domain.") + // .def("apply", &lsExtrude::apply, "Perform extrusion."); + // lsFileFormats pybind11::enum_(module, "lsFileFormatEnum") .value("VTK_LEGACY", lsFileFormatEnum::VTK_LEGACY) diff --git a/Tests/Extrude/CMakeLists.txt b/Tests/Extrude/CMakeLists.txt new file mode 100644 index 00000000..4ed0d7b1 --- /dev/null +++ b/Tests/Extrude/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.14) + +project("Extrude") + +add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp) +target_include_directories(${PROJECT_NAME} PUBLIC ${VIENNALS_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${VIENNALS_LIBRARIES}) + +add_dependencies(buildTests ${PROJECT_NAME}) +add_test(NAME ${PROJECT_NAME} COMMAND $) diff --git a/Tests/Extrude/Extrude.cpp b/Tests/Extrude/Extrude.cpp new file mode 100644 index 00000000..e4dfdd05 --- /dev/null +++ b/Tests/Extrude/Extrude.cpp @@ -0,0 +1,77 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + Simple example showing how to use lsExtrude in order + to transform a 2D trench into a 3D level set. + \example Extrude.cpp +*/ + +int main() { + + omp_set_num_threads(4); + + double extent = 15; + double gridDelta = 0.5; + + // 2D domain boundaries + double bounds[2 * 2] = {-extent, extent, -extent, extent}; + lsDomain::BoundaryType boundaryCons[2]; + boundaryCons[0] = lsDomain::BoundaryType::REFLECTIVE_BOUNDARY; + boundaryCons[1] = lsDomain::BoundaryType::INFINITE_BOUNDARY; + + auto trench = + lsSmartPointer>::New(bounds, boundaryCons, gridDelta); + + { + double origin[2] = {0., 0.}; + double normal[2] = {0., 1.}; + + lsMakeGeometry( + trench, lsSmartPointer>::New(origin, normal)) + .apply(); + + auto cutOut = lsSmartPointer>::New(bounds, boundaryCons, + gridDelta); + + double minPoint[2] = {-5., -5.}; + double maxPoint[2] = {5., gridDelta}; + + lsMakeGeometry( + cutOut, lsSmartPointer>::New(minPoint, maxPoint)) + .apply(); + + lsBooleanOperation(trench, cutOut, + lsBooleanOperationEnum::RELATIVE_COMPLEMENT) + .apply(); + } + + { + auto mesh = lsSmartPointer>::New(); + lsToMesh(trench, mesh).apply(); + lsVTKWriter(mesh, "trench_initial.vtp").apply(); + } + + std::array extrudeExtent = {-5., 5.}; + std::array, 3> boundaryConds{ + lsBoundaryConditionEnum<3>::REFLECTIVE_BOUNDARY, + lsBoundaryConditionEnum<3>::INFINITE_BOUNDARY, + lsBoundaryConditionEnum<3>::REFLECTIVE_BOUNDARY}; + auto trench_3D = lsSmartPointer>::New(); + lsExtrude(trench, trench_3D, extrudeExtent, 2, boundaryConds).apply(); + + { + auto mesh = lsSmartPointer>::New(); + lsToMesh(trench_3D, mesh).apply(); + lsVTKWriter(mesh, "trench_extrude.vtp").apply(); + } + + return 0; +} diff --git a/include/lsExtrude.hpp b/include/lsExtrude.hpp new file mode 100644 index 00000000..9c83977e --- /dev/null +++ b/include/lsExtrude.hpp @@ -0,0 +1,145 @@ +#ifndef LS_EXTRUDE_HPP +#define LS_EXTRUDE_HPP + +#include +#include +#include + +/// Extrudes a 2D Level Set into a 3D domain. The axis in which should be +/// extruded can be set and boundary conditions in the 3D domain must be +/// specified. +template class lsExtrude { + lsSmartPointer> inputLevelSet = nullptr; + lsSmartPointer> outputLevelSet = nullptr; + std::array extent = {0., 0.}; + int extrudeDim = 0; + std::array, 3> boundaryConds; + +public: + lsExtrude() {} + lsExtrude(lsSmartPointer> passedInputLS, + lsSmartPointer> passedOutputLS, + std::array passedExtent, const int passedExtrudeDim, + std::array, 3> passedBoundaryConds) + : inputLevelSet(passedInputLS), outputLevelSet(passedOutputLS), + extent(passedExtent), extrudeDim(passedExtrudeDim), + boundaryConds(passedBoundaryConds) {} + + void setInputLevelSet(lsSmartPointer> passedInputLS) { + inputLevelSet = passedInputLS; + } + + // The 3D output LS will be overwritten by the extruded LS + void setOutputLevelSet(lsSmartPointer> &passedOutputLS) { + outputLevelSet = passedOutputLS; + } + + // Set the min and max extent in the extruded dimension + void setExtent(std::array passedExtent) { extent = passedExtent; } + + // Set which index of the added dimension (x: 0, y: 1, z: 2) + void setExtrudeDimension(const int passedExtrudeDim) { + extrudeDim = passedExtrudeDim; + } + + void setBoundaryConditions( + std::array, 3> passedBoundaryConds) { + boundaryConds = passedBoundaryConds; + } + + void + setBoundaryConditions(lsBoundaryConditionEnum<3> passedBoundaryConds[3]) { + for (int i = 0; i < 3; i++) + boundaryConds[i] = passedBoundaryConds[i]; + } + + void apply() { + if (inputLevelSet == nullptr) { + lsMessage::getInstance() + .addWarning( + "No input Level Set supplied to lsExtrude! Not converting.") + .print(); + } + if (outputLevelSet == nullptr) { + lsMessage::getInstance() + .addWarning( + "No output Level Set supplied to lsExtrude! Not converting.") + .print(); + return; + } + + // x and y of the input LS get transformed to these indices + const auto extrudeDims = getExtrudeDims(); + + // create new domain based on 2D extent + { + const T gridDelta = inputLevelSet->getGrid().getGridDelta(); + auto minBounds = inputLevelSet->getGrid().getMinBounds(); + auto maxBounds = inputLevelSet->getGrid().getMaxBounds(); + + double domainBounds[2 * 3]; + domainBounds[2 * extrudeDim] = extent[0]; + domainBounds[2 * extrudeDim + 1] = extent[1]; + domainBounds[2 * extrudeDims[0]] = gridDelta * minBounds[0]; + domainBounds[2 * extrudeDims[0] + 1] = gridDelta * maxBounds[0]; + domainBounds[2 * extrudeDims[1]] = gridDelta * minBounds[1]; + domainBounds[2 * extrudeDims[1] + 1] = gridDelta * maxBounds[1]; + + auto tmpLevelSet = lsSmartPointer>::New( + domainBounds, boundaryConds.data(), gridDelta); + outputLevelSet->deepCopy(tmpLevelSet); + } + + auto surface = lsSmartPointer>::New(); + lsToSurfaceMesh(inputLevelSet, surface).apply(); + + auto &lines = surface->template getElements<2>(); + auto &nodes = surface->getNodes(); + const unsigned numNodes = nodes.size(); + + // add new nodes shifted by the extent + for (unsigned i = 0; i < numNodes; i++) { + nodes[i][extrudeDims[1]] = nodes[i][1]; + nodes[i][extrudeDims[0]] = nodes[i][0]; + nodes[i][extrudeDim] = extent[1]; + + nodes.push_back(nodes[i]); + + nodes[i][extrudeDim] = extent[0]; + } + + // add triangles in places of lines + for (unsigned i = 0; i < lines.size(); i++) { + std::array triangle = {lines[i][1], lines[i][0], + lines[i][0] + numNodes}; + if (extrudeDim == 1) { + std::swap(triangle[0], triangle[2]); + } + surface->insertNextTriangle(triangle); + triangle[0] = lines[i][0] + numNodes; + triangle[1] = lines[i][1] + numNodes; + triangle[2] = lines[i][1]; + if (extrudeDim == 1) { + std::swap(triangle[0], triangle[2]); + } + surface->insertNextTriangle(triangle); + } + surface->template getElements<2>().clear(); // remove lines + + lsFromSurfaceMesh(outputLevelSet, surface).apply(); + } + +private: + inline std::array getExtrudeDims() const { + assert(extrudeDim < 3); + if (extrudeDim == 0) { + return {1, 2}; + } else if (extrudeDim == 1) { + return {0, 2}; + } else { + return {0, 1}; + } + } +}; + +#endif // LS_EXTRUDE_HPP \ No newline at end of file