├── .github ├── FUNDING.yml └── workflows │ └── cmake.yml ├── CITATION.cff ├── CMakeLists.txt ├── Contact.cxx ├── Contact.h ├── LICENSE ├── Merger.cxx ├── Merger.h ├── Optimize.cxx ├── Optimize.h ├── README.md ├── Utilities.cxx ├── Utilities.h ├── doc ├── branched_strips.svg ├── common_strips.svg └── interPts.svg ├── module ├── CMakeLists.txt └── vtk.module ├── paraview ├── CMakeLists.txt ├── module │ ├── CMakeLists.txt │ ├── vtk.module │ └── vtkPolyDataBooleanFilter.xml └── paraview.plugin ├── run_some_tests.sh ├── testing ├── data │ ├── branched.vtk │ ├── branched3.vtk │ ├── branched4.vtk │ ├── branched6.vtk │ ├── cross.vtk │ ├── merger.vtk │ └── non-manifold.vtk ├── generate_frieze.py ├── pytest.ini ├── test_congruence.cxx ├── test_filter.py ├── test_merger.cxx └── test_python.py ├── vtkPolyDataBooleanFilter.cxx └── vtkPolyDataBooleanFilter.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: zippy84 2 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | 8 | jobs: 9 | Build: 10 | runs-on: ${{matrix.os}} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-22.04, ubuntu-latest] 14 | cxx: [g++, clang++] 15 | build_type: [Debug, Release] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Install Deps 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get -y upgrade 24 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata 25 | sudo apt-get -y install libvtk9-dev python3-vtk9 python3-pytest 26 | 27 | - name: Configure CMake 28 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build_type}} 29 | env: 30 | CXX: ${{matrix.cxx}} 31 | CXXFLAGS: ${{matrix.cxxflags}} 32 | 33 | - name: Build 34 | run: cmake --build ${{github.workspace}}/build --config ${{matrix.build_type}} 35 | 36 | - name: Test 37 | working-directory: ${{github.workspace}}/build 38 | run: ctest --output-on-failure -C ${{matrix.build_type}} 39 | 40 | Coverage: 41 | if: github.ref == 'refs/heads/master' 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v3 45 | 46 | - name: Install Deps 47 | run: | 48 | sudo apt-get update 49 | sudo apt-get -y upgrade 50 | sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata 51 | sudo apt-get -y install libvtk9-dev python3-vtk9 python3-pytest gcovr 52 | 53 | - name: Configure CMake 54 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Profile -DVTKBOOL_COVERAGE=ON 55 | 56 | - name: Build 57 | run: cmake --build ${{github.workspace}}/build 58 | 59 | - name: Test 60 | working-directory: ${{github.workspace}}/build 61 | run: | 62 | ctest --output-on-failure 63 | gcovr -r .. . --exclude-throw-branches --xml -o coverage.xml 64 | 65 | - name: Upload coverage reports to Codecov 66 | uses: codecov/codecov-action@v3 67 | with: 68 | token: ${{secrets.CODECOV_TOKEN}} 69 | 70 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: vtkbool 3 | message: 'If you use this software, please cite it as below.' 4 | type: software 5 | authors: 6 | - family-names: Römer 7 | given-names: Ronald 8 | identifiers: 9 | - type: doi 10 | value: 10.5281/zenodo.10461186 11 | repository-code: 'https://github.com/zippy84/vtkbool' 12 | license: Apache-2.0 13 | version: 3.2.0 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2012-2025 Ronald Römer 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.12 FATAL_ERROR) 16 | project(vtkbool 17 | VERSION 3.2 18 | HOMEPAGE_URL https://github.com/zippy84/vtkbool) 19 | 20 | set(CMAKE_CXX_STANDARD 17) 21 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 22 | 23 | set(CMAKE_CXX_OUTPUT_EXTENSION_REPLACE 1) 24 | 25 | if(MSVC) 26 | add_compile_options(/EHsc) 27 | add_compile_definitions(_SCL_SECURE_NO_WARNINGS) 28 | add_compile_definitions(_USE_MATH_DEFINES) 29 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 30 | else() 31 | add_compile_options(-Wall -Wextra -fPIC -Wconversion) 32 | endif() 33 | 34 | option(VTKBOOL_PARAVIEW "" OFF) 35 | option(VTKBOOL_DEBUG "" OFF) 36 | option(VTKBOOL_COVERAGE "" OFF) 37 | 38 | mark_as_advanced(VTKBOOL_DEBUG) 39 | mark_as_advanced(VTKBOOL_COVERAGE) 40 | 41 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND VTKBOOL_COVERAGE) 42 | set(CMAKE_C_FLAGS_PROFILE --coverage) 43 | set(CMAKE_CXX_FLAGS_PROFILE --coverage) 44 | add_link_options("--coverage") 45 | endif() 46 | 47 | include_directories(".") 48 | 49 | if(VTKBOOL_DEBUG) 50 | add_definitions(-DDEBUG) 51 | endif() 52 | 53 | if(VTKBOOL_PARAVIEW) 54 | find_package(ParaView REQUIRED) 55 | 56 | if(ParaView_FOUND) 57 | paraview_plugin_scan( 58 | PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/paraview/paraview.plugin" 59 | PROVIDES_PLUGINS plugins 60 | ENABLE_BY_DEFAULT ON) 61 | 62 | set(BUILD_SHARED_LIBS ON) 63 | 64 | include(GNUInstallDirs) 65 | 66 | paraview_plugin_build( 67 | PLUGINS ${plugins}) 68 | 69 | endif() 70 | 71 | else() 72 | 73 | find_package(VTK REQUIRED COMPONENTS FiltersSources IOLegacy FiltersExtraction FiltersGeometry FiltersModeling FiltersFlowPaths OPTIONAL_COMPONENTS WrappingPythonCore NO_MODULE) 74 | 75 | if(VTK_FOUND) 76 | 77 | if(VTK_VERSION VERSION_LESS "9.0.0") 78 | message(FATAL_ERROR "vtkbool requires VTK 9.0.0 or newer.") 79 | endif() 80 | 81 | vtk_module_scan( 82 | MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/module/vtk.module" 83 | REQUEST_MODULES vtkbool 84 | PROVIDES_MODULES modules 85 | ENABLE_TESTS ON) 86 | 87 | set(BUILD_SHARED_LIBS ON) 88 | 89 | include(GNUInstallDirs) 90 | 91 | vtk_module_build(MODULES ${modules}) 92 | 93 | if (VTK_WrappingPythonCore_FOUND) 94 | 95 | vtk_module_wrap_python( 96 | MODULES ${modules} 97 | PYTHON_PACKAGE "vtkbool" 98 | BUILD_STATIC OFF 99 | INSTALL_HEADERS OFF) 100 | 101 | include(CTest) 102 | 103 | vtk_module_python_default_destination(python_default_destination) 104 | 105 | add_test(NAME "import_vtkbool" 106 | COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/testing/test_python.py) 107 | 108 | set_property(TEST "import_vtkbool" APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/${python_default_destination}/vtkbool") 109 | 110 | add_test(NAME "test_filter" 111 | COMMAND ${Python3_EXECUTABLE} -m pytest 112 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testing) 113 | 114 | set_property(TEST "test_filter" APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/${python_default_destination}/vtkbool") 115 | 116 | add_executable(test_merger testing/test_merger.cxx) 117 | target_link_libraries(test_merger PRIVATE vtkbool ${VTK_LIBRARIES}) 118 | 119 | add_executable(test_congruence testing/test_congruence.cxx) 120 | target_link_libraries(test_congruence PRIVATE vtkbool ${VTK_LIBRARIES}) 121 | 122 | vtk_module_autoinit( 123 | TARGETS test_merger test_congruence 124 | MODULES ${VTK_LIBRARIES} 125 | ) 126 | 127 | add_test(NAME "test_merger" 128 | COMMAND test_merger 129 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testing) 130 | 131 | add_test(NAME "test_congruence" 132 | COMMAND test_congruence 133 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testing) 134 | 135 | add_test(NAME "generate_frieze" 136 | COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/testing/generate_frieze.py 137 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/testing) 138 | 139 | set_property(TEST "generate_frieze" APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${CMAKE_BINARY_DIR}/${python_default_destination}/vtkbool") 140 | 141 | endif() 142 | 143 | endif() 144 | 145 | endif() 146 | -------------------------------------------------------------------------------- /Contact.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "Contact.h" 18 | #include "Optimize.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | // #define _debA 0 27 | // #define _debB 0 28 | 29 | #if (defined(_debA) && defined(_debB)) 30 | vtkIdType _idA, _idB; 31 | #endif 32 | 33 | vtkSmartPointer Clean (vtkPolyData *pd) { 34 | 35 | auto clean = vtkSmartPointer::New(); 36 | clean->SetOutputPointsPrecision(vtkAlgorithm::DOUBLE_PRECISION); 37 | clean->SetTolerance(1e-6); 38 | 39 | clean->ConvertLinesToPointsOff(); 40 | clean->ConvertPolysToLinesOff(); 41 | clean->ConvertStripsToPolysOff(); 42 | 43 | clean->SetInputData(pd); 44 | 45 | clean->Update(); 46 | 47 | auto cleaned = clean->GetOutput(); 48 | 49 | vtkIdType numCells = cleaned->GetNumberOfCells(); 50 | 51 | auto newPd = vtkSmartPointer::New(); 52 | newPd->SetPoints(cleaned->GetPoints()); 53 | newPd->Allocate(numCells); 54 | 55 | auto cellIds = vtkSmartPointer::New(); 56 | cellIds->SetName("OrigCellIds"); 57 | cellIds->Allocate(numCells); 58 | 59 | vtkCellIterator *cellItr = cleaned->NewCellIterator(); 60 | 61 | vtkIdType cellId; 62 | vtkIdList *ptIds; 63 | 64 | vtkIdType num; 65 | const vtkIdType *pts; 66 | 67 | for (cellItr->InitTraversal(); !cellItr->IsDoneWithTraversal(); cellItr->GoToNextCell()) { 68 | cellId = cellItr->GetCellId(); 69 | ptIds = cellItr->GetPointIds(); 70 | 71 | if (cellItr->GetCellType() == VTK_TRIANGLE || cellItr->GetCellType() == VTK_POLYGON) { 72 | newPd->InsertNextCell(cellItr->GetCellType(), ptIds); 73 | cellIds->InsertNextValue(cellId); 74 | 75 | } else if (cellItr->GetCellType() == VTK_TRIANGLE_STRIP) { 76 | auto cells = vtkSmartPointer::New(); 77 | 78 | vtkTriangleStrip::DecomposeStrip(cellItr->GetNumberOfPoints(), ptIds->GetPointer(0), cells); 79 | 80 | for (cells->InitTraversal(); cells->GetNextCell(num, pts);) { 81 | if (pts[0] != pts[1] && pts[1] != pts[2] && pts[2] != pts[0]) { 82 | newPd->InsertNextCell(VTK_TRIANGLE, num, pts); 83 | cellIds->InsertNextValue(cellId); 84 | } 85 | } 86 | 87 | } else if (cellItr->GetCellType() == VTK_QUAD) { 88 | 89 | double pA[3], pB[3], pC[3], pD[3]; 90 | 91 | cleaned->GetPoint(ptIds->GetId(0), pA); 92 | cleaned->GetPoint(ptIds->GetId(1), pB); 93 | cleaned->GetPoint(ptIds->GetId(2), pC); 94 | cleaned->GetPoint(ptIds->GetId(3), pD); 95 | 96 | double det = vtkMath::Determinant3x3(pB[0]-pA[0], pC[0]-pA[0], pD[0]-pA[0], 97 | pB[1]-pA[1], pC[1]-pA[1], pD[1]-pA[1], 98 | pB[2]-pA[2], pC[2]-pA[2], pD[2]-pA[2]); 99 | 100 | if (std::abs(det) < 1e-10) { 101 | newPd->InsertNextCell(VTK_POLYGON, ptIds); 102 | cellIds->InsertNextValue(cellId); 103 | } else { 104 | const vtkIdType cellA[] = {ptIds->GetId(0), ptIds->GetId(1), ptIds->GetId(2)}; 105 | const vtkIdType cellB[] = {ptIds->GetId(2), ptIds->GetId(3), ptIds->GetId(0)}; 106 | 107 | newPd->InsertNextCell(VTK_TRIANGLE, 3, cellA); 108 | cellIds->InsertNextValue(cellId); 109 | 110 | newPd->InsertNextCell(VTK_TRIANGLE, 3, cellB); 111 | cellIds->InsertNextValue(cellId); 112 | } 113 | } 114 | } 115 | 116 | cellItr->Delete(); 117 | 118 | newPd->GetCellData()->SetScalars(cellIds); 119 | newPd->Squeeze(); 120 | 121 | return newPd; 122 | } 123 | 124 | Contact::Contact (vtkPolyData *newPdA, vtkPolyData *newPdB) : newPdA(newPdA), newPdB(newPdB) { 125 | assert(newPdA->GetCellData()->GetScalars("OrigCellIds") != nullptr); 126 | assert(newPdB->GetCellData()->GetScalars("OrigCellIds") != nullptr); 127 | 128 | pts = vtkSmartPointer::New(); 129 | pts->SetDataTypeToDouble(); 130 | 131 | lines = vtkSmartPointer::New(); 132 | lines->SetPoints(pts); 133 | lines->Allocate(1000); 134 | 135 | contA = vtkSmartPointer::New(); 136 | contB = vtkSmartPointer::New(); 137 | 138 | contA->Allocate(1000); 139 | contB->Allocate(1000); 140 | 141 | contA->SetName("cA"); 142 | contB->SetName("cB"); 143 | 144 | lines->GetCellData()->AddArray(contA); 145 | lines->GetCellData()->AddArray(contB); 146 | 147 | sourcesA = vtkSmartPointer::New(); 148 | sourcesB = vtkSmartPointer::New(); 149 | 150 | sourcesA->Allocate(1000); 151 | sourcesB->Allocate(1000); 152 | 153 | sourcesA->SetNumberOfComponents(2); 154 | sourcesB->SetNumberOfComponents(2); 155 | 156 | sourcesA->SetName("sourcesA"); 157 | sourcesB->SetName("sourcesB"); 158 | 159 | lines->GetCellData()->AddArray(sourcesA); 160 | lines->GetCellData()->AddArray(sourcesB); 161 | 162 | touchesEdgesA = false; 163 | touchesEdgesB = false; 164 | 165 | GetNonManifoldEdges(newPdA, edgesA); 166 | GetNonManifoldEdges(newPdB, edgesB); 167 | 168 | treeA = vtkSmartPointer::New(); 169 | treeA->SetDataSet(newPdA); 170 | treeA->BuildLocator(); 171 | 172 | treeB = vtkSmartPointer::New(); 173 | treeB->SetDataSet(newPdB); 174 | treeB->BuildLocator(); 175 | } 176 | 177 | vtkSmartPointer Contact::GetLines (vtkPolyData *pdA, vtkLinearTransform *transA, vtkPolyData *pdB, vtkLinearTransform *transB) { 178 | 179 | auto matrix = vtkSmartPointer::New(); 180 | 181 | if (pdA != nullptr) { 182 | newPdA = pdA; 183 | } 184 | 185 | if (pdB != nullptr) { 186 | newPdB = pdB; 187 | } 188 | 189 | auto matrixA = vtkSmartPointer::New(); 190 | 191 | if (transA != nullptr) { 192 | matrixA = transA->GetMatrix(); 193 | } 194 | 195 | auto matrixB = vtkSmartPointer::New(); 196 | 197 | if (transB != nullptr) { 198 | matrixB = transB->GetMatrix(); 199 | } 200 | 201 | auto tmpMatrix = vtkSmartPointer::New(); 202 | 203 | vtkMatrix4x4::Invert(matrixA, tmpMatrix); 204 | vtkMatrix4x4::Multiply4x4(tmpMatrix, matrixB, matrix); 205 | 206 | sourcesA->Reset(); 207 | sourcesB->Reset(); 208 | contA->Reset(); 209 | contB->Reset(); 210 | 211 | lines->Reset(); 212 | 213 | treeA->IntersectWithOBBTree(treeB, matrix, InterNodes, this); 214 | 215 | IntersectReplacements(); 216 | 217 | if (touchesEdgesA || touchesEdgesB) { 218 | throw std::runtime_error("Intersection goes through non-manifold edges."); 219 | } 220 | 221 | auto clean = vtkSmartPointer::New(); 222 | clean->SetInputData(lines); 223 | clean->ToleranceIsAbsoluteOn(); 224 | clean->SetAbsoluteTolerance(1e-5); 225 | clean->Update(); 226 | 227 | auto cleaned = clean->GetOutput(); 228 | 229 | vtkCellIterator *cellItr = cleaned->NewCellIterator(); 230 | 231 | vtkIdType cellId; 232 | 233 | for (cellItr->InitTraversal(); !cellItr->IsDoneWithTraversal(); cellItr->GoToNextCell()) { 234 | cellId = cellItr->GetCellId(); 235 | 236 | if (cellItr->GetCellType() != VTK_LINE) { 237 | cleaned->DeleteCell(cellId); 238 | } 239 | } 240 | 241 | cellItr->Delete(); 242 | 243 | cleaned->RemoveDeletedCells(); 244 | 245 | return cleaned; 246 | } 247 | 248 | void Contact::GetNonManifoldEdges (vtkPolyData *pd, NonManifoldEdgesType &edges) { 249 | 250 | pd->BuildLinks(); 251 | 252 | vtkCellIterator *cellItr = pd->NewCellIterator(); 253 | 254 | vtkIdType cellId; 255 | vtkIdList *ptIds; 256 | 257 | auto neigs = vtkSmartPointer::New(); 258 | 259 | vtkIdType i, j, k; 260 | 261 | vtkIdType idA, idB; 262 | 263 | vtkIdType n; 264 | 265 | vtkIdType num; 266 | const vtkIdType *pts; 267 | 268 | vtkIdType a, b; 269 | 270 | for (cellItr->InitTraversal(); !cellItr->IsDoneWithTraversal(); cellItr->GoToNextCell()) { 271 | cellId = cellItr->GetCellId(); 272 | ptIds = cellItr->GetPointIds(); 273 | 274 | for (i = 0; i < ptIds->GetNumberOfIds(); i++) { 275 | j = i+1; 276 | 277 | if (j == ptIds->GetNumberOfIds()) { 278 | j = 0; 279 | } 280 | 281 | idA = ptIds->GetId(i); 282 | idB = ptIds->GetId(j); 283 | 284 | pd->GetCellEdgeNeighbors(cellId, idA, idB, neigs); 285 | 286 | if (neigs->GetNumberOfIds() > 1) { 287 | 288 | n = 0; 289 | 290 | for (k = 0; k < neigs->GetNumberOfIds(); k++) { 291 | pd->GetCellPoints(neigs->GetId(k), num, pts); 292 | 293 | for (a = 0; a < num; a++) { 294 | b = a+1; 295 | 296 | if (b == num) { 297 | b = 0; 298 | } 299 | 300 | if (pts[a] == idB && pts[b] == idA) { 301 | n++; 302 | } 303 | } 304 | } 305 | 306 | if (n > 1) { 307 | edges.emplace(idA, idB); 308 | edges.emplace(idB, idA); 309 | } 310 | } 311 | } 312 | } 313 | 314 | cellItr->Delete(); 315 | } 316 | 317 | void Contact::InterEdgeLine (InterPtsType &interPts, const Point3d &pA, const Point3d &pB, Src src) { 318 | // schnitt mit x-achse 319 | 320 | double v[3]; 321 | Point3d::GetVec(pA, pB, v); 322 | 323 | double w[] = {-v[1], v[0]}; 324 | 325 | double c = w[0]*pA.x+w[1]*pA.y; 326 | 327 | if (std::abs(w[0]) < 1e-10) { 328 | if (std::abs(pA.y) < 1e-5 && std::abs(pB.y) < 1e-5) { 329 | interPts.emplace_back(pA.x, 0, 0, pA.x, pA.id, pB.id, End::A, src, PointSrc::Copied); 330 | interPts.emplace_back(pB.x, 0, 0, pB.x, pA.id, pB.id, End::B, src, PointSrc::Copied); 331 | } 332 | } else { 333 | double yA = pA.y-1e-6*v[1]; 334 | double yB = pB.y+1e-6*v[1]; 335 | 336 | if (std::signbit(yA) != std::signbit(yB)) { 337 | double x = c/w[0]; 338 | 339 | double sA[] = {pA.x-x, pA.y}; 340 | double sB[] = {pB.x-x, pB.y}; 341 | 342 | double dA = sA[0]*sA[0]+sA[1]*sA[1]; 343 | double dB = sB[0]*sB[0]+sB[1]*sB[1]; 344 | 345 | End end = dA < 1e-12 ? End::A : (dB < 1e-12 ? End::B : End::None); 346 | 347 | interPts.emplace_back(x, 0, 0, x, pA.id, pB.id, end, src, PointSrc::Calculated); 348 | } 349 | } 350 | 351 | } 352 | 353 | bool Contact::InterPolyLine (InterPtsType &interPts, const Base2 &base, const Poly &poly, Src src) { 354 | 355 | #if (defined(_debA) && defined(_debB)) 356 | if (_idA == _debA && _idB == _debB) { 357 | std::cout << "InterPolyLine()" << std::endl; 358 | } 359 | #endif 360 | 361 | Poly::const_iterator itrA, itrB; 362 | 363 | double q[3]; 364 | 365 | for (itrA = poly.begin(); itrA != poly.end(); ++itrA) { 366 | itrB = itrA+1; 367 | 368 | if (itrB == poly.end()) { 369 | itrB = poly.begin(); 370 | } 371 | 372 | InterEdgeLine(interPts, *itrA, *itrB, src); // schnitt mit x-achse 373 | } 374 | 375 | for (auto& p : interPts) { 376 | base.BackTransform(p.pt, q); 377 | 378 | std::copy_n(q, 3, p.pt); 379 | } 380 | 381 | std::sort(interPts.begin(), interPts.end(), [](auto &a, auto &b) { return a.t < b.t; }); 382 | 383 | struct Cmp { 384 | const double tol = 1e-5; 385 | 386 | bool operator() (const InterPt &lhs, const InterPt &rhs) const { 387 | if (lhs.pointSrc == PointSrc::Copied && rhs.pointSrc == PointSrc::Copied) { 388 | if (lhs.GetEnd() == rhs.GetEnd()) { 389 | return false; 390 | } 391 | } else if (lhs.pointSrc == PointSrc::Copied) { 392 | const vtkIdType end = lhs.GetEnd(); 393 | 394 | if (end == rhs.edge.f || end == rhs.edge.g) { 395 | return false; 396 | } 397 | } else if (rhs.pointSrc == PointSrc::Copied) { 398 | const vtkIdType end = rhs.GetEnd(); 399 | 400 | if (end == lhs.edge.f || end == lhs.edge.g) { 401 | return false; 402 | } 403 | } 404 | 405 | const double d = lhs.t-rhs.t; 406 | 407 | if (std::abs(d) < tol) { 408 | return false; 409 | } else { 410 | return d < 0; 411 | } 412 | } 413 | }; 414 | 415 | std::map grouped; 416 | 417 | std::vector sortedPts; 418 | 419 | for (auto &p : interPts) { 420 | grouped[p].push_back(p); 421 | } 422 | 423 | for (auto& [k, v] : grouped) { 424 | std::sort(v.begin(), v.end(), [](const InterPt &lhs, const InterPt &rhs) { return lhs.pointSrc > rhs.pointSrc; }); 425 | 426 | sortedPts.push_back(v); 427 | } 428 | 429 | std::map allEnds; 430 | 431 | decltype(sortedPts)::iterator itr; 432 | 433 | for (itr = sortedPts.begin(); itr != sortedPts.end(); ++itr) { 434 | if (itr == sortedPts.begin()) { 435 | if (itr->size() == 2) { 436 | itr->pop_back(); 437 | } 438 | } else if (itr == sortedPts.end()-1) { 439 | if (itr->size() == 2) { 440 | itr->pop_back(); 441 | } 442 | } else if (itr->size() == 1 && itr->back().end != End::None) { 443 | itr->push_back(itr->back()); 444 | } 445 | 446 | if (itr->back().end != End::None) { 447 | auto p = itr->back(); 448 | 449 | allEnds.emplace(p.end == End::A ? p.edge.f : p.edge.g, p.t); 450 | } 451 | 452 | // hier schneidet sich das polygon selbst 453 | 454 | if (itr->size() > 2) { 455 | return false; 456 | } 457 | 458 | if (itr->size() == 2) { 459 | // schnitt durch kongruente kanten 460 | 461 | if (itr->back().end == End::None) { 462 | return false; 463 | } 464 | 465 | auto &edgeA = itr->front().edge; 466 | auto &edgeB = itr->back().edge; 467 | 468 | if (edgeA.f == edgeB.g && edgeB.f == edgeA.g) { 469 | return false; 470 | } 471 | 472 | } 473 | 474 | } 475 | 476 | std::map> pts; 477 | 478 | for (auto &p : poly) { 479 | pts.emplace(p.id, p); 480 | } 481 | 482 | vtkIdType ind; 483 | 484 | vtkIdType prev, next; 485 | 486 | for (itr = sortedPts.begin(); itr != sortedPts.end(); ++itr) { 487 | auto p = itr->back(); 488 | 489 | if (p.end == End::None) { 490 | continue; 491 | } 492 | 493 | if (p.end == End::A) { 494 | ind = p.edge.f; 495 | 496 | next = p.edge.g; 497 | 498 | auto _itr = std::find_if(poly.begin(), poly.end(), [&ind](auto &p) { return p.id == ind; }); 499 | 500 | if (_itr == poly.begin()) { 501 | prev = poly.back().id; 502 | } else { 503 | prev = std::prev(_itr)->id; 504 | } 505 | 506 | } else { 507 | ind = p.edge.g; 508 | 509 | prev = p.edge.f; 510 | 511 | auto _itr = std::find_if(poly.begin(), poly.end(), [&ind](auto &p) { return p.id == ind; }); 512 | 513 | if (_itr == poly.end()-1) { 514 | next = poly.front().id; 515 | } else { 516 | next = std::next(_itr)->id; 517 | } 518 | } 519 | 520 | if (itr->size() == 2) { 521 | if (allEnds.count(prev) == 0 && allEnds.count(next) == 1) { 522 | const Point3d &q = pts.at(prev); 523 | 524 | if ((allEnds.at(next) < p.t && q.y < 0) || (allEnds.at(next) > p.t && q.y > 0)) { 525 | itr->pop_back(); 526 | } 527 | 528 | } else if (allEnds.count(prev) == 1 && allEnds.count(next) == 0) { 529 | const Point3d &q = pts.at(next); 530 | 531 | if ((allEnds.at(prev) < p.t && q.y > 0) || (allEnds.at(prev) > p.t && q.y < 0)) { 532 | itr->pop_back(); 533 | } 534 | } 535 | } 536 | 537 | if (allEnds.count(prev) == 0 && allEnds.count(next) == 0) { 538 | const Point3d &a = pts.at(prev); 539 | const Point3d &b = pts.at(next); 540 | 541 | if (std::signbit(a.y) != std::signbit(b.y)) { 542 | if (itr->size() == 2) { 543 | itr->pop_back(); 544 | } 545 | } else { 546 | if ((a.x > b.x) == std::signbit(a.y)) { 547 | itr->clear(); 548 | } 549 | } 550 | 551 | } 552 | 553 | } 554 | 555 | InterPtsType _interPts; 556 | 557 | for (const auto &pts : sortedPts) { 558 | std::copy(pts.begin(), pts.end(), std::back_inserter(_interPts)); 559 | } 560 | 561 | interPts.swap(_interPts); 562 | 563 | #if (defined(_debA) && defined(_debB)) 564 | if (_idA == _debA && _idB == _debB) { 565 | for (auto &p : interPts) { 566 | std::cout << p << std::endl; 567 | } 568 | } 569 | #endif 570 | 571 | return true; 572 | } 573 | 574 | void Contact::InterPolys (vtkIdType idA, vtkIdType idB) { 575 | 576 | #if (defined(_debA) && defined(_debB)) 577 | _idA = idA; _idB = idB; 578 | 579 | if (_idA == _debA && _idB == _debB) { 580 | std::cout << "InterPolys(" << idA << ", " << idB << ")" << std::endl; 581 | } 582 | #endif 583 | 584 | vtkIdType numA, numB; 585 | const vtkIdType *ptsA, *ptsB; 586 | 587 | newPdA->GetCellPoints(idA, numA, ptsA); 588 | newPdB->GetCellPoints(idB, numB, ptsB); 589 | 590 | Poly polyA, polyB; 591 | GetPoly(newPdA->GetPoints(), numA, ptsA, polyA); 592 | GetPoly(newPdB->GetPoints(), numB, ptsB, polyB); 593 | 594 | double nA[3], nB[3], r[3], s[3]; 595 | 596 | ComputeNormal(polyA, nA); 597 | ComputeNormal(polyB, nB); 598 | 599 | #if (defined(_debA) && defined(_debB)) 600 | if (_idA == _debA && _idB == _debB) { 601 | std::cout << "nA [" << nA[0] << ", " << nA[1] << ", " << nA[2] << "]" << std::endl; 602 | std::cout << "nB [" << nB[0] << ", " << nB[1] << ", " << nB[2] << "]" << std::endl; 603 | } 604 | #endif 605 | 606 | if (vtkMath::Dot(nA, nB) > .9999999999) { 607 | return; 608 | } 609 | 610 | double dA = polyA[0].x*nA[0]+polyA[0].y*nA[1]+polyA[0].z*nA[2]; 611 | double dB = polyB[0].x*nB[0]+polyB[0].y*nB[1]+polyB[0].z*nB[2]; 612 | 613 | vtkMath::Cross(nA, nB, r); 614 | vtkMath::Normalize(r); 615 | 616 | std::array, 3> dets { 617 | std::make_tuple(0, 1, 2, nA[0]*nB[1]-nB[0]*nA[1]), 618 | std::make_tuple(0, 2, 1, nA[0]*nB[2]-nB[0]*nA[2]), 619 | std::make_tuple(1, 2, 0, nA[1]*nB[2]-nB[1]*nA[2]) 620 | }; 621 | 622 | const auto& [i, j, k, det] = *std::max_element(dets.begin(), dets.end(), [](auto &a, auto &b) { return std::abs(std::get<3>(a)) < std::abs(std::get<3>(b)); }); 623 | 624 | s[i] = (dA*nB[j]-dB*nA[j])/det; 625 | s[j] = (dB*nA[i]-dA*nB[i])/det; 626 | s[k] = 0; 627 | 628 | #if (defined(_debA) && defined(_debB)) 629 | if (_idA == _debA && _idB == _debB) { 630 | std::cout << "det " << det << std::endl; 631 | std::cout << "r [" << r[0] << ", " << r[1] << ", " << r[2] << "]" << std::endl; 632 | std::cout << "s [" << s[0] << ", " << s[1] << ", " << s[2] << "]" << std::endl; 633 | } 634 | #endif 635 | 636 | Base2 baseA(s, r, nA); 637 | Base2 baseB(s, r, nB); 638 | 639 | Poly transA, transB; 640 | 641 | FlattenPoly2(polyA, transA, baseA); 642 | FlattenPoly2(polyB, transB, baseB); 643 | 644 | #if (defined(_debA) && defined(_debB)) 645 | if (_idA == _debA && _idB == _debB) { 646 | std::cout << "transA {"; 647 | for (auto& p : transA) { 648 | std::cout << p << ", "; 649 | } 650 | std::cout << std::endl; 651 | 652 | std::cout << "transB {"; 653 | for (auto& p : transB) { 654 | std::cout << p << ", "; 655 | } 656 | std::cout << std::endl; 657 | } 658 | #endif 659 | 660 | bool isPlanarA = std::find_if(transA.begin(), transA.end(), [](auto &p) { return std::abs(p.z) > 1e-6; }) == transA.end(); 661 | bool isPlanarB = std::find_if(transB.begin(), transB.end(), [](auto &p) { return std::abs(p.z) > 1e-6; }) == transB.end(); 662 | 663 | bool hasReplA = replsA.count(idA) == 1; 664 | bool hasReplB = replsB.count(idB) == 1; 665 | 666 | if (!isPlanarA && !hasReplA && newPdA->GetCellType(idA) != VTK_TRIANGLE) { 667 | try { 668 | auto newIds = PreventEqualCaptPoints::TriangulateCell(newPdA, idA, {}); 669 | replsA.emplace(idA, newIds); 670 | hasReplA = true; 671 | } catch (...) {} 672 | } 673 | 674 | if (!isPlanarB && !hasReplB && newPdB->GetCellType(idB) != VTK_TRIANGLE) { 675 | try { 676 | auto newIds = PreventEqualCaptPoints::TriangulateCell(newPdB, idB, {}); 677 | replsB.emplace(idB, newIds); 678 | hasReplB = true; 679 | } catch (...) {} 680 | } 681 | 682 | if (hasReplA || hasReplB) { 683 | pairs.emplace_back(idA, idB); 684 | return; 685 | } 686 | 687 | InterPtsType intersPtsA, intersPtsB; 688 | 689 | if (!InterPolyLine(intersPtsA, baseA, transA, Src::A)) { 690 | throw std::runtime_error("Found invalid intersection points."); 691 | } 692 | 693 | if (!InterPolyLine(intersPtsB, baseB, transB, Src::B)) { 694 | throw std::runtime_error("Found invalid intersection points."); 695 | } 696 | 697 | if (!CheckInters(intersPtsA, newPdA)) { 698 | std::stringstream ss; 699 | ss << "Intersection points do not lie on the edges (cells " << idA << ", " << idB << ")."; 700 | 701 | throw std::runtime_error(ss.str()); 702 | } 703 | 704 | if (!CheckInters(intersPtsB, newPdB)) { 705 | std::stringstream ss; 706 | ss << "Intersection points do not lie on the edges (cells " << idA << ", " << idB << ")."; 707 | 708 | throw std::runtime_error(ss.str()); 709 | } 710 | 711 | if ((intersPtsA.size() & 1) == 0 && (intersPtsB.size() & 1) == 0) { 712 | AddContactLines(intersPtsA, intersPtsB, idA, idB); 713 | } 714 | 715 | } 716 | 717 | bool Contact::CheckInters (const InterPtsType &interPts, vtkPolyData *pd) { 718 | #if (defined(_debA) && defined(_debB)) 719 | if (_idA == _debA && _idB == _debB) { 720 | std::cout << "CheckInters()" << std::endl; 721 | } 722 | #endif 723 | 724 | double ptA[3], 725 | ptB[3], 726 | v[3], 727 | w[3], 728 | k, 729 | l, 730 | alpha, 731 | d; 732 | 733 | for (auto &p : interPts) { 734 | 735 | #if (defined(_debA) && defined(_debB)) 736 | if (_idA == _debA && _idB == _debB) { 737 | std::cout << p << std::endl; 738 | } 739 | #endif 740 | 741 | pd->GetPoint(p.edge.f, ptA); 742 | pd->GetPoint(p.edge.g, ptB); 743 | 744 | vtkMath::Subtract(ptA, ptB, v); 745 | vtkMath::Normalize(v); 746 | vtkMath::Subtract(ptA, p.pt, w); 747 | 748 | k = vtkMath::Norm(w); 749 | l = vtkMath::Dot(v, w); 750 | alpha = std::acos(l/k); 751 | 752 | #if (defined(_debA) && defined(_debB)) 753 | if (_idA == _debA && _idB == _debB) { 754 | std::cout << "alpha " << alpha << std::endl; 755 | } 756 | #endif 757 | 758 | if (std::isnan(alpha)) { 759 | continue; 760 | } 761 | 762 | d = std::sin(alpha)*k; 763 | 764 | #if (defined(_debA) && defined(_debB)) 765 | if (_idA == _debA && _idB == _debB) { 766 | std::cout << "d " << d << std::endl; 767 | } 768 | #endif 769 | 770 | if (d < 1e-5) { 771 | continue; 772 | } 773 | 774 | return false; 775 | 776 | } 777 | 778 | return true; 779 | 780 | } 781 | 782 | void Contact::OverlapLines (OverlapsType &overlaps, InterPtsType &intersA, InterPtsType &intersB) { 783 | 784 | auto Add = [](InterPt &a, InterPt &b, InterPt &c, InterPt &d) { 785 | a.Merge(c); 786 | b.Merge(d); 787 | 788 | return std::make_tuple(a, b); 789 | }; 790 | 791 | InterPtsType::iterator itr, itr2; 792 | 793 | for (itr = intersA.begin(); itr != intersA.end(); itr += 2) { 794 | for (itr2 = intersB.begin(); itr2 != intersB.end(); itr2 += 2) { 795 | if (itr->t <= itr2->t && (itr+1)->t > itr2->t) { 796 | if ((itr2+1)->t < (itr+1)->t) { 797 | overlaps.push_back(Add(*itr2, *(itr2+1), *itr, *(itr+1))); 798 | } else { 799 | overlaps.push_back(Add(*itr2, *(itr+1), *itr, *(itr2+1))); 800 | } 801 | } else if (itr2->t <= itr->t && (itr2+1)->t > itr->t) { 802 | if ((itr+1)->t < (itr2+1)->t) { 803 | overlaps.push_back(Add(*itr, *(itr+1), *itr2, *(itr2+1))); 804 | } else { 805 | overlaps.push_back(Add(*itr, *(itr2+1), *itr2, *(itr+1))); 806 | } 807 | } 808 | } 809 | } 810 | 811 | } 812 | 813 | void Contact::AddContactLines (InterPtsType &intersA, InterPtsType &intersB, vtkIdType idA, vtkIdType idB) { 814 | 815 | if (intersA.size() == 0 || intersB.size() == 0) { 816 | return; 817 | } 818 | 819 | OverlapsType overlaps; 820 | OverlapLines(overlaps, intersA, intersB); 821 | 822 | OverlapsType::const_iterator itr; 823 | 824 | for (itr = overlaps.begin(); itr != overlaps.end(); ++itr) { 825 | auto &f = std::get<0>(*itr); 826 | auto &s = std::get<1>(*itr); 827 | 828 | if ((f.src == Src::A && edgesA.count(f.edge) == 1) || (s.src == Src::A && edgesA.count(s.edge) == 1)) { 829 | touchesEdgesA = true; 830 | } 831 | 832 | if ((f.src == Src::B && edgesB.count(f.edge) == 1) || (s.src == Src::B && edgesB.count(s.edge) == 1)) { 833 | touchesEdgesB = true; 834 | } 835 | 836 | vtkIdList *linePts = vtkIdList::New(); 837 | 838 | linePts->InsertNextId(pts->InsertNextPoint(f.pt)); 839 | linePts->InsertNextId(pts->InsertNextPoint(s.pt)); 840 | 841 | lines->InsertNextCell(VTK_LINE, linePts); 842 | 843 | linePts->Delete(); 844 | 845 | const vtkIdType tupleA[] = {f.srcA, s.srcA}; 846 | const vtkIdType tupleB[] = {f.srcB, s.srcB}; 847 | 848 | sourcesA->InsertNextTypedTuple(tupleA); 849 | sourcesB->InsertNextTypedTuple(tupleB); 850 | 851 | contA->InsertNextValue(idA); 852 | contB->InsertNextValue(idB); 853 | } 854 | 855 | } 856 | 857 | int Contact::InterNodes (vtkOBBNode *nodeA, vtkOBBNode *nodeB, vtkMatrix4x4 *vtkNotUsed(matrix), void *ptr) { 858 | auto _this = reinterpret_cast(ptr); 859 | 860 | vtkIdList *cellsA = nodeA->Cells; 861 | vtkIdList *cellsB = nodeB->Cells; 862 | 863 | vtkIdType numCellsA = cellsA->GetNumberOfIds(); 864 | vtkIdType numCellsB = cellsB->GetNumberOfIds(); 865 | 866 | vtkIdType i, j, cellA, cellB; 867 | 868 | for (i = 0; i < numCellsA; i++) { 869 | cellA = cellsA->GetId(i); 870 | 871 | for (j = 0; j < numCellsB; j++) { 872 | cellB = cellsB->GetId(j); 873 | 874 | _this->InterPolys(cellA, cellB); 875 | } 876 | } 877 | 878 | return 0; 879 | } 880 | 881 | void Contact::IntersectReplacements () { 882 | if (pairs.empty()) { 883 | return; 884 | } 885 | 886 | while (!pairs.empty()) { 887 | vtkIdType i; 888 | 889 | auto iterA = vtkArrayIteratorTemplate::New(); 890 | iterA->Initialize(contA); 891 | 892 | auto iterB = vtkArrayIteratorTemplate::New(); 893 | iterB->Initialize(contB); 894 | 895 | for (i = 0; i < iterA->GetNumberOfValues(); i++) { 896 | if (replsA.count(iterA->GetValue(i)) == 1 || replsB.count(iterB->GetValue(i)) == 1) { 897 | lines->DeleteCell(i); 898 | 899 | pairs.emplace_back(iterA->GetValue(i), iterB->GetValue(i)); 900 | } 901 | } 902 | 903 | PairsType current; 904 | 905 | current.swap(pairs); 906 | 907 | for (auto& [a, b] : current) { 908 | auto itrA = replsA.find(a); 909 | auto itrB = replsB.find(b); 910 | 911 | IdsType cellsA, cellsB; 912 | 913 | if (itrA == replsA.end()) { 914 | cellsA.push_back(a); 915 | } else { 916 | auto ids = itrA->second; 917 | std::copy(ids.begin(), ids.end(), std::back_inserter(cellsA)); 918 | } 919 | 920 | if (itrB == replsB.end()) { 921 | cellsB.push_back(b); 922 | } else { 923 | auto ids = itrB->second; 924 | std::copy(ids.begin(), ids.end(), std::back_inserter(cellsB)); 925 | } 926 | 927 | for (auto &idA : cellsA) { 928 | for (auto &idB : cellsB) { 929 | InterPolys(idA, idB); 930 | } 931 | } 932 | } 933 | 934 | } 935 | 936 | lines->RemoveDeletedCells(); 937 | 938 | contA = vtkIdTypeArray::SafeDownCast(lines->GetCellData()->GetScalars("cA")); 939 | contB = vtkIdTypeArray::SafeDownCast(lines->GetCellData()->GetScalars("cB")); 940 | 941 | sourcesA = vtkIdTypeArray::SafeDownCast(lines->GetCellData()->GetScalars("sourcesA")); 942 | sourcesB = vtkIdTypeArray::SafeDownCast(lines->GetCellData()->GetScalars("sourcesB")); 943 | 944 | // contA und contB aktualisieren 945 | 946 | auto oldCellIdsA = vtkSmartPointer::New(); 947 | auto oldCellIdsB = vtkSmartPointer::New(); 948 | 949 | oldCellIdsA->SetName("OldCellIds"); 950 | oldCellIdsB->SetName("OldCellIds"); 951 | 952 | vtkIdType numCellsA = newPdA->GetNumberOfCells(); 953 | vtkIdType numCellsB = newPdB->GetNumberOfCells(); 954 | 955 | oldCellIdsA->SetNumberOfValues(numCellsA); 956 | oldCellIdsB->SetNumberOfValues(numCellsB); 957 | 958 | vtkIdType i; 959 | 960 | for (i = 0; i < numCellsA; i++) { 961 | oldCellIdsA->SetValue(i, i); 962 | } 963 | 964 | for (i = 0; i < numCellsB; i++) { 965 | oldCellIdsB->SetValue(i, i); 966 | } 967 | 968 | newPdA->GetCellData()->AddArray(oldCellIdsA); 969 | newPdB->GetCellData()->AddArray(oldCellIdsB); 970 | 971 | for (auto& [k, v] : replsA) { 972 | newPdA->DeleteCell(k); 973 | } 974 | 975 | for (auto& [k, v] : replsB) { 976 | newPdB->DeleteCell(k); 977 | } 978 | 979 | newPdA->RemoveDeletedCells(); 980 | newPdB->RemoveDeletedCells(); 981 | 982 | numCellsA = newPdA->GetNumberOfCells(); 983 | numCellsB = newPdB->GetNumberOfCells(); 984 | 985 | oldCellIdsA = vtkIdTypeArray::SafeDownCast(newPdA->GetCellData()->GetScalars("OldCellIds")); 986 | oldCellIdsB = vtkIdTypeArray::SafeDownCast(newPdB->GetCellData()->GetScalars("OldCellIds")); 987 | 988 | std::map newCellIdsA, newCellIdsB; 989 | 990 | auto iterA = vtkArrayIteratorTemplate::New(); 991 | iterA->Initialize(oldCellIdsA); 992 | 993 | for (i = 0; i < numCellsA; i++) { 994 | newCellIdsA.emplace(iterA->GetValue(i), i); 995 | } 996 | 997 | auto iterB = vtkArrayIteratorTemplate::New(); 998 | iterB->Initialize(oldCellIdsB); 999 | 1000 | for (i = 0; i < numCellsB; i++) { 1001 | newCellIdsB.emplace(iterB->GetValue(i), i); 1002 | } 1003 | 1004 | vtkIdType numLines = lines->GetNumberOfCells(); 1005 | 1006 | try { 1007 | 1008 | auto _iterA = vtkArrayIteratorTemplate::New(); 1009 | _iterA->Initialize(contA); 1010 | 1011 | for (i = 0; i < numLines; i++) { 1012 | _iterA->SetValue(i, newCellIdsA.at(_iterA->GetValue(i))); 1013 | } 1014 | 1015 | auto _iterB = vtkArrayIteratorTemplate::New(); 1016 | _iterB->Initialize(contB); 1017 | 1018 | for (i = 0; i < numLines; i++) { 1019 | _iterB->SetValue(i, newCellIdsB.at(_iterB->GetValue(i))); 1020 | } 1021 | 1022 | } catch (const std::out_of_range &e) { 1023 | throw std::runtime_error(""); 1024 | } 1025 | 1026 | newPdA->GetCellData()->RemoveArray("OldCellIds"); 1027 | newPdB->GetCellData()->RemoveArray("OldCellIds"); 1028 | 1029 | } 1030 | -------------------------------------------------------------------------------- /Contact.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __Contact_h 18 | #define __Contact_h 19 | 20 | #include "Utilities.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | enum class Src { 27 | A, 28 | B 29 | }; 30 | 31 | enum class End { 32 | None, 33 | A, 34 | B 35 | }; 36 | 37 | enum class PointSrc { 38 | Calculated, 39 | Copied 40 | }; 41 | 42 | class InterPt { 43 | public: 44 | InterPt () = delete; 45 | 46 | InterPt (double x, double y, double z, double t, vtkIdType a, vtkIdType b, End end, Src src, PointSrc pointSrc) : t(t), edge(a, b), end(end), src(src), srcA(NOTSET), srcB(NOTSET), pointSrc(pointSrc) { 47 | pt[0] = x; 48 | pt[1] = y; 49 | pt[2] = z; 50 | } 51 | 52 | double pt[3], t; 53 | Pair edge; 54 | End end; 55 | Src src; 56 | vtkIdType srcA, srcB; 57 | 58 | PointSrc pointSrc; 59 | 60 | friend std::ostream& operator<< (std::ostream &out, const InterPt &s) { 61 | out << "pt [" << s.pt[0] << ", " << s.pt[1] << ", " << s.pt[2] << "]" 62 | << ", t " << s.t 63 | << ", edge " << s.edge 64 | << ", end " << s.end 65 | << ", src " << s.src 66 | << ", pointSrc " << s.pointSrc; 67 | 68 | return out; 69 | } 70 | 71 | void Merge (const InterPt &other) { 72 | assert(src != other.src); 73 | 74 | if (src == Src::A) { 75 | srcA = GetEnd(); 76 | } else { 77 | srcB = GetEnd(); 78 | } 79 | 80 | if (std::abs(other.t-t) < 1e-5) { 81 | if (other.src == Src::A) { 82 | srcA = other.GetEnd(); 83 | } else { 84 | srcB = other.GetEnd(); 85 | } 86 | } 87 | } 88 | 89 | inline vtkIdType GetEnd () const { 90 | if (end == End::A) { 91 | return edge.f; 92 | } 93 | 94 | if (end == End::B) { 95 | return edge.g; 96 | } 97 | 98 | return NOTSET; 99 | } 100 | 101 | }; 102 | 103 | typedef std::vector InterPtsType; 104 | typedef std::vector> OverlapsType; 105 | 106 | typedef std::set NonManifoldEdgesType; 107 | 108 | typedef std::vector> PairsType; 109 | 110 | vtkSmartPointer Clean (vtkPolyData *pd); 111 | 112 | class Contact { 113 | public: 114 | Contact () = delete; 115 | Contact (vtkPolyData *newPdA, vtkPolyData *newPdB); 116 | 117 | vtkPolyData *newPdA, *newPdB; 118 | 119 | vtkSmartPointer pts; 120 | vtkSmartPointer lines; 121 | vtkSmartPointer contA, contB, sourcesA, sourcesB; 122 | 123 | bool touchesEdgesA, touchesEdgesB; 124 | 125 | NonManifoldEdgesType edgesA, edgesB; 126 | 127 | vtkSmartPointer treeA, treeB; 128 | 129 | vtkSmartPointer GetLines (vtkPolyData *pdA = nullptr, vtkLinearTransform *transA = nullptr, vtkPolyData *pdB = nullptr, vtkLinearTransform *transB = nullptr); 130 | 131 | void GetNonManifoldEdges (vtkPolyData *pd, NonManifoldEdgesType &edges); 132 | 133 | void InterEdgeLine (InterPtsType &interPts, const Point3d &pA, const Point3d &pB, Src src); 134 | 135 | bool InterPolyLine (InterPtsType &interPts, const Base2 &base, const Poly &poly, Src src); 136 | 137 | void InterPolys (vtkIdType idA, vtkIdType idB); 138 | 139 | bool CheckInters (const InterPtsType &interPts, vtkPolyData *pd); 140 | 141 | void OverlapLines (OverlapsType &overlaps, InterPtsType &intersA, InterPtsType &intersB); 142 | 143 | void AddContactLines (InterPtsType &intersA, InterPtsType &intersB, vtkIdType idA, vtkIdType idB); 144 | 145 | static int InterNodes (vtkOBBNode *nodeA, vtkOBBNode *nodeB, vtkMatrix4x4 *vtkNotUsed(matrix), void *ptr); 146 | 147 | PairsType pairs; 148 | 149 | std::map replsA, replsB; 150 | 151 | void IntersectReplacements (); 152 | }; 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /Merger.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "Merger.h" 18 | 19 | #include 20 | 21 | Merger::Merger (vtkPolyData *pd, const PStrips &pStrips, const StripsType &strips, const IdsType &descIds, vtkIdType origId) : pd(pd), pStrips(pStrips), origId(origId) { 22 | 23 | const StripPtsType &pts = pStrips.pts; 24 | const Base &base = pStrips.base; 25 | 26 | vtkIdType i, num; 27 | const vtkIdType *cell; 28 | 29 | double pt[3]; 30 | 31 | for (auto id : descIds) { 32 | pd->GetCellPoints(id, num, cell); 33 | 34 | Poly p; 35 | 36 | for (i = 0; i < num; i++) { 37 | pd->GetPoint(cell[i], pt); 38 | 39 | double proj[2]; 40 | Transform(pt, proj, base); 41 | 42 | p.emplace_back(proj[0], proj[1], 0, cell[i]); 43 | 44 | } 45 | 46 | polys.push_back(p); 47 | 48 | pd->DeleteCell(id); 49 | } 50 | 51 | for (auto &strip : strips) { 52 | Poly p; 53 | 54 | for (auto &sp : strip) { 55 | const double *pt = pts.at(sp.ind).pt; 56 | 57 | double proj[2]; 58 | Transform(pt, proj, base); 59 | 60 | p.emplace_back(proj[0], proj[1], 0, NOTSET, sp.ind); 61 | } 62 | 63 | p.pop_back(); 64 | 65 | double n[3]; 66 | ComputeNormal(p, n); 67 | 68 | if (n[2] < 0) { 69 | Poly q(p.rbegin(), p.rend()); 70 | p.swap(q); 71 | } 72 | 73 | innerIds.push_back(polys.size()); 74 | 75 | polys.push_back(p); 76 | } 77 | 78 | } 79 | 80 | void Merger::Run () { 81 | // mergen mit hilfe von vtkKdTree und vtkModifiedBSPTree 82 | 83 | vtkPoints *pdPts = pd->GetPoints(); 84 | vtkIdTypeArray *origCellIds = vtkIdTypeArray::SafeDownCast(pd->GetCellData()->GetScalars("OrigCellIds")); 85 | 86 | assert(origCellIds != nullptr); 87 | 88 | std::vector groups(polys.size()); 89 | 90 | PolysType::const_iterator itrA, itrB; 91 | 92 | std::size_t i {0}; 93 | 94 | for (itrA = polys.begin(); itrA != polys.end(); ++itrA) { 95 | if (std::find(innerIds.begin(), innerIds.end(), i) != innerIds.end()) { 96 | std::size_t j {0}; 97 | for (itrB = polys.begin(); itrB != polys.end(); ++itrB) { 98 | if (itrA != itrB && PointInPoly(*itrB, *itrA->begin())) { 99 | groups[j].push_back(i); 100 | } 101 | j++; 102 | } 103 | } 104 | i++; 105 | } 106 | 107 | std::size_t parent = 0; 108 | 109 | for (auto &group : groups) { 110 | GroupType parents; 111 | 112 | for (auto &index : group) { 113 | const GroupType &_group = groups[index]; 114 | parents.insert(parents.end(), _group.begin(), _group.end()); 115 | } 116 | 117 | std::sort(group.begin(), group.end()); 118 | std::sort(parents.begin(), parents.end()); 119 | 120 | GroupType _group {parent++}; 121 | std::set_difference(group.begin(), group.end(), parents.begin(), parents.end(), std::back_inserter(_group)); 122 | 123 | #ifdef DEBUG 124 | std::cout << "["; 125 | for (auto &index : _group) { 126 | std::cout << index << ", "; 127 | } 128 | std::cout << "]" << std::endl; 129 | #endif 130 | 131 | PolysType merged; 132 | 133 | MergeGroup(_group, merged); 134 | 135 | std::map newIds; 136 | 137 | for (auto &poly : merged) { 138 | auto newCell = vtkSmartPointer::New(); 139 | 140 | for (auto &p : poly) { 141 | vtkIdType id = p.id; 142 | 143 | if (id == NOTSET) { 144 | auto itr = newIds.find(p.otherId); 145 | 146 | if (itr == newIds.end()) { 147 | auto &q = pStrips.pts.at(p.otherId); 148 | 149 | id = pdPts->InsertNextPoint(q.pt); 150 | newIds.emplace(p.otherId, id); 151 | } else { 152 | id = itr->second; 153 | } 154 | } 155 | 156 | newCell->InsertNextId(id); 157 | 158 | } 159 | 160 | pd->InsertNextCell(VTK_POLYGON, newCell); 161 | origCellIds->InsertNextValue(origId); 162 | } 163 | 164 | } 165 | 166 | } 167 | 168 | void Merger::MergeGroup (const GroupType &group, PolysType &merged) { 169 | if (group.size() == 1) { 170 | merged.push_back(polys.at(group.back())); 171 | 172 | return; 173 | } 174 | 175 | auto pts = vtkSmartPointer::New(); 176 | pts->SetDataTypeToDouble(); 177 | 178 | IndexedPolysType indexedPolys; 179 | 180 | ReferencedPointsType refPts; 181 | 182 | SourcesType sources; 183 | std::size_t src = 0; 184 | 185 | for (auto &index : group) { 186 | const Poly &poly = polys.at(index); 187 | 188 | IndexedPoly ids; 189 | 190 | for (auto &p : poly) { 191 | vtkIdType id = pts->InsertNextPoint(p.x, p.y, p.z); 192 | 193 | ids.push_back(id); 194 | sources.emplace(id, src); 195 | 196 | refPts.emplace(id, p); 197 | } 198 | 199 | indexedPolys.push_back(std::move(ids)); 200 | src++; 201 | } 202 | 203 | auto kdTree = vtkSmartPointer::New(); 204 | kdTree->OmitZPartitioning(); 205 | kdTree->BuildLocatorFromPoints(pts); 206 | 207 | auto linesA = vtkSmartPointer::New(); 208 | linesA->SetPoints(pts); 209 | linesA->Allocate(1); 210 | 211 | IndexedPoly::const_iterator itrA, itrB; 212 | 213 | for (const auto &ids : indexedPolys) { 214 | for (itrA = ids.begin(); itrA != ids.end(); ++itrA) { 215 | itrB = itrA+1; 216 | if (itrB == ids.end()) { 217 | itrB = ids.begin(); 218 | } 219 | 220 | vtkIdList *line = vtkIdList::New(); 221 | line->InsertNextId(*itrA); 222 | line->InsertNextId(*itrB); 223 | 224 | linesA->InsertNextCell(VTK_LINE, line); 225 | 226 | line->Delete(); 227 | } 228 | } 229 | 230 | #ifdef DEBUG 231 | WriteVTK("linesA.vtk", linesA); 232 | #endif 233 | 234 | auto bspTreeA = vtkSmartPointer::New(); 235 | bspTreeA->SetDataSet(linesA); 236 | 237 | int n = 0; 238 | 239 | PolyConnsType polyConns; 240 | 241 | FindConns(linesA, kdTree, bspTreeA, polyConns, indexedPolys, sources, n); 242 | 243 | PolyConnsType connected {{0, {}}}; 244 | _IdsType restricted; // keine der conns darf im gleichen punkt beginnen 245 | 246 | auto linesB = vtkSmartPointer::New(); 247 | linesB->SetPoints(pts); 248 | linesB->Allocate(1); 249 | 250 | auto bspTreeB = vtkSmartPointer::New(); 251 | bspTreeB->SetDataSet(linesB); 252 | 253 | ConnsType firstConns; 254 | 255 | std::size_t i, numPolys = indexedPolys.size(); 256 | 257 | double ptA[3], ptB[3]; 258 | 259 | while (connected.size() < numPolys) { 260 | 261 | bool foundOne = false; 262 | 263 | for (i = 1; i < numPolys; i++) { 264 | if (connected.count(i) == 0) { 265 | const ConnsType &conns = polyConns[i]; 266 | 267 | for (auto &conn : conns) { 268 | if (connected.count(sources.at(conn.j)) == 1 269 | && restricted.count(conn.j) == 0) { 270 | 271 | pts->GetPoint(conn.i, ptA); 272 | pts->GetPoint(conn.j, ptB); 273 | 274 | if (bspTreeB->IntersectWithLine(ptA, ptB, 1e-5, nullptr, nullptr) == 0) { 275 | connected[sources.at(conn.i)].push_back(conn); 276 | 277 | // das andere poly auch aktualisieren 278 | connected[sources.at(conn.j)].emplace_back(conn.d, conn.j, conn.i); 279 | 280 | restricted.insert(conn.i); 281 | restricted.insert(conn.j); 282 | 283 | vtkIdList *line = vtkIdList::New(); 284 | line->InsertNextId(conn.i); 285 | line->InsertNextId(conn.j); 286 | 287 | linesB->InsertNextCell(VTK_LINE, line); 288 | 289 | line->Delete(); 290 | 291 | bspTreeB->Modified(); 292 | 293 | foundOne = true; 294 | 295 | firstConns.push_back(conn); 296 | 297 | break; 298 | } 299 | 300 | } 301 | } 302 | } 303 | } 304 | 305 | if (!foundOne) { 306 | if (!FindConns(linesA, kdTree, bspTreeA, polyConns, indexedPolys, sources, n)) { 307 | throw std::runtime_error("Merging failed."); 308 | } 309 | } 310 | } 311 | 312 | std::map> chains; 313 | 314 | PolyConnsType::const_iterator itrC; 315 | 316 | for (itrC = connected.begin(); itrC != connected.end(); ++itrC) { 317 | auto &chain = chains[itrC->first]; 318 | chain.push_back(itrC->first); 319 | 320 | while (chain.back() != 0) { 321 | chain.push_back(sources.at(connected.at(chain.back()).front().j)); 322 | } 323 | } 324 | 325 | #ifdef DEBUG 326 | std::cout << connected; 327 | 328 | decltype(chains)::const_iterator itrD; 329 | 330 | for (itrD = chains.begin(); itrD != chains.end(); ++itrD) { 331 | std::cout << itrD->first << ": ["; 332 | for (auto &id : itrD->second) { 333 | std::cout << id << ", "; 334 | } 335 | std::cout << "]" << std::endl; 336 | } 337 | #endif 338 | 339 | std::set solved {0}; 340 | 341 | std::deque searchInds; 342 | 343 | for (i = 1; i < numPolys; i++) { 344 | if (connected.at(i).size() == 1) { 345 | searchInds.push_back(i); 346 | } 347 | } 348 | 349 | while (!searchInds.empty()) { 350 | PriosType prios; 351 | 352 | for (auto ind : searchInds) { 353 | PolyPriosType polyPrios; 354 | 355 | #ifdef DEBUG 356 | std::cout << "ind " << ind << std::endl; 357 | #endif 358 | 359 | const Conn &first = connected.at(ind).back(); 360 | 361 | for (auto &conn : polyConns.at(ind)) { 362 | auto &src = sources.at(conn.j); 363 | 364 | if (polyPrios.count(src) == 1) { 365 | continue; 366 | } 367 | 368 | if (conn.i != first.i 369 | && conn.j != first.j 370 | && restricted.count(conn.i) == 0 371 | && restricted.count(conn.j) == 0) { 372 | 373 | pts->GetPoint(conn.i, ptA); 374 | pts->GetPoint(conn.j, ptB); 375 | 376 | if (bspTreeB->IntersectWithLine(ptA, ptB, 1e-5, nullptr, nullptr) == 0) { 377 | auto &chainA = chains.at(ind), 378 | &chainB = chains.at(src); 379 | 380 | std::set _chainA(chainA.begin(), chainA.end()), 381 | _chainB(chainB.begin(), chainB.end()); 382 | 383 | // gemeinsame eltern 384 | std::set shared; 385 | 386 | std::set_intersection(_chainA.begin(), _chainA.end(), _chainB.begin(), _chainB.end(), std::inserter(shared, shared.end())); 387 | 388 | // gemeinsame eltern müssen sich alle in solved befinden 389 | if (std::includes(solved.begin(), solved.end(), shared.begin(), shared.end())) { 390 | std::set solvable; 391 | 392 | std::set_difference(_chainA.begin(), _chainA.end(), solved.begin(), solved.end(), std::inserter(solvable, solvable.end())); 393 | std::set_difference(_chainB.begin(), _chainB.end(), solved.begin(), solved.end(), std::inserter(solvable, solvable.end())); 394 | 395 | polyPrios.emplace(std::piecewise_construct, 396 | std::forward_as_tuple(src), 397 | std::forward_as_tuple(conn, solvable, -conn.d)); 398 | } 399 | } 400 | } 401 | } 402 | 403 | PolyPriosType::const_iterator itr; 404 | for (itr = polyPrios.begin(); itr != polyPrios.end(); ++itr) { 405 | prios.insert(itr->second); 406 | } 407 | } 408 | 409 | if (!prios.empty()) { 410 | auto &prio = *prios.rbegin(); 411 | 412 | #ifdef DEBUG 413 | std::cout << "found " << prio << std::endl; 414 | #endif 415 | 416 | auto &conns = connected.at(sources.at(prio.conn.i)); 417 | 418 | conns.push_back(prio.conn); 419 | 420 | connected.at(sources.at(prio.conn.j)).emplace_back(prio.conn.d, prio.conn.j, prio.conn.i); 421 | 422 | restricted.insert(prio.conn.i); 423 | restricted.insert(prio.conn.j); 424 | 425 | vtkIdList *line = vtkIdList::New(); 426 | line->InsertNextId(prio.conn.i); 427 | line->InsertNextId(prio.conn.j); 428 | 429 | linesB->InsertNextCell(VTK_LINE, line); 430 | 431 | line->Delete(); 432 | 433 | bspTreeB->Modified(); 434 | 435 | solved.insert(prio.solvable.begin(), prio.solvable.end()); 436 | 437 | searchInds.erase(std::find(searchInds.begin(), searchInds.end(), sources.at(prio.conn.i))); 438 | 439 | auto itr = std::find(searchInds.begin(), searchInds.end(), sources.at(prio.conn.j)); 440 | 441 | if (itr != searchInds.end()) { 442 | searchInds.erase(itr); 443 | } 444 | } else { 445 | if (!FindConns(linesA, kdTree, bspTreeA, polyConns, indexedPolys, sources, n)) { 446 | break; 447 | } 448 | } 449 | } 450 | 451 | #ifdef DEBUG 452 | std::cout << connected; 453 | #endif 454 | 455 | // fallback 456 | 457 | double pt[3]; 458 | 459 | if (!searchInds.empty()) { 460 | for (auto ind : searchInds) { 461 | std::vector newChain; 462 | 463 | for (auto c : chains.at(ind)) { 464 | if (solved.find(c) != solved.end()) { 465 | break; 466 | } 467 | 468 | newChain.push_back(c); 469 | } 470 | 471 | ConnsType &conns = connected.at(ind); 472 | 473 | decltype(newChain)::const_reverse_iterator itr; 474 | 475 | for (itr = newChain.rbegin(); itr != newChain.rend(); itr++) { 476 | // gesucht ist hier die kürzeste verbindung 477 | 478 | #ifdef DEBUG 479 | std::cout << "itr " << *itr << std::endl; 480 | #endif 481 | 482 | // polyConns.at(*itr) ist nach d sortiert 483 | 484 | std::shared_ptr found; 485 | 486 | for (auto &conn : polyConns.at(*itr)) { 487 | auto &src = sources.at(conn.j); 488 | 489 | if (solved.find(src) != solved.end()) { 490 | if (restricted.count(conn.i) == 0 491 | // && restricted.count(conn.j) == 0 492 | && std::find_if(conns.begin(), conns.end(), [&conn](const Conn &other) { return conn.i == other.i || conn.j == other.j; }) == conns.end()) { 493 | 494 | pts->GetPoint(conn.i, ptA); 495 | pts->GetPoint(conn.j, ptB); 496 | 497 | auto intersPts = vtkSmartPointer::New(); 498 | intersPts->SetDataTypeToDouble(); 499 | 500 | auto c = bspTreeB->IntersectWithLine(ptA, ptB, 1e-5, intersPts, nullptr); 501 | 502 | if (c == 0) { 503 | found = std::make_shared(conn); 504 | 505 | break; 506 | } 507 | 508 | // wenn schnittpunkte existieren, dann müssen alle mit ptB übereinstimmen 509 | 510 | vtkIdType i, numPts = intersPts->GetNumberOfPoints(); 511 | 512 | std::set foundPts {{ptB[0], ptB[1], ptB[2]}}; 513 | 514 | for (i = 0; i < numPts; i++) { 515 | intersPts->GetPoint(i, pt); 516 | foundPts.emplace(pt[0], pt[1], pt[2]); 517 | } 518 | 519 | if (foundPts.size() == 1) { 520 | found = std::make_shared(conn); 521 | 522 | break; 523 | } 524 | 525 | } 526 | } 527 | } 528 | 529 | if (found) { 530 | #ifdef DEBUG 531 | std::cout << "found " << *found << std::endl; 532 | #endif 533 | 534 | conns.push_back(*found); 535 | 536 | connected.at(sources.at(found->j)).emplace_back(found->d, found->j, found->i); 537 | 538 | restricted.insert(found->i); 539 | restricted.insert(found->j); 540 | 541 | vtkIdList *line = vtkIdList::New(); 542 | line->InsertNextId(found->i); 543 | line->InsertNextId(found->j); 544 | 545 | linesB->InsertNextCell(VTK_LINE, line); 546 | 547 | line->Delete(); 548 | 549 | bspTreeB->Modified(); 550 | 551 | solved.insert(*itr); 552 | 553 | } else { 554 | throw std::runtime_error("Merging failed."); 555 | } 556 | 557 | } 558 | } 559 | } 560 | 561 | #ifdef DEBUG 562 | WriteVTK("linesB.vtk", linesB); 563 | #endif 564 | 565 | ConnsType2 usedConns(firstConns.begin(), firstConns.end()); 566 | 567 | IndexedPoly polyA {indexedPolys.front()}; 568 | 569 | MergeStage1(indexedPolys, refPts, sources, firstConns, polyA); 570 | 571 | IndexedPolysType splitted {polyA}; 572 | 573 | ConnsType2 leftConns; 574 | 575 | for (itrC = connected.begin(); itrC != connected.end(); ++itrC) { 576 | if (itrC->first == 0) { 577 | continue; 578 | } 579 | 580 | auto &conns = itrC->second; 581 | 582 | ConnsType::const_iterator itr; 583 | 584 | for (itr = conns.begin()+1; itr != conns.end(); ++itr) { 585 | Conn conn(0, itr->j, itr->i); 586 | 587 | if (usedConns.find(conn) == usedConns.end()) { 588 | if (itr->i < itr->j) { 589 | leftConns.emplace(0, itr->i, itr->j); 590 | } else { 591 | leftConns.insert(std::move(conn)); 592 | } 593 | } 594 | } 595 | 596 | } 597 | 598 | #ifdef DEBUG 599 | std::cout << "leftConns: ["; 600 | for (auto &conn : leftConns) { 601 | std::cout << conn << ", "; 602 | } 603 | std::cout << "]" << std::endl; 604 | #endif 605 | 606 | MergeStage2(leftConns, refPts, usedConns, splitted); 607 | 608 | PolysType newPolys; 609 | GetPolys(refPts, splitted, newPolys); 610 | 611 | #ifdef DEBUG 612 | WritePolys("merged_stage2.vtk", newPolys); 613 | #endif 614 | 615 | std::move(newPolys.begin(), newPolys.end(), std::back_inserter(merged)); 616 | 617 | } 618 | 619 | bool Merger::FindConns (vtkPolyData *lines, vtkSmartPointer kdTree, vtkSmartPointer bspTree, PolyConnsType &polyConns, const IndexedPolysType &indexedPolys, const SourcesType &sources, int &n) { 620 | 621 | vtkPoints *pts = lines->GetPoints(); 622 | 623 | if (n > pts->GetNumberOfPoints()) { 624 | return false; 625 | } 626 | 627 | n += 10; 628 | 629 | auto foundPts = vtkSmartPointer::New(); 630 | 631 | vtkIdType i, numPts; 632 | 633 | vtkIdType idB; 634 | 635 | auto lineIds = vtkSmartPointer::New(); 636 | 637 | double ptA[3], ptB[3]; 638 | 639 | bool good; 640 | 641 | vtkIdType j; 642 | vtkIdType _idA, _idB; 643 | 644 | std::map> _polyConns; 645 | 646 | auto line = vtkSmartPointer::New(); 647 | 648 | for (const auto &ids : indexedPolys) { 649 | for (vtkIdType idA : ids) { 650 | pts->GetPoint(idA, ptA); 651 | 652 | kdTree->FindClosestNPoints(n, ptA, foundPts); 653 | 654 | numPts = foundPts->GetNumberOfIds(); 655 | 656 | for (i = 0; i < numPts; i++) { 657 | idB = foundPts->GetId(i); 658 | 659 | auto srcA = sources.at(idA), 660 | srcB = sources.at(idB); 661 | 662 | if (srcA == srcB) { 663 | continue; 664 | } 665 | 666 | pts->GetPoint(idB, ptB); 667 | 668 | good = true; 669 | 670 | if (bspTree->IntersectWithLine(ptA, ptB, 1e-5, nullptr, lineIds) == 1) { 671 | for (j = 0; j < lineIds->GetNumberOfIds(); j++) { 672 | lines->GetCellPoints(lineIds->GetId(j), line); 673 | 674 | _idA = line->GetId(0); 675 | _idB = line->GetId(1); 676 | 677 | if (_idA != idA && _idA != idB 678 | && _idB != idA && _idB != idB) { 679 | 680 | good = false; 681 | break; 682 | } 683 | } 684 | } 685 | 686 | if (good) { 687 | double d = vtkMath::Distance2BetweenPoints(ptA, ptB); 688 | 689 | _polyConns[srcA].emplace(d, idA, idB); 690 | _polyConns[srcB].emplace(d, idB, idA); 691 | } 692 | } 693 | } 694 | } 695 | 696 | decltype(_polyConns)::const_iterator itr; 697 | 698 | for (itr = _polyConns.begin(); itr != _polyConns.end(); ++itr) { 699 | auto &_conns = itr->second; 700 | 701 | ConnsType conns(_conns.begin(), _conns.end()); 702 | std::sort(conns.begin(), conns.end()); 703 | 704 | polyConns[itr->first].swap(conns); 705 | 706 | } 707 | 708 | return true; 709 | } 710 | 711 | void Merger::MergeStage1 (const IndexedPolysType &indexedPolys, [[maybe_unused]] const ReferencedPointsType &refPts, const SourcesType &sources, const ConnsType &conns, IndexedPoly &polyA) { 712 | 713 | for (const auto &conn : conns) { 714 | auto itrA = std::find(polyA.begin(), polyA.end(), conn.j); 715 | 716 | assert(itrA != polyA.end()); 717 | 718 | IndexedPoly polyB(indexedPolys.at(sources.at(conn.i))); 719 | 720 | auto itrB = std::find(polyB.begin(), polyB.end(), conn.i); 721 | 722 | assert(itrB != polyB.end()); 723 | 724 | std::rotate(polyA.begin(), itrA, polyA.end()); 725 | std::rotate(polyB.begin(), itrB, polyB.end()); 726 | 727 | IndexedPoly newPoly {polyA}; 728 | newPoly.push_back(polyA.front()); 729 | newPoly.push_back(polyB.front()); 730 | 731 | newPoly.insert(newPoly.end(), polyB.rbegin(), polyB.rend()); 732 | 733 | polyA.swap(newPoly); 734 | 735 | } 736 | 737 | #ifdef DEBUG 738 | PolysType newPolys; 739 | GetPolys(refPts, {polyA}, newPolys); 740 | 741 | WritePolys("merged_stage1.vtk", newPolys); 742 | #endif 743 | 744 | } 745 | 746 | void Merger::MergeStage2 (const ConnsType2 &conns, const ReferencedPointsType &refPts, const ConnsType2 &usedConns, IndexedPolysType &splitted) { 747 | std::set endPts; 748 | 749 | for (const Conn &conn : usedConns) { 750 | endPts.emplace(refPts.at(conn.i)); 751 | endPts.emplace(refPts.at(conn.j)); 752 | } 753 | 754 | IndexedPolysType::iterator itr; 755 | 756 | double vA[3], vB[3], w[3], ang, phi; 757 | 758 | const double n[] = {0, 0, 1}; 759 | 760 | IndexedPoly::iterator itrA, itrB; 761 | 762 | IndexedPoly::iterator prev, next; 763 | 764 | for (auto &conn : conns) { 765 | for (itr = splitted.begin(); itr != splitted.end(); ++itr) { 766 | IndexedPoly poly(itr->begin(), itr->end()); 767 | 768 | if (endPts.count(refPts.at(conn.i)) == 0) { 769 | itrA = std::find(poly.begin(), poly.end(), conn.i); 770 | } else { 771 | Point3d::GetVec(refPts.at(conn.i), refPts.at(conn.j), w); 772 | 773 | itrA = poly.begin(); 774 | while ((itrA = std::find(itrA, poly.end(), conn.i)) != poly.end()) { 775 | next = itrA+1; 776 | if (next == poly.end()) { 777 | next = poly.begin(); 778 | } 779 | 780 | if (itrA == poly.begin()) { 781 | prev = poly.end()-1; 782 | } else { 783 | prev = itrA-1; 784 | } 785 | 786 | Point3d::GetVec(refPts.at(conn.i), refPts.at(*next), vA); 787 | Point3d::GetVec(refPts.at(conn.i), refPts.at(*prev), vB); 788 | 789 | ang = GetAngle(vA, vB, n); 790 | phi = GetAngle(vA, w, n); 791 | 792 | if (phi < ang) { 793 | break; 794 | } 795 | 796 | ++itrA; 797 | } 798 | } 799 | 800 | if (itrA == poly.end()) { 801 | continue; 802 | } 803 | 804 | std::rotate(poly.begin(), itrA, poly.end()); 805 | 806 | if (endPts.count(refPts.at(conn.j)) == 0) { 807 | itrB = std::find(poly.begin(), poly.end(), conn.j); 808 | } else { 809 | Point3d::GetVec(refPts.at(conn.j), refPts.at(conn.i), w); 810 | 811 | itrB = poly.begin(); 812 | while ((itrB = std::find(itrB, poly.end(), conn.j)) != poly.end()) { 813 | next = itrB+1; 814 | if (next == poly.end()) { 815 | next = poly.begin(); 816 | } 817 | 818 | if (itrB == poly.begin()) { 819 | prev = poly.end()-1; 820 | } else { 821 | prev = itrB-1; 822 | } 823 | 824 | Point3d::GetVec(refPts.at(conn.j), refPts.at(*next), vA); 825 | Point3d::GetVec(refPts.at(conn.j), refPts.at(*prev), vB); 826 | 827 | ang = GetAngle(vA, vB, n); 828 | phi = GetAngle(vA, w, n); 829 | 830 | if (phi < ang) { 831 | break; 832 | } 833 | 834 | ++itrB; 835 | } 836 | } 837 | 838 | if (itrB == poly.end()) { 839 | continue; 840 | } 841 | 842 | IndexedPoly newPolyA(poly.begin(), itrB+1); 843 | IndexedPoly newPolyB(itrB, poly.end()); 844 | 845 | newPolyB.push_back(poly.front()); 846 | 847 | splitted.erase(itr); 848 | 849 | splitted.push_back(std::move(newPolyA)); 850 | splitted.push_back(std::move(newPolyB)); 851 | 852 | endPts.emplace(refPts.at(conn.i)); 853 | endPts.emplace(refPts.at(conn.j)); 854 | 855 | break; 856 | } 857 | } 858 | 859 | } 860 | -------------------------------------------------------------------------------- /Merger.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __Merger_h 18 | #define __Merger_h 19 | 20 | #include "vtkPolyDataBooleanFilter.h" 21 | 22 | typedef std::vector GroupType; 23 | 24 | typedef std::map SourcesType; 25 | 26 | class Conn { 27 | public: 28 | Conn () = delete; 29 | Conn (double d, vtkIdType i, vtkIdType j) : d(d), i(i), j(j) {} 30 | 31 | double d; 32 | vtkIdType i, j; 33 | 34 | bool operator< (const Conn &other) const { 35 | return d < other.d; 36 | } 37 | 38 | friend std::ostream& operator<< (std::ostream &out, const Conn &c) { 39 | out << "Conn(d=" << c.d 40 | << ", i=" << c.i 41 | << ", j=" << c.j 42 | << ")"; 43 | return out; 44 | } 45 | 46 | }; 47 | 48 | struct ConnCmp { 49 | bool operator() (const Conn &a, const Conn &b) const { 50 | return std::tie(a.i, a.j) < std::tie(b.i, b.j); 51 | } 52 | }; 53 | 54 | typedef std::vector ConnsType; 55 | typedef std::map PolyConnsType; 56 | 57 | typedef std::set ConnsType2; 58 | 59 | inline std::ostream& operator<< (std::ostream &out, const PolyConnsType& polyConns) { 60 | PolyConnsType::const_iterator itr; 61 | 62 | for (itr = polyConns.begin(); itr != polyConns.end(); ++itr) { 63 | out << itr->first << ": ["; 64 | for (auto &conn : itr->second) { 65 | out << conn << ", "; 66 | } 67 | out << "]" << std::endl; 68 | } 69 | 70 | return out; 71 | } 72 | 73 | class Prio { 74 | public: 75 | Prio () = delete; 76 | Prio (const Conn &conn, const std::set &solvable, double d) : conn(conn), solvable(solvable), d(d) {} 77 | 78 | Conn conn; 79 | std::set solvable; 80 | double d; 81 | 82 | friend std::ostream& operator<< (std::ostream &out, const Prio &p) { 83 | out << "Prio(conn=" << p.conn 84 | << ", d=" << p.d 85 | << ")"; 86 | return out; 87 | } 88 | }; 89 | 90 | struct Cmp { 91 | bool operator() (const Prio &a, const Prio &b) const { 92 | const auto _a = a.solvable.size(), 93 | _b = b.solvable.size(); 94 | return std::tie(_a, a.d) < std::tie(_b, b.d); 95 | } 96 | }; 97 | 98 | typedef std::set PriosType; 99 | 100 | typedef std::map PolyPriosType; 101 | 102 | class Merger { 103 | vtkPolyData *pd; 104 | const PStrips &pStrips; 105 | vtkIdType origId; 106 | 107 | PolysType polys; 108 | std::vector innerIds; 109 | public: 110 | Merger () = delete; 111 | Merger (vtkPolyData *pd, const PStrips &pStrips, const StripsType &strips, const IdsType &descIds, vtkIdType origId); 112 | void Run (); 113 | 114 | private: 115 | void MergeGroup (const GroupType &group, PolysType &merged); 116 | bool FindConns (vtkPolyData *lines, vtkSmartPointer kdTree, vtkSmartPointer bspTree, PolyConnsType &polyConns, const IndexedPolysType &indexedPolys, const SourcesType &sources, int &n); 117 | 118 | void MergeStage1 (const IndexedPolysType &indexedPolys, const ReferencedPointsType &refPts, const SourcesType &sources, const ConnsType &conns, IndexedPoly &polyA); 119 | void MergeStage2 (const ConnsType2 &conns, const ReferencedPointsType &refPts, const ConnsType2 &usedConns, IndexedPolysType &splitted); 120 | }; 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /Optimize.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "Optimize.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // #define _DEBUG 26 | 27 | PreventEqualCaptPoints::PreventEqualCaptPoints (vtkPolyData *pdA, vtkPolyData *pdB) : pdA(pdA), pdB(pdB) {} 28 | 29 | void PreventEqualCaptPoints::Run () { 30 | #ifdef _DEBUG 31 | WriteVTK("captA.vtk", pdA); 32 | WriteVTK("captB.vtk", pdB); 33 | #endif 34 | 35 | pdA->BuildLinks(); 36 | pdB->BuildLinks(); 37 | 38 | Find(pdA, pdB, "A"); 39 | 40 | #ifdef _DEBUG 41 | WriteVTK("modB.vtk", pdB); 42 | #endif 43 | 44 | Find(pdB, pdA, "B"); 45 | 46 | #ifdef _DEBUG 47 | WriteVTK("modA.vtk", pdA); 48 | #endif 49 | } 50 | 51 | void PreventEqualCaptPoints::Find (vtkPolyData *pd, vtkPolyData *other, [[maybe_unused]] const std::string &name) { 52 | #ifdef _DEBUG 53 | std::cout << "Find(" << name << ")" << std::endl; 54 | #endif 55 | 56 | vtkIdType num; 57 | const vtkIdType *poly; 58 | 59 | vtkIdType i, j; 60 | 61 | std::set lines; 62 | 63 | auto polyItr = vtk::TakeSmartPointer(pd->GetPolys()->NewIterator()); 64 | 65 | for (polyItr->GoToFirstCell(); !polyItr->IsDoneWithTraversal(); polyItr->GoToNextCell()) { 66 | polyItr->GetCurrentCell(num, poly); 67 | 68 | for (i = 0; i < num; i++) { 69 | j = i+1; 70 | 71 | if (j == num) { 72 | j = 0; 73 | } 74 | 75 | if (poly[i] < poly[j]) { 76 | lines.emplace(poly[i], poly[j]); 77 | } else { 78 | lines.emplace(poly[j], poly[i]); 79 | } 80 | } 81 | } 82 | 83 | auto tree = vtkSmartPointer::New(); 84 | tree->SetDataSet(other); 85 | tree->BuildLocator(); 86 | 87 | auto pts = vtkSmartPointer::New(); 88 | pts->SetDataTypeToDouble(); 89 | 90 | auto cells = vtkSmartPointer::New(); 91 | 92 | double pA[3], pB[3]; 93 | 94 | vtkIdType cellId; 95 | 96 | double tr[2]; 97 | 98 | #ifdef _DEBUG 99 | auto pdVerts = vtkSmartPointer::New(); 100 | pdVerts->Allocate(1); 101 | 102 | auto ptsVerts = vtkSmartPointer::New(); 103 | ptsVerts->SetDataTypeToDouble(); 104 | #endif 105 | 106 | std::map> pointSnaps; 107 | std::map> edgeSnaps; 108 | 109 | for (auto &line : lines) { 110 | pd->GetPoint(line.f, pA); 111 | pd->GetPoint(line.g, pB); 112 | 113 | if (tree->IntersectWithLine(pA, pB, 1e-5, pts, cells) == 0) { 114 | continue; 115 | } 116 | 117 | for (i = 0; i < pts->GetNumberOfPoints(); i++) { 118 | const double *pt = pts->GetPoint(i); 119 | 120 | Point3d sA(pt[0], pt[1], pt[2]); 121 | 122 | cellId = cells->GetId(i); 123 | 124 | other->GetCellPoints(cellId, num, poly); 125 | 126 | Base base(other->GetPoints(), num, poly); 127 | 128 | Poly polyA, polyB; 129 | 130 | GetPoly(other->GetPoints(), num, poly, polyA); 131 | 132 | FlattenPoly(polyA, polyB, base); 133 | 134 | Transform(pt, tr, base); 135 | 136 | Point3d sB(tr[0], tr[1], 0); 137 | 138 | if (PointInPoly(polyB, sB)) { 139 | #ifdef _DEBUG 140 | auto vert = vtkSmartPointer::New(); 141 | vert->InsertNextId(ptsVerts->InsertNextPoint(pt)); 142 | 143 | pdVerts->InsertNextCell(VTK_VERTEX, vert); 144 | #endif 145 | 146 | // snap auf ecke oder kante? 147 | 148 | auto snap = std::find_if(polyA.begin(), polyA.end(), [&](const Point3d &p) { return Point3d::GetDist(p, sA) < 1e-10; }); 149 | 150 | if (snap != polyA.end()) { 151 | double d = Point3d::GetDist(*snap, sA); 152 | 153 | pointSnaps[snap->id].emplace_back(cellId, line, *snap, sA, d); 154 | 155 | } else { 156 | // projektion auf kante 157 | 158 | auto edgeProj = GetEdgeProj(polyA, sA); 159 | 160 | if (edgeProj != nullptr) { 161 | edgeSnaps[edgeProj->proj].emplace_back(cellId, line, edgeProj->edge, edgeProj->proj, sA, edgeProj->d); 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | for (const auto& [id, snaps] : pointSnaps) { 169 | if (snaps.size() > 1) { 170 | #ifdef _DEBUG 171 | std::cout << "id " << id << ", snaps" << std::endl; 172 | 173 | for (const auto &s : snaps) { 174 | std::cout << s << std::endl; 175 | } 176 | #endif 177 | 178 | std::set allLines; 179 | 180 | std::transform(snaps.begin(), snaps.end(), std::inserter(allLines, allLines.end()), [](const SnapPoint &p) { return p.line; }); 181 | 182 | #ifdef _DEBUG 183 | std::cout << "allLines" << std::endl; 184 | 185 | for (const auto &line : allLines) { 186 | std::cout << line << std::endl; 187 | } 188 | #endif 189 | 190 | auto itrA = snaps.begin(); 191 | decltype(itrA) itrB; 192 | 193 | double d; 194 | 195 | bool collapse = true; 196 | 197 | for (; itrA != snaps.end()-1 && collapse; ++itrA) { 198 | for (itrB = itrA+1; itrB < snaps.end(); ++itrB) { 199 | d = Point3d::GetDist(itrA->inter, itrB->inter); 200 | 201 | #ifdef _DEBUG 202 | std::cout << d << std::endl; 203 | #endif 204 | 205 | if (d > 1e-10) { 206 | collapse = false; 207 | break; 208 | } 209 | } 210 | } 211 | 212 | if (collapse) { 213 | continue; 214 | } 215 | 216 | if (allLines.size() == 1) { 217 | std::shared_ptr proj; 218 | ProjOnLine(pd, snaps.back().line, snaps.back().point, proj); 219 | 220 | PreventEqualCaptPoints::MovePoint(other, id, *proj); 221 | 222 | } else { 223 | // gemeinsamen punkt ermitteln 224 | 225 | std::set allIds; 226 | 227 | for (auto &line : allLines) { 228 | allIds.insert(line.f); 229 | allIds.insert(line.g); 230 | } 231 | 232 | if (allIds.size() == allLines.size()+1) { 233 | std::vector _allLines(allLines.begin(), allLines.end()); 234 | 235 | vtkIdType s = _allLines[0] & _allLines[1]; 236 | 237 | #ifdef _DEBUG 238 | std::cout << "line " 239 | << _allLines[0] 240 | << " & " 241 | << _allLines[1] 242 | << " -> " 243 | << s 244 | << std::endl; 245 | #endif 246 | 247 | double pt[3]; 248 | pd->GetPoint(s, pt); 249 | 250 | Point3d p(pt[0], pt[1], pt[2]); 251 | 252 | PreventEqualCaptPoints::MovePoint(other, id, p); 253 | } else { 254 | throw std::runtime_error(""); 255 | } 256 | } 257 | } 258 | } 259 | 260 | using Snap = std::tuple; 261 | 262 | struct Cmp { 263 | bool operator() (const Snap &l, const Snap &r) const { 264 | return std::get<2>(l) < std::get<2>(r); 265 | } 266 | }; 267 | 268 | std::map> allEdgeSnaps; 269 | 270 | double pt[3], t; 271 | 272 | for (const auto& [proj, snaps] : edgeSnaps) { 273 | if (snaps.size() > 1) { 274 | if (snaps.size() > 2) { 275 | #ifdef _DEBUG 276 | std::cout << proj << std::endl; 277 | 278 | for (const auto &s : snaps) { 279 | std::cout << s << std::endl; 280 | 281 | auto &line = s.line; 282 | 283 | pd->GetPoint(line.f, pA); 284 | pd->GetPoint(line.g, pB); 285 | 286 | std::cout << Point3d(pA[0], pA[1], pA[2]) << std::endl; 287 | std::cout << Point3d(pB[0], pB[1], pB[2]) << std::endl; 288 | } 289 | #endif 290 | 291 | continue; 292 | } 293 | 294 | const auto &snapA = snaps[0]; 295 | const auto &snapB = snaps[1]; 296 | 297 | { 298 | Pair edge(snapA.edge); 299 | 300 | if (edge.f > edge.g) { 301 | std::swap(edge.f, edge.g); 302 | } 303 | 304 | std::shared_ptr p; 305 | 306 | if (snapA.line == snapB.line) { 307 | ProjOnLine(pd, snapA.line, snapA.proj, p); 308 | 309 | } else { 310 | vtkIdType s = snapA.line & snapB.line; 311 | 312 | pd->GetPoint(s, pt); 313 | 314 | p = std::make_shared(pt[0], pt[1], pt[2]); 315 | } 316 | 317 | other->GetPoint(edge.f, pt); 318 | 319 | Point3d q(pt[0], pt[1], pt[2]); 320 | 321 | t = Point3d::GetDist(q, snapA.proj); 322 | 323 | allEdgeSnaps[edge].emplace(snapA, *p, t); 324 | } 325 | } 326 | } 327 | 328 | std::map newCells; 329 | 330 | for (const auto& [edge, data] : allEdgeSnaps) { 331 | Points pts; 332 | 333 | for (const auto &d : data) { 334 | const auto &p = std::get<1>(d); 335 | pts.emplace_back(p.x, p.y, p.z, other->GetPoints()->InsertNextPoint(p.x, p.y, p.z)); 336 | } 337 | 338 | const auto &first = std::get<0>(*(data.begin())); 339 | 340 | auto neigs = vtkSmartPointer::New(); 341 | 342 | other->GetCellEdgeNeighbors(first.cellId, first.edge.f, first.edge.g, neigs); 343 | 344 | if (neigs->GetNumberOfIds() != 1) { 345 | throw std::runtime_error(""); 346 | } 347 | 348 | vtkIdType neig = neigs->GetId(0); 349 | 350 | auto _edge = Pair(first.edge.g, first.edge.f); 351 | 352 | if (edge == first.edge) { 353 | std::for_each(pts.begin(), pts.end(), [&](const Point3d &p) { newCells[first.cellId][first.edge].emplace_back(p); }); 354 | std::for_each(pts.rbegin(), pts.rend(), [&](const Point3d &p) { newCells[neig][_edge].emplace_back(p); }); 355 | } else { 356 | std::for_each(pts.rbegin(), pts.rend(), [&](const Point3d &p) { newCells[first.cellId][first.edge].emplace_back(p); }); 357 | std::for_each(pts.begin(), pts.end(), [&](const Point3d &p) { newCells[neig][_edge].emplace_back(p); }); 358 | } 359 | } 360 | 361 | for (const auto& [cellId, edges] : newCells) { 362 | PreventEqualCaptPoints::TriangulateCell(other, cellId, edges); 363 | other->DeleteCell(cellId); 364 | } 365 | 366 | other->RemoveDeletedCells(); 367 | 368 | #ifdef _DEBUG 369 | pdVerts->SetPoints(ptsVerts); 370 | 371 | auto fileName = "verts" + name + ".vtk"; 372 | 373 | WriteVTK(fileName.c_str(), pdVerts); 374 | #endif 375 | 376 | } 377 | 378 | IdsType PreventEqualCaptPoints::TriangulateCell (vtkPolyData *pd, vtkIdType cellId, const Edges &edges) { 379 | vtkIdTypeArray *origCellIds = vtkIdTypeArray::SafeDownCast(pd->GetCellData()->GetScalars("OrigCellIds")); 380 | 381 | IdsType newCellIds; 382 | 383 | vtkIdType num; 384 | const vtkIdType *poly; 385 | 386 | pd->GetCellPoints(cellId, num, poly); 387 | 388 | Poly _poly; 389 | 390 | double pt[3]; 391 | 392 | vtkIdType i, j; 393 | 394 | vtkIdType newNum = num; 395 | 396 | for (i = 0; i < num; i++) { 397 | j = i+1; 398 | 399 | if (j == num) { 400 | j = 0; 401 | } 402 | 403 | pd->GetPoint(poly[i], pt); 404 | 405 | _poly.emplace_back(pt[0], pt[1], pt[2], poly[i]); 406 | 407 | Pair edge(poly[i], poly[j]); 408 | 409 | auto itr = edges.find(edge); 410 | 411 | if (itr != edges.end()) { 412 | auto &pts = itr->second; 413 | 414 | for (const auto &p : pts) { 415 | _poly.emplace_back(p); 416 | 417 | newNum++; 418 | } 419 | } 420 | } 421 | 422 | auto vtkPoly = vtkSmartPointer::New(); 423 | 424 | vtkPoly->GetPointIds()->SetNumberOfIds(newNum); 425 | vtkPoly->GetPoints()->SetNumberOfPoints(newNum); 426 | 427 | Base base(pd->GetPoints(), num, poly); 428 | 429 | Poly flattened; 430 | 431 | FlattenPoly(_poly, flattened, base); 432 | 433 | for (const auto &p : flattened) { 434 | vtkPoly->GetPointIds()->SetId(p.id, p.id); 435 | vtkPoly->GetPoints()->SetPoint(p.id, p.x, p.y, p.z); 436 | } 437 | 438 | auto triangles = vtkSmartPointer::New(); 439 | 440 | #if (VTK_MAJOR_VERSION >= 9 && VTK_MINOR_VERSION > 3) 441 | if (vtkPoly->TriangulateLocalIds(0, triangles) != 1) { 442 | throw std::runtime_error(""); 443 | } 444 | #else 445 | if (vtkPoly->Triangulate(triangles) != 1) { 446 | throw std::runtime_error(""); 447 | } 448 | #endif 449 | 450 | auto ids = vtkSmartPointer::New(); 451 | 452 | for (const auto &p : _poly) { 453 | ids->InsertNextId(p.id); 454 | } 455 | 456 | double pA[3], pB[3], pC[3]; 457 | 458 | for (i = 0; i < triangles->GetNumberOfIds(); i += 3) { 459 | pd->GetPoint(ids->GetId(triangles->GetId(i)), pA); 460 | pd->GetPoint(ids->GetId(triangles->GetId(i+1)), pB); 461 | pd->GetPoint(ids->GetId(triangles->GetId(i+2)), pC); 462 | 463 | Poly p = { {pA[0], pA[1], pA[2]}, {pB[0], pB[1], pB[2]}, {pC[0], pC[1], pC[2]} }; 464 | 465 | double quality = GetTriangleQuality(p); 466 | 467 | if (quality < 0.001) { 468 | throw std::runtime_error(""); 469 | } 470 | } 471 | 472 | vtkIdType origId = origCellIds->GetValue(cellId); 473 | 474 | auto triangle = vtkSmartPointer::New(); 475 | triangle->SetNumberOfIds(3); 476 | 477 | for (i = 0; i < triangles->GetNumberOfIds(); i += 3) { 478 | triangle->SetId(0, ids->GetId(triangles->GetId(i))); 479 | triangle->SetId(1, ids->GetId(triangles->GetId(i+1))); 480 | triangle->SetId(2, ids->GetId(triangles->GetId(i+2))); 481 | 482 | newCellIds.push_back(pd->InsertNextCell(VTK_TRIANGLE, triangle)); 483 | 484 | origCellIds->InsertNextValue(origId); 485 | } 486 | 487 | return newCellIds; 488 | 489 | } 490 | 491 | void PreventEqualCaptPoints::MovePoint (vtkPolyData *pd, vtkIdType ind, const Point3d &p) { 492 | auto cells = vtkSmartPointer::New(); 493 | pd->GetPointCells(ind, cells); 494 | 495 | vtkIdType i, cellId; 496 | 497 | for (i = 0; i < cells->GetNumberOfIds(); i++) { 498 | cellId = cells->GetId(i); 499 | 500 | if (pd->GetCellType(cellId) == VTK_POLYGON) { 501 | PreventEqualCaptPoints::TriangulateCell(pd, cellId, {}); 502 | pd->DeleteCell(cellId); 503 | } 504 | } 505 | 506 | #ifdef _DEBUG 507 | double pt[3]; 508 | pd->GetPoints()->GetPoint(ind, pt); 509 | 510 | Point3d q(pt[0], pt[1], pt[2]); 511 | 512 | std::cout << q 513 | << " -> " 514 | << p 515 | << std::endl; 516 | #endif 517 | 518 | pd->GetPoints()->SetPoint(ind, p.x, p.y, p.z); 519 | } 520 | -------------------------------------------------------------------------------- /Optimize.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __Optimize_h 18 | #define __Optimize_h 19 | 20 | #include "Utilities.h" 21 | 22 | typedef std::map Edges; 23 | 24 | class SnapPoint { 25 | public: 26 | SnapPoint () = delete; 27 | SnapPoint (vtkIdType cellId, const Pair &line, const Point3d &point, const Point3d &inter, double d) : cellId(cellId), line(line), point(point), inter(inter), d(d) {} 28 | 29 | vtkIdType cellId; 30 | Pair line; 31 | Point3d point; 32 | Point3d inter; 33 | double d; 34 | 35 | friend std::ostream& operator<< (std::ostream &out, const SnapPoint &s) { 36 | out << "cellId " << s.cellId 37 | << ", line " << s.line 38 | << ", point " << s.point 39 | << ", inter " << s.inter 40 | << ", d " << s.d; 41 | 42 | return out; 43 | } 44 | }; 45 | 46 | class SnapEdge { 47 | public: 48 | SnapEdge () = delete; 49 | SnapEdge (vtkIdType cellId, const Pair &line, const Pair &edge, const Point3d &proj, const Point3d &inter, double d) : cellId(cellId), line(line), edge(edge), proj(proj), inter(inter), d(d) {} 50 | 51 | vtkIdType cellId; 52 | Pair line; 53 | Pair edge; 54 | Point3d proj; 55 | Point3d inter; 56 | double d; 57 | 58 | friend std::ostream& operator<< (std::ostream &out, const SnapEdge &s) { 59 | out << "cellId " << s.cellId 60 | << ", line " << s.line 61 | << ", edge " << s.edge 62 | << ", proj " << s.proj 63 | << ", inter " << s.inter 64 | << ", d " << s.d; 65 | 66 | return out; 67 | } 68 | }; 69 | 70 | class PreventEqualCaptPoints { 71 | vtkPolyData *pdA, *pdB; 72 | public: 73 | static IdsType TriangulateCell (vtkPolyData *pd, vtkIdType cellId, const Edges &edges); 74 | static void MovePoint (vtkPolyData *pd, vtkIdType ind, const Point3d &p); 75 | 76 | PreventEqualCaptPoints () = delete; 77 | PreventEqualCaptPoints (vtkPolyData *pdA, vtkPolyData *pdB); 78 | void Run (); 79 | private: 80 | void Find (vtkPolyData *pd, vtkPolyData *other, const std::string &name); 81 | }; 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vtkbool [![CMake](https://github.com/zippy84/vtkbool/actions/workflows/cmake.yml/badge.svg)](https://github.com/zippy84/vtkbool/actions/workflows/cmake.yml) [![codecov](https://codecov.io/gh/zippy84/vtkbool/branch/master/graph/badge.svg?token=EUV9QKEW1M)](https://codecov.io/gh/zippy84/vtkbool) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.10461186.svg)](https://zenodo.org/doi/10.5281/zenodo.10461186) 2 | 3 | ## About 4 | 5 | This is an extension of the graphics library VTK. The goal of the extension is to equip the library with rebust boolean operations on polygonal meshes. I started the project at the end of my studies in mechanical engineering at the University of Applied Sciences ([HTWK](http://htwk-leipzig.de/)) in Leipzig. I used VTK to develop a program, which I had to create for a paper. At this time I would have wished, that this feature already exists. There was several implementations from third parties, but after some tests, I came to the conclusion, that none of them worked correct. I decided to start with my own implementation. This library is the result of my efforts. 6 | 7 | ## Features 8 | 9 | - based on VTK 10 | - 4 operation types available (union, intersection, difference and difference2 - difference with interchanged operands) 11 | - triangulation is not needed 12 | - all types of polygonal cells are supported (triangles, quads, polygons, triangle-strips) 13 | - triangle-strips and quads will be transformed into triangles (quads only if their points are not on the same plane) 14 | - non-convex polygons are allowed 15 | - meshes can be stacked (coplanar polygons are right handled) 16 | - the meshes don’t need to be watertight 17 | - CellData is passed (attached by the rules of vtkAppendPolyData) 18 | - contact-lines are available in the 3th output 19 | - the filter is able to embed holes 20 | - compileable as ParaView plugin 21 | - Python wrapped 22 | 23 | ## Limitations 24 | 25 | - the filter assumes well defined triangles, quads and polygons 26 | - PointData is not preserved - you have to do your own mapping with *OrigCellIdsA* and *OrigCellIdsB* 27 | 28 | ## Requirements 29 | 30 | - CMake >= 3.12 31 | - VTK >= 9.0 32 | - C++17 compiler 33 | 34 | ### Optional 35 | 36 | - ParaView >= 5.0 37 | - Python 3.x 38 | 39 | ## Library 40 | 41 | To include vtkbool into your program, you have to compile it as a library. All you need is an installation of VTK with header files. If you have installed VTK over your package manager, CMake is able to find the required files. Otherwise you have to set **VTK\_DIR** manually. It must be a path like */home/zippy/VTK9/lib/cmake/vtk-9.1* or *C:/Users/zippy/VTK9/lib/cmake/vtk-9.1*. 42 | 43 | The usage of the library is very simple. Look at the example in the section below. You can set the operation mode by calling one of the named methods: 44 | 45 | - `SetOperModeToNone` 46 | - `SetOperModeToUnion` 47 | - `SetOperModeToIntersection` 48 | - `SetOperModeToDifference` 49 | - `SetOperModeToDifference2` 50 | 51 | The alternative is the more generic `SetOperMode`. The method must be called with the number of the desired operation, an integer between 0 and 4, with the same meaning as mentioned before. The default is Union. 52 | 53 | ### C++ Example 54 | 55 | Create a directory somewhere in your file system, download vtkbool and unpack it into that. 56 | 57 | ``` 58 | mkdir example 59 | cd example 60 | git clone https://github.com/zippy84/vtkbool.git 61 | ``` 62 | 63 | Then create the following two files: 64 | 65 | **test.cxx** 66 | 67 | ```C++ 68 | #include 69 | #include 70 | #include 71 | #include 72 | 73 | #include "vtkPolyDataBooleanFilter.h" 74 | 75 | int main (int argc, char *argv[]) { 76 | auto cube = vtkSmartPointer::New(); 77 | cube->SetYLength(.5); 78 | 79 | auto cyl = vtkSmartPointer::New(); 80 | cyl->SetResolution(32); 81 | cyl->SetHeight(.5); 82 | cyl->SetCenter(0, .5, 0); 83 | 84 | auto bf = vtkSmartPointer::New(); 85 | bf->SetInputConnection(0, cube->GetOutputPort()); 86 | bf->SetInputConnection(1, cyl->GetOutputPort()); 87 | bf->SetOperModeToDifference(); 88 | 89 | auto writer = vtkSmartPointer::New(); 90 | writer->SetInputConnection(bf->GetOutputPort()); 91 | writer->SetFileName("result.vtk"); 92 | writer->Update(); 93 | 94 | return 0; 95 | } 96 | ``` 97 | 98 | **CMakeLists.txt** 99 | 100 | ```CMake 101 | cmake_minimum_required(VERSION 3.12 FATAL_ERROR) 102 | project(test) 103 | 104 | set(CMAKE_CXX_STANDARD 17) 105 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 106 | 107 | # find_package(VTK REQUIRED COMPONENTS FiltersSources IOLegacy) 108 | 109 | find_package(VTK REQUIRED COMPONENTS FiltersSources IOLegacy FiltersExtraction FiltersGeometry FiltersModeling FiltersFlowPaths WrappingPythonCore) 110 | 111 | if(VTK_FOUND) 112 | include_directories(vtkbool) 113 | add_subdirectory(vtkbool) 114 | 115 | add_executable(test test.cxx) 116 | target_link_libraries(test PRIVATE vtkbool ${VTK_LIBRARIES}) 117 | 118 | vtk_module_autoinit( 119 | TARGETS test 120 | MODULES ${VTK_LIBRARIES} 121 | ) 122 | endif(VTK_FOUND) 123 | ``` 124 | 125 | Inside the `example` directory, create a subdirectory called `build` and `cd` into it. You should have a directory structure that looks something like this: 126 | 127 | ``` 128 | example 129 | ├── build 130 | ├── CMakeLists.txt 131 | ├── test.cxx 132 | └── vtkbool 133 | ├── CMakeLists.txt 134 | ├── ... 135 | └── vtkPolyDataBooleanFilter.h 136 | ``` 137 | 138 | From inside the `build` directory, run `ccmake ..`, follow the instructions, and finally type `make`. 139 | 140 | Running `./test` will now produce the `result.vtk` file. 141 | 142 | ## ParaView Plugin 143 | 144 | To build the plugin you have to compile ParaView from source. Download the current version from and follow the compilation instructions. As soon as ParaView is compiled, it may take a while, you can build the plugin by activating the **VTKBOOL_PARAVIEW** option within CMake. In CMake you also have to point to **ParaView_DIR** if CMake can't found it and it is not installed in a common location like */usr/lib* or */usr/local/lib*. Make sure **PARAVIEW_INSTALL_DEVELOPMENT_FILES** is set. 145 | 146 | When everything has been compiled successfully, you can install the plugin. 147 | 148 | ## Python 149 | 150 | The Python module will be generated automatically, if three conditions are met: 151 | 152 | - vtkbool is configured as a library 153 | - Python 3 is installed with header files 154 | - VTK itself is wrapped to Python 155 | 156 | After a successful compilation, the module can be used as follows: 157 | 158 | ```python 159 | #!/usr/bin/env python 160 | 161 | import sys 162 | sys.path.append('/path/to/your/build/directory') # also look into the python files in the testing directory 163 | 164 | from vtkmodules.vtkFiltersSources import vtkCubeSource, vtkSphereSource 165 | from vtkmodules.vtkIOLegacy import vtkPolyDataWriter 166 | from vtkbool import vtkPolyDataBooleanFilter 167 | 168 | cube = vtkCubeSource() 169 | 170 | sphere = vtkSphereSource() 171 | sphere.SetCenter(.5, .5, .5) 172 | sphere.SetThetaResolution(20) 173 | sphere.SetPhiResolution(20) 174 | 175 | bf = vtkPolyDataBooleanFilter() 176 | bf.SetInputConnection(0, cube.GetOutputPort()) 177 | bf.SetInputConnection(1, sphere.GetOutputPort()) 178 | bf.SetOperModeToDifference() 179 | 180 | # write the result, if you want ... 181 | 182 | writer = vtkPolyDataWriter() 183 | writer.SetInputConnection(bf.GetOutputPort()) 184 | writer.SetFileName('result.vtk') 185 | 186 | writer.Update() 187 | ``` 188 | 189 | Or with VTK >= 9.4: 190 | 191 | ```python 192 | #!/usr/bin/env python 193 | 194 | import sys 195 | sys.path.append('/path/to/your/build/directory') 196 | 197 | from vtkmodules.vtkFiltersSources import vtkCubeSource, vtkSphereSource 198 | from vtkmodules.vtkIOLegacy import vtkPolyDataWriter 199 | from vtkmodules.util.execution_model import select_ports 200 | 201 | from vtkbool import vtkPolyDataBooleanFilter, OPER_DIFFERENCE 202 | 203 | cube = vtkCubeSource() 204 | sphere = vtkSphereSource(center=[.5, .5, .5], theta_resolution=20, phi_resolution=20) 205 | 206 | bf = vtkPolyDataBooleanFilter(oper_mode=OPER_DIFFERENCE) 207 | cube >> bf 208 | sphere >> select_ports(1, bf) 209 | 210 | (bf >> vtkPolyDataWriter(file_name='result.vtk')).update() 211 | 212 | ``` 213 | 214 | ## Conda 215 | 216 | The library is also available at [conda-forge](https://anaconda.org/conda-forge/vtkbool). In your virtual environment you can install the package with: 217 | 218 | ``` 219 | conda install -c conda-forge vtkbool 220 | ``` 221 | 222 | Unlike in the python example, you need to import it like this: 223 | 224 | ```python 225 | from vtkbool.vtkbool import vtkPolyDataBooleanFilter 226 | ``` 227 | 228 | ## Errors and their meaning 229 | 230 | - *Contact failed with ...* 231 | 232 | - *Found invalid intersection points.* 233 | 234 | This problem occurs when an intersection point is located on congruent edges of a self-intersecting polygon. 235 | 236 | - *Intersection points do not lie on the edges (cells a, b).* 237 | 238 | At least one intersection point does not lie on the boundary of the intersected polygon. The points of the polygon do not lie on a plane. The polygon is degenerated. 239 | 240 | - *Intersection goes through non-manifold edges.* 241 | 242 | Non-manifold edges are generally not a problem. Unless they are part of the intersection. 243 | 244 | - *Cannot prevent equal capture points.* 245 | 246 | A capture point is the projection of a point onto one of the edges of the intersected polygon. This point, not the projection, is usually used by two lines that are assigned to the two adjacent polygons, sharing this edge. There is a case where two different points have the same projection. The error occurs when the problem could not be solved. 247 | 248 | - *There is no contact.* 249 | 250 | What it says. 251 | 252 | - *At least one line-end has only one neighbor.* 253 | 254 | The intersection is incomplete. Therefore the cell cannot be divided. 255 | 256 | - *Strips are invalid.* 257 | 258 | There are intersection lines that intersect themselves. Either one of the inputs contains an assembly or there is one self-intersecting polygon that is involved in the intersection. 259 | 260 | - *CutCells failed.* 261 | 262 | Will be printed out only, if some holes couldn't be merged into their outer polygons. 263 | 264 | - *Boolean operation failed.* 265 | 266 | A boolean operation can fail at the end, if some of the intersection lines are not part of the result. 267 | 268 | ## Copyright 269 | 270 | 2012-2025 Ronald Römer 271 | 272 | ## License 273 | 274 | Apache License, Version 2.0 275 | -------------------------------------------------------------------------------- /Utilities.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "Utilities.h" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | double ComputeNormal (vtkPoints *pts, double *n, vtkIdType num, const vtkIdType *poly) { 31 | n[0] = 0; n[1] = 0; n[2] = 0; 32 | 33 | double pA[3], pB[3]; 34 | 35 | vtkIdType i, a, b; 36 | 37 | for (i = 0; i < num; i++) { 38 | a = poly[i]; 39 | b = poly[i+1 == num ? 0 : i+1]; 40 | 41 | pts->GetPoint(a, pA); 42 | pts->GetPoint(b, pB); 43 | 44 | n[0] += (pA[1]-pB[1])*(pA[2]+pB[2]); 45 | n[1] += (pA[2]-pB[2])*(pA[0]+pB[0]); 46 | n[2] += (pA[0]-pB[0])*(pA[1]+pB[1]); 47 | } 48 | 49 | return vtkMath::Normalize(n); 50 | } 51 | 52 | void FindPoints (vtkKdTreePointLocator *pl, const double *pt, vtkIdList *pts, double tol) { 53 | pts->Reset(); 54 | 55 | vtkPolyData *pd = vtkPolyData::SafeDownCast(pl->GetDataSet()); 56 | 57 | vtkIdList *closest = vtkIdList::New(); 58 | 59 | pl->FindPointsWithinRadius(std::max(1e-3, tol), pt, closest); 60 | 61 | vtkIdType i, numPts = closest->GetNumberOfIds(); 62 | 63 | double c[3], v[3]; 64 | 65 | for (i = 0; i < numPts; i++) { 66 | pd->GetPoint(closest->GetId(i), c); 67 | vtkMath::Subtract(pt, c, v); 68 | 69 | if (vtkMath::Norm(v) < tol) { 70 | pts->InsertNextId(closest->GetId(i)); 71 | } 72 | } 73 | 74 | closest->Delete(); 75 | } 76 | 77 | #ifdef DEBUG 78 | void WriteVTK (const char *name, vtkPolyData *pd) { 79 | std::cout << "Writing " << name << std::endl; 80 | 81 | vtkPolyDataWriter *w = vtkPolyDataWriter::New(); 82 | w->SetInputData(pd); 83 | w->SetFileName(name); 84 | w->Update(); 85 | w->Delete(); 86 | } 87 | #endif 88 | 89 | double GetAngle (const double *vA, const double *vB, const double *n) { 90 | // http://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors 91 | 92 | double _vA[3]; 93 | 94 | vtkMath::Cross(n, vA, _vA); 95 | double ang = std::atan2(vtkMath::Dot(_vA, vB), vtkMath::Dot(vA, vB)); 96 | 97 | if (ang < 0) { 98 | ang += 2*M_PI; 99 | } 100 | 101 | return ang; 102 | } 103 | 104 | Base::Base (vtkPoints *pts, vtkIdType num, const vtkIdType *poly) { 105 | ComputeNormal(pts, n, num, poly); 106 | 107 | double ptA[3], 108 | ptB[3]; 109 | 110 | pts->GetPoint(poly[0], ptA); 111 | pts->GetPoint(poly[1], ptB); 112 | 113 | ei[0] = ptB[0]-ptA[0]; 114 | ei[1] = ptB[1]-ptA[1]; 115 | ei[2] = ptB[2]-ptA[2]; 116 | 117 | vtkMath::Normalize(ei); 118 | 119 | ej[0] = n[1]*ei[2]-n[2]*ei[1]; 120 | ej[1] = -n[0]*ei[2]+n[2]*ei[0]; 121 | ej[2] = n[0]*ei[1]-n[1]*ei[0]; 122 | 123 | vtkMath::Normalize(ej); 124 | 125 | d = n[0]*ptA[0]+n[1]*ptA[1]+n[2]*ptA[2]; 126 | } 127 | 128 | void Transform (const double *in, double *out, const Base &base) { 129 | double x = base.ei[0]*in[0]+base.ei[1]*in[1]+base.ei[2]*in[2], 130 | y = base.ej[0]*in[0]+base.ej[1]*in[1]+base.ej[2]*in[2]; 131 | 132 | out[0] = x; 133 | out[1] = y; 134 | } 135 | 136 | /*void BackTransform (const double *in, double *out, const Base &base) { 137 | double x = in[0]*base.ei[0]+in[1]*base.ej[0]+base.d*base.n[0], 138 | y = in[0]*base.ei[1]+in[1]*base.ej[1]+base.d*base.n[1], 139 | z = in[0]*base.ei[2]+in[1]*base.ej[2]+base.d*base.n[2]; 140 | 141 | out[0] = x; 142 | out[1] = y; 143 | out[2] = z; 144 | }*/ 145 | 146 | double ComputeNormal (const Poly &poly, double *n) { 147 | n[0] = 0; n[1] = 0; n[2] = 0; 148 | 149 | Poly::const_iterator itrA, itrB; 150 | 151 | for (itrA = poly.begin(); itrA != poly.end(); ++itrA) { 152 | itrB = itrA+1; 153 | 154 | if (itrB == poly.end()) { 155 | itrB = poly.begin(); 156 | } 157 | 158 | const Point3d &ptA = *itrA, 159 | &ptB = *itrB; 160 | 161 | n[0] += (ptA.y-ptB.y)*(ptA.z+ptB.z); 162 | n[1] += (ptA.z-ptB.z)*(ptA.x+ptB.x); 163 | n[2] += (ptA.x-ptB.x)*(ptA.y+ptB.y); 164 | } 165 | 166 | return vtkMath::Normalize(n); 167 | } 168 | 169 | bool PointInPoly (const Poly &poly, const Point3d &p) { 170 | bool in = false; 171 | 172 | Poly::const_iterator itrA, itrB; 173 | 174 | for (itrA = poly.begin(); itrA != poly.end(); ++itrA) { 175 | itrB = itrA+1; 176 | 177 | if (itrB == poly.end()) { 178 | itrB = poly.begin(); 179 | } 180 | 181 | const Point3d &ptA = *itrA, 182 | &ptB = *itrB; 183 | 184 | if ((ptA.x <= p.x || ptB.x <= p.x) 185 | && ((ptA.y < p.y && ptB.y >= p.y) 186 | || (ptB.y < p.y && ptA.y >= p.y))) { 187 | 188 | // schnittpunkt mit bounding box und strahlensatz 189 | if (ptA.x+(p.y-ptA.y)*(ptB.x-ptA.x)/(ptB.y-ptA.y) < p.x) { 190 | in = !in; 191 | } 192 | } 193 | } 194 | 195 | return in; 196 | } 197 | 198 | #ifdef DEBUG 199 | void WritePolys (const char *name, const PolysType &polys) { 200 | auto pts = vtkSmartPointer::New(); 201 | pts->SetDataTypeToDouble(); 202 | 203 | auto pd = vtkSmartPointer::New(); 204 | pd->SetPoints(pts); 205 | pd->Allocate(1); 206 | 207 | for (auto &poly : polys) { 208 | auto cell = vtkSmartPointer::New(); 209 | 210 | for (auto &p : poly) { 211 | cell->InsertNextId(pts->InsertNextPoint(p.x, p.y, p.z)); 212 | } 213 | 214 | pd->InsertNextCell(VTK_POLYGON, cell); 215 | } 216 | 217 | WriteVTK(name, pd); 218 | } 219 | #endif 220 | 221 | void GetPolys (const ReferencedPointsType &pts, const IndexedPolysType &indexedPolys, PolysType &polys) { 222 | for (const auto &poly : indexedPolys) { 223 | Poly newPoly; 224 | 225 | for (auto &id : poly) { 226 | newPoly.push_back(pts.at(id)); 227 | } 228 | 229 | polys.push_back(std::move(newPoly)); 230 | } 231 | } 232 | 233 | void GetPoly (vtkPoints *pts, vtkIdType num, const vtkIdType *poly, Poly &out) { 234 | vtkIdType i; 235 | 236 | double p[3]; 237 | 238 | for (i = 0; i < num; i++) { 239 | pts->GetPoint(poly[i], p); 240 | 241 | out.emplace_back(p[0], p[1], p[2], poly[i]); 242 | } 243 | } 244 | 245 | void FlattenPoly (const Poly &poly, Poly &out, const Base &base) { 246 | double pt[3], tr[2]; 247 | 248 | vtkIdType i = 0; 249 | 250 | for (auto &p : poly) { 251 | pt[0] = p.x; 252 | pt[1] = p.y; 253 | pt[2] = p.z; 254 | 255 | Transform(pt, tr, base); 256 | 257 | out.emplace_back(tr[0], tr[1], 0, i++); 258 | } 259 | } 260 | 261 | void FlattenPoly2 (const Poly &poly, Poly &out, const Base2 &base) { 262 | double pt[3], tr[3]; 263 | 264 | for (auto &p : poly) { 265 | pt[0] = p.x; 266 | pt[1] = p.y; 267 | pt[2] = p.z; 268 | 269 | base.Transform(pt, tr); 270 | 271 | out.emplace_back(tr[0], tr[1], tr[2], p.id); 272 | } 273 | } 274 | 275 | std::shared_ptr GetEdgeProj (const Poly &poly, const Point3d &p) { 276 | Poly::const_iterator itrA, itrB; 277 | 278 | double d, t; 279 | 280 | std::shared_ptr proj; 281 | 282 | for (itrA = poly.begin(); itrA != poly.end(); ++itrA) { 283 | itrB = itrA+1; 284 | 285 | if (itrB == poly.end()) { 286 | itrB = poly.begin(); 287 | } 288 | 289 | ProjOnLine(*itrA, *itrB, p, &d, &t, proj); 290 | 291 | if (d > 0 && d < 1e-5 && t > 0 && t < 1) { 292 | return std::make_shared(itrA->id, itrB->id, *proj, d); 293 | } 294 | 295 | } 296 | 297 | return nullptr; 298 | } 299 | 300 | void ProjOnLine (const Point3d &a, const Point3d &b, const Point3d &p, double *d, double *t, std::shared_ptr &proj) { 301 | double v[3], w[3]; 302 | 303 | double v_ = Point3d::GetVec(a, b, v); 304 | double w_ = Point3d::GetVec(a, p, w); 305 | 306 | double pr = std::min(std::max(vtkMath::Dot(v, w), -1.), 1.); 307 | 308 | *d = std::sin(std::acos(pr))*w_; 309 | 310 | vtkMath::MultiplyScalar(v, std::sqrt(w_*w_-*d**d)); 311 | 312 | proj = std::make_shared(a.x+v[0], a.y+v[1], a.z+v[2]); 313 | 314 | *t = pr*w_/v_; 315 | } 316 | 317 | void ProjOnLine (vtkPolyData *pd, const Pair &line, const Point3d &p, std::shared_ptr &proj) { 318 | double pA[3], pB[3]; 319 | 320 | pd->GetPoint(line.f, pA); 321 | pd->GetPoint(line.g, pB); 322 | 323 | Point3d a(pA[0], pA[1], pA[2]); 324 | Point3d b(pB[0], pB[1], pB[2]); 325 | 326 | double d, t; 327 | 328 | ProjOnLine(a, b, p, &d, &t, proj); 329 | } 330 | 331 | vtkSmartPointer CreatePolyData (const PolysType &polys) { 332 | auto pts = vtkSmartPointer::New(); 333 | pts->SetDataTypeToDouble(); 334 | 335 | auto pd = vtkSmartPointer::New(); 336 | pd->SetPoints(pts); 337 | pd->Allocate(100); 338 | 339 | for (const auto &poly : polys) { 340 | auto cell = vtkSmartPointer::New(); 341 | 342 | for (const auto &p : poly) { 343 | cell->InsertNextId(pts->InsertNextPoint(p.x, p.y, p.z)); 344 | } 345 | 346 | pd->InsertNextCell(VTK_POLYGON, cell); 347 | } 348 | 349 | pd->Squeeze(); 350 | 351 | return pd; 352 | } 353 | 354 | double GetTriangleQuality (const Poly &poly) { 355 | double n[3]; 356 | 357 | double l = ComputeNormal(poly, n); 358 | 359 | double d = 0; 360 | 361 | Poly::const_iterator itrA, itrB; 362 | 363 | for (itrA = poly.begin(); itrA != poly.end(); ++itrA) { 364 | itrB = itrA+1; 365 | 366 | if (itrB == poly.end()) { 367 | itrB = poly.begin(); 368 | } 369 | 370 | d += std::sqrt(Point3d::GetDist(*itrA, *itrB)); 371 | } 372 | 373 | return 10.392304845413264*l/(d*d); 374 | } 375 | -------------------------------------------------------------------------------- /Utilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __Utilities_h 18 | #define __Utilities_h 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #define NOTSET -1 37 | 38 | double GetAngle (const double *vA, const double *vB, const double *n); 39 | 40 | double ComputeNormal (vtkPoints *pts, double *n, vtkIdType num, const vtkIdType *poly); 41 | 42 | void FindPoints (vtkKdTreePointLocator *pl, const double *pt, vtkIdList *pts, double tol = 1e-6); 43 | 44 | #ifdef DEBUG 45 | void WriteVTK (const char *name, vtkPolyData *pd); 46 | #endif 47 | 48 | class Point3d { 49 | public: 50 | const double x, y, z; 51 | vtkIdType id, otherId; 52 | 53 | Point3d () = delete; 54 | Point3d (const double x, const double y, const double z, vtkIdType id = NOTSET, vtkIdType otherId = NOTSET) : x(x), y(y), z(z), id(id), otherId(otherId) {} 55 | bool operator< (const Point3d &other) const { 56 | const long x1 = std::lround(x*1e5), 57 | y1 = std::lround(y*1e5), 58 | z1 = std::lround(z*1e5), 59 | x2 = std::lround(other.x*1e5), 60 | y2 = std::lround(other.y*1e5), 61 | z2 = std::lround(other.z*1e5); 62 | 63 | return std::tie(x1, y1, z1) < std::tie(x2, y2, z2); 64 | } 65 | bool operator== (const Point3d &other) const { 66 | return !(*this < other) && !(other < *this); 67 | } 68 | 69 | friend std::ostream& operator<< (std::ostream &out, const Point3d &p) { 70 | out << "Point3d(x=" << p.x 71 | << ", y=" << p.y 72 | << ", z=" << p.z 73 | << ", id=" << p.id 74 | << ", otherId=" << p.otherId << ")"; 75 | return out; 76 | } 77 | 78 | static double GetVec (const Point3d &a, const Point3d &b, double *v) { 79 | v[0] = b.x-a.x; 80 | v[1] = b.y-a.y; 81 | v[2] = b.z-a.z; 82 | 83 | return vtkMath::Normalize(v); 84 | } 85 | 86 | static double GetDist (const Point3d &a, const Point3d &b) { 87 | double dx = b.x-a.x; 88 | double dy = b.y-a.y; 89 | double dz = b.z-a.z; 90 | 91 | return dx*dx+dy*dy+dz*dz; 92 | } 93 | 94 | }; 95 | 96 | typedef std::vector IdsType; 97 | typedef std::set _IdsType; 98 | 99 | class Pair { 100 | public: 101 | vtkIdType f, g; 102 | Pair () = delete; 103 | Pair (vtkIdType f, vtkIdType g) : f(f), g(g) {} 104 | bool operator< (const Pair &other) const { 105 | return std::tie(f, g) < std::tie(other.f, other.g); 106 | } 107 | bool operator== (const Pair &other) const { 108 | return f == other.f && g == other.g; 109 | } 110 | vtkIdType operator& (const Pair &other) const { 111 | if (f == other.f || f == other.g) { 112 | return f; 113 | } 114 | return g; 115 | } 116 | friend std::ostream& operator<< (std::ostream &out, const Pair &p) { 117 | out << "(" << p.f << ", " << p.g << ")"; 118 | return out; 119 | } 120 | }; 121 | 122 | class Base { 123 | public: 124 | Base () {} 125 | Base (vtkPoints *pts, vtkIdType num, const vtkIdType *poly); 126 | double n[3], ei[3], ej[3], d; 127 | }; 128 | 129 | void Transform (const double *in, double *out, const Base &base); 130 | // void BackTransform (const double *in, double *out, const Base &base); 131 | 132 | class Base2 { 133 | public: 134 | Base2 () {} 135 | Base2 (double *_t, double *_ei, double *_ek) { 136 | std::copy_n(_t, 3, t); 137 | std::copy_n(_ei, 3, ei); 138 | std::copy_n(_ek, 3, ek); 139 | 140 | ej[0] = ek[1]*ei[2]-ek[2]*ei[1]; 141 | ej[1] = -ek[0]*ei[2]+ek[2]*ei[0]; 142 | ej[2] = ek[0]*ei[1]-ek[1]*ei[0]; 143 | 144 | vtkMath::Normalize(ej); 145 | } 146 | void Transform (const double *in, double *out) const { 147 | double R[][3] = { 148 | { ei[0], ei[1], ei[2] }, 149 | { ej[0], ej[1], ej[2] }, 150 | { ek[0], ek[1], ek[2] } 151 | }; 152 | 153 | double _t[] = { in[0]-t[0], in[1]-t[1], in[2]-t[2] }; 154 | 155 | out[0] = R[0][0]*_t[0]+R[0][1]*_t[1]+R[0][2]*_t[2]; 156 | out[1] = R[1][0]*_t[0]+R[1][1]*_t[1]+R[1][2]*_t[2]; 157 | out[2] = R[2][0]*_t[0]+R[2][1]*_t[1]+R[2][2]*_t[2]; 158 | } 159 | void BackTransform (const double *in, double *out) const { 160 | double R[][3] = { 161 | { ei[0], ej[0], ek[0] }, 162 | { ei[1], ej[1], ek[1] }, 163 | { ei[2], ej[2], ek[2] } 164 | }; 165 | 166 | double _out[] = { 167 | R[0][0]*in[0]+R[0][1]*in[1]+R[0][2]*in[2], 168 | R[1][0]*in[0]+R[1][1]*in[1]+R[1][2]*in[2], 169 | R[2][0]*in[0]+R[2][1]*in[1]+R[2][2]*in[2] 170 | }; 171 | 172 | out[0] = _out[0]+t[0]; 173 | out[1] = _out[1]+t[1]; 174 | out[2] = _out[2]+t[2]; 175 | } 176 | double t[3], ei[3], ej[3], ek[3]; 177 | }; 178 | 179 | template 180 | std::ostream& operator<< (typename std::enable_if::value, std::ostream>::type& stream, const T& e) { 181 | return stream << static_cast::type>(e); 182 | } 183 | 184 | typedef std::vector Poly, Points; 185 | typedef std::vector PolysType; 186 | 187 | double ComputeNormal (const Poly &poly, double *n); 188 | bool PointInPoly (const Poly &poly, const Point3d &p); 189 | 190 | #ifdef DEBUG 191 | void WritePolys (const char *name, const PolysType &polys); 192 | #endif 193 | 194 | typedef std::deque IndexedPoly; 195 | typedef std::vector IndexedPolysType; 196 | 197 | typedef std::map> ReferencedPointsType; 198 | 199 | void GetPolys (const ReferencedPointsType &pts, const IndexedPolysType &indexedPolys, PolysType &polys); 200 | 201 | void GetPoly (vtkPoints *pts, vtkIdType num, const vtkIdType *poly, Poly &out); 202 | void FlattenPoly (const Poly &poly, Poly &out, const Base &base); 203 | void FlattenPoly2 (const Poly &poly, Poly &out, const Base2 &base); 204 | 205 | class Proj { 206 | public: 207 | Proj (vtkIdType a, vtkIdType b, const Point3d &proj, double d) : edge(a, b), proj(proj), d(d) {} 208 | 209 | Pair edge; 210 | Point3d proj; 211 | double d; 212 | }; 213 | 214 | std::shared_ptr GetEdgeProj (const Poly &poly, const Point3d &p); 215 | 216 | void ProjOnLine (const Point3d &a, const Point3d &b, const Point3d &p, double *d, double *t, std::shared_ptr &proj); 217 | void ProjOnLine (vtkPolyData *pd, const Pair &line, const Point3d &p, std::shared_ptr &proj); 218 | 219 | vtkSmartPointer CreatePolyData (const PolysType &polys); 220 | 221 | double GetTriangleQuality (const Poly &poly); 222 | 223 | #endif 224 | -------------------------------------------------------------------------------- /module/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(srcs 2 | ../Utilities.cxx 3 | ../Optimize.cxx 4 | ../Contact.cxx 5 | ../Merger.cxx 6 | ../vtkPolyDataBooleanFilter.cxx 7 | ) 8 | 9 | set(headers 10 | ../vtkPolyDataBooleanFilter.h 11 | ) 12 | 13 | vtk_module_add_module(vtkbool 14 | SOURCES ${srcs} 15 | HEADERS ${headers} 16 | ) 17 | -------------------------------------------------------------------------------- /module/vtk.module: -------------------------------------------------------------------------------- 1 | NAME 2 | vtkbool 3 | DEPENDS 4 | VTK::CommonCore 5 | VTK::CommonExecutionModel 6 | VTK::FiltersPoints 7 | VTK::IOLegacy 8 | VTK::FiltersFlowPaths 9 | TEST_DEPENDS 10 | VTK::FiltersSources 11 | DESCRIPTION 12 | This module contains a class to do polydata boolean operations. 13 | -------------------------------------------------------------------------------- /paraview/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | paraview_add_plugin(PolyDataBooleanFilter 2 | VERSION "${vtkbool_VERSION}" 3 | MODULES vtkbool 4 | MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/module/vtk.module") 5 | -------------------------------------------------------------------------------- /paraview/module/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(srcs 2 | ../../Utilities.cxx 3 | ../../Optimize.cxx 4 | ../../Contact.cxx 5 | ../../Merger.cxx 6 | ../../vtkPolyDataBooleanFilter.cxx 7 | ) 8 | 9 | set(headers 10 | ../../vtkPolyDataBooleanFilter.h 11 | ) 12 | 13 | vtk_module_add_module(vtkbool 14 | FORCE_STATIC 15 | SOURCES ${srcs} 16 | HEADERS ${headers} 17 | ) 18 | 19 | paraview_add_server_manager_xmls( 20 | XMLS vtkPolyDataBooleanFilter.xml 21 | ) 22 | -------------------------------------------------------------------------------- /paraview/module/vtk.module: -------------------------------------------------------------------------------- 1 | NAME 2 | vtkbool 3 | DEPENDS 4 | VTK::CommonCore 5 | VTK::CommonExecutionModel 6 | VTK::FiltersPoints 7 | VTK::IOLegacy 8 | VTK::FiltersFlowPaths 9 | TEST_DEPENDS 10 | VTK::FiltersSources 11 | -------------------------------------------------------------------------------- /paraview/module/vtkPolyDataBooleanFilter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | First polydata. 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Second polydata. 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Sets the operation mode. 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /paraview/paraview.plugin: -------------------------------------------------------------------------------- 1 | NAME 2 | PolyDataBooleanFilter 3 | REQUIRES_MODULES 4 | VTK::CommonCore 5 | VTK::FiltersCore 6 | DESCRIPTION 7 | This module contains a class to do polydata boolean operations. 8 | -------------------------------------------------------------------------------- /run_some_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PYTHONPATH=/home/zippy/vtkbool/build/lib/python3.13/site-packages/vtkbool:$PYTHONPATH 3 | pushd testing 4 | 5 | # pytest test_filter.py::test_triangle_strips -s 6 | 7 | # pytest test_filter.py::test_equal_capt_pts -s 8 | # pytest test_filter.py::test_equal_capt_pts_2 -s 9 | # pytest test_filter.py::test_equal_capt_pts_3 -s 10 | 11 | # gdb --args python -m pytest test_filter.py::test_simple -s 12 | 13 | pytest test_filter.py 14 | 15 | # pytest test_filter.py::test_simple -s 16 | 17 | popd 18 | -------------------------------------------------------------------------------- /testing/data/cross.vtk: -------------------------------------------------------------------------------- 1 | # vtk DataFile Version 5.1 2 | vtk output 3 | ASCII 4 | DATASET POLYDATA 5 | POINTS 70 double 6 | 0.5 1 0 0.43301269412 1 -0.25 0.25 1 -0.43301269412 7 | 3.0616171315e-17 1 -0.5 -0.25 1 -0.43301269412 -0.43301269412 1 -0.25 8 | -0.5 1 -6.1232342629e-17 -0.43301269412 1 0.25 -0.25 1 0.43301269412 9 | -9.1848507326e-17 1 0.5 0.25 1 0.43301269412 0.43301269412 1 0.25 10 | 0.43301269412 -1 0.25 0.25 -1 0.43301269412 -9.1848507326e-17 -1 0.5 11 | -0.25 -1 0.43301269412 -0.43301269412 -1 0.25 -0.5 -1 -6.1232342629e-17 12 | -0.43301269412 -1 -0.25 -0.25 -1 -0.43301269412 3.0616171315e-17 -1 -0.5 13 | 0.25 -1 -0.43301269412 0.43301269412 -1 -0.25 0.5 -1 0 14 | 0.43301269412 -0.43301269412 -0.25 0.5 -0.5 0 0.5 0.5 0 15 | 0.43301269412 0.43301269412 -0.25 0.25 -0.25 -0.43301269412 0.25 0.25 -0.43301269412 16 | 4.4290609559e-16 -1.1830696364e-18 -0.5 -0.25 -0.25 -0.43301269412 -0.25 0.25 -0.43301269412 17 | -0.43301269412 -0.43301269412 -0.25 -0.43301269412 0.43301269412 -0.25 -0.5 -0.5 -5.7130552552e-17 18 | -0.5 0.5 0 -0.43301269412 -0.43301269412 0.25 -0.43301269412 0.43301269412 0.25 19 | -0.25 -0.25 0.43301269412 -0.25 0.25 0.43301269412 -8.5695822653e-17 1.3634875996e-16 0.5 20 | 0.25 -0.25 0.43301269412 0.25 0.25 0.43301269412 0.43301269412 -0.43301269412 0.25 21 | 0.43301269412 0.43301269412 0.25 -1 0.5 0 -1 0.43301269412 -0.25 22 | -1 0.25 -0.43301269412 -1 2.5266076962e-16 -0.5 -1 -0.25 -0.43301269412 23 | -1 -0.43301269412 -0.25 -1 -0.5 -6.1232342629e-17 -1 -0.43301269412 0.25 24 | -1 -0.25 0.43301269412 -1 1.301960976e-16 0.5 -1 0.25 0.43301269412 25 | -1 0.43301269412 0.25 1 0.43301269412 0.25 1 0.25 0.43301269412 26 | 1 -3.1389309902e-16 0.5 1 -0.25 0.43301269412 1 -0.43301269412 0.25 27 | 1 -0.5 -6.1232342629e-17 1 -0.43301269412 -0.25 1 -0.25 -0.43301269412 28 | 1 -1.9142844023e-16 -0.5 1 0.25 -0.43301269412 1 0.43301269412 -0.25 29 | 1 0.5 0 30 | POLYGONS 53 240 31 | OFFSETS vtktypeint64 32 | 0 12 24 28 32 36 40 44 48 33 | 52 56 60 64 68 72 76 80 84 34 | 88 92 96 100 104 108 112 116 120 35 | 132 144 148 152 156 160 164 168 172 36 | 176 180 184 188 192 196 200 204 208 37 | 212 216 220 224 228 232 236 240 38 | CONNECTIVITY vtktypeint64 39 | 0 1 2 3 4 5 6 7 8 40 | 9 10 11 12 13 14 15 16 17 41 | 18 19 20 21 22 23 23 22 24 42 | 25 0 26 27 1 22 21 28 24 43 | 1 27 29 2 21 20 30 28 2 44 | 29 30 3 20 19 31 30 3 30 45 | 32 4 19 18 33 31 4 32 34 46 | 5 18 17 35 33 5 34 36 6 47 | 17 16 37 35 6 36 38 7 16 48 | 15 39 37 7 38 40 8 15 14 49 | 41 39 8 40 41 9 14 13 42 50 | 41 9 41 43 10 13 12 44 42 51 | 10 43 45 11 12 23 25 44 11 52 | 45 26 0 46 47 48 49 50 51 53 | 52 53 54 55 56 57 58 59 60 54 | 61 62 63 64 65 66 67 68 69 55 | 46 36 34 47 69 68 27 26 47 56 | 34 32 48 68 67 29 27 48 32 57 | 30 49 67 66 30 29 49 30 31 58 | 50 66 65 28 30 50 31 33 51 59 | 65 64 24 28 51 33 35 52 64 60 | 63 25 24 52 35 37 53 63 62 61 | 44 25 53 37 39 54 62 61 42 62 | 44 54 39 41 55 61 60 41 42 63 | 55 41 40 56 60 59 43 41 56 64 | 40 38 57 59 58 45 43 57 38 65 | 36 46 58 69 26 45 66 | -------------------------------------------------------------------------------- /testing/data/merger.vtk: -------------------------------------------------------------------------------- 1 | # vtk DataFile Version 5.1 2 | vtk output 3 | ASCII 4 | DATASET POLYDATA 5 | POINTS 24 double 6 | -0.4 0 0.5 -0.4 0 -0.5 0 -0.4 0.5 7 | 0 -0.4 -0.5 0.4 0 0.5 0.4 0 -0.5 8 | 0 0.4 0.5 0 0.4 -0.5 -0.25 0 0.5 9 | -0.25 0 -0.5 -0.15 -0.1 0.5 -0.15 -0.1 -0.5 10 | -0.05 0 0.5 -0.05 0 -0.5 -0.15 0.1 0.5 11 | -0.15 0.1 -0.5 0.05 0 0.5 0.05 0 -0.5 12 | 0.15 -0.1 0.5 0.15 -0.1 -0.5 0.25 0 0.5 13 | 0.25 0 -0.5 0.15 0.1 0.5 0.15 0.1 -0.5 14 | 15 | POLYGONS 17 84 16 | OFFSETS vtktypeint64 17 | 0 9 18 27 36 40 44 48 52 18 | 56 60 64 68 72 76 80 84 19 | CONNECTIVITY vtktypeint64 20 | 0 2 4 20 18 16 12 10 8 21 | 4 6 0 8 14 12 16 22 20 22 | 5 3 1 9 11 13 17 19 21 23 | 1 7 5 21 23 17 13 15 9 24 | 0 1 3 2 2 3 5 4 4 25 | 5 7 6 6 7 1 0 8 10 26 | 11 9 10 12 13 11 12 14 15 27 | 13 14 8 9 15 16 18 19 17 28 | 18 20 21 19 20 22 23 21 22 29 | 16 17 23 30 | -------------------------------------------------------------------------------- /testing/data/non-manifold.vtk: -------------------------------------------------------------------------------- 1 | # vtk DataFile Version 5.1 2 | vtk output 3 | ASCII 4 | DATASET POLYDATA 5 | POINTS 58 double 6 | -1 -0.5 -1 -1 0.5 -1 1 0.5 -1 7 | 1 -0.5 -1 -1 -0.5 1 1 -0.5 1 8 | 1 0.5 1 -1 0.5 1 1 -0.5 0.6950153131 9 | 1 0.5 0.6950153131 1 0.5 -0.6950153131 1 -0.5 -0.6950153131 10 | 0.88023614883 0.5 -0.73860579729 0.88023614883 -0.5 -0.73860579729 0.61976385117 0.5 -0.73860579729 11 | 0.61976385117 -0.5 -0.73860579729 0.375 0.5 -0.64951902628 0.375 -0.5 -0.64951902628 12 | 0.17546667159 0.5 -0.48209071159 0.17546667159 -0.5 -0.48209071159 0.045230533928 0.5 -0.2565151155 13 | 0.045230533928 -0.5 -0.2565151155 0.045230533928 0.5 0.2565151155 0.17546667159 0.5 0.48209071159 14 | 0.17546667159 -0.5 0.48209071159 0.045230533928 -0.5 0.2565151155 0.375 0.5 0.64951902628 15 | 0.375 -0.5 0.64951902628 0.61976385117 0.5 0.73860579729 0.61976385117 -0.5 0.73860579729 16 | 0.88023614883 0.5 0.73860579729 0.88023614883 -0.5 0.73860579729 -1 0.5 0.6950153131 17 | -1 -0.5 0.6950153131 -1 -0.5 -0.6950153131 -1 0.5 -0.6950153131 18 | -0.88023614883 -0.5 0.73860579729 -0.61976385117 -0.5 0.73860579729 -0.375 -0.5 0.64951902628 19 | -0.17546667159 -0.5 0.48209071159 -0.045230533928 -0.5 0.2565151155 0 -0.5 0 20 | -0.045230533928 -0.5 -0.2565151155 -0.17546667159 -0.5 -0.48209071159 -0.375 -0.5 -0.64951902628 21 | -0.61976385117 -0.5 -0.73860579729 -0.88023614883 -0.5 -0.73860579729 0 0.5 0 22 | -0.045230533928 0.5 0.2565151155 -0.17546667159 0.5 0.48209071159 -0.375 0.5 0.64951902628 23 | -0.61976385117 0.5 0.73860579729 -0.88023614883 0.5 0.73860579729 -0.88023614883 0.5 -0.73860579729 24 | -0.61976385117 0.5 -0.73860579729 -0.375 0.5 -0.64951902628 -0.17546667159 0.5 -0.48209071159 25 | -0.045230533928 0.5 -0.2565151155 26 | POLYGONS 35 180 27 | OFFSETS vtktypeint64 28 | 0 4 8 12 16 20 24 28 32 29 | 36 40 44 48 52 56 60 64 79 30 | 94 109 124 128 132 136 140 144 148 31 | 152 156 160 164 168 172 176 180 32 | CONNECTIVITY vtktypeint64 33 | 0 1 2 3 4 5 6 7 6 34 | 5 8 9 3 2 10 11 10 12 35 | 13 11 12 14 15 13 14 16 17 36 | 15 16 18 19 17 18 20 21 19 37 | 22 23 24 25 23 26 27 24 26 38 | 28 29 27 28 30 31 29 30 9 39 | 8 31 4 7 32 33 0 34 35 40 | 1 25 24 27 29 31 8 5 4 41 | 33 36 37 38 39 40 41 0 3 42 | 11 13 15 17 19 21 41 42 43 43 | 44 45 46 34 7 6 9 30 28 44 | 26 23 22 47 48 49 50 51 52 45 | 32 1 35 53 54 55 56 57 47 46 | 20 18 16 14 12 10 2 20 47 47 | 41 21 22 25 41 47 47 57 42 48 | 41 57 56 43 42 56 55 44 43 49 | 55 54 45 44 54 53 46 45 53 50 | 35 34 46 36 33 32 52 52 51 51 | 37 36 51 50 38 37 50 49 39 52 | 38 49 48 40 39 48 47 41 40 53 | -------------------------------------------------------------------------------- /testing/generate_frieze.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # *-* coding: UTF-8 *-* 3 | 4 | # Copyright 2012-2025 Ronald Römer 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import sys 19 | sys.path.extend(['/home/zippy/vtkbool/build/lib/python3.11/site-packages/vtkbool']) 20 | 21 | from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints 22 | from vtkmodules.vtkCommonDataModel import vtkPolyData, VTK_POLYGON 23 | from vtkmodules.vtkCommonTransforms import vtkTransform 24 | from vtkmodules.vtkIOLegacy import vtkPolyDataReader, vtkPolyDataWriter 25 | from vtkmodules.vtkIOGeometry import vtkSTLWriter 26 | from vtkmodules.vtkFiltersCore import vtkAppendPolyData, vtkCleanPolyData, vtkReverseSense, vtkPolyDataNormals 27 | from vtkmodules.vtkFiltersSources import vtkPlaneSource 28 | from vtkmodules.vtkFiltersGeneral import vtkTransformPolyDataFilter 29 | from vtkmodules.vtkFiltersModeling import vtkLinearExtrusionFilter 30 | from vtkmodules.vtkCommonExecutionModel import vtkTrivialProducer 31 | 32 | from vtkbool import vtkPolyDataBooleanFilter 33 | 34 | import math 35 | import os 36 | import re 37 | 38 | from collections import namedtuple 39 | 40 | Bnds = namedtuple('Bounds', 'x1 x2 y1 y2 z1 z2') 41 | 42 | def extrude(pts, h, z=0): 43 | cell = vtkIdList() 44 | 45 | _pts = vtkPoints() 46 | _pts.SetDataTypeToDouble() 47 | 48 | for pt in pts: 49 | cell.InsertNextId(_pts.InsertNextPoint(pt[0], pt[1], z)) 50 | 51 | pd = vtkPolyData() 52 | pd.Allocate(1) 53 | pd.SetPoints(_pts) 54 | pd.InsertNextCell(VTK_POLYGON, cell) 55 | 56 | prod = vtkTrivialProducer() 57 | prod.SetOutput(pd) 58 | 59 | extr = vtkLinearExtrusionFilter() 60 | extr.SetInputConnection(prod.GetOutputPort()) 61 | extr.SetVector(0, 0, h) 62 | 63 | pn = vtkPolyDataNormals() 64 | pn.SetInputConnection(extr.GetOutputPort()) 65 | pn.AutoOrientNormalsOn() 66 | 67 | return pn 68 | 69 | class Frieze: 70 | def __init__ (self, usr_cfg): 71 | self.cfg = { 'w': 39, 'a': 3.25/6, 'b': 1, 'c': .4333333333, 'e': .15/2, 'f': 3.25/3, 72 | 'q': 1.0833333, 'r': .625, 'end': 'A', 'flip': False, 'ang': 45, 'end_seqs': [(), ()], 'shift': 0, 'sym': False } 73 | 74 | self.cfg.update(usr_cfg) 75 | 76 | if self.cfg['end'] == 'A': 77 | self.cfg.update({ 'o_a': self.cfg['a'], 'o_b': 0, 'o_c': 0 }) 78 | 79 | elif self.cfg['end'] == 'B': 80 | self.cfg.update({ 'o_a': -self.cfg['b'], 'o_b': 0, 'o_c': self.cfg['b'] }) 81 | 82 | elif self.cfg['end'] == 'C': 83 | self.cfg.update({ 'o_a': 0, 'o_b': self.cfg['a'], 'o_c': 0 }) 84 | 85 | elif self.cfg['end'] == 'D': 86 | self.cfg.update({ 'o_a': -self.cfg['a'], 'o_b': 2*self.cfg['a'], 'o_c': 0 }) 87 | 88 | def draw_bricks(self, seq, end_seq): 89 | seq = list(seq) 90 | 91 | pts = [] 92 | 93 | if self.cfg['sym']: 94 | 95 | assert self.cfg['end'] == 'D' 96 | 97 | fixed = sum(seq[1:-1])+sum(end_seq) 98 | 99 | print(fixed) 100 | 101 | w = self.cfg['w']+2*self.cfg['a'] 102 | 103 | i = 0 104 | 105 | while True: 106 | curr = (i*seq[-1]+fixed)*self.cfg['f'] 107 | 108 | if (w-curr)/2 < seq[0]*self.cfg['f']: 109 | break 110 | 111 | i += 1 112 | 113 | offs = (w-curr)/2 114 | 115 | seq_ = [0] + seq[1:-1] + [seq[-1]]*i + list(reversed(end_seq)) 116 | 117 | for i in range(1, len(seq_)): 118 | seq_[i] += seq_[i-1] 119 | 120 | print(seq_) 121 | 122 | for s in seq_: 123 | mid = self.cfg['a']-offs-s*self.cfg['f'] 124 | 125 | pts.extend([[mid+self.cfg['e'], -self.cfg['a']], 126 | [mid+self.cfg['e'], -self.cfg['a']+2*self.cfg['e']], 127 | [mid-self.cfg['e'], -self.cfg['a']+2*self.cfg['e']], 128 | [mid-self.cfg['e'], -self.cfg['a']]]) 129 | 130 | else: 131 | 132 | n, m = divmod(self.cfg['w']+self.cfg['o_b'], self.cfg['f']) 133 | 134 | if abs(m-self.cfg['f']) < 1e-5: 135 | n += 1 136 | m = 0 137 | 138 | print(n, m) 139 | 140 | n = int(n) 141 | n, m_ = divmod(n-sum(seq[:-1]), seq[-1]) 142 | 143 | print('->', n, m_) 144 | 145 | seq.extend([seq[-1]]*(n-1)) 146 | 147 | print(seq) 148 | 149 | if m_ == 0 and m < 1e-5: 150 | del seq[-1] 151 | 152 | if end_seq: 153 | seq[-len(end_seq):] = reversed(end_seq) 154 | 155 | # wenn 0, dann löschen 156 | seq = [ s for s in seq if s > 0 ] 157 | 158 | for i in range(1, len(seq)): 159 | seq[i] += seq[i-1] 160 | 161 | for s in seq: 162 | mid = self.cfg['a']-s*self.cfg['f'] 163 | 164 | pts.extend([[mid+self.cfg['e'], -self.cfg['a']], 165 | [mid+self.cfg['e'], -self.cfg['a']+2*self.cfg['e']], 166 | [mid-self.cfg['e'], -self.cfg['a']+2*self.cfg['e']], 167 | [mid-self.cfg['e'], -self.cfg['a']]]) 168 | 169 | pts.extend([[-self.cfg['w']+self.cfg['o_a'], -self.cfg['a']], 170 | [-self.cfg['w']+self.cfg['o_a'], self.cfg['b']], 171 | [self.cfg['a'], self.cfg['b']], 172 | [self.cfg['a'], -self.cfg['a']]]) 173 | 174 | return pts 175 | 176 | def draw_zz_bricks(self): 177 | j = int(self.cfg['shift']//self.cfg['c'])+1 178 | 179 | self.cfg['shift'] = self.cfg['shift']%self.cfg['c'] 180 | 181 | n, m = divmod(self.cfg['w']+self.cfg['o_c']-self.cfg['shift'], self.cfg['c']) 182 | 183 | n = int(n) 184 | 185 | print(n, m) 186 | 187 | if self.cfg['sym']: 188 | self.cfg['shift'] = m/2 189 | 190 | pts = [ [-i*self.cfg['c']-self.cfg['shift'], ((i+j)%2)*self.cfg['c']] for i in range(n+1) ] 191 | 192 | if m > 1e-3: 193 | f = -1 if pts[-1][1] > 1e-5 else 1 194 | 195 | pts.append([ pts[-1][0]-m, pts[-1][1]+f*m ]) 196 | 197 | pts.extend([ [-self.cfg['w']-self.cfg['o_c'], self.cfg['b']], 198 | [0, self.cfg['b']] ]) 199 | 200 | if self.cfg['shift'] > 1e-5: 201 | y = self.cfg['c']-self.cfg['shift'] if j%2 == 0 else self.cfg['shift'] 202 | 203 | pts.append([0, y]) 204 | 205 | return pts 206 | 207 | def draw_spacer(self): 208 | pts = [[-self.cfg['w']+self.cfg['o_a'], 2*self.cfg['e']], 209 | [-self.cfg['w']+self.cfg['o_a'], self.cfg['b']], 210 | [self.cfg['a'], self.cfg['b']], 211 | [self.cfg['a'], 2*self.cfg['e']]] 212 | 213 | if self.cfg['end'] == 'B': 214 | pts[:1] = [[-self.cfg['w']-2*self.cfg['e'], 2*self.cfg['e']], 215 | [-self.cfg['w']-2*self.cfg['e'], -self.cfg['a']], 216 | [-self.cfg['w']-self.cfg['b'], -self.cfg['a']]] 217 | 218 | return pts 219 | 220 | def export(self, name): 221 | extr = extrude(self.draw_bricks(self.cfg['seqs'][0], self.cfg['end_seqs'][0]), self.cfg['r'], self.cfg['q']/2) 222 | extr1 = extrude(self.draw_spacer(), 2*self.cfg['e'], self.cfg['q']/2+self.cfg['r']) 223 | 224 | extr2 = extrude(self.draw_bricks(self.cfg['seqs'][1], self.cfg['end_seqs'][1]), -self.cfg['r'], -self.cfg['q']/2) 225 | extr3 = extrude(self.draw_spacer(), -2*self.cfg['e'], -self.cfg['q']/2-self.cfg['r']) 226 | 227 | extr4 = extrude(self.draw_zz_bricks(), self.cfg['q'], -self.cfg['q']/2) 228 | 229 | # extr + extr1 230 | bf = vtkPolyDataBooleanFilter() 231 | bf.SetInputConnection(extr.GetOutputPort()) 232 | bf.SetInputConnection(1, extr1.GetOutputPort()) 233 | 234 | # extr2 + extr3 235 | bf1 = vtkPolyDataBooleanFilter() 236 | bf1.SetInputConnection(extr2.GetOutputPort()) 237 | bf1.SetInputConnection(1, extr3.GetOutputPort()) 238 | 239 | app = vtkAppendPolyData() 240 | app.AddInputConnection(bf.GetOutputPort()) 241 | app.AddInputConnection(bf1.GetOutputPort()) 242 | 243 | bf2 = vtkPolyDataBooleanFilter() 244 | bf2.SetInputConnection(app.GetOutputPort()) 245 | bf2.SetInputConnection(1, extr4.GetOutputPort()) 246 | 247 | ang = math.radians(self.cfg['ang']) 248 | 249 | v = [0, 5] 250 | 251 | _v = [math.cos(ang)*v[0]-math.sin(ang)*v[1], 252 | math.sin(ang)*v[0]+math.cos(ang)*v[1]] 253 | 254 | plane = vtkPlaneSource() 255 | plane.SetOrigin(0, 0, 0) 256 | plane.SetPoint1(0, 0, 5) 257 | plane.SetPoint2(_v[0], _v[1], 0) 258 | plane.SetCenter(0, 0 , 0) 259 | plane.SetResolution(2, 2) 260 | 261 | bf3 = vtkPolyDataBooleanFilter() 262 | bf3.SetInputConnection(bf2.GetOutputPort()) 263 | bf3.SetInputConnection(1, plane.GetOutputPort()) 264 | bf3.SetOperModeToDifference() 265 | 266 | result = bf3 267 | 268 | if self.cfg['end'] == 'D': 269 | 270 | _v = [math.cos(-ang)*v[0]-math.sin(-ang)*v[1], 271 | math.sin(-ang)*v[0]+math.cos(-ang)*v[1]] 272 | 273 | plane1 = vtkPlaneSource() 274 | plane1.SetOrigin(0, 0, 0) 275 | plane1.SetPoint2(0, 0, 5) 276 | plane1.SetPoint1(_v[0], _v[1], 0) 277 | plane1.SetCenter(-self.cfg['w'], 0 , 0) 278 | plane1.SetResolution(2, 2) 279 | 280 | bf4 = vtkPolyDataBooleanFilter() 281 | bf4.SetInputConnection(result.GetOutputPort()) 282 | bf4.SetInputConnection(1, plane1.GetOutputPort()) 283 | bf4.SetOperModeToDifference() 284 | 285 | result = bf4 286 | 287 | if 'clip' in self.cfg: 288 | plane2 = vtkPlaneSource() 289 | plane2.SetOrigin(0, 0, 0) 290 | plane2.SetPoint1(0, 0, 5) 291 | plane2.SetPoint2(v[0], v[1], 0) 292 | plane2.SetCenter(-self.cfg['clip'], 0 , 0) 293 | plane2.SetResolution(2, 2) 294 | 295 | bf5 = vtkPolyDataBooleanFilter() 296 | bf5.SetInputConnection(result.GetOutputPort()) 297 | bf5.SetInputConnection(1, plane2.GetOutputPort()) 298 | bf5.SetOperModeToDifference() 299 | 300 | result = bf5 301 | 302 | if 'pins' in self.cfg: 303 | app1 = vtkAppendPolyData() 304 | 305 | t = self.cfg.get('clip', 0) 306 | fake_w = self.cfg.get('fake_w', self.cfg['w']) 307 | 308 | u = (fake_w-t)/self.cfg['pins']/2 309 | 310 | off = .2 # offset, sodass man die pins leichter hineinstecken kann 311 | 312 | h = self.cfg['q']+2*self.cfg['r']-off 313 | half_w = (5-off)/2 314 | 315 | mids = [] 316 | 317 | for i in range(self.cfg['pins']): 318 | mid = t+u*(1+i*2) 319 | 320 | pin = extrude([ [-mid-half_w, self.cfg['b']], [-mid-half_w, self.cfg['b']+1.5], 321 | [-mid+half_w, self.cfg['b']+1.5], [-mid+half_w, self.cfg['b']] ], h, -h/2) 322 | 323 | app1.AddInputConnection(pin.GetOutputPort()) 324 | 325 | mids.append(mid) 326 | 327 | print('mids', mids) 328 | 329 | bf6 = vtkPolyDataBooleanFilter() 330 | bf6.SetInputConnection(result.GetOutputPort()) 331 | bf6.SetInputConnection(1, app1.GetOutputPort()) 332 | 333 | result = bf6 334 | 335 | if self.cfg['flip']: 336 | tra = vtkTransform() 337 | tra.Scale(-1, 1, 1) 338 | 339 | tf = vtkTransformPolyDataFilter() 340 | tf.SetInputConnection(result.GetOutputPort()) 341 | tf.SetTransform(tra) 342 | 343 | rf = vtkReverseSense() 344 | rf.SetInputConnection(tf.GetOutputPort()) 345 | 346 | rf.Update() 347 | 348 | bnds = Bnds(*rf.GetOutput().GetBounds()) 349 | 350 | tra2 = vtkTransform() 351 | tra2.Translate(0, 0, -bnds.z1) 352 | 353 | tf2 = vtkTransformPolyDataFilter() 354 | tf2.SetInputConnection(rf.GetOutputPort()) 355 | tf2.SetTransform(tra2) 356 | 357 | clean = vtkCleanPolyData() 358 | clean.SetInputConnection(tf2.GetOutputPort()) 359 | 360 | writer = vtkPolyDataWriter() 361 | writer.SetInputConnection(clean.GetOutputPort()) 362 | writer.SetFileName(name) 363 | writer.Update() 364 | 365 | else: 366 | result.Update() 367 | bnds = Bnds(*result.GetOutput().GetBounds()) 368 | 369 | tra2 = vtkTransform() 370 | tra2.Translate(0, 0, -bnds.z1) 371 | 372 | tf2 = vtkTransformPolyDataFilter() 373 | tf2.SetInputConnection(result.GetOutputPort()) 374 | tf2.SetTransform(tra2) 375 | 376 | clean = vtkCleanPolyData() 377 | clean.SetInputConnection(tf2.GetOutputPort()) 378 | 379 | writer = vtkPolyDataWriter() 380 | writer.SetInputConnection(clean.GetOutputPort()) 381 | writer.SetFileName(name) 382 | writer.Update() 383 | 384 | if __name__ == '__main__': 385 | cfgs = [ 386 | { 'seqs': [(2, 2), (2, 1, 2)], 'w': 39., 'pins': 2, 'fake_w': 40*3.25/3 }, # 0 387 | { 'seqs': [(1, 1), (1, 1)], 'w': 3.25, 'end': 'B', 'flip': True }, 388 | { 'seqs': [(2, 2), (2, 1, 2)], 'w': 40*3.25/3, 'pins': 2 }, 389 | { 'seqs': [(1, 1), (1, 1)], 'w': 11.375, 'end': 'C', 'flip': True, 'pins': 1 }, # 3 390 | { 'seqs': [(2, 2), (2, 1, 2)], 'w': 91*3.25/3, 'end': 'D', 'end_seqs': [(), (1,)], 'pins': 5 }, 391 | { 'seqs': [(1, 1), (1, 1)], 'w': 85*3.25/3, 'end': 'C', 'end_seqs': [(0,), (0,)], 'shift': 3*.4333333333/2, 'pins': 5 }, 392 | { 'seqs': [(2, 1, 2), (2, 2)], 'w': 40*3.25/3, 'end': 'C', 'flip': True, 'end_seqs': [(), (1, 2)], 'pins': 2 }, # 6 393 | { 'seqs': [(1, 1), (1, 1)], 'w': 3.25, 'end': 'B' }, 394 | { 'seqs': [(2, 2), (2, 1, 2)], 'w': 40*3.25/3, 'flip': True, 'clip': 8*3.25/3, 'pins': 2 }, 395 | { 'seqs': [(1, 1), (1, 1)], 'w': 7*3.25/3, 'end': 'B', 'flip': True, 'ang': 22.5, 'pins': 1, 'fake_w': 7*3.25/3+1 }, # 9 396 | { 'seqs': [(1, 1), (1, 1)], 'w': 47*3.25/3, 'ang': 22.5, 'pins': 3 }, 397 | { 'seqs': [(2, 2), (2, 1, 2)], 'w': 18.3848, 'end': 'D', 'ang': 22.5, 'sym': True, 'end_seqs': [(1,), ()], 'pins': 1 }, 398 | { 'seqs': [(1,), (1,)], 'w': 3.25/3, 'end': 'B', 'flip': True }, # 12 399 | { 'seqs': [(1, 1), (1, 1)], 'w': 11.5*3.25/3, 'end': 'C', 'pins': 1 }, 400 | { 'seqs': [(1, 1), (1, 1)], 'w': 11.5*3.25/3, 'end': 'C', 'flip': True, 'pins': 1 } 401 | ] 402 | 403 | os.makedirs('einzeln', exist_ok=True) 404 | os.makedirs('stl', exist_ok=True) 405 | 406 | cell_counts = [524, 98, 576, 264, 1274, 1867, 582, 98, 472, 194, 1048, 272, 56, 284, 284] 407 | 408 | for i, cfg in enumerate(cfgs): 409 | print(f'~~ {i} ~~') 410 | 411 | f = f'einzeln/test{i}.vtk' 412 | Frieze(cfg).export(f) 413 | 414 | assert os.path.exists(f) 415 | 416 | reader = vtkPolyDataReader() 417 | reader.SetFileName(f) 418 | 419 | writer = vtkSTLWriter() 420 | writer.SetInputConnection(reader.GetOutputPort()) 421 | writer.SetFileName(f'stl/test{i}.stl') 422 | 423 | writer.Update() 424 | 425 | assert reader.GetOutput().GetNumberOfCells() == cell_counts[i] 426 | -------------------------------------------------------------------------------- /testing/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = True 3 | addopts = --basetemp=out 4 | xfail_strict = True 5 | -------------------------------------------------------------------------------- /testing/test_congruence.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "Contact.h" 18 | 19 | int main() { 20 | auto pdA = CreatePolyData({ { {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0} } }); 21 | 22 | double z = std::tan(0.0005/180*M_PI)*.5; 23 | 24 | auto pdB = CreatePolyData({ 25 | { {0, 0, 0}, {0, .5, 0}, {1, .5, 0}, {1, 0, 0} }, 26 | { {1, .5, 0}, {0, .5, 0}, {0, 1, z}, {1, 1, z} } 27 | }); 28 | 29 | auto _pdA = Clean(pdA); 30 | auto _pdB = Clean(pdB); 31 | 32 | auto lines = Contact(_pdA, _pdB).GetLines(); 33 | 34 | if (lines->GetNumberOfCells() == 0) { 35 | return EXIT_FAILURE; 36 | } 37 | 38 | #ifdef DEBUG 39 | WriteVTK("lines.vtk", lines); 40 | #endif 41 | 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /testing/test_filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # *-* coding: UTF-8 *-* 3 | 4 | # Copyright 2012-2025 Ronald Römer 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import sys 19 | sys.path.extend(['/home/zippy/vtkbool/build/lib/python3.13/site-packages/vtkbool']) 20 | 21 | from collections import defaultdict 22 | from operator import itemgetter 23 | import math 24 | import pytest 25 | from functools import reduce 26 | 27 | from vtkmodules.vtkCommonCore import vtkIdList, vtkIdTypeArray, vtkPoints 28 | from vtkmodules.vtkCommonDataModel import vtkPolyData, VTK_POLYGON 29 | from vtkmodules.vtkFiltersSources import vtkCubeSource, vtkCylinderSource, vtkSphereSource, vtkPolyLineSource 30 | from vtkmodules.vtkCommonDataModel import vtkKdTreePointLocator 31 | from vtkmodules.vtkFiltersCore import vtkAppendPolyData, vtkCleanPolyData, vtkPolyDataNormals 32 | from vtkmodules.vtkIOLegacy import vtkPolyDataWriter, vtkPolyDataReader 33 | from vtkmodules.vtkCommonExecutionModel import vtkTrivialProducer 34 | from vtkmodules.vtkFiltersModeling import vtkRotationalExtrusionFilter 35 | from vtkmodules.vtkFiltersGeneral import vtkTransformPolyDataFilter 36 | from vtkmodules.vtkCommonTransforms import vtkTransform 37 | from vtkmodules.vtkCommonExecutionModel import vtkAlgorithm 38 | from vtkmodules.vtkFiltersModeling import vtkLinearExtrusionFilter 39 | 40 | from vtkbool import vtkPolyDataBooleanFilter 41 | 42 | def check_result(bf, expected_regs=None): 43 | lines = bf.GetOutput(2) 44 | 45 | lines.BuildLinks() 46 | 47 | pdA = bf.GetOutput(0) 48 | pdB = bf.GetOutput(1) 49 | 50 | pdA.BuildLinks() 51 | pdB.BuildLinks() 52 | 53 | contsA = lines.GetCellData().GetArray('cA') 54 | contsB = lines.GetCellData().GetArray('cB') 55 | 56 | assert isinstance(contsA, vtkIdTypeArray) 57 | assert isinstance(contsB, vtkIdTypeArray) 58 | 59 | regionsA = pdA.GetPointData().GetArray('RegionId') 60 | regionsB = pdB.GetPointData().GetArray('RegionId') 61 | 62 | assert isinstance(regionsA, vtkIdTypeArray) 63 | assert isinstance(regionsB, vtkIdTypeArray) 64 | 65 | valuesA = set(regionsA.GetValue(i) for i in range(regionsA.GetNumberOfValues())) 66 | valuesB = set(regionsB.GetValue(i) for i in range(regionsB.GetNumberOfValues())) 67 | 68 | if isinstance(expected_regs, list): 69 | assert len(valuesA) == expected_regs[0] 70 | assert len(valuesB) == expected_regs[1] 71 | else: 72 | print([len(valuesA), len(valuesB)]) 73 | 74 | for name, pd in [('pdA', pdA), ('pdB', pdB)]: 75 | print(f'checking {name}') 76 | 77 | loc = vtkKdTreePointLocator() 78 | loc.SetDataSet(pd) 79 | loc.BuildLocator() 80 | 81 | it = lines.GetLines().NewIterator() 82 | 83 | while not it.IsDoneWithTraversal(): 84 | 85 | line = it.GetCurrentCell() 86 | 87 | # print('line', it.GetCurrentCellId()) 88 | 89 | idA = line.GetId(0) 90 | idB = line.GetId(1) 91 | 92 | linkedA = vtkIdList() 93 | linkedB = vtkIdList() 94 | 95 | lines.GetPointCells(idA, linkedA) 96 | lines.GetPointCells(idB, linkedB) 97 | 98 | _linkedA = linkedA.GetNumberOfIds() 99 | _linkedB = linkedB.GetNumberOfIds() 100 | 101 | ptA = lines.GetPoint(idA) 102 | ptB = lines.GetPoint(idB) 103 | 104 | # print(ptA) 105 | # print(ptB) 106 | 107 | ptsA = vtkIdList() 108 | ptsB = vtkIdList() 109 | 110 | loc.FindPointsWithinRadius(1e-5, ptA, ptsA) 111 | loc.FindPointsWithinRadius(1e-5, ptB, ptsB) 112 | 113 | neigs = defaultdict(list) 114 | 115 | for pts in [ptsA, ptsB]: 116 | polys = vtkIdList() 117 | 118 | for i in range(pts.GetNumberOfIds()): 119 | pd.GetPointCells(pts.GetId(i), polys) 120 | 121 | for j in range(polys.GetNumberOfIds()): 122 | neigs[polys.GetId(j)].append(pts.GetId(i)) 123 | 124 | direct_neigs = {} 125 | 126 | for poly_id, point_ids in neigs.items(): 127 | if len(point_ids) > 1: 128 | equal_points = defaultdict(list) 129 | 130 | for point_id in point_ids: 131 | x, y, z = pd.GetPoint(point_id) 132 | 133 | equal_points[f'{x:.5f},{y:.5f},{z:.5f}'].append(point_id) 134 | 135 | groups = list(equal_points.values()) 136 | 137 | if len(groups) == 2: 138 | poly = vtkIdList() 139 | pd.GetCellPoints(poly_id, poly) 140 | 141 | next_inds = [ i+1 for i in range(poly.GetNumberOfIds()-1) ] + [0] 142 | 143 | groups_ = [ [ (id_, poly.IsId(id_)) for id_ in group ] for group in groups ] 144 | 145 | for group in groups_: 146 | if len(group) > 1: 147 | group.sort(key=itemgetter(1)) 148 | 149 | # mehr als 2 punkte an gleicher stelle? 150 | assert len(group) < 3 151 | 152 | it_a = map(itemgetter(1), group + group[:1]) 153 | it_b = iter(it_a) 154 | next(it_b) 155 | 156 | for a, b in zip(it_a, it_b): 157 | # benachbart? 158 | c = next_inds[a] 159 | assert c != b 160 | 161 | group_a, group_b = groups_ 162 | 163 | possible_edges = [ [i, j] for i in group_a for j in group_b ] 164 | 165 | edges = [] 166 | 167 | for a, b in possible_edges: 168 | id_i, i = a 169 | id_j, j = b 170 | 171 | next_i = next_inds[i] 172 | next_j = next_inds[j] 173 | 174 | if next_i == j \ 175 | or next_j == i: 176 | 177 | # reihenfolge ist dann nicht mehr wichtig 178 | edges.append([id_i, id_j]) 179 | 180 | # print(edges) 181 | 182 | direct_neigs[poly_id] = edges 183 | 184 | # print(direct_neigs) 185 | 186 | match len(direct_neigs): 187 | case 2: 188 | a, b = direct_neigs.values() 189 | 190 | assert len(a) == 1 191 | assert len(b) == 1 192 | 193 | ids = set(a[0] + b[0]) 194 | 195 | if _linkedA == _linkedB: 196 | assert len(ids) == 4 197 | 198 | elif _linkedA == 2: 199 | end_ids = set() 200 | 201 | for i in range(_linkedA): 202 | _line = vtkIdList() 203 | lines.GetCellPoints(linkedA.GetId(i), _line) 204 | 205 | end_ids.add(_line.GetId(0)) 206 | end_ids.add(_line.GetId(1)) 207 | 208 | end_ids.remove(idA) 209 | 210 | if len(end_ids) == 2: 211 | assert len(ids) == 4 212 | else: 213 | assert len(ids) == 3 214 | 215 | elif _linkedB == 2: 216 | end_ids = set() 217 | 218 | for i in range(_linkedB): 219 | _line = vtkIdList() 220 | lines.GetCellPoints(linkedB.GetId(i), _line) 221 | 222 | end_ids.add(_line.GetId(0)) 223 | end_ids.add(_line.GetId(1)) 224 | 225 | end_ids.remove(idB) 226 | 227 | if len(end_ids) == 2: 228 | assert len(ids) == 4 229 | else: 230 | assert len(ids) == 3 231 | 232 | else: 233 | # bspw. bei _linkedA == 4, _linkedB == 6 234 | assert len(ids) == 4 235 | 236 | case 1: 237 | a, *_ = direct_neigs.values() 238 | 239 | assert len(a) == 2 240 | 241 | ids = set(sum(a, [])) 242 | 243 | if _linkedA == 2 \ 244 | or _linkedB == 2: 245 | 246 | assert len(ids) == 3 247 | 248 | else: 249 | assert len(ids) == 4 250 | 251 | case 0: 252 | assert False 253 | 254 | it.GoToNextCell() 255 | 256 | print('-> ok') 257 | 258 | def write_result(bf, d): 259 | for i, name in enumerate(['pdA', 'pdB', 'lines']): 260 | writer = vtkPolyDataWriter() 261 | writer.SetFileName(d / f'{name}.vtk') 262 | writer.SetInputConnection(bf.GetOutputPort(i)) 263 | writer.Update() 264 | 265 | def extrude_polygon(poly, z): 266 | cell = vtkIdList() 267 | 268 | pts = vtkPoints() 269 | pts.SetDataTypeToDouble() 270 | 271 | for pt in poly: 272 | cell.InsertNextId(pts.InsertNextPoint(*pt, 0)) 273 | 274 | pd = vtkPolyData() 275 | pd.Allocate(1) 276 | pd.SetPoints(pts) 277 | pd.InsertNextCell(VTK_POLYGON, cell) 278 | 279 | extr = vtkLinearExtrusionFilter() 280 | extr.SetInputData(pd) 281 | extr.SetVector(0, 0, z) 282 | 283 | normals = vtkPolyDataNormals() 284 | normals.SetInputConnection(extr.GetOutputPort()) 285 | normals.AutoOrientNormalsOn() 286 | 287 | return normals 288 | 289 | def create_polydata(pts, polys): 290 | _pts = vtkPoints() 291 | _pts.SetDataTypeToDouble() 292 | 293 | for pt in pts: 294 | _pts.InsertNextPoint(*pt) 295 | 296 | pd = vtkPolyData() 297 | pd.Allocate(1) 298 | pd.SetPoints(_pts) 299 | 300 | for poly in polys: 301 | cell = vtkIdList() 302 | for i in poly: 303 | cell.InsertNextId(i) 304 | pd.InsertNextCell(VTK_POLYGON, cell) 305 | 306 | prod = vtkTrivialProducer() 307 | prod.SetOutput(pd) 308 | 309 | return prod 310 | 311 | def test_simple(tmp_path): 312 | cube = vtkCubeSource() 313 | cube.SetYLength(.5) 314 | 315 | cyl = vtkCylinderSource() 316 | cyl.SetResolution(32) 317 | cyl.SetHeight(.5) 318 | cyl.SetCenter(0, .5, 0) 319 | 320 | bf = vtkPolyDataBooleanFilter() 321 | bf.SetInputConnection(0, cube.GetOutputPort()) 322 | bf.SetInputConnection(1, cyl.GetOutputPort()) 323 | bf.SetOperModeToNone() 324 | 325 | bf.Update() 326 | 327 | write_result(bf, tmp_path) 328 | check_result(bf, [2, 2]) 329 | 330 | def test_simple_2(tmp_path): 331 | cube = vtkCubeSource() 332 | cube.SetYLength(.5) 333 | 334 | cyl = vtkCylinderSource() 335 | cyl.SetResolution(32) 336 | cyl.SetHeight(.5) 337 | cyl.SetCenter(0, .25, 0) 338 | 339 | bf = vtkPolyDataBooleanFilter() 340 | bf.SetInputConnection(0, cube.GetOutputPort()) 341 | bf.SetInputConnection(1, cyl.GetOutputPort()) 342 | bf.SetOperModeToNone() 343 | 344 | bf.Update() 345 | 346 | write_result(bf, tmp_path) 347 | check_result(bf, [2, 2]) 348 | 349 | def test_simple_3(tmp_path): 350 | cube = vtkCubeSource() 351 | cube.SetYLength(.5) 352 | 353 | cyl = vtkCylinderSource() 354 | cyl.SetResolution(32) 355 | cyl.SetHeight(.5) 356 | 357 | bf = vtkPolyDataBooleanFilter() 358 | bf.SetInputConnection(0, cube.GetOutputPort()) 359 | bf.SetInputConnection(1, cyl.GetOutputPort()) 360 | bf.SetOperModeToNone() 361 | 362 | bf.Update() 363 | 364 | write_result(bf, tmp_path) 365 | check_result(bf, [6, 6]) 366 | 367 | def test_simple_4(tmp_path): 368 | cube = vtkCubeSource() 369 | 370 | cyl = vtkPolyData() 371 | cyl.Allocate(1) 372 | 373 | pts = vtkPoints() 374 | pts.SetDataTypeToDouble() 375 | 376 | bottom = vtkIdList() 377 | top = vtkIdList() 378 | 379 | phi = math.pi/16 380 | 381 | for i in range(32): 382 | x0 = .5*math.cos(i*phi) 383 | z0 = .5*math.sin(i*phi) 384 | 385 | x1 = .5*math.cos((i+1)*phi) 386 | z1 = .5*math.sin((i+1)*phi) 387 | 388 | top.InsertNextId(pts.InsertNextPoint(x0, .75, z0)) 389 | bottom.InsertNextId(pts.InsertNextPoint(x0, -.25, z0)) 390 | 391 | for j in range(4): 392 | side = vtkIdList() 393 | 394 | side.InsertNextId(pts.InsertNextPoint(x0, -.25+j/4, z0)) 395 | side.InsertNextId(pts.InsertNextPoint(x0, -.25+(j+1)/4, z0)) 396 | side.InsertNextId(pts.InsertNextPoint(x1, -.25+(j+1)/4, z1)) 397 | side.InsertNextId(pts.InsertNextPoint(x1, -.25+j/4, z1)) 398 | 399 | cyl.InsertNextCell(VTK_POLYGON, side) 400 | 401 | cyl.SetPoints(pts) 402 | 403 | cyl.ReverseCell(cyl.InsertNextCell(VTK_POLYGON, top)) 404 | cyl.InsertNextCell(VTK_POLYGON, bottom) 405 | 406 | prod = vtkTrivialProducer() 407 | prod.SetOutput(cyl) 408 | 409 | bf = vtkPolyDataBooleanFilter() 410 | bf.SetInputConnection(0, cube.GetOutputPort()) 411 | bf.SetInputConnection(1, prod.GetOutputPort()) 412 | bf.SetOperModeToNone() 413 | 414 | bf.Update() 415 | 416 | write_result(bf, tmp_path) 417 | check_result(bf, [2, 2]) 418 | 419 | def test_same(tmp_path): 420 | sphere = vtkSphereSource() 421 | 422 | bf = vtkPolyDataBooleanFilter() 423 | bf.SetInputConnection(0, sphere.GetOutputPort()) 424 | bf.SetInputConnection(1, sphere.GetOutputPort()) 425 | bf.SetOperModeToNone() 426 | 427 | bf.Update() 428 | 429 | write_result(bf, tmp_path) 430 | check_result(bf, [56, 56]) 431 | 432 | @pytest.mark.xfail 433 | def test_intersecting_strips(): 434 | cubeA = vtkCubeSource() 435 | cubeA.SetBounds(-5, 5, -2.5, 2.5, -1.25, 1.25) 436 | 437 | cubeB = vtkCubeSource() 438 | cubeB.SetBounds(-1.25, 1.25, -1.25, 1.25, -1.25, 1.25) 439 | 440 | traA = vtkTransform() 441 | traA.Translate(-1.25, 0, 0) 442 | traA.RotateZ(45) 443 | 444 | traB = vtkTransform() 445 | traB.Translate(1.25, 0, 0) 446 | traB.RotateZ(45) 447 | 448 | tfA = vtkTransformPolyDataFilter() 449 | tfA.SetTransform(traA) 450 | tfA.SetInputConnection(cubeB.GetOutputPort()) 451 | 452 | tfB = vtkTransformPolyDataFilter() 453 | tfB.SetTransform(traB) 454 | tfB.SetInputConnection(cubeB.GetOutputPort()) 455 | 456 | app = vtkAppendPolyData() 457 | app.AddInputConnection(tfA.GetOutputPort()) 458 | app.AddInputConnection(tfB.GetOutputPort()) 459 | 460 | bf = vtkPolyDataBooleanFilter() 461 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 462 | bf.SetInputConnection(1, app.GetOutputPort()) 463 | bf.SetOperModeToNone() 464 | 465 | bf.Update() 466 | 467 | check_result(bf) 468 | 469 | @pytest.mark.xfail 470 | def test_intersecting_strips_2(): 471 | cube = vtkCubeSource() 472 | cube.SetBounds(-10, 10, 0, 12, 0, 10) 473 | 474 | poly = [ 475 | [-5, 0], 476 | [3, 8], 477 | [-3, 8], 478 | [5, 0], 479 | [8, 0], 480 | [8, 10], 481 | [-8, 10], 482 | [-8, 0] 483 | ] 484 | 485 | pd = extrude_polygon(poly, 10) 486 | 487 | bf = vtkPolyDataBooleanFilter() 488 | bf.SetInputConnection(0, cube.GetOutputPort()) 489 | bf.SetInputConnection(1, pd.GetOutputPort()) 490 | bf.SetOperModeToNone() 491 | bf.Update() 492 | 493 | check_result(bf) 494 | 495 | def test_touch(tmp_path): 496 | cube = vtkCubeSource() 497 | 498 | cylinder = vtkCylinderSource() 499 | cylinder.SetResolution(32) 500 | cylinder.SetRadius(.25) 501 | cylinder.SetCenter(.25, 0, 0) 502 | 503 | bf = vtkPolyDataBooleanFilter() 504 | bf.SetInputConnection(0, cube.GetOutputPort()) 505 | bf.SetInputConnection(1, cylinder.GetOutputPort()) 506 | bf.SetOperModeToNone() 507 | 508 | bf.Update() 509 | 510 | write_result(bf, tmp_path) 511 | check_result(bf, [3, 3]) 512 | 513 | def test_merger(tmp_path): 514 | cube = vtkCubeSource() 515 | 516 | cyl = vtkCylinderSource() 517 | cyl.SetRadius(.25) 518 | 519 | bf = vtkPolyDataBooleanFilter() 520 | bf.SetInputConnection(0, cube.GetOutputPort()) 521 | bf.SetInputConnection(1, cyl.GetOutputPort()) 522 | bf.SetOperModeToNone() 523 | 524 | bf.Update() 525 | 526 | write_result(bf, tmp_path) 527 | check_result(bf, [3, 3]) 528 | 529 | def test_merger_2(tmp_path): 530 | cube = vtkCubeSource() 531 | 532 | reader = vtkPolyDataReader() 533 | reader.SetFileName('data/merger.vtk') 534 | 535 | bf = vtkPolyDataBooleanFilter() 536 | bf.SetInputConnection(0, cube.GetOutputPort()) 537 | bf.SetInputConnection(1, reader.GetOutputPort()) 538 | bf.SetOperModeToNone() 539 | 540 | bf.Update() 541 | 542 | write_result(bf, tmp_path) 543 | check_result(bf, [7, 5]) 544 | 545 | def test_quads(tmp_path): 546 | sphereA = vtkSphereSource() 547 | sphereA.LatLongTessellationOn() 548 | sphereA.SetCenter(-.25, 0, 0) 549 | 550 | sphereB = vtkSphereSource() 551 | sphereB.LatLongTessellationOn() 552 | sphereB.SetCenter(.25, 0, 0) 553 | 554 | bf = vtkPolyDataBooleanFilter() 555 | bf.SetInputConnection(0, sphereA.GetOutputPort()) 556 | bf.SetInputConnection(1, sphereB.GetOutputPort()) 557 | bf.SetOperModeToNone() 558 | 559 | bf.Update() 560 | 561 | write_result(bf, tmp_path) 562 | check_result(bf, [2, 2]) 563 | 564 | def test_triangle_strips(tmp_path): 565 | line = vtkPolyLineSource() 566 | line.SetNumberOfPoints(19) 567 | 568 | phi = math.pi/18 569 | 570 | for i in range(19): 571 | line.SetPoint(i, 0, math.cos(i*phi), math.sin(i*phi)) 572 | 573 | extr = vtkRotationalExtrusionFilter() 574 | extr.SetInputConnection(line.GetOutputPort()) 575 | extr.SetResolution(18) 576 | extr.SetRotationAxis(0, 1, 0) 577 | 578 | sphere = vtkSphereSource() 579 | sphere.SetRadius(1) 580 | 581 | tra = vtkTransform() 582 | tra.RotateX(90) 583 | 584 | tf = vtkTransformPolyDataFilter() 585 | tf.SetInputConnection(sphere.GetOutputPort()) 586 | tf.SetTransform(tra) 587 | 588 | bf = vtkPolyDataBooleanFilter() 589 | bf.SetInputConnection(0, extr.GetOutputPort()) 590 | bf.SetInputConnection(1, tf.GetOutputPort()) 591 | bf.SetOperModeToNone() 592 | 593 | bf.Update() 594 | 595 | write_result(bf, tmp_path) 596 | check_result(bf, [49, 49]) 597 | 598 | def test_special(tmp_path): 599 | cubeA = vtkCubeSource() 600 | cubeA.SetBounds(-5, 5, -10, 0, 0, 10) 601 | 602 | poly = [ 603 | [0, 0], 604 | [1, 0], 605 | [2, -1], 606 | [3, 0], 607 | [4, -1], 608 | [5, 0], 609 | [5, 10], 610 | [-5, 10], 611 | [-5, 0], 612 | [-4, 1], 613 | [-3, 0], 614 | [-2, 1], 615 | [-1, 0] 616 | ] 617 | 618 | cubeB = extrude_polygon(poly, 10) 619 | 620 | bf = vtkPolyDataBooleanFilter() 621 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 622 | bf.SetInputConnection(1, cubeB.GetOutputPort()) 623 | bf.SetOperModeToNone() 624 | 625 | bf.Update() 626 | 627 | write_result(bf, tmp_path) 628 | 629 | assert bf.GetOutput(2).GetNumberOfCells() == 32 630 | 631 | @pytest.mark.xfail 632 | def test_non_manifolds(): 633 | reader = vtkPolyDataReader() 634 | reader.SetFileName('data/non-manifold.vtk') 635 | 636 | cyl = vtkCylinderSource() 637 | cyl.SetCenter(.75, 0, 0) 638 | cyl.SetRadius(.75) 639 | cyl.SetResolution(18) 640 | 641 | bf = vtkPolyDataBooleanFilter() 642 | bf.SetInputConnection(0, reader.GetOutputPort()) 643 | bf.SetInputConnection(1, cyl.GetOutputPort()) 644 | bf.SetOperModeToNone() 645 | 646 | bf.Update() 647 | 648 | check_result(bf) 649 | 650 | def test_branched(tmp_path): 651 | reader = vtkPolyDataReader() 652 | reader.SetFileName('data/branched.vtk') 653 | 654 | cube = vtkCubeSource() 655 | cube.SetBounds(-.3283653157, .3283653157, -.5, .5, -.375, .375) 656 | 657 | bf = vtkPolyDataBooleanFilter() 658 | bf.SetInputConnection(0, cube.GetOutputPort()) 659 | bf.SetInputConnection(1, reader.GetOutputPort()) 660 | bf.SetOperModeToNone() 661 | bf.Update() 662 | 663 | write_result(bf, tmp_path) 664 | check_result(bf, [4, 4]) 665 | 666 | def test_branched_2(tmp_path): 667 | reader = vtkPolyDataReader() 668 | reader.SetFileName('data/branched.vtk') 669 | 670 | cube = vtkCubeSource() 671 | cube.SetBounds(-.3283653157, .3283653157, -.5, .5, -.75, .75) 672 | 673 | bf = vtkPolyDataBooleanFilter() 674 | bf.SetInputConnection(0, cube.GetOutputPort()) 675 | bf.SetInputConnection(1, reader.GetOutputPort()) 676 | bf.SetOperModeToNone() 677 | bf.Update() 678 | 679 | write_result(bf, tmp_path) 680 | check_result(bf, [8, 8]) 681 | 682 | @pytest.mark.xfail 683 | def test_branched_3(): 684 | reader = vtkPolyDataReader() 685 | reader.SetFileName('data/branched3.vtk') 686 | 687 | cube = vtkCubeSource() 688 | cube.SetBounds(-.3283653157, .3283653157, -.5, .5, -.75, .75) 689 | 690 | bf = vtkPolyDataBooleanFilter() 691 | bf.SetInputConnection(0, cube.GetOutputPort()) 692 | bf.SetInputConnection(1, reader.GetOutputPort()) 693 | bf.SetOperModeToNone() 694 | bf.Update() 695 | 696 | check_result(bf) 697 | 698 | def test_branched_4(tmp_path): 699 | reader = vtkPolyDataReader() 700 | reader.SetFileName('data/branched4.vtk') 701 | 702 | cube = vtkCubeSource() 703 | cube.SetBounds(-.3283653157, .3283653157, -.5, .5, -.375, .375) 704 | 705 | bf = vtkPolyDataBooleanFilter() 706 | bf.SetInputConnection(0, cube.GetOutputPort()) 707 | bf.SetInputConnection(1, reader.GetOutputPort()) 708 | bf.SetOperModeToNone() 709 | bf.Update() 710 | 711 | write_result(bf, tmp_path) 712 | check_result(bf, [6, 7]) 713 | 714 | def test_branched_5(tmp_path): 715 | reader = vtkPolyDataReader() 716 | reader.SetFileName('data/branched.vtk') 717 | 718 | traA = vtkTransform() 719 | traA.Translate(0, -.5, 0) 720 | 721 | tfA = vtkTransformPolyDataFilter() 722 | tfA.SetInputConnection(reader.GetOutputPort()) 723 | tfA.SetTransform(traA) 724 | 725 | traB = vtkTransform() 726 | traB.Translate(0, .5, 0) 727 | 728 | tfB = vtkTransformPolyDataFilter() 729 | tfB.SetInputConnection(reader.GetOutputPort()) 730 | tfB.SetTransform(traB) 731 | 732 | app = vtkAppendPolyData() 733 | app.AddInputConnection(tfA.GetOutputPort()) 734 | app.AddInputConnection(tfB.GetOutputPort()) 735 | 736 | cube = vtkCubeSource() 737 | cube.SetBounds(-.3283653157, .3283653157, -1, 1, -.375, .375) 738 | 739 | bf = vtkPolyDataBooleanFilter() 740 | bf.SetInputConnection(0, cube.GetOutputPort()) 741 | bf.SetInputConnection(1, app.GetOutputPort()) 742 | bf.SetOperModeToNone() 743 | bf.Update() 744 | 745 | write_result(bf, tmp_path) 746 | check_result(bf, [7, 8]) 747 | 748 | def test_branched_6(tmp_path): 749 | reader = vtkPolyDataReader() 750 | reader.SetFileName('data/branched6.vtk') 751 | 752 | cube = vtkCubeSource() 753 | cube.SetBounds(-.3283653157, .3283653157, -1, 1, -.33371031284, .33371031284) 754 | 755 | bf = vtkPolyDataBooleanFilter() 756 | bf.SetInputConnection(0, cube.GetOutputPort()) 757 | bf.SetInputConnection(1, reader.GetOutputPort()) 758 | bf.SetOperModeToNone() 759 | bf.Update() 760 | 761 | write_result(bf, tmp_path) 762 | check_result(bf, [6, 5]) 763 | 764 | def test_bad_shaped(tmp_path): 765 | cubeA = vtkCubeSource() 766 | cubeA.SetBounds(-2.5, 2.5, 0, 5, 0, 5) 767 | 768 | z = 4.75 769 | 770 | pts = [ 771 | [2.5, -2.5, 0], 772 | [2.5, -2.5, 5], 773 | [0, -2.5, z], 774 | [-2.5, -2.5, 5], 775 | [-2.5, -2.5, 0], 776 | 777 | [-2.5, 2.5, 0], 778 | [-2.5, 2.5, 5], 779 | [0, 2.5, z], 780 | [2.5, 2.5, 5], 781 | [2.5, 2.5, 0], 782 | ] 783 | 784 | polys = [ 785 | [0, 1, 2, 3, 4], 786 | [5, 6, 7, 8, 9], 787 | [9, 8, 1, 0], 788 | [4, 3, 6, 5], 789 | [9, 0, 4, 5], 790 | [1, 8, 7, 6, 3, 2] 791 | ] 792 | 793 | cubeB = create_polydata(pts, polys) 794 | 795 | bf = vtkPolyDataBooleanFilter() 796 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 797 | bf.SetInputConnection(1, cubeB.GetOutputPort()) 798 | bf.SetOperModeToNone() 799 | bf.Update() 800 | 801 | write_result(bf, tmp_path) 802 | check_result(bf, [5, 5]) 803 | 804 | @pytest.mark.xfail 805 | def test_self_intersecting_polys(): 806 | cubeA = vtkCubeSource() 807 | cubeA.SetCenter(0, 0, 1.375) # 1, 1.25, 1.375 808 | 809 | pts = [ 810 | [.5, .5, 0], 811 | [-.5, .5, 0], 812 | [-.5, -.5, 0], 813 | [.5 , -.5, 0], 814 | 815 | [.5, 0, .5], # 4 816 | [0, .5, .5], 817 | [-.5, 0, .5], 818 | [0, -.5, .5], 819 | 820 | [.5, 0, .75], # 8 821 | [0, .5, .75], 822 | [-.5, 0, .75], 823 | [0, -.5, .75], 824 | 825 | [.5, 0, 1], # 12 826 | [.5, .5, 1], 827 | [0, .5, 1], 828 | [-.5, .5, 1], 829 | [-.5, 0, 1], # 16 830 | [-.5, -.5, 1], 831 | [0, -.5, 1], 832 | [.5, -.5, 1] 833 | ] 834 | 835 | polys = [ 836 | [3, 2, 1, 0], 837 | [12, 13, 14, 15, 16, 17, 18, 19], 838 | [3, 19, 18, 11, 7, 11, 18, 17, 2], 839 | [0, 13, 12, 8, 4, 8, 12, 19, 3], 840 | [1, 15, 14, 9, 5, 9, 14, 13, 0], 841 | [2, 17, 16, 10, 6, 10, 16, 15, 1] 842 | ] 843 | 844 | cubeB = create_polydata(pts, polys) 845 | 846 | bf = vtkPolyDataBooleanFilter() 847 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 848 | bf.SetInputConnection(1, cubeB.GetOutputPort()) 849 | bf.SetOperModeToNone() 850 | bf.Update() 851 | 852 | check_result(bf) 853 | 854 | def test_equal_capt_pts(tmp_path): 855 | cyl = vtkCylinderSource() 856 | cyl.SetHeight(2) 857 | cyl.SetResolution(12) 858 | 859 | z = .0000025 860 | # z = .0000075 # verursacht ein anderes bekanntes problem 861 | 862 | tra = vtkTransform() 863 | tra.RotateZ(90) 864 | tra.Translate(0, 0, z) 865 | 866 | tf = vtkTransformPolyDataFilter() 867 | tf.SetTransform(tra) 868 | tf.SetInputConnection(cyl.GetOutputPort()) 869 | 870 | bf = vtkPolyDataBooleanFilter() 871 | bf.SetInputConnection(0, cyl.GetOutputPort()) 872 | bf.SetInputConnection(1, tf.GetOutputPort()) 873 | bf.SetOperModeToNone() 874 | bf.Update() 875 | 876 | write_result(bf, tmp_path) 877 | check_result(bf, [4, 4]) 878 | 879 | def test_equal_capt_pts_2(tmp_path): 880 | reader = vtkPolyDataReader() 881 | reader.SetFileName('data/cross.vtk') 882 | 883 | z = .000001 884 | 885 | tra = vtkTransform() 886 | tra.RotateZ(45) 887 | tra.Translate(0, 0, z) 888 | 889 | tf = vtkTransformPolyDataFilter() 890 | tf.SetTransform(tra) 891 | tf.SetInputConnection(reader.GetOutputPort()) 892 | 893 | bf = vtkPolyDataBooleanFilter() 894 | bf.SetInputConnection(0, reader.GetOutputPort()) 895 | bf.SetInputConnection(1, tf.GetOutputPort()) 896 | bf.SetOperModeToNone() 897 | bf.Update() 898 | 899 | write_result(bf, tmp_path) 900 | check_result(bf, [24, 24]) 901 | 902 | def test_equal_capt_pts_3(tmp_path): 903 | z = .000005 904 | 905 | poly = [ 906 | [0, 0], 907 | [3, 3], 908 | [6, 0], 909 | [9, 3], 910 | [12, 0], 911 | [15, 3], 912 | [18, 0], 913 | [21, 3], 914 | [24, 0], 915 | [24, -8], 916 | [0, -8] 917 | ] 918 | 919 | rack = extrude_polygon(poly, 20) 920 | 921 | cyl = vtkCylinderSource() 922 | cyl.SetHeight(30) 923 | cyl.SetRadius(5) 924 | cyl.SetResolution(12) 925 | cyl.SetOutputPointsPrecision(vtkAlgorithm.DOUBLE_PRECISION) 926 | 927 | tra = vtkTransform() 928 | tra.Translate(12, -2-z, 10) 929 | tra.RotateZ(90) 930 | 931 | tf = vtkTransformPolyDataFilter() 932 | tf.SetTransform(tra) 933 | tf.SetInputConnection(cyl.GetOutputPort()) 934 | 935 | bf = vtkPolyDataBooleanFilter() 936 | bf.SetInputConnection(0, rack.GetOutputPort()) 937 | bf.SetInputConnection(1, tf.GetOutputPort()) 938 | bf.SetOperModeToNone() 939 | bf.Update() 940 | 941 | write_result(bf, tmp_path) 942 | check_result(bf, [6, 6]) 943 | 944 | def test_transform_matrix(tmp_path): 945 | cubeA = vtkCubeSource() 946 | cubeA.SetBounds(-1.5, 1.5, -1.5, 1.5, -1.5, 1.5) 947 | 948 | cubeB = vtkCubeSource() 949 | cubeB.SetBounds(-3, 3, -.5, .5, -.5, .5) 950 | 951 | transform = vtkTransform() 952 | matrix = transform.GetMatrix() 953 | 954 | bf = vtkPolyDataBooleanFilter() 955 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 956 | bf.SetInputConnection(1, cubeB.GetOutputPort()) 957 | bf.SetOperModeToNone() 958 | 959 | bf.SetMatrix(1, matrix) 960 | 961 | for i in range(18): 962 | transform.Identity() 963 | transform.RotateY(i*10) 964 | 965 | matrix = transform.GetMatrix() 966 | 967 | # bf.SetMatrix(1, matrix) 968 | 969 | print(bf.GetMatrix(1)) 970 | 971 | bf.Update() 972 | 973 | path = tmp_path / f'transformed_{i}' 974 | path.mkdir() 975 | 976 | write_result(bf, path) 977 | check_result(bf, [3, 3]) 978 | -------------------------------------------------------------------------------- /testing/test_merger.cxx: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "Merger.h" 25 | 26 | Poly CreateRegularPoly (double r, vtkIdType step, double x, double y, double rotate = 0) { 27 | Poly poly; 28 | 29 | double phi = 2*M_PI/static_cast(step); 30 | 31 | vtkIdType i; 32 | 33 | double x1, y1, x2, y2; 34 | 35 | double alpha = rotate*M_PI/180; 36 | 37 | for (i = 0; i < step; i++) { 38 | double _i = static_cast(i); 39 | 40 | x1 = r*std::cos(_i*phi); 41 | y1 = r*std::sin(_i*phi); 42 | 43 | x2 = std::cos(alpha)*x1-std::sin(alpha)*y1; 44 | y2 = std::sin(alpha)*x1+std::cos(alpha)*y1; 45 | 46 | poly.emplace_back(x2+x, y2+y, 0); 47 | } 48 | 49 | return poly; 50 | } 51 | 52 | bool Test (vtkPolyData *pd, PolysType &polys, vtkIdType numCells, [[maybe_unused]] const char *name) { 53 | 54 | auto cellIds = vtkSmartPointer::New(); 55 | cellIds->SetName("OrigCellIds"); 56 | cellIds->InsertNextValue(0); 57 | pd->GetCellData()->SetScalars(cellIds); 58 | 59 | PStrips pStrips {pd, 0}; 60 | 61 | Base &base = pStrips.base; 62 | 63 | base.ei[0] = 1; base.ei[1] = 0; base.ei[2] = 0; 64 | base.ej[0] = 0; base.ej[1] = 1; base.ej[2] = 0; 65 | base.n[0] = 0; base.n[1] = 0; base.n[2] = 1; 66 | base.d = 0; 67 | 68 | StripsType holes; 69 | 70 | vtkIdType ind {0}; 71 | std::size_t stripId {0}; 72 | 73 | for (auto &poly : polys) { 74 | StripType strip; 75 | 76 | poly.push_back(poly.front()); 77 | 78 | for (const auto &p : poly) { 79 | StripPt sp; 80 | sp.ind = ind; 81 | sp.polyId = 0; 82 | 83 | sp.pt[0] = p.x; 84 | sp.pt[1] = p.y; 85 | sp.pt[2] = p.z; 86 | 87 | pStrips.pts.emplace(ind, std::move(sp)); 88 | 89 | strip.emplace_back(ind, stripId); 90 | 91 | ind++; 92 | } 93 | 94 | holes.push_back(strip); 95 | 96 | stripId++; 97 | } 98 | 99 | IdsType descIds {0}; 100 | 101 | try { 102 | Merger(pd, pStrips, holes, descIds, 0).Run(); 103 | } catch (const std::runtime_error &e) { 104 | return false; 105 | } 106 | 107 | // dafür ist der Merger nicht zuständig 108 | pd->RemoveDeletedCells(); 109 | 110 | { 111 | // schneiden sich die einzelnen polygone selbst? 112 | 113 | vtkIdType i, num; 114 | const vtkIdType *cell; 115 | 116 | double pt[3]; 117 | 118 | auto cellItr = vtk::TakeSmartPointer(pd->GetPolys()->NewIterator()); 119 | 120 | for (cellItr->GoToFirstCell(); !cellItr->IsDoneWithTraversal(); cellItr->GoToNextCell()) { 121 | cellItr->GetCurrentCell(num, cell); 122 | 123 | std::set poly; 124 | 125 | for (i = 0; i < num; i++) { 126 | pd->GetPoint(cell[i], pt); 127 | if (!poly.emplace(pt[0], pt[1], pt[2]).second) { 128 | // schlägt fehl, wenn bereits vorhanden 129 | return false; 130 | } 131 | } 132 | 133 | } 134 | } 135 | 136 | #ifdef DEBUG 137 | WriteVTK(name, pd); 138 | #endif 139 | 140 | if (pd->GetNumberOfCells() != numCells) { 141 | return false; 142 | } 143 | 144 | return true; 145 | } 146 | 147 | int main() { 148 | auto pdA = CreatePolyData({ CreateRegularPoly(8, 18, 0, 0) }); 149 | 150 | PolysType polysA { 151 | CreateRegularPoly(.25, 6, 3.5, 0), 152 | CreateRegularPoly(.25, 6, -3.5, 0), 153 | CreateRegularPoly(.5, 6, 1, 0), 154 | CreateRegularPoly(.5, 6, -1, 0), 155 | CreateRegularPoly(.5, 6, 0, 1, 30), 156 | CreateRegularPoly(.5, 6, 0, -1, 30) 157 | }; 158 | 159 | if (!Test(pdA, polysA, 10, "polysA")) { 160 | return EXIT_FAILURE; 161 | } 162 | 163 | auto pdB = CreatePolyData({ { {5, 5, 0}, {-5, 5, 0}, {-5, -5, 0}, {5, -5, 0} } }); 164 | 165 | Poly polyA = CreateRegularPoly(4, 18, 0, 0, 10); 166 | Poly polyB = CreateRegularPoly(3, 18, 0, 0, 10); 167 | 168 | std::copy(polyB.rbegin(), polyB.rend(), std::back_inserter(polyA)); 169 | 170 | PolysType polysB { 171 | CreateRegularPoly(2, 18, 0, 0, 5), 172 | polyA 173 | }; 174 | 175 | if (!Test(pdB, polysB, 5, "polysB")) { 176 | return EXIT_FAILURE; 177 | } 178 | 179 | const double y = 7.5-1.5/std::cos(M_PI/6)-std::tan(M_PI/6)*4.5; 180 | 181 | auto pdC = CreatePolyData({ { {0, 0, 0}, {25, 0, 0}, {25, 25, 0}, {0, 25, 0} } }); 182 | 183 | PolysType polysC { 184 | { {2, 5, 0}, {17, 5, 0}, {17, 20, 0}, {2, 20, 0}, {7.5/std::tan(M_PI/6)+2, 12.5, 0} }, 185 | { {6.5, 12.5-y, 0}, {y/std::tan(M_PI/6)+6.5, 12.5, 0}, {6.5, 12.5+y, 0} } 186 | }; 187 | 188 | if (!Test(pdC, polysC, 5, "polysC")) { 189 | return EXIT_FAILURE; 190 | } 191 | 192 | return EXIT_SUCCESS; 193 | } 194 | -------------------------------------------------------------------------------- /testing/test_python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # *-* coding: UTF-8 *-* 3 | 4 | # Copyright 2012-2025 Ronald Römer 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import sys 19 | sys.path.extend(['/home/zippy/vtkbool/build/lib/python3.13/site-packages/vtkbool']) 20 | 21 | from vtkmodules.vtkFiltersSources import vtkCubeSource 22 | from vtkmodules.vtkIOLegacy import vtkPolyDataWriter 23 | 24 | from vtkbool import vtkPolyDataBooleanFilter 25 | 26 | cubeA = vtkCubeSource() 27 | cubeB = vtkCubeSource() 28 | 29 | bf = vtkPolyDataBooleanFilter() 30 | bf.SetInputConnection(0, cubeA.GetOutputPort()) 31 | bf.SetInputConnection(1, cubeB.GetOutputPort()) 32 | bf.Update() 33 | -------------------------------------------------------------------------------- /vtkPolyDataBooleanFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012-2025 Ronald Römer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __vtkPolyDataBooleanFilter_h 18 | #define __vtkPolyDataBooleanFilter_h 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "Contact.h" 35 | 36 | #include "Utilities.h" 37 | 38 | enum OperMode { 39 | OPER_NONE = 0, 40 | OPER_UNION, 41 | OPER_INTERSECTION, 42 | OPER_DIFFERENCE, 43 | OPER_DIFFERENCE2 44 | }; 45 | 46 | enum class Capt { 47 | Not = 1 << 0, 48 | Edge = 1 << 1, 49 | A = 1 << 2, 50 | B = 1 << 3, 51 | Branched = 1 << 4, 52 | Boundary = 0xe 53 | }; 54 | 55 | // inline std::underlying_type_t operator| (Capt lhs, Capt rhs) { 56 | // return static_cast>(lhs) | 57 | // static_cast>(rhs); 58 | // } 59 | 60 | inline std::underlying_type_t operator& (Capt lhs, Capt rhs) { 61 | return static_cast>(lhs) & 62 | static_cast>(rhs); 63 | } 64 | 65 | enum class Side { 66 | None, 67 | Start, 68 | End 69 | }; 70 | 71 | enum class Loc { 72 | None, 73 | Inside, 74 | Outside 75 | }; 76 | 77 | class StripPt { 78 | public: 79 | StripPt () : t(0), capt(Capt::Not), catched(true) { 80 | edge[0] = NOTSET; 81 | edge[1] = NOTSET; 82 | } 83 | 84 | double t; 85 | Capt capt; 86 | double captPt[3]; 87 | 88 | vtkIdType ind, edge[2]; 89 | 90 | double pt[3]; 91 | double cutPt[3]; 92 | 93 | friend std::ostream& operator<< (std::ostream &out, const StripPt &s) { 94 | out << "ind " << s.ind 95 | << ", edge [" << s.edge[0] << ", " << s.edge[1] << "]" 96 | << ", t " << s.t 97 | << ", capt " << s.capt 98 | << ", polyId " << s.polyId; 99 | return out; 100 | } 101 | 102 | vtkIdType polyId; 103 | 104 | bool catched; 105 | }; 106 | 107 | class StripPtR { 108 | public: 109 | StripPtR () = delete; 110 | 111 | StripPtR (vtkIdType ind, std::size_t strip) : ind(ind), strip(strip), ref(NOTSET), side(Side::None) { 112 | desc[0] = NOTSET; 113 | desc[1] = NOTSET; 114 | } 115 | 116 | vtkIdType ind; 117 | std::size_t strip; 118 | vtkIdType ref, desc[2]; 119 | Side side; 120 | 121 | friend std::ostream& operator<< (std::ostream &out, const StripPtR &s) { 122 | out << "ind " << s.ind 123 | << ", desc [" << s.desc[0] << ", " << s.desc[1] << "]" 124 | << ", strip " << s.strip 125 | << ", side " << s.side 126 | << ", ref " << s.ref; 127 | return out; 128 | } 129 | }; 130 | 131 | typedef std::map StripPtsType; 132 | typedef std::deque StripType; 133 | typedef std::vector StripsType; 134 | 135 | typedef std::vector> _StripsType; 136 | 137 | class PStrips { 138 | public: 139 | PStrips (vtkPolyData *pd, vtkIdType cellId) { 140 | const vtkIdType *cell; 141 | vtkIdType numPts; 142 | 143 | pd->GetCellPoints(cellId, numPts, cell); 144 | 145 | for (vtkIdType i = 0; i < numPts; i++) { 146 | poly.push_back(cell[i]); 147 | } 148 | 149 | ComputeNormal(pd->GetPoints(), n, numPts, cell); 150 | 151 | base = Base(pd->GetPoints(), numPts, cell); 152 | } 153 | 154 | StripPtsType pts; 155 | StripsType strips; 156 | 157 | double n[3]; 158 | IdsType poly; 159 | Base base; 160 | }; 161 | 162 | typedef std::map PolyStripsType; 163 | 164 | typedef std::vector> RefsType; 165 | typedef std::vector> ConstRefsType; 166 | 167 | class VTK_EXPORT vtkPolyDataBooleanFilter : public vtkPolyDataAlgorithm { 168 | vtkPolyData *resultA, *resultB, *resultC; 169 | 170 | vtkSmartPointer modPdA, modPdB, contLines; 171 | 172 | vtkSmartPointer cellDataA, cellDataB; 173 | vtkSmartPointer cellIdsA, cellIdsB; 174 | 175 | vtkIdTypeArray *contsA, *contsB; 176 | 177 | vtkMTimeType timePdA, timePdB; 178 | 179 | PolyStripsType polyStripsA, polyStripsB; 180 | 181 | void GetStripPoints (vtkPolyData *pd, vtkIdTypeArray *sources, PStrips &pStrips, IdsType &lines); 182 | bool GetPolyStrips (vtkPolyData *pd, vtkIdTypeArray *conts, vtkIdTypeArray *sources, PolyStripsType &polyStrips); 183 | bool CleanStrips (); 184 | void RemoveDuplicates (IdsType &lines); 185 | void CompleteStrips (PStrips &pStrips); 186 | bool HasArea (const StripType &strip) const; 187 | bool CutCells (vtkPolyData *pd, PolyStripsType &polyStrips); 188 | void RestoreOrigPoints (vtkPolyData *pd, PolyStripsType &polyStrips); 189 | void DisjoinPolys (vtkPolyData *pd, PolyStripsType &polyStrips); 190 | void ResolveOverlaps (vtkPolyData *pd, PolyStripsType &polyStrips); 191 | void AddAdjacentPoints (vtkPolyData *pd, vtkIdTypeArray *conts, PolyStripsType &polyStrips); 192 | void MergePoints (vtkPolyData *pd, PolyStripsType &polyStrips); 193 | bool CombineRegions (); 194 | 195 | int OperMode; 196 | 197 | vtkMatrix4x4 *matrices[2]; 198 | vtkLinearTransform *transforms[2]; 199 | 200 | vtkSmartPointer cleanA, cleanB; 201 | 202 | std::shared_ptr contact; 203 | 204 | vtkMTimeType timeMatrixA, timeMatrixB; 205 | 206 | public: 207 | vtkTypeMacro(vtkPolyDataBooleanFilter, vtkPolyDataAlgorithm); 208 | static vtkPolyDataBooleanFilter* New (); 209 | 210 | vtkSetClampMacro(OperMode, int, OPER_NONE, OPER_DIFFERENCE2); 211 | vtkGetMacro(OperMode, int); 212 | 213 | void SetOperModeToNone () { OperMode = OPER_NONE; Modified(); } 214 | void SetOperModeToUnion () { OperMode = OPER_UNION; Modified(); } 215 | void SetOperModeToIntersection () { OperMode = OPER_INTERSECTION; Modified(); } 216 | void SetOperModeToDifference () { OperMode = OPER_DIFFERENCE; Modified(); } 217 | void SetOperModeToDifference2 () { OperMode = OPER_DIFFERENCE2; Modified(); } 218 | 219 | void SetMatrix (int i, vtkMatrix4x4 *matrix); 220 | vtkMatrix4x4* GetMatrix (int i); 221 | 222 | vtkMTimeType GetMTime () override; 223 | 224 | protected: 225 | vtkPolyDataBooleanFilter (); 226 | ~vtkPolyDataBooleanFilter (); 227 | 228 | int RequestData (vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) override; 229 | 230 | void PrintSelf (ostream&, vtkIndent) override {}; 231 | 232 | private: 233 | vtkPolyDataBooleanFilter (const vtkPolyDataBooleanFilter&) = delete; 234 | void operator= (const vtkPolyDataBooleanFilter&) = delete; 235 | 236 | }; 237 | 238 | #endif 239 | --------------------------------------------------------------------------------