diff --git a/.editorconfig b/.editorconfig index c300dcab..42a846ad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -24,4 +24,4 @@ indent_style = tab indent_size = 4 [LICENSE] -insert_final_newline = false +insert_final_newline = false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4ddd20fc..3b619832 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,14 @@ -# Byte-compiled / optimized / DLL files +# Python bytecode and cache __pycache__/ +**/__pycache__/ *.py[cod] *$py.class -# C extensions -*.so - -# Distribution / packaging +# Distribution and packaging .Python -env/ build/ +_build/ +**/build/ develop-eggs/ dist/ downloads/ @@ -20,22 +19,62 @@ lib64/ parts/ sdist/ var/ -#wheels/ *.egg-info/ .installed.cfg *.egg -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# External dependencies +ext/** +external/ +**/external/ + +# IDE and editor settings +.vscode/ +.vs/ +.idea/ +*.suo +*.user +*.sln +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +ipch/ +.spyderproject +.spyproject +.ropeproject +scripts/.idea/** + +# Compiled files and binaries +*.pyd +*.so +*.dll +*.exe +*.obj +*.pdb +*.ilk +*.exp +*.lib +*.lprof *.manifest *.spec -# Installer logs -pip-log.txt -pip-delete-this-directory.txt +# CMake generated +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +compile_commands.json +CTestTestfile.cmake +Testing/ +_deps/ + +# Virtual environments +.env +.venv +venv/ +ENV/ +.python-version -# Unit test / coverage reports +# Testing and coverage htmlcov/ .tox/ .coverage @@ -45,90 +84,41 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation +# Documentation docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation /site +generated/ +docs/api/** +!docs/api/*.rst +!docs/api/*.py -# mypy -.mypy_cache/ - -# ============================================================================== -# COMPAS CGAL -# ============================================================================== - +# COMPAS CGAL specific *.3dmbak +*.3dm *.rhl *.rui_bak -*.so - -temp - -.DS_Store - -.vscode/** -.idea/** - -ext/** - -dist/** - -scripts/.idea/** - -generated - -*.3dm - +temp/ recipe/** !recipe/sha256.py -.pytest_cache -.ruff_cache -.vscode +# Logs and temporary files +*.log +pip-log.txt +pip-delete-this-directory.txt +local_settings.py +celerybeat-schedule + +# Other +*.mo +*.pot +*.sage.py +instance/ +.webassets-cache +.scrapy +.DS_Store +Thumbs.db +.ipynb_checkpoints \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b192d6be..397cc1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +## [1.0.1] 2025-03-06 + +### Added + +### Changed + +* Nanobind integration. + +### Removed + +* Files related to pybind11. ## [0.7.2] 2024-10-29 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..84e0e614 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,210 @@ +cmake_minimum_required(VERSION 3.15...3.26) + +project(cgal_wrapper LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# ===================================================================== +# Dependencies +# ===================================================================== +include(ExternalProject) + +# Define source directories for external dependencies +set(EXTERNAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external") +set(CGAL_SOURCE_DIR "${EXTERNAL_DIR}/cgal") +set(BOOST_SOURCE_DIR "${EXTERNAL_DIR}/boost") +set(EIGEN_SOURCE_DIR "${EXTERNAL_DIR}/eigen") + +# Create a custom target for all external dependencies +add_custom_target(external_downloads ALL) + +# ======================================================================== +# CGAL +# ======================================================================== +if(NOT EXISTS "${CGAL_SOURCE_DIR}") + message(STATUS "Downloading CGAL...") + ExternalProject_Add( + cgal_download + URL https://github.com/CGAL/cgal/releases/download/v6.0.1/CGAL-6.0.1.zip + SOURCE_DIR "${CGAL_SOURCE_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + UPDATE_COMMAND "" + PATCH_COMMAND "" + TLS_VERIFY ON + ) + add_dependencies(external_downloads cgal_download) +endif() + +# ======================================================================== +# BOOST +# ======================================================================== +if(NOT EXISTS "${BOOST_SOURCE_DIR}") + message(STATUS "Configuring Boost...") + ExternalProject_Add(boost_download + URL https://archives.boost.io/release/1.82.0/source/boost_1_82_0.tar.gz + SOURCE_DIR "${BOOST_SOURCE_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory /boost ${BOOST_SOURCE_DIR}/boost + LOG_DOWNLOAD + ON + ) + + add_dependencies(external_downloads boost_download) +endif() + +# ======================================================================== +# EIGEN +# ======================================================================== +if(NOT EXISTS "${EIGEN_SOURCE_DIR}") + message(STATUS "Downloading Eigen...") + ExternalProject_Add( + eigen_download + URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz + SOURCE_DIR "${EIGEN_SOURCE_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + UPDATE_COMMAND "" + PATCH_COMMAND "" + ) + add_dependencies(external_downloads eigen_download) +endif() + +# Add include directories +set(CGAL_INCLUDE_DIR "${CGAL_SOURCE_DIR}/include") +set(BOOST_INCLUDE_DIR "${BOOST_SOURCE_DIR}") +set(EIGEN_INCLUDE_DIR "${EIGEN_SOURCE_DIR}") + +# Print include directories for verification +message(STATUS "====================================================") +message(STATUS "CGAL INCLUDE DIRECTORY: ${CGAL_INCLUDE_DIR}") +message(STATUS "BOOST INCLUDE DIRECTORY: ${BOOST_INCLUDE_DIR}") +message(STATUS "EIGEN INCLUDE DIRECTORY: ${EIGEN_INCLUDE_DIR}") +message(STATUS "====================================================") + +include_directories( + ${CGAL_INCLUDE_DIR} + ${BOOST_INCLUDE_DIR} + ${EIGEN_INCLUDE_DIR} +) + +# Incase linking is not working check the includes +# add_compile_options(/showIncludes) + +# Define libraries as header only +add_definitions( + -DCGAL_HEADER_ONLY + -DBOOST_ALL_NO_LIB + -DBOOST_ALL_DYN_LINK=0 + -DCGAL_DISABLE_GMP + -DCGAL_USE_BOOST_MP +) + +# ===================================================================== +# BOOST and CGAL flags to configure multiprecision +# ===================================================================== +set(CGAL_DO_NOT_USE_MPZF ON CACHE BOOL "Do not use MPZF") +set(CGAL_USE_GMPXX OFF CACHE BOOL "Disable GMPXX") +set(CGAL_DISABLE_GMP ON CACHE BOOL "Disable GMP in CGAL") +set(CMAKE_DISABLE_FIND_PACKAGE_GMP ON CACHE BOOL "Disable CMake find package for GMP") +set(CMAKE_DISABLE_FIND_PACKAGE_MPFR ON CACHE BOOL "Disable CMake find package for MPFR") +set(CGAL_NT_USE_BOOST_MP ON CACHE BOOL "Use Boost Multiprecision") + +# Eigen is header-only by default, no special defines needed + +# ===================================================================== +# Binding +# ===================================================================== + +if (NOT SKBUILD) + message(WARNING "\ + This CMake file is meant to be executed using 'scikit-build'. Running + it directly will almost certainly not produce the desired result. If + you are a user trying to install this package, please use the command + below, which will install all necessary build dependencies, compile + the package in an isolated environment, and then install it. + ===================================================================== + $ pip install . + ===================================================================== + If you are a software developer, and this is your own package, then + it is usually much more efficient to install the build dependencies + in your environment once and use the following command that avoids + a costly creation of a new virtual environment at every compilation: + ===================================================================== + $ pip install nanobind scikit-build-core[pyproject] + $ conda install cmake + $ pip install --no-build-isolation -ve . + $ pip install --no-build-isolation -ve . -Ceditable.rebuild=true + ===================================================================== + You may optionally add -Ceditable.rebuild=true to auto-rebuild when + the package is imported. Otherwise, you need to re-run the above + after editing C++ files.") +endif() + +# Try to import all Python components potentially needed by nanobind +find_package(Python 3.8 + REQUIRED COMPONENTS Interpreter Development.Module + OPTIONAL_COMPONENTS Development.SABIModule) + +# Import nanobind through CMake's find_package mechanism +find_package(nanobind CONFIG REQUIRED) + +# We are now ready to compile the actual extension module + +# Automatically include all C++ source files from the main src directory +# Note: Any new source files must follow nanobind type conversion guidelines +# for proper Python/C++ type handling, especially for: +# - Eigen::Ref for matrix passing +# - std::tuple return types +# - numpy array handling (float64, int32) +# - row-major order enforcement +# See: https://github.com/wjakob/nanobind/tree/master/tests +# +# Important: If you add a new .cpp file, you must rebuild the project with: +# pip install --no-build-isolation -ve . -Ceditable.rebuild=true +file(GLOB CPP_SOURCES + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" +) + +# Verify we found all expected source files and remind about type handling +message(STATUS "Found C++ source files (ensure proper type conversion in each):") +foreach(source ${CPP_SOURCES}) + message(STATUS " ${source}") +endforeach() + +nanobind_add_module( + compas_cgal_ext + STABLE_ABI + NB_STATIC + ${CPP_SOURCES} +) + +# Set include directories for the extension +target_include_directories(compas_cgal_ext PRIVATE + ${CGAL_INCLUDE_DIR} + ${BOOST_INCLUDE_DIR} + ${EIGEN_INCLUDE_DIR} +) + +# Make sure external dependencies are downloaded before building the extension +add_dependencies(compas_cgal_ext external_downloads) + +# Enhanced PCH configuration +set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON) +set(CMAKE_PCH_WARN_INVALID ON) + +# Configure PCH for the extension +target_precompile_headers(compas_cgal_ext + PRIVATE + src/compas.h +) + +# Install directive for scikit-build-core +install(TARGETS compas_cgal_ext LIBRARY DESTINATION compas_cgal) \ No newline at end of file diff --git a/LICENSE b/LICENSE index 0a041280..153d416d 100644 --- a/LICENSE +++ b/LICENSE @@ -162,4 +162,4 @@ General Public License ever published by the Free Software Foundation. whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the -Library. +Library. \ No newline at end of file diff --git a/README.md b/README.md index 022f6c96..0ab124cb 100644 --- a/README.md +++ b/README.md @@ -6,77 +6,11 @@ [![Conda - Platform](https://img.shields.io/conda/pn/conda-forge/compas_cgal)](https://anaconda.org/conda-forge/compas_cgal) This package provides Python bindings for specific algorithms of CGAL. -The bindings are generated with PyBind11 and data is exchanged using NumPy arrays. +The bindings are generated with Nanobind and data is exchanged using NumPy arrays. -More information is available in the docs: +More information of installation, api and other details please follow documentation: -## Installation - -`compas_cgal` is available via `conda-forge` for Windows, OSX, and Linux, -and can be installed using `conda`. - -```bash -conda create -n cgal -c conda-forge compas compas_cgal --yes -``` - -## Dev Install - -Create a development environment with the required dependencies using `conda` -and compile and install an editable version of `compas_cgal` using `setuptools`. - -**Windows**: - -```bash -conda create -n cgal-dev python=3.10 mpir mpfr boost-cpp eigen=3.3 cgal-cpp pybind11 scip=9.0.0 --yes -conda activate cgal-dev -git clone https://github.com/compas-dev/compas_cgal -cd compas_cgal -pip install -e .'[dev]' -``` - -**Mac**: - -```bash -conda create -n cgal-dev python=3.10 gmp mpfr boost-cpp eigen=3.3 cgal-cpp pybind11 scip=9.1 --yes -conda activate cgal-dev -git clone https://github.com/compas-dev/compas_cgal -cd compas_cgal -pip install -e .'[dev]' -``` - -> Note that the version of eigen is important and should be `3.3`. - -To add a new c++ module to the Python wrapper, or to exclude some of the existing modules during development -you can modify the list of extension modules in `setup.py`. - -```python -ext_modules = [ - Extension( - 'compas_cgal._cgal', - sorted([ - 'src/compas_cgal.cpp', - 'src/compas.cpp', - 'src/meshing.cpp', - 'src/booleans.cpp', - 'src/slicer.cpp', - 'src/intersections.cpp', - 'src/measure.cpp', - ]), - include_dirs=[ - './include', - get_eigen_include(), - get_pybind_include() - ], - library_dirs=[ - get_library_dirs(), - ], - libraries=['mpfr', 'gmp'], - language='c++' - ), -] -``` - ## Usage The provided functionality can be used directly from the `compas_cgal` package diff --git a/data/PLACEHOLDER b/data/PLACEHOLDER deleted file mode 100644 index b7a57098..00000000 --- a/data/PLACEHOLDER +++ /dev/null @@ -1 +0,0 @@ -# container for sample data files diff --git a/data/elephant.off b/data/elephant.off index 0f158b33..6c1572b0 100644 --- a/data/elephant.off +++ b/data/elephant.off @@ -1,8337 +1,1889 @@ -OFF -2775 5558 0 - -0.262933 0.102269 0.138247 -0.0843142 0.0418575 -0.0419302 -0.0676609 -0.0308717 0.133371 -0.202895 0.468475 0.0802072 -0.113075 -0.465378 -0.0546734 -0.225577 -0.277149 -0.193776 --0.146525 -0.22403 -0.169286 --0.0249865 -0.129931 -0.13238 --0.160965 -0.480027 -0.0707824 -0.0794063 -0.415277 -0.0839096 -0.0469116 -0.050008 0.252355 --0.0998372 -0.193745 -0.16268 --0.111131 -0.104825 -0.14166 --0.061459 -0.0977343 -0.133353 --0.247783 -0.131539 0.0722694 --0.145972 -0.486627 -0.0633275 -0.0916759 0.0333604 -0.133002 --0.184954 -0.325468 -0.133853 -0.0847778 -0.432659 -0.0978527 --0.210776 -0.299151 0.0751516 --0.151539 -0.388715 0.01282 --0.154564 -0.438244 -0.0249412 --0.123411 -0.0182656 0.0135824 -0.00773278 -0.053391 -0.0428874 --0.0774524 -0.292395 -0.106335 -0.0201594 -0.263586 0.119859 --0.105646 -0.0365155 -0.0770751 --0.113391 -0.41896 -0.0906771 --0.128213 -0.46259 -0.0994907 --0.160526 -0.468057 -0.0393445 -0.197609 -0.127492 -0.0574767 -0.0757306 -0.449539 -0.0614557 -0.147148 -0.412461 -0.0801639 -0.103782 -0.333007 0.0288926 -0.108917 -0.401981 -0.00841926 -0.11392 -0.408931 -0.0973654 -0.129733 -0.31331 -0.0672325 -0.0904388 -0.356531 -0.0803906 -0.0359798 -0.255445 -0.113562 --0.22312 -0.107007 -0.0611904 --0.163408 -0.433458 -0.0704785 --0.0915185 -0.473885 -0.0165533 --0.141417 -0.474418 -0.0834065 --0.0944539 -0.479454 -0.0663683 --0.085985 -0.101282 0.158093 --0.0225438 -0.0889567 -0.0848587 -0.092707 -0.109496 -0.114561 --0.159213 -0.159272 -0.138793 --0.24541 -0.230248 -0.110578 -0.183744 -0.231931 0.0176086 -0.294273 -0.0955008 0.116328 --0.16477 -0.385872 -0.0413331 --0.185303 -0.313456 -0.00374653 --0.297084 -0.262835 -0.00415653 -0.167756 -0.128776 0.197921 --0.0273811 0.0608812 -0.0989637 -0.208303 0.00452459 -0.0746544 --0.150831 -0.448909 -0.0901866 --0.0720206 -0.147118 -0.154796 --0.0197986 -0.210658 -0.154154 --0.130593 -0.498558 -0.021161 -0.129662 -0.373473 -0.0794411 --0.120692 -0.481168 -0.0685411 --0.108692 -0.494274 -0.0429275 --0.0970902 -0.252938 -0.14843 --0.0098054 -0.335319 -0.0147292 -0.0769246 -0.410346 -0.0391239 --0.132409 -0.464154 -0.0197707 --0.102806 -0.4217 0.00855496 --0.0760332 -0.34128 0.100617 --0.104435 -0.377391 0.0553722 --0.14788 -0.33188 0.0922673 --0.151409 -0.218926 0.165812 --0.111518 -0.286925 0.148566 --0.0458796 -0.210094 0.18627 -0.0439919 -0.147905 0.137964 --0.0358575 -0.361749 0.0457868 -0.0284346 -0.311464 0.0537885 -0.0409609 -0.0869988 -0.085353 -0.118462 -0.0465078 -0.0483438 --0.141108 -0.422593 -0.0846889 --0.129145 -0.379198 -0.0712284 --0.149426 -0.329507 -0.0473837 --0.212723 -0.300322 -0.0706413 --0.156151 -0.403775 -0.0651516 --0.077704 -0.400019 -0.0437424 --0.0783601 -0.342543 -0.0687412 --0.0202922 -0.309739 -0.0758353 -0.0501325 -0.290557 -0.0430839 -0.0954448 -0.256567 0.0733017 -0.144565 -0.158299 0.0913557 -0.0562207 -0.179498 -0.140905 -0.16863 -0.175124 -0.176771 -0.121523 -0.241503 -0.138152 -0.196749 0.136407 0.00365942 -0.14057 0.277481 0.154966 -0.156168 -0.385879 -0.0324162 --0.146564 -0.288069 -0.168907 --0.212533 -0.256019 -0.170893 -0.0775241 -0.353084 -0.0221894 --0.161054 -0.48311 -0.0516067 --0.151386 -0.488726 -0.0297486 --0.167299 -0.464731 -0.058517 --0.167494 -0.44542 -0.0440266 --0.166175 -0.416312 -0.0405929 --0.155665 -0.409399 -0.0112037 --0.130777 -0.428881 -0.00549876 --0.162855 -0.460869 -0.0783452 --0.0949104 -0.0628065 -0.118829 --0.171364 -0.0681205 -0.104606 --0.189411 -0.0391257 -0.0301772 --0.201329 -0.0450332 0.0672375 --0.0621659 -0.0595357 -0.0819357 --0.0724451 -0.0386843 -0.0212262 --0.0317016 -0.00827391 0.0812953 -0.125617 -0.445138 -0.086335 -0.16914 -0.45638 -0.0514974 --0.188699 -0.102786 0.151505 --0.127982 -0.406882 0.0143939 --0.130706 -0.384 0.0373578 --0.17253 -0.34721 0.0442322 --0.136499 -0.357829 0.0655808 --0.101729 -0.398953 0.0314689 --0.06477 -0.393705 0.00175031 --0.0683606 -0.382507 0.0422398 --0.0619729 -0.361557 0.0725343 --0.0129886 -0.329893 0.099078 --0.0485077 -0.306718 0.14972 --0.0586671 -0.359537 -0.0297503 --0.105132 -0.354471 0.0834642 --0.111953 -0.324339 0.110194 --0.146952 -0.298663 0.122985 --0.147935 -0.259899 0.146585 --0.224685 -0.230598 0.12386 --0.194785 -0.274899 0.125615 --0.1144 -0.250007 0.185149 --0.10665 -0.185071 0.204627 --0.155805 -0.154327 0.174517 --0.215755 -0.165279 0.142947 --0.0743299 -0.277016 0.17853 --0.0218198 -0.257755 0.16445 -0.000800113 -0.199671 0.138749 --0.0254547 -0.133829 0.140633 -0.0635315 -0.20852 0.111271 --0.0256804 -0.0569419 0.140514 --0.0948894 -0.0414189 0.110395 --0.0705488 -0.0374163 0.0415111 --0.00760713 -0.0195458 0.0164934 -0.0499878 0.0289775 0.0673286 -0.140466 0.0718009 0.144428 --0.00188983 0.0792593 -0.003093 -0.0559872 -0.0140882 -0.011506 --0.227407 -0.0888195 0.0117762 --0.273528 -0.167121 -0.0102922 --0.0402782 -0.263586 -0.140793 --0.137564 -0.296782 -0.109847 --0.166109 -0.381665 -0.0120175 --0.16983 -0.360755 0.0137933 --0.16456 -0.354211 -0.0308766 --0.185156 -0.334386 0.0190529 --0.207978 -0.298552 0.0310885 --0.252507 -0.243462 0.0511336 --0.223388 -0.268692 -0.019703 --0.193938 -0.322649 0.048456 --0.175095 -0.332532 0.0736524 --0.176111 -0.310734 0.103586 --0.147337 -0.494175 -0.0467278 --0.129359 -0.490613 -0.0537016 --0.148829 -0.364106 -0.0543963 --0.120087 -0.343197 -0.0662243 --0.130345 -0.305692 -0.0722136 --0.171529 -0.299424 -0.0833938 --0.181343 -0.292949 -0.0416997 -0.207831 -0.217496 -0.0966998 -0.180453 0.174523 0.151623 -0.0997558 -0.44202 -0.0211251 -0.141829 -0.427452 -0.0246256 -0.231902 -0.0518172 0.0262484 -0.222165 -0.00765217 0.131632 -0.257605 0.0468554 0.0496674 -0.134457 -0.362329 0.00455646 -0.162364 -0.298449 0.0119315 -0.167632 -0.334853 -0.0258239 -0.166781 -0.258058 -0.0451734 -0.196618 -0.203737 -0.0367216 -0.204966 -0.148702 0.029823 -0.188775 -0.0899398 0.0842549 -0.118735 -0.0897053 0.120666 -0.108518 -0.0623755 0.19827 -0.0943505 -0.134977 0.262277 -0.0987744 0.0186293 0.19077 -0.164162 -0.016078 0.17849 -0.217401 -0.0732307 0.174029 -0.244384 -0.159022 0.158735 -0.0938974 -0.414078 -0.0986619 -0.0882094 -0.386204 -0.0874415 -0.110237 -0.433985 -0.106491 -0.0752909 -0.377302 -0.0545033 -0.136893 -0.424401 -0.103861 -0.101623 -0.446793 -0.0879738 -0.0803589 -0.447974 -0.0828166 -0.0955794 -0.458697 -0.0690652 -0.0852254 -0.472952 -0.043714 -0.0423481 -0.131534 -0.117698 -0.0102534 -0.163639 -0.135844 --0.0294235 -0.16777 -0.154302 --0.0581903 -0.19678 -0.162941 --0.0495263 -0.23206 -0.157295 -0.0213258 -0.204668 -0.142847 -0.0591534 -0.221375 -0.128414 -0.094939 -0.204956 -0.158559 -0.102368 -0.149902 -0.152017 -0.161838 -0.127189 -0.119413 -0.0883157 -0.255475 -0.0965547 -0.00337956 -0.243572 -0.138992 --0.00441584 -0.28038 -0.109802 -0.139454 -0.244447 -0.0884262 -0.17799 -0.253819 -0.13106 -0.239512 -0.249475 -0.139746 -0.239769 -0.199663 -0.201225 -0.139899 -0.220537 -0.184787 -0.188104 -0.214287 -0.208834 -0.241446 -0.186766 -0.139849 -0.209446 -0.161297 -0.101029 --0.199048 -0.317265 -0.100601 --0.293252 -0.31357 -0.122798 --0.243299 -0.355691 -0.130502 --0.247594 -0.319033 -0.0988683 --0.291668 -0.287409 -0.0637038 --0.248832 -0.272185 -0.0865389 --0.259174 -0.220646 -0.0469428 --0.282126 -0.263379 -0.124759 --0.269731 -0.294935 -0.18724 --0.247297 -0.165961 -0.0866554 -0.0601914 -0.0464014 -0.0549033 --0.211085 -0.18579 -0.128927 --0.200721 -0.132547 -0.108259 --0.0634953 -0.15489 0.179525 -0.153912 -0.0887253 -0.0733484 -0.185145 -0.0471144 -0.0260532 -0.211443 0.0144377 0.0032337 -0.139575 0.0029693 0.000737671 -0.166751 0.0710484 -0.0323169 -0.107842 0.107181 0.0410238 -0.241648 0.0800111 -0.0150955 --0.140262 -0.4992 -0.033581 --0.123763 -0.497978 -0.0364981 --0.108133 -0.495253 -0.0202341 --0.1219 -0.481038 -0.0158085 --0.110456 -0.461331 -0.014079 --0.087635 -0.441699 -0.0445804 --0.0916102 -0.451765 -0.0239283 --0.0811529 -0.421697 -0.020421 --0.0723115 -0.470105 -0.0423155 --0.102577 -0.44033 -0.0062072 --0.139118 -0.480546 -0.0199347 --0.149266 -0.466768 -0.0254061 -0.222317 -0.0925366 -0.0161067 --0.0417433 -0.361447 0.00632817 --0.00965295 -0.347119 0.0218202 -0.0216724 -0.318231 0.010836 --0.00474667 -0.341486 0.0613973 -0.0698488 -0.285166 0.0205835 --0.108371 -0.29389 -0.0941984 --0.11094 -0.279255 -0.127432 --0.0852691 -0.31423 -0.083395 --0.0516331 -0.309713 -0.0908278 --0.0463843 -0.329032 -0.0615785 --0.13427 -0.295211 -0.140976 --0.16029 -0.312528 -0.151535 --0.208046 -0.313472 -0.188061 --0.177638 -0.29982 -0.178676 --0.173332 -0.259819 -0.179118 --0.186949 -0.225139 -0.161631 --0.121077 -0.271737 -0.158365 --0.222098 -0.229029 -0.143015 --0.254895 -0.254361 -0.158387 --0.119307 -0.241249 -0.168619 --0.0940058 -0.224454 -0.161034 --0.119452 -0.213541 -0.164903 --0.133444 -0.181194 -0.153714 --0.123212 -0.142937 -0.146434 --0.154852 -0.114002 -0.127562 --0.16919 -0.193253 -0.151916 --0.202578 -0.281459 -0.18846 --0.238459 -0.276537 -0.185897 --0.240282 -0.308392 -0.197824 --0.31592 -0.411023 -0.222975 --0.28556 -0.354091 -0.210885 --0.235315 -0.349789 -0.176342 --0.247249 -0.413591 -0.205119 --0.313616 -0.39085 -0.145549 --0.265064 -0.38952 -0.162643 --0.137308 -0.0765217 -0.126585 --0.138269 -0.043252 -0.101811 --0.149787 -0.024537 -0.0569204 --0.190238 -0.325155 -0.164883 --0.214126 -0.342005 -0.145019 --0.11169 -0.0249744 -0.0351987 --0.154494 -0.0206052 -0.0150451 --0.170024 -0.0235376 0.033341 --0.149964 -0.0212297 0.0915499 --0.146292 -0.0570396 0.143171 --0.204533 -0.050976 0.0222898 --0.222839 -0.0748301 0.0497298 --0.218112 -0.0836084 0.106186 --0.241131 -0.10804 0.0423425 --0.253857 -0.126023 0.000914005 --0.268045 -0.155579 0.0359862 --0.254096 -0.191499 0.0837602 --0.284127 -0.206805 0.0257642 --0.255388 -0.139415 -0.0455141 --0.266486 -0.179542 -0.0496151 --0.255448 -0.200689 -0.0776309 --0.238672 -0.192995 -0.107883 --0.225097 -0.163824 -0.109937 --0.22995 -0.135947 -0.0896828 --0.209528 -0.114818 -0.0862766 --0.197715 -0.0771741 -0.074156 --0.18152 -0.102481 -0.104911 --0.212552 -0.0754645 -0.0336012 --0.179069 -0.0426745 -0.0757348 --0.191113 -0.0400122 0.118805 -0.0761558 -0.344791 -0.0536197 -0.0734921 -0.316433 -0.0291825 -0.0865869 -0.298841 -0.0691012 -0.0805876 -0.328744 0.0029047 -0.0952029 -0.361348 0.00820626 -0.11906 -0.273469 -0.0673767 -0.0841053 -0.310717 0.0264162 -0.125408 -0.292597 0.041066 -0.0878905 -0.285295 0.052224 -0.0491288 -0.272854 0.0806291 -0.143869 -0.242882 0.0528378 -0.117789 -0.207199 0.0870858 -0.169353 -0.193132 0.0552639 -0.0969256 -0.166289 0.115505 -0.054006 -0.288058 0.0505697 -0.0199581 -0.301066 0.0971587 --0.00766116 -0.296894 0.132807 -0.0766599 -0.302889 0.00259846 -0.0459981 -0.299327 0.00649094 -0.0275534 -0.307808 -0.0222832 -0.0149271 -0.300169 -0.0594622 -0.0407712 -0.276916 -0.0798141 -0.0598138 -0.291021 -0.0166231 -0.105499 -0.310063 0.042044 -0.129965 -0.318676 0.0271739 --0.0843085 -0.494264 -0.0321529 --0.0752323 -0.238787 0.196776 --0.295928 -0.43397 -0.176216 -0.0851801 -0.382062 -0.0130588 -0.0187394 -0.0952484 0.145146 -0.0700063 -0.10227 0.135872 -0.0221841 -0.0461712 0.144279 -0.0234739 0.0145751 0.123876 --0.00955997 -0.0145334 0.135796 --0.0455413 -0.0274983 0.116817 --0.0630042 -0.0646849 0.123712 --0.0996182 -0.0685448 0.139479 --0.129134 -0.0937854 0.159793 --0.112763 -0.133107 0.183753 --0.0586283 -0.0384282 0.0801443 --0.0976008 -0.0306336 0.0712047 --0.187313 -0.236737 0.146886 --0.186919 -0.194456 0.158247 --0.276732 -0.200888 -0.0224537 --0.291326 -0.23573 -0.0313163 --0.262172 -0.26119 -0.0228289 --0.244304 -0.258186 0.0138536 --0.238049 -0.253808 -0.053252 --0.278468 -0.245353 0.0237178 --0.250374 -0.233381 -0.0762153 --0.317786 -0.266287 -0.0397057 --0.29694 -0.227134 0.00135872 --0.28761 -0.282597 -0.0302299 --0.0768305 -0.203891 0.202176 --0.107975 -0.220055 0.202264 --0.134773 -0.20066 0.190676 --0.13214 -0.167949 0.192848 --0.157713 -0.187173 0.17141 --0.0792541 -0.17391 0.197354 --0.103166 -0.157466 0.196315 --0.0861693 -0.139966 0.183699 --0.0642112 -0.126048 0.16286 --0.0525585 -0.0978366 0.139755 -0.173523 -0.0454835 0.124705 -0.124762 -0.0100876 0.132612 -0.0801162 0.0231847 0.117816 -0.0903158 0.0691509 0.0824377 -0.111706 0.138719 0.113497 -0.109897 0.0476799 0.0291314 -0.0568194 0.0789592 0.018431 -0.166721 0.186565 0.0672199 -0.252858 0.160254 0.0700128 -0.103948 0.0891733 -0.0142249 -0.151331 0.103958 0.00831307 -0.156258 0.148984 0.0332697 -0.195526 0.176469 0.0301312 -0.246249 0.159404 0.0147221 -0.272985 0.107792 0.0403664 -0.220496 0.208803 0.0718273 -0.172006 0.242405 0.105809 -0.284857 0.213191 0.163319 -0.220004 0.262975 0.168971 -0.243752 0.187223 0.124992 -0.303317 0.156118 0.132187 -0.124557 0.160014 0.070217 -0.145476 0.178627 0.113721 -0.143822 0.145597 0.146983 -0.199699 0.112576 0.148784 -0.221259 0.0552492 0.134196 -0.124553 0.109697 0.139987 -0.100062 0.0751807 0.125633 -0.107686 0.0453443 0.15701 -0.152453 0.0251604 0.154394 -0.160741 0.1045 0.158385 -0.183054 0.0708926 0.1447 -0.188656 0.0314673 0.13741 -0.286002 0.0789154 0.0896861 -0.044874 0.0868553 -0.0397086 -0.0292084 0.0351428 -0.0773123 --0.00652383 0.0868322 -0.0563984 -0.153395 -0.330946 0.00521793 -0.165687 0.227811 0.159326 -0.176713 -0.253714 -0.180764 -0.276141 0.126578 0.0974557 --0.0329659 -0.0648403 -0.0508016 --0.022748 -0.0460692 -0.0136176 --0.0397109 -0.0394184 0.0195973 -0.00993129 -0.0278328 -0.0155697 -0.0270531 -0.00832198 0.0106523 -0.0103826 0.00500549 0.0538795 -0.0666135 0.0125544 0.0261568 -0.103369 0.00889595 0.155654 -0.11659 -0.0298082 0.170544 -0.153029 -0.0696813 0.168481 -0.113461 -0.0186162 0.216944 -0.10035 -0.0580327 0.251516 -0.150235 -0.084971 0.233939 -0.0675669 -0.0946043 0.253879 -0.0719166 -0.0391569 0.214813 -0.10687 -0.103697 0.22877 -0.136543 -0.144876 0.235107 -0.147147 -0.0351617 0.149371 -0.1197 -0.0491892 0.122959 -0.148505 -0.0696344 0.112799 -0.152678 -0.114026 0.101195 -0.181641 -0.136376 0.0700095 -0.177794 -0.0667748 0.102185 -0.219377 -0.0856118 0.124697 -0.231869 -0.0572983 0.0801841 -0.250938 -0.00881912 0.0578761 -0.198815 -0.0628459 0.124187 -0.177363 -0.0676828 0.147605 -0.177944 -0.101552 0.169756 -0.213844 -0.114113 0.199683 -0.250347 -0.109904 0.165803 -0.259893 -0.130195 0.122038 -0.180633 -0.0723966 0.203599 -0.114076 -0.0955963 0.272717 -0.201886 -0.0333539 0.161782 -0.240288 -0.0485439 0.141813 -0.261312 -0.0227503 0.104643 -0.273982 -0.0593402 0.119899 -0.280362 -0.074792 0.0701015 -0.238595 0.0177583 0.0951404 -0.14643 -0.0478943 0.212489 --0.227331 -0.265687 0.0979085 --0.244646 -0.230401 0.0887962 --0.293688 -0.268968 -0.164669 --0.297979 -0.308788 -0.162348 --0.312868 -0.346598 -0.144794 --0.344082 -0.367401 -0.192127 --0.317302 -0.337357 -0.184462 --0.280828 -0.350074 -0.123607 -0.105148 0.105807 0.112878 -0.112708 0.122082 0.0774818 -0.133565 0.128779 0.0503619 --0.153371 -0.370618 0.0324641 -0.254239 -0.0926281 0.0998171 --0.0128437 0.0136567 0.10826 -0.0175667 0.0217155 0.0871281 -0.0477136 0.0340255 0.104217 -0.0750182 0.0489044 0.100742 -0.0776037 0.0433948 0.0700673 -0.0973389 0.0603657 0.0539823 -0.0861821 0.0686274 0.0252903 -0.0735784 0.0589072 -0.0094551 -0.0829016 0.102631 0.0164944 -0.0811061 0.0911963 0.0569078 -0.0940457 0.0476479 0.121838 -0.105428 0.0231317 0.131003 -0.0916425 -0.00738665 0.126455 -0.0604145 5.34629e-005 0.128347 -0.10359 0.0595398 0.00049955 -0.144344 0.0457444 0.00327488 -0.122523 0.0419513 -0.0300499 -0.11811 0.0162342 -0.0830375 -0.170091 0.0155571 -0.145681 -0.138389 0.0626357 -0.0743287 -0.165069 0.0235027 -0.0532363 -0.177768 0.0409007 -0.100742 -0.136707 0.0380317 -0.122381 -0.124141 0.00775387 -0.157586 -0.154959 -0.000810753 -0.105169 -0.105591 0.0562623 -0.100272 -0.0594242 0.0548004 -0.106957 -0.201162 -0.0168583 -0.120783 -0.0976411 0.0972697 0.0801317 -0.0943337 0.0848541 0.102457 -0.0890479 0.0650811 0.109825 -0.0389773 0.0745551 -0.0779593 -0.0138551 0.0593589 -0.108474 -0.0773146 0.0765993 -0.0629692 -0.118409 0.00125651 -0.11931 -0.14578 -0.0101392 -0.137264 -0.178622 -0.0192175 -0.148664 -0.15461 0.0388676 -0.0266866 -0.193622 0.0322602 -0.0272748 -0.1942 0.050466 -0.0642106 -0.173634 0.0303747 -0.00069076 -0.179101 -0.0110744 -0.00523936 -0.230507 0.0422098 -0.0173425 -0.202418 0.070871 -0.0135348 -0.22302 0.0184239 -0.0441485 -0.215157 -0.0248187 0.00288885 -0.233855 -0.00344678 0.0259658 -0.238719 0.033163 0.0205233 -0.25481 0.0672767 0.0179763 -0.222129 -0.0581209 -0.00731524 -0.227145 -0.0819987 0.0145053 -0.219253 -0.118982 0.0109807 -0.211654 -0.158762 -0.0192499 -0.210611 -0.101269 0.0490966 -0.200451 -0.190104 0.0116714 -0.203938 -0.0712158 -0.0282028 -0.173774 -0.0735362 -0.0475631 -0.2027 -0.10102 -0.0400541 -0.212795 -0.129107 -0.0298945 -0.206091 -0.151217 -0.0455168 -0.202467 -0.150775 -0.0745887 -0.209378 -0.187531 -0.0743719 -0.181149 -0.126003 -0.0892617 -0.205972 -0.177843 -0.0383337 -0.188301 -0.142819 -0.114464 -0.208779 -0.164491 -0.140437 -0.171227 -0.151151 -0.147387 -0.136617 -0.138704 -0.143839 -0.131975 -0.166643 -0.169617 -0.128263 -0.113939 -0.11894 -0.116875 -0.0858965 -0.091059 -0.0786764 -0.0829677 -0.090746 -0.187859 -0.214057 -0.0686071 -0.175679 -0.237132 -0.0957975 -0.178163 -0.10588 -0.0665832 -0.269129 0.0763722 0.0451229 -0.27889 0.058723 0.0679827 -0.267652 0.0453551 0.105512 -0.2613 0.0363273 0.0775663 -0.250278 0.0199214 0.0566543 -0.250269 0.00638094 0.0769677 -0.262967 -0.0160504 0.0796628 -0.285344 -0.0433845 0.0872574 -0.24971 -0.0386027 0.0640826 -0.229012 -0.0528564 0.0533874 -0.210073 -0.0746435 0.0657137 -0.221889 -0.0775407 0.0410909 -0.302572 -0.0710887 0.0949218 -0.236871 -0.0290995 0.0413907 -0.21774 0.0486965 -0.0445342 -0.224846 0.0339602 -0.0740774 -0.244042 0.0044793 -0.10874 -0.254336 0.102378 0.0100739 -0.220262 0.108437 -0.00385435 -0.184431 0.103867 -0.0063665 -0.227766 0.138272 0.00117268 -0.21603 0.168687 0.00560537 -0.214962 0.252952 4.36931e-005 -0.24674 0.211475 0.00674743 -0.208103 0.206716 0.00898043 -0.198252 0.239152 0.0425261 -0.241235 0.183275 -0.00372162 -0.24846 0.187657 0.0253371 -0.236173 0.217937 0.0406376 -0.208251 0.202717 0.0430183 -0.190765 0.204953 0.0676283 -0.199822 0.228771 0.0907818 -0.212055 0.262964 0.121847 -0.167888 0.20936 0.0941323 -0.230768 0.216285 0.106419 -0.226441 0.220307 0.1523 -0.160904 0.217518 0.126833 -0.151535 0.251202 0.133586 -0.158786 0.306935 0.0984538 -0.193903 0.396086 0.195106 -0.119767 0.364922 0.154966 -0.158434 0.284691 0.125614 -0.188265 0.327264 0.175224 -0.139712 0.323472 0.144742 -0.247398 0.226772 0.208026 -0.162628 0.357991 0.158091 -0.132463 0.334186 0.215838 -0.184892 0.418747 0.128073 -0.148158 0.405567 0.1589 -0.116952 0.392105 0.208798 -0.128904 0.307582 0.18276 -0.173163 0.280669 0.197839 -0.237808 0.190239 0.0542196 -0.242415 0.187695 0.0885314 -0.257589 0.159811 0.105872 -0.25799 0.149475 0.1552 -0.249199 0.167605 0.0439231 -0.270589 0.141138 0.0478035 -0.291007 0.120776 0.0668204 -0.27986 0.0943457 0.0648346 -0.262028 0.128688 0.0220064 -0.283888 0.104244 0.08796 -0.278117 0.09277 0.114653 -0.296211 0.119268 0.134225 -0.25745 0.0686993 0.129638 -0.231499 0.0874258 0.144645 -0.23624 0.121986 0.150481 -0.21822 0.158716 0.152133 -0.276736 0.0681718 0.110294 -0.286087 0.0545245 0.0921962 -0.114064 0.342002 0.184102 -0.184346 0.361594 0.187357 -0.161147 0.378013 0.229421 -0.186402 0.32651 0.232026 -0.224199 0.278314 0.231623 -0.178684 0.387524 0.165454 -0.207571 0.416517 0.162223 -0.115261 0.360345 0.210089 -0.111327 0.375408 0.183082 -0.123148 0.405595 0.17979 -0.161317 0.441861 0.200022 -0.134075 0.42215 0.208124 -0.166581 0.413272 0.221996 -0.184888 0.467555 0.155941 -0.139501 0.435526 0.173911 -0.158717 0.435906 0.144337 -0.167498 0.45215 0.103084 -0.137763 0.359978 0.22921 -0.165611 0.348074 0.227085 -0.15984 0.318857 0.217125 -0.185554 0.304169 0.211919 -0.205103 0.278784 0.204981 -0.244841 0.279219 0.196675 -0.226891 0.25345 0.209087 -0.216714 0.31142 0.225032 -0.18164 0.33693 0.20427 -0.203702 0.303717 0.18726 -0.193627 0.296232 0.146589 -0.191637 0.257284 0.18429 -0.157803 0.257523 0.177293 -0.175634 0.311565 0.125155 -0.161107 0.457828 0.173386 -0.194169 0.447882 0.185268 -0.194408 0.477711 0.118315 -0.219599 0.449898 0.13857 -0.135294 0.387364 0.228733 -0.145147 0.282789 0.187345 -0.143956 0.303159 0.203618 -0.177446 0.366384 0.212315 -0.179329 0.391491 0.217312 -0.18672 0.412483 0.210145 -0.202606 0.421792 0.189409 -0.174085 0.42914 0.210627 -0.153641 0.426813 0.21312 -0.144424 0.408706 0.223131 -0.190464 0.428693 0.201747 -0.178837 0.441581 0.19921 -0.169704 0.452082 0.187978 -0.180705 0.459908 0.173619 -0.202088 0.458198 0.166952 -0.151672 0.449754 0.186725 -0.142475 0.436806 0.198199 -0.127991 0.425256 0.188222 -0.210178 0.437176 0.172385 -0.119636 0.410278 0.199562 -0.206557 0.46857 0.145869 -0.215386 0.468035 0.123827 -0.207969 0.436894 0.106764 -0.212477 0.477963 0.10116 -0.128023 0.4054 0.215288 -0.223073 0.454408 0.0921651 -0.184017 0.321535 0.149004 -0.164058 0.339436 0.133054 -0.141763 0.356765 0.138374 -0.138767 0.331395 0.111494 -0.18387 0.5 0.0894472 -0.178253 0.341112 0.15974 -0.158692 0.397227 0.229447 -0.261987 0.258572 0.22454 -0.209557 0.191204 0.156985 -0.19703 0.220129 0.168363 -0.114158 0.395299 0.191384 -0.237163 0.239354 0.0176536 -0.22873 0.228755 -0.0079498 -0.204237 0.229854 -0.000985107 -0.191898 0.248869 0.0164149 -0.195144 0.299533 0.0501168 -0.211408 0.277777 0.0261684 -0.181525 0.269092 0.042102 -0.232338 -0.0296485 0.018274 -0.220704 0.448894 0.117889 -0.211836 0.430361 0.128709 -0.200007 0.22745 0.0221073 -0.206622 0.221816 0.0395427 -0.223655 0.239447 0.0484776 -0.206846 0.263872 0.061817 -0.192412 0.289403 0.0880222 -0.200823 -0.0669613 0.089238 -0.1998 0.487191 0.0879627 -0.178418 0.478766 0.0726805 -0.171907 0.478 0.098484 -0.179607 0.4432 0.070784 -0.215421 0.44036 0.0572748 -0.206111 0.438517 0.081171 -0.184196 0.432818 0.0953164 -0.172787 0.433405 0.115219 -0.16681 0.454422 0.128591 -0.158037 0.463512 0.080819 -0.300027 -0.106441 0.0798503 -0.301565 -0.138608 0.110073 -0.223785 -0.0676095 0.104371 -0.244386 -0.0720528 0.0937201 -0.25624 -0.0594904 0.0758547 -0.271472 -0.0437947 0.0690266 -0.283677 -0.0568988 0.0744319 -0.294284 -0.0698767 0.0795338 -0.293906 -0.0887216 0.0736412 -0.275743 -0.101071 0.0877101 -0.309738 -0.0894098 0.0874741 -0.287014 -0.123527 0.0945964 -0.311125 -0.118131 0.0991123 -0.298899 -0.117613 0.118193 -0.276193 -0.109289 0.135451 -0.270493 -0.137621 0.153632 -0.286312 -0.136798 0.132025 -0.257826 -0.0797919 0.144654 -0.303792 -0.0864306 0.104131 -0.28817 -0.0747287 0.113826 -0.276632 -0.0878111 0.128274 -0.30695 -0.103289 0.107245 -0.287682 -0.0559229 0.10414 -0.272738 -0.0407993 0.108633 -0.253435 -0.0365139 0.124117 -0.295415 -0.0573592 0.0898843 -0.270105 -0.0629299 0.0693418 -0.263375 -0.0783235 0.0820667 --0.216962 -0.20051 0.140681 --0.236047 -0.180496 0.115013 --0.230292 -0.136771 0.112494 --0.247625 -0.159292 0.0914591 --0.209896 -0.129892 0.141237 -0.222682 0.288177 0.190351 -0.233778 0.296853 0.212089 -0.209365 0.287061 0.167624 -0.208618 0.271128 0.146657 -0.223776 0.247151 0.14266 -0.250594 0.280585 0.221005 -0.215525 0.238928 0.164607 -0.248724 0.243405 0.176599 -0.282927 0.23674 0.194658 -0.253819 0.208937 0.178714 -0.265424 0.184312 0.155732 -0.308614 0.169998 0.183237 -0.273365 0.179729 0.192265 -0.265912 0.204786 0.204674 -0.300509 0.203464 0.194384 -0.281969 0.151677 0.177526 -0.279246 0.127373 0.158699 -0.31079 0.143896 0.162421 -0.30954 0.171009 0.155179 -0.288086 0.1804 0.137744 -0.264265 0.175268 0.132545 -0.241184 0.168571 0.143536 -0.282052 0.162312 0.123541 -0.290218 0.138764 0.11928 -0.232003 0.191432 0.146429 -0.237199 0.211128 0.131059 -0.175486 0.13591 0.157401 -0.244193 0.0453066 0.121153 -0.216838 0.0295154 0.115567 -0.0778181 0.0182774 -0.0959304 -0.132697 0.385267 0.165833 -0.155812 0.38306 0.160495 --0.00373338 0.0386319 -0.0871428 -0.0052284 0.0508015 -0.0521262 --0.0272532 0.0521944 -0.0650671 --0.0417118 0.0760468 -0.0274796 -0.0432101 0.0478592 -0.0430105 -0.0360437 0.064037 -0.0095129 -0.0264403 0.0878588 0.0105855 -0.0200841 0.0963175 -0.0204482 -0.0508265 0.0939603 -0.0091335 -0.0753367 0.087282 -0.0290458 --0.0114666 0.0989277 -0.0268583 -0.189464 0.426182 0.111762 --0.04038 -0.0265907 0.0536548 -0.188037 0.19051 0.048384 -0.170897 0.170857 0.0404072 -0.180803 0.154042 0.0195245 -0.168583 0.128396 0.0100026 -0.150344 0.161847 0.0580756 -0.146195 0.173828 0.0846654 -0.123104 0.163389 0.100752 -0.131952 0.158423 0.126057 -0.154039 0.169296 0.137953 -0.163282 0.191526 0.127822 -0.170691 0.206066 0.147249 -0.123979 0.136658 0.135 -0.136161 0.125537 0.148878 -0.153818 0.131557 0.161379 -0.111897 0.12133 0.126074 -0.111889 0.144276 0.0890707 -0.11658 0.140768 0.0690434 -0.119959 0.124948 0.0613596 -0.107779 0.107117 0.0626571 -0.122618 0.115267 0.0466942 -0.127454 0.104238 0.0219653 -0.136258 0.119892 0.0320023 -0.129073 0.0915077 -0.00265103 -0.130797 0.0780035 -0.0369633 -0.10768 0.094992 0.00979378 -0.163926 0.154671 0.152149 -0.0894836 0.0909923 0.00058556 -0.0689505 0.0963924 0.00171312 -0.0612997 0.100634 0.0224348 -0.0675451 0.0846698 0.038694 -0.0795109 0.103357 0.0384133 -0.0848094 0.0754581 0.0444653 -0.110567 0.10366 0.130086 -0.12281 0.0864143 0.139975 -0.117855 0.062854 0.143513 -0.13666 0.0472165 0.155281 -0.128164 0.0235742 0.176647 -0.163067 0.0498951 0.143567 -0.143932 0.0949004 0.145284 -0.179917 0.317727 0.0742859 -0.183725 0.275085 0.0676723 -0.16838 0.29297 0.0787056 -0.0930951 0.102542 0.05002 -0.100339 0.0681106 0.0373411 -0.102886 0.0622715 0.0197467 -0.121511 0.0540863 0.0117598 -0.124719 0.0242285 0.0166428 -0.0967861 -0.00310471 -0.0020113 -0.12138 0.0519179 -0.0102922 -0.0990646 0.0492208 -0.022422 -0.0873807 -0.0275369 -0.03209 -0.200694 -0.191636 -0.0546067 -0.206298 -0.170055 -0.0598788 -0.209964 -0.168421 -0.0791806 -0.221182 -0.183261 -0.0963771 -0.222775 -0.172837 -0.120159 -0.235715 -0.195921 -0.115182 -0.253933 -0.218526 -0.134037 -0.311213 -0.253911 -0.191866 -0.279294 -0.244732 -0.16099 -0.266185 -0.201338 -0.169529 -0.285572 -0.216415 -0.213382 -0.273765 -0.285731 -0.187819 -0.259679 -0.265381 -0.248632 -0.24894 -0.227823 -0.231771 -0.232153 -0.25966 -0.225227 -0.254118 -0.290735 -0.220386 -0.336364 -0.328047 -0.241676 -0.281317 -0.319577 -0.26697 -0.295033 -0.317038 -0.218433 -0.327766 -0.263669 -0.27537 -0.320681 -0.238904 -0.235706 -0.333487 -0.28367 -0.222752 -0.25789 -0.299076 -0.25318 -0.280382 -0.278404 -0.287734 -0.262726 -0.334272 -0.234674 -0.315714 -0.303377 -0.28762 -0.358898 -0.298323 -0.270079 -0.292961 -0.250812 -0.263064 -0.260427 0.269097 0.206442 -0.273912 0.251948 0.207483 -0.274266 0.226866 0.218876 -0.254508 0.262332 0.186312 -0.268737 0.247011 0.182676 -0.278883 0.230014 0.174886 -0.292676 0.216891 0.181537 -0.301666 0.196568 0.170643 -0.235991 0.25839 0.179999 -0.216996 0.262302 0.191107 -0.233602 0.240223 0.192553 -0.26623 0.217767 0.166603 -0.291208 0.219735 0.206357 -0.285626 0.200003 0.208179 -0.295054 0.181857 0.198439 --0.119559 -0.0454446 0.130205 --0.148541 -0.0288065 0.124554 --0.122421 -0.0280036 0.104512 --0.169628 -0.0428483 0.136658 --0.192691 -0.0700149 0.138018 --0.165949 -0.0805689 0.151606 --0.157867 -0.111652 0.162619 --0.182289 -0.134815 0.160033 --0.171616 -0.0265274 0.119564 --0.182821 -0.0294707 0.089096 --0.207158 -0.0941133 0.130893 --0.0813687 -0.408143 0.0168626 --0.0493082 -0.255289 0.183439 --0.0417823 -0.281988 0.16825 --0.0232624 -0.242141 -0.150317 -0.237084 0.148575 0.150278 -0.21825 0.135883 0.152701 -0.196547 0.147262 0.152063 -0.248839 0.134889 0.149793 -0.25417 0.116897 0.146677 -0.154738 -0.248351 -0.113516 -0.149894 -0.252291 -0.14374 -0.127807 -0.247316 -0.112579 -0.100037 -0.239188 -0.118127 -0.171952 -0.258325 -0.155783 -0.206243 -0.267544 -0.164319 -0.152779 -0.244265 -0.170212 -0.238869 -0.271194 -0.164355 -0.19948 -0.240785 -0.114164 -0.228533 -0.228656 -0.117975 -0.0806348 -0.448964 -0.0364622 -0.092817 -0.46403 -0.0236687 -0.131465 -0.464547 -0.0186627 -0.111576 -0.45856 -0.0162136 -0.12236 -0.437795 -0.0167687 -0.116113 -0.473014 -0.0333379 -0.141834 -0.466374 -0.0462667 -0.15629 -0.45187 -0.0265272 -0.162053 -0.430562 -0.0436087 -0.170805 -0.433786 -0.074571 -0.150694 -0.440322 -0.089161 -0.142403 -0.453672 -0.0687084 -0.20922 0.0225383 -0.118012 -0.245897 0.00269769 -0.0710137 --0.0868896 -0.445579 -0.0827631 --0.0899978 -0.418668 -0.0662628 --0.0919895 -0.382051 -0.0731611 --0.286076 -0.18977 0.00251657 -0.166397 -0.235956 -0.0665238 -0.18289 -0.231659 -0.0402536 -0.183601 -0.256036 -0.0114407 -0.19304 -0.222737 -0.0114233 -0.168396 -0.264129 0.0198747 -0.175145 -0.292863 -0.0261367 -0.159612 -0.311932 -0.0502102 -0.151795 -0.349231 -0.058414 -0.168467 0.120276 0.165442 -0.179322 0.109989 0.151163 -0.191745 0.091503 0.1476 -0.207409 0.0731218 0.143583 -0.170472 0.088013 0.148441 -0.198308 0.0526608 0.137187 --0.288444 -0.322548 -0.196751 --0.258254 -0.336596 -0.201797 --0.260706 -0.370334 -0.191889 --0.262012 -0.38355 -0.234182 -0.169409 0.331718 0.106522 --0.0883279 -0.427369 -0.00320489 --0.0757242 -0.410706 -0.00350047 --0.0694098 -0.396348 -0.0215868 --0.339105 -0.28249 -0.133907 -0.14338 -0.190029 -0.185968 -0.113197 -0.189729 -0.178573 -0.161752 -0.208101 -0.1989 -0.163143 -0.233988 -0.192556 -0.187542 -0.24244 -0.20457 -0.214342 -0.231174 -0.221761 -0.200695 -0.263551 -0.191549 -0.0888974 -0.174918 -0.165344 -0.0728578 -0.155488 -0.148655 -0.0857975 -0.13271 -0.133069 -0.0496654 -0.153477 -0.133288 -0.208417 -0.252602 -0.211864 -0.214499 -0.20684 -0.209109 -0.212326 -0.182246 -0.176825 -0.196622 -0.193456 -0.194925 --0.0366034 0.0848157 -0.0747335 --0.0106036 0.0767347 -0.0825468 --0.248014 -0.143811 -0.0711582 -0.156176 -0.353723 -0.00967102 -0.161881 -0.35946 -0.0354879 -0.154192 -0.374021 -0.054565 -0.153835 -0.401954 -0.0551512 -0.147106 -0.376782 -0.0111704 -0.141013 -0.401853 -0.0175381 -0.127378 -0.38782 -0.00428773 -0.152558 -0.410563 -0.0345712 -0.144573 -0.387551 -0.0699167 -0.129797 -0.395951 -0.0860393 -0.110844 -0.383365 -0.0877166 -0.111358 -0.362136 -0.0828181 -0.10863 -0.332992 -0.0757964 -0.131091 -0.348484 -0.0736181 -0.114528 -0.372564 0.00601769 -0.116893 -0.350867 0.0177725 -0.143657 -0.369483 -0.0686154 -0.0433039 -0.239647 0.0998892 --0.318832 -0.357055 -0.211401 --0.299837 -0.377374 -0.238049 --0.340344 -0.383626 -0.224893 --0.356366 -0.419986 -0.188103 --0.285529 -0.404192 -0.228972 --0.356375 -0.393121 -0.201407 --0.349321 -0.392013 -0.165399 --0.328811 -0.42272 -0.163607 --0.345548 -0.417553 -0.216974 --0.322795 -0.427865 -0.195551 --0.33518 -0.363207 -0.161311 --0.0812327 -0.396788 0.0342935 --0.065289 -0.38943 0.0224745 --0.0508718 -0.371639 0.0298172 --0.260668 -0.216401 0.0653687 --0.2704 -0.185201 0.0538295 -0.187032 0.486356 0.0996338 -0.279593 -0.136382 0.110973 -0.26837 -0.117918 0.105466 -0.248285 -0.109463 0.116456 -0.232397 -0.125931 0.141691 -0.210603 -0.141776 0.171116 -0.137574 -0.108705 0.207737 -0.169921 0.29167 0.0522744 -0.00992085 -0.113945 -0.0964453 -0.258261 -0.257702 -0.154872 -0.275321 -0.267702 -0.17038 -0.295633 -0.272896 -0.184932 -0.295815 -0.294739 -0.20199 -0.314713 -0.27743 -0.201437 -0.327332 -0.256197 -0.214678 -0.340385 -0.261017 -0.241446 -0.313356 -0.232789 -0.209684 -0.296344 -0.226073 -0.182212 -0.31592 -0.301772 -0.216769 -0.318635 -0.326842 -0.222796 -0.307178 -0.325937 -0.251416 -0.274745 -0.303144 -0.21295 -0.263287 -0.308346 -0.232527 -0.255382 -0.319889 -0.248754 -0.332002 -0.310475 -0.229091 -0.353636 -0.307458 -0.245121 -0.335859 -0.313851 -0.265914 -0.340499 -0.300896 -0.286697 -0.344989 -0.280039 -0.276203 -0.327449 -0.28026 -0.295194 -0.304942 -0.268737 -0.28762 -0.34983 -0.283684 -0.251927 -0.343136 -0.265469 -0.261534 -0.326713 -0.248963 -0.256716 -0.307917 -0.24034 -0.252462 -0.279159 -0.231625 -0.241522 -0.300353 -0.229376 -0.234338 -0.311358 -0.251222 -0.26759 -0.299024 -0.289572 -0.301481 -0.279907 -0.305571 -0.290358 -0.263002 -0.29286 -0.276914 -0.260616 -0.313748 -0.268771 -0.275922 -0.322371 -0.224156 -0.284975 -0.33259 -0.244221 -0.303023 -0.336946 -0.233483 -0.289605 -0.333838 -0.221905 -0.166435 0.39886 0.154509 -0.189152 0.404825 0.153461 -0.197133 0.400723 0.1737 -0.17424 0.413608 0.143911 -0.169142 0.426834 0.132439 -0.188725 0.386035 0.180698 -0.186219 0.378081 0.198788 --0.267025 -0.372115 -0.138391 --0.248022 -0.367522 -0.158602 -0.174505 0.292586 0.0990952 -0.186251 0.309263 0.0928235 -0.181697 0.30513 0.109614 -0.189499 0.279776 0.119317 -0.172647 0.294457 0.120787 -0.158057 0.303956 0.130265 -0.143476 0.291331 0.139139 -0.131312 0.30031 0.159002 -0.186095 0.298863 0.131041 -0.198975 0.28677 0.132576 -0.172236 0.277192 0.117866 -0.184313 0.260452 0.109718 -0.15755 0.265746 0.122578 -0.145983 0.269471 0.137935 -0.151234 0.256355 0.155644 -0.124293 0.323596 0.164277 -0.126678 0.342635 0.150186 -0.129524 0.341544 0.128525 -0.146576 0.349856 0.118779 -0.192269 0.30374 0.0759625 -0.202728 0.285813 0.0690781 -0.210479 0.281567 0.0483558 -0.222196 0.26209 0.0407369 -0.224073 0.261141 0.0186008 -0.162756 0.306191 0.114529 -0.149682 0.318744 0.121136 -0.152677 0.341974 0.100993 -0.164071 0.331208 0.0841361 -0.146523 0.323278 0.093798 -0.15695 0.312461 0.0714443 -0.22266 -0.203518 -0.100877 -0.262076 -0.292278 -0.202356 -0.249754 -0.28212 -0.183581 -0.242451 -0.284733 -0.203885 -0.229725 -0.273053 -0.211906 -0.243386 -0.275428 -0.225419 -0.255999 -0.283472 -0.239546 --0.261873 -0.405224 -0.222848 --0.280772 -0.420055 -0.201182 --0.274389 -0.414773 -0.173401 --0.298626 -0.411672 -0.158377 --0.28738 -0.389898 -0.148508 --0.300008 -0.369107 -0.135836 --0.257381 -0.39915 -0.185758 --0.257531 -0.421709 -0.186727 --0.319983 -0.369357 -0.146016 --0.25404 -0.39271 -0.206532 --0.269186 -0.375948 -0.213104 -0.171129 0.312683 0.0555484 -0.186538 0.309469 0.0612947 -0.17697 0.322584 0.0897939 -0.180094 0.317456 0.102692 -0.0389455 -0.294719 0.0707663 -0.19085 0.129482 0.148059 -0.26893 -0.330546 -0.250405 -0.261495 -0.324416 -0.260179 -0.163955 0.0845671 -0.00775852 -0.172992 0.467003 0.114773 -0.17962 0.47069 0.135115 -0.167392 0.460661 0.148013 -0.0927702 -0.0102964 0.178794 -0.0791092 -0.00358862 0.211868 -0.0484002 -0.0727004 0.143042 -0.0857054 -0.0664246 0.132462 -0.170606 0.462905 0.162683 -0.107346 -0.291576 0.0507084 -0.123054 -0.26548 0.0555752 -0.1033 -0.273351 0.0618915 --0.217347 -0.0684085 0.0793768 --0.232534 -0.1003 0.0785864 -0.160705 0.203815 0.112095 -0.157448 0.189802 0.0951937 -0.163855 0.222961 0.107992 -0.178039 0.226994 0.0939715 -0.157779 0.236558 0.121407 -0.156862 0.233096 0.141593 -0.254115 0.147478 0.0328869 -0.246739 0.139758 0.0163119 --0.313249 -0.26088 -0.138114 --0.319034 -0.272336 -0.0954566 --0.312031 -0.286413 -0.151154 --0.318615 -0.301412 -0.127758 --0.31358 -0.30952 -0.0911544 --0.342054 -0.297563 -0.104135 --0.285707 -0.289103 -0.098473 --0.332554 -0.290038 -0.0650158 -0.224391 0.444149 0.0748945 -0.224127 0.466497 0.0733316 -0.00933378 -0.0890982 -0.073455 --0.196836 -0.0544369 -0.0547609 --0.268852 -0.35939 -0.204575 --0.134821 -0.144762 0.184037 --0.134018 -0.119846 0.172991 --0.108849 -0.110436 0.169417 --0.142893 -0.258813 -0.176858 -0.163435 0.422628 0.144619 -0.149105 0.425301 0.157986 -0.151653 0.445126 0.160854 -0.205634 0.453218 0.0682861 -0.196116 0.437078 0.0613117 -0.305429 0.136514 0.131635 -0.304223 0.128705 0.150462 -0.294739 0.1352 0.16485 -0.29983 0.148475 0.176204 -0.293633 0.16134 0.186506 -0.312816 0.146868 0.144611 -0.290574 0.119539 0.149687 -0.280449 0.10933 0.137413 -0.285959 0.112117 0.117297 -0.154398 0.116961 0.162863 -0.147042 0.108372 0.152274 -0.179394 0.214392 0.162515 -0.185651 0.194518 0.157404 -0.180628 0.232354 0.173507 -0.198969 0.238329 0.174545 -0.207694 0.253512 0.177641 -0.203869 0.264161 0.186147 -0.189501 0.273156 0.193533 -0.17312 0.263127 0.188581 --0.206119 -0.0619918 0.116346 --0.201545 -0.0465532 0.095691 -0.256266 0.152102 0.0503785 -0.264034 0.143148 0.0672501 -0.26342 0.152022 0.0867622 -0.269601 0.144159 0.103885 -0.281713 0.137095 0.0629023 -0.296165 0.127981 0.0376256 -0.300117 0.13462 0.0559043 -0.287642 0.140563 0.0456708 -0.278661 0.132147 0.0309413 -0.271646 0.118273 0.028754 -0.262921 0.1025 0.0261376 -0.261118 0.0855637 0.0169037 -0.25422 0.0767023 0.000233527 -0.237362 0.0575218 0.00198963 -0.283388 0.119704 0.0383643 -0.282941 0.112062 0.0540402 -0.249937 0.0937398 -0.00575339 -0.231931 0.0954578 -0.0107377 -0.210437 0.0914748 -0.0140972 -0.239252 0.109782 0.000233887 -0.191402 0.0870306 -0.0134669 -0.184376 0.0704441 -0.0225342 -0.184719 0.0606504 -0.044543 -0.166145 0.0578967 -0.0668264 -0.200251 0.061117 -0.0313226 -0.217304 0.057967 -0.0199728 -0.227686 0.12158 0.00370932 -0.20937 0.123042 0.000300618 -0.244333 0.124524 0.00810813 -0.295429 0.120597 0.0522453 -0.178132 0.0791963 -0.0112798 -0.177197 -0.279627 -0.000252101 -0.173639 -0.299787 -0.00606886 -0.172196 -0.313975 -0.0223573 -0.166117 -0.326159 -0.00858114 -0.168079 -0.321719 -0.0379096 -0.162521 -0.339731 -0.0445028 -0.151373 -0.32895 -0.0576036 -0.312369 -0.105021 0.0899766 -0.306 -0.11652 0.0866674 -0.301418 -0.129182 0.0942967 -0.290875 -0.135236 0.101483 -0.289966 -0.143698 0.1109 -0.295915 -0.13953 0.122573 -0.279108 -0.148197 0.122497 -0.27841 -0.152315 0.142258 -0.265867 -0.157983 0.158785 -0.256687 -0.14661 0.137703 -0.25422 -0.140442 0.172759 -0.232237 -0.161116 0.192798 -0.192807 -0.160413 0.202508 -0.178601 -0.140578 0.237092 -0.154384 -0.117167 0.24831 -0.13004 -0.134226 0.269277 -0.17845 -0.105479 0.223698 -0.247464 -0.158843 0.179106 -0.226891 -0.158442 0.171794 -0.209982 -0.159856 0.187271 -0.217821 -0.141328 0.207646 -0.23745 -0.141109 0.186416 -0.229503 -0.142546 0.156329 -0.214657 -0.125289 0.155947 -0.193847 -0.12199 0.171057 -0.217246 -0.105649 0.140104 -0.198069 -0.0856194 0.142621 -0.193693 -0.14343 0.18682 -0.198045 -0.145975 0.222277 -0.178989 -0.164931 0.225321 -0.158831 -0.146831 0.218292 -0.158889 -0.159611 0.244735 -0.138513 -0.154491 0.257842 -0.156487 -0.13924 0.256434 --0.290841 -0.208347 -0.00770324 --0.29533 -0.224009 -0.017137 --0.30561 -0.24462 -0.0155011 --0.278365 -0.220452 -0.0277869 --0.267819 -0.241114 -0.0357093 --0.286737 -0.259306 -0.0462551 --0.310093 -0.264404 -0.0203987 --0.307167 -0.250649 -0.0341839 --0.307612 -0.281242 -0.0310235 --0.321036 -0.286281 -0.0471544 --0.310491 -0.270102 -0.0639157 --0.311345 -0.301149 -0.0633156 --0.297683 -0.291436 -0.0450671 --0.294949 -0.304108 -0.0790095 --0.285459 -0.27779 -0.0488974 --0.296472 -0.275126 -0.0168658 --0.276222 -0.270671 -0.00788631 --0.246454 -0.248267 -0.0329014 --0.244456 -0.252303 -0.00937036 --0.304286 -0.259682 -0.0492577 --0.294933 -0.27203 -0.0604957 --0.300998 -0.278796 -0.0743602 --0.316674 -0.274623 -0.0789273 --0.337714 -0.280945 -0.0853892 --0.325825 -0.275251 -0.0655092 --0.332977 -0.302425 -0.0833069 --0.293564 -0.290291 -0.0772666 --0.298024 -0.281309 -0.0905074 --0.293634 -0.272694 -0.109468 --0.270428 -0.274072 -0.107657 --0.256077 -0.253991 -0.120812 --0.261078 -0.296954 -0.0999473 --0.2381 -0.297373 -0.0849839 --0.232752 -0.278906 -0.0645746 --0.272041 -0.319246 -0.10842 --0.21032 -0.284441 -0.0448701 --0.256436 -0.341131 -0.11181 --0.225098 -0.336223 -0.113241 --0.200175 -0.288996 -0.0158483 --0.223009 -0.317757 -0.0905879 --0.301096 -0.262202 -0.123506 --0.32405 -0.263071 -0.116908 --0.292477 -0.257052 -0.14263 --0.270149 -0.255918 -0.142108 --0.275486 -0.258003 -0.162669 --0.259523 -0.2674 -0.178244 --0.251069 -0.250018 -0.0970302 --0.24056 -0.260706 -0.172311 --0.232499 -0.247239 -0.154361 --0.34182 -0.274562 -0.107339 --0.294559 -0.307923 -0.0994756 --0.275844 -0.269552 -0.0355918 --0.227344 -0.270897 -0.045083 -0.25093 0.170443 0.120028 -0.248384 0.184009 0.106523 -0.235777 0.198062 0.103222 -0.23641 0.211305 0.086191 -0.221152 0.226047 0.0898201 -0.223243 0.24123 0.111495 -0.203883 0.250541 0.105061 -0.226958 0.225862 0.123117 -0.219737 0.240254 0.12789 -0.214138 0.253162 0.132941 -0.232275 0.224561 0.138088 -0.109391 -0.247931 -0.0995264 -0.104952 -0.262437 -0.0820427 -0.080763 -0.274056 -0.0783487 -0.0604155 -0.262392 -0.0950373 --0.227936 -0.329497 -0.190147 --0.214125 -0.337031 -0.170051 -0.263325 -0.0528405 0.0685009 -0.251926 -0.0484757 0.0715634 -0.237277 -0.0480579 0.0658708 -0.244491 -0.055646 0.0790843 -0.239659 -0.0616442 0.0888399 -0.226162 -0.0616059 0.0920824 -0.21522 -0.0631162 0.0781895 -0.212545 -0.0629682 0.0966232 -0.196781 -0.0599901 0.105959 -0.210127 -0.0657511 0.11188 -0.250495 -0.0660959 0.0846797 -0.234672 -0.0671026 0.0978575 -0.237261 -0.0846396 0.108485 -0.25989 -0.0423809 0.0666642 -0.259987 -0.0257531 0.0664908 -0.247883 -0.0247631 0.0554477 -0.242689 -0.0135836 0.0440572 -0.246291 0.00496067 0.0457059 -0.245187 0.0234669 0.0372413 -0.250061 0.17416 0.133813 -0.259114 0.167602 0.148828 -0.26511 0.166925 0.171546 -0.238618 0.181672 0.138204 -0.229669 0.176644 0.149535 -0.262049 0.187134 0.176237 -0.261795 0.200868 0.165371 -0.276513 0.199318 0.153854 -0.292677 0.199982 0.157209 -0.302715 0.186361 0.157188 -0.308755 0.181779 0.171774 -0.305926 0.190608 0.185482 -0.312423 0.167782 0.169221 -0.31381 0.157653 0.158921 -0.310783 0.154703 0.171639 -0.301108 0.177938 0.144841 -0.293264 0.189309 0.147256 -0.279322 0.188458 0.145304 -0.262483 0.193127 0.192418 -0.274289 0.192768 0.20161 -0.254841 0.206509 0.193978 -0.245154 0.222549 0.189318 -0.253012 0.22552 0.174345 -0.265429 0.23121 0.17081 --0.253206 -0.236908 -0.0443888 -0.149428 0.386275 0.232451 -0.147867 0.371017 0.232576 -0.152926 0.356705 0.229375 -0.149032 0.338169 0.225131 -0.166114 0.363727 0.223417 -0.172256 0.353426 0.215568 -0.179541 0.338962 0.221969 -0.181625 0.354174 0.201921 -0.186426 0.343494 0.187951 -0.192075 0.328517 0.215563 -0.192453 0.320313 0.195502 -0.17815 0.351274 0.174302 -0.17655 0.370022 0.172318 -0.165276 0.33179 0.225541 -0.00939661 -0.323412 0.0804555 -0.117561 -0.144032 0.250595 -0.120353 -0.125008 0.233582 -0.111251 -0.145731 0.271784 -0.11093 -0.120815 0.285658 -0.0754192 -0.0921393 0.301481 -0.0769558 -0.124739 0.290267 -0.0748528 -0.117401 0.265004 -0.0426218 -0.103655 0.282975 -0.0504123 -0.0659143 0.287583 -0.0836935 -0.0669285 0.279976 -0.0588543 -0.11803 0.279938 -0.0586609 -0.105277 0.297458 -0.0557269 -0.0839063 0.29826 -0.0381914 -0.083198 0.286361 -0.0371995 -0.0680895 0.266535 -0.047335 -0.0888232 0.265922 -0.05563 -0.0732275 0.247968 -0.0616189 -0.0543441 0.232231 -0.0811821 -0.0784737 0.231059 -0.0764784 -0.0353683 0.254905 -0.0689625 -0.0703705 0.29266 -0.0661415 -0.0548095 0.278244 -0.0608765 -0.028919 0.235586 -0.087271 -0.018263 0.234864 -0.0756435 -0.10953 0.298971 -0.0951645 -0.0988538 0.288259 -0.0930654 -0.116235 0.295574 -0.0947803 -0.133863 0.284699 -0.0966033 -0.0830131 0.273711 -0.113798 -0.076403 0.258582 -0.127442 -0.0600385 0.234949 -0.132794 -0.0953943 0.252522 -0.0489111 -0.0556973 0.272686 -0.0606107 -0.0431898 0.263976 -0.0971312 -0.036293 0.245012 -0.110687 -0.0488392 0.23675 -0.126176 -0.0414728 0.221378 -0.136057 -0.0243951 0.201716 -0.122078 0.00127519 0.196707 -0.142291 0.00027007 0.178375 -0.0664164 -0.0191578 0.217877 -0.0802696 -0.0234954 0.199319 -0.0910082 -0.0454502 0.20083 -0.0798776 -0.132726 0.273919 -0.102454 0.00166634 0.210465 -0.0802091 -0.00534582 0.192641 -0.0863635 0.00814252 0.179895 -0.0946306 0.0282399 0.168655 -0.0981336 0.029523 0.148764 -0.101777 0.0395859 0.135111 -0.102082 0.0581642 0.134881 -0.0956437 0.0321468 0.122829 -0.0938658 0.0161985 0.122282 -0.107901 0.00369122 0.128399 -0.078491 0.00576055 0.124425 -0.0841199 0.0382034 0.11313 -0.0659972 0.0331947 0.109714 -0.0520056 0.0196069 0.12098 -0.108345 -0.0142416 0.126349 -0.0915975 -0.0313276 0.129913 -0.122833 -0.0287943 0.124051 -0.146418 -0.0426624 0.119895 -0.142658 -0.0228961 0.131632 -0.132595 -0.0194368 0.150249 -0.160419 -0.0327892 0.133975 -0.163125 -0.050543 0.147173 -0.060233 0.0422739 0.0970401 -0.0469107 0.0353785 0.0851224 -0.0330985 0.0252247 0.0760721 -0.0344131 0.0145341 0.0525338 -0.0185411 0.0141735 0.0691085 --0.00651953 0.00766464 0.081092 -0.0314926 0.0314677 0.0951056 -0.0155266 0.0258059 0.107793 -0.11957 0.0050674 0.144577 -0.11348 -0.010764 0.15919 -0.0864989 0.00930931 0.196192 -0.112679 0.0343949 0.172228 -0.110056 0.0172326 0.144505 --0.231861 -0.256194 -0.0373807 --0.233847 -0.249852 -0.0220477 --0.230235 -0.258784 -0.00973726 --0.217151 -0.280061 0.00814621 --0.228026 -0.273051 0.0389012 --0.222798 -0.271648 -0.00409819 --0.229682 -0.268052 0.0118625 --0.212876 -0.279385 -0.0140325 --0.214439 -0.277583 -0.0303969 --0.199886 -0.285356 -0.0320197 --0.187179 -0.295224 -0.0255156 --0.167219 -0.315028 -0.0304058 --0.156845 -0.302235 -0.0544314 --0.236758 -0.254924 0.00227314 -0.0884351 -0.404348 -0.0195924 -0.0722252 -0.268984 0.0670772 -0.070147 -0.245252 0.0862941 -0.0966471 -0.229612 0.0882203 -0.0926053 -0.203806 0.103764 -0.0777673 -0.186021 0.118999 -0.0417862 -0.182948 0.126604 -0.0709209 -0.158179 0.128709 -0.095465 -0.128791 0.124296 -0.0102764 -0.16375 0.133447 --0.0233099 -0.171526 0.153541 --0.0499934 -0.0465621 -0.00582837 --0.0749973 -0.0355191 0.0101971 --0.0645299 -0.324704 -0.077104 -0.221779 0.470802 0.0892895 -0.219761 0.464582 0.1032 -0.246597 0.0246927 0.0795416 -0.253586 0.0320272 0.0977802 -0.252394 0.0305168 0.0670227 -0.261064 0.0424817 0.0637394 -0.267205 0.0556508 0.058182 -0.262106 0.0615289 0.0436471 -0.250683 0.0500973 0.0298185 -0.272972 0.0454164 0.0731281 -0.274554 0.0385023 0.090004 -0.285852 0.0505507 0.0780368 -0.287544 0.066644 0.0791877 -0.280213 0.0764884 0.0661465 -0.226162 0.0537579 -0.00800313 -0.231726 0.0372941 0.00212281 -0.226833 0.070796 -0.0125112 --0.0228881 0.0822405 -0.0141151 --0.0164496 0.062439 -0.0353807 --0.0311204 0.09353 -0.0291259 --0.0468617 0.0890411 -0.0503417 --0.0192694 0.0958145 -0.0445491 --0.0451838 0.0631149 -0.0497516 --0.0349277 0.0970377 -0.0439657 --0.0281121 0.0908295 -0.0597278 --0.0422149 0.0875756 -0.0360098 --0.0493018 0.0748281 -0.0407497 --0.0508974 0.0756362 -0.0555946 --0.045787 0.0616401 -0.0714534 --0.0463878 0.0860458 -0.0643425 --0.0469 0.0754389 -0.0734442 --0.0361876 0.0721397 -0.0877453 --0.0344753 0.0506229 -0.0835042 --0.0209274 0.0429352 -0.0951495 --0.00835098 0.0548898 -0.109767 --0.0439408 0.0675081 -0.0809412 --0.0384794 0.0604865 -0.0888934 --0.0325578 0.0529818 -0.0938199 --0.0250287 0.0507859 -0.100857 --0.0171358 0.0586173 -0.103485 --0.00108771 0.0669554 -0.09638 -0.0141047 0.0764389 -0.0789872 --0.0166767 0.0687863 -0.0931008 --0.0154196 0.0443974 -0.106822 -0.00804033 0.0334573 -0.118139 --0.00353356 0.0420783 -0.115203 -0.00267945 0.0333406 -0.10199 -0.0284162 0.0260832 -0.102463 -0.0140719 0.0342408 -0.0876383 -0.01206 0.042083 -0.0690778 -0.0262722 0.0834922 -0.057317 -0.052314 0.0257381 -0.0888721 -0.0550064 0.0153199 -0.121681 -0.0742668 0.0307726 -0.0684202 -0.0254542 0.0462258 -0.0537161 -0.0192751 0.0579365 -0.0299961 -0.043388 0.0389601 -0.0621497 -0.0632571 0.0407552 -0.0511604 -0.0628168 0.0511918 -0.0295138 --0.00769957 0.0468719 -0.0674097 -0.0356439 0.036944 -0.129564 -0.00985644 0.0486694 -0.11871 -0.0293481 0.0500299 -0.116265 -0.034304 0.0639803 -0.0982281 -0.0203799 0.0394103 -0.126099 -0.0274107 0.0238815 -0.120304 --0.00989932 0.038173 -0.0977698 -0.0409915 0.0204434 -0.130582 -0.0603476 0.0361414 -0.135064 -0.0745924 0.0111822 -0.144256 -0.0995383 0.0164619 -0.152082 -0.079494 0.0098491 -0.119596 -0.109969 -0.00178903 -0.14121 -0.128369 -0.00667529 -0.145234 -0.147176 -0.000628591 -0.159936 -0.143478 0.0207956 -0.143584 -0.15945 -0.0140125 -0.151421 -0.167046 -0.013357 -0.130058 -0.175024 -0.000463673 -0.161193 -0.200548 0.00730904 -0.144289 -0.228188 0.00606999 -0.13371 -0.232374 -0.0225206 -0.12062 -0.217352 -0.0205681 -0.149989 -0.196446 -0.0094088 -0.159916 -0.0468892 0.0318746 -0.136603 -0.0588847 0.0194871 -0.141629 -0.0497176 0.0425705 -0.12546 -0.067316 0.0466181 -0.121528 -0.0829023 0.0554408 -0.10632 -0.0702884 0.0671402 -0.0864572 -0.0736048 0.0280122 -0.141966 --0.0393368 0.0927115 -0.058201 --0.074253 -0.462729 -0.0674302 --0.0882268 -0.46748 -0.0836924 --0.10265 -0.454789 -0.102517 --0.100302 -0.434985 -0.0953132 --0.123508 -0.439573 -0.0964816 -0.30376 -0.329009 -0.220778 --0.34576 -0.287787 -0.1184 --0.333412 -0.299886 -0.118936 --0.318278 -0.306436 -0.109371 --0.342798 -0.273541 -0.122349 --0.330946 -0.265681 -0.13448 --0.32759 -0.278136 -0.147142 --0.313986 -0.269795 -0.152301 --0.304937 -0.277784 -0.160852 --0.289134 -0.290053 -0.171285 --0.300398 -0.29313 -0.159904 --0.305136 -0.304081 -0.143109 --0.303823 -0.326091 -0.148763 --0.297334 -0.338307 -0.131821 --0.0297344 -0.320795 0.126249 --0.0440833 -0.337561 0.102751 --0.0635963 -0.3222 0.124646 --0.0820192 -0.303394 0.145977 --0.0280108 -0.346215 0.0792887 -0.190023 -0.166658 -0.160984 -0.187606 0.489479 0.0788413 -0.169798 0.492699 0.0805537 -0.160239 0.479513 0.0724411 -0.160997 0.477019 0.0876359 -0.167909 0.48647 0.0907839 -0.168839 0.465921 0.0685027 -0.185848 0.461592 0.0756332 -0.163713 0.449815 0.0719867 -0.171034 0.442192 0.0868531 -0.172622 0.439121 0.100638 -0.176648 -0.0540189 0.138439 -0.191763 0.435033 0.0761478 -0.150024 0.450095 0.173316 -0.142684 0.444609 0.180888 -0.134925 0.435737 0.185852 -0.0901553 -0.0455623 0.257862 -0.0764097 -0.0467149 0.266788 -0.218445 0.190081 -0.00453558 -0.212983 0.187132 0.0158667 -0.234912 0.203072 -0.0090602 -0.217724 0.208098 -0.00630956 -0.209095 0.231498 0.0538229 -0.223398 0.222537 0.0510417 -0.229495 0.205818 0.051858 -0.241614 0.200114 0.0387668 -0.245196 0.212463 0.0252865 -0.232616 0.199508 0.066443 -0.245867 0.187445 0.0704899 -0.218046 0.211438 0.0580939 -0.206013 0.207163 0.0667587 -0.208167 0.217137 0.0808695 -0.189985 0.216171 0.0819923 --0.328288 -0.282754 -0.0562593 --0.321215 -0.270892 -0.0527491 --0.321885 -0.294532 -0.0580289 --0.324301 -0.301836 -0.070993 --0.309665 -0.294095 -0.0515043 --0.298437 -0.298535 -0.0585809 --0.293755 -0.297175 -0.0685565 --0.301865 -0.303693 -0.0670723 --0.312242 -0.307406 -0.0760424 -0.113855 0.0103494 0.135894 -0.117356 -0.000872108 0.134011 -0.24192 0.231796 0.179613 -0.236568 0.24336 0.180529 -0.226028 0.250706 0.186633 -0.280884 -0.220307 -0.163899 -0.26654 -0.211351 -0.150233 -0.265056 -0.233891 -0.14678 -0.318482 -0.331697 -0.239506 -0.324395 -0.322699 -0.253479 -0.31194 -0.314679 -0.269415 -0.329739 -0.334727 -0.229668 -0.336703 -0.323152 -0.229076 -0.344428 -0.313085 -0.23571 -0.342498 -0.293727 -0.236916 -0.350543 -0.322922 -0.2465 -0.353252 -0.314 -0.261759 -0.347047 -0.326986 -0.235121 -0.0827363 -0.465408 -0.0592863 -0.0757733 -0.45735 -0.0483001 -0.0758332 -0.442944 -0.0486429 -0.0763754 -0.427162 -0.064874 -0.0810297 -0.429275 -0.0354104 -0.0760881 -0.402196 -0.0628009 -0.0803331 -0.462498 -0.0366385 -0.075746 -0.427719 -0.0496393 -0.213629 0.454927 0.154222 -0.216172 0.433851 0.152191 -0.213875 -0.163106 0.208849 -0.228479 0.0837143 -0.0171959 -0.214862 0.0777937 -0.0178642 -0.203914 0.0820668 -0.0169156 -0.213551 0.066976 -0.0133462 --0.291696 -0.291685 -0.053913 --0.288445 -0.286967 -0.0453936 --0.283457 -0.280518 -0.0392925 -0.198493 0.267303 0.113918 --0.00816198 -0.315253 0.11742 -0.312224 -0.337685 -0.224702 -0.0776115 -0.0114015 0.225043 -0.094616 -0.00802053 0.222738 -0.212523 -0.00317223 -0.153514 -0.228944 -0.0069181 -0.146501 -0.230166 -0.0223035 -0.138067 -0.212902 -0.0238532 -0.132712 -0.194522 -0.0219296 -0.138137 -0.240306 -0.00819778 -0.130311 -0.24497 -0.0122967 -0.113875 -0.221562 -0.0094445 -0.0973672 -0.242491 -0.00970392 -0.0927411 --0.195428 -0.293105 -0.0588105 --0.174639 -0.293714 -0.0615636 --0.159198 -0.295096 -0.0708856 --0.150745 -0.297027 -0.0916319 --0.165094 -0.309633 -0.116063 --0.129532 -0.293794 -0.0901166 --0.12154 -0.29002 -0.105668 --0.103655 -0.284754 -0.110478 --0.083304 -0.273535 -0.126518 --0.126229 -0.291149 -0.124142 --0.0551206 -0.282297 -0.123125 --0.0279253 -0.280704 -0.123568 --0.0254074 -0.296918 -0.100527 --0.143636 -0.296672 -0.0765836 -0.228772 0.249576 0.00807469 -0.246793 0.197559 -0.000456927 -0.249351 0.185655 0.00965958 -0.077471 -0.38535 -0.0716786 -0.0780138 -0.366405 -0.0698751 -0.0796117 -0.349597 -0.0694382 -0.0817673 -0.324046 -0.067713 -0.0920794 -0.339278 -0.0766687 -0.0745961 -0.308674 -0.0513846 -0.0686559 -0.285979 -0.0584538 -0.0701691 -0.296007 -0.0346939 -0.0958416 -0.324563 -0.0739969 -0.111686 -0.315208 -0.0712996 -0.118212 -0.295045 -0.0671537 -0.145789 -0.285221 -0.0572786 -0.142444 -0.258951 -0.0651148 -0.125807 -0.256448 -0.0777825 -0.0725557 -0.302564 -0.0188272 -0.0757738 -0.318738 -0.0113816 -0.0755625 -0.334153 -0.022547 -0.0716925 -0.292684 -0.00740334 -0.0574002 -0.292216 -0.00131701 -0.0442788 -0.29974 -0.0118635 -0.0322898 -0.309073 -0.00126775 -0.0158934 -0.319374 -0.00927168 -0.00270388 -0.318254 -0.036966 -0.00321031 -0.33353 0.006095 -0.0101246 -0.330737 0.0331595 -0.0571801 -0.291727 0.0142738 -0.0411549 -0.302632 0.0323113 -0.0568056 -0.290026 0.0325413 -0.0726196 -0.279494 0.0399911 -0.0843075 -0.293947 0.0355701 -0.0802412 -0.296732 0.0208207 -0.0757523 -0.290551 0.00911073 -0.0904109 -0.307387 0.0401159 -0.0937218 -0.321431 0.0333001 -0.0876185 -0.332554 0.0196986 --0.261918 -0.356193 -0.123562 --0.274572 -0.363058 -0.128675 --0.283885 -0.374671 -0.137095 -0.0993425 -0.475253 -0.0345906 -0.105878 -0.470302 -0.022596 -0.161209 0.00758193 -0.157082 -0.162861 -0.00496129 -0.160125 -0.14622 0.0234899 0.00861402 -0.16407 0.00688456 0.00334424 -0.187476 0.0108016 -0.000104135 -0.198618 0.0267239 -0.00776275 -0.213159 0.032032 -0.0211609 -0.229764 0.0271876 -0.0288068 -0.229938 0.0432028 -0.0342052 -0.231973 0.0361819 -0.05417 -0.241585 0.0230077 -0.0674939 -0.235826 0.0200922 -0.0913851 -0.229532 0.00831192 -0.0604593 -0.228417 0.0182383 -0.11087 -0.214387 0.0313739 -0.095916 -0.23826 0.0319925 -0.0407356 -0.240603 0.0153722 -0.0478262 -0.185434 0.0390225 -0.0120783 -0.174046 0.035224 -0.0278333 -0.183039 0.0254835 -0.0435084 -0.185088 0.013208 -0.0664572 -0.184517 -0.00115634 -0.0944301 -0.15883 0.0112792 -0.0789407 -0.166768 0.0454452 -0.0125857 -0.149871 0.0473393 -0.0127261 -0.24097 0.0273212 -0.0523021 -0.244689 0.0183228 -0.0578172 -0.239121 0.00955592 -0.0564169 -0.23067 0.0133196 -0.0511149 -0.217815 0.013095 -0.0555127 -0.202953 0.0215885 -0.0445658 -0.223255 0.0309758 -0.00933388 -0.246338 0.00680161 -0.0920968 --0.29666 -0.251794 -0.0420083 --0.282377 -0.246563 -0.0382484 --0.274248 -0.257163 -0.0411355 -0.121069 0.3764 0.220244 -0.124178 0.361607 0.222425 -0.12316 0.347289 0.21517 -0.120818 0.335014 0.200629 -0.131773 0.318764 0.203442 -0.240354 -0.189104 -0.174254 -0.226426 -0.17846 -0.157236 -0.25469 -0.197929 -0.186668 -0.262527 -0.207499 -0.20859 -0.275709 -0.206748 -0.189755 -0.268196 -0.218714 -0.226562 -0.252109 -0.214264 -0.221034 -0.231966 -0.219863 -0.222417 -0.16439 -0.290409 -0.045741 -0.174232 0.353361 0.161991 -0.167416 0.346038 0.151615 -0.153417 0.351454 0.147309 -0.141711 0.365653 0.160008 -0.174784 0.33351 0.146976 -0.172695 0.32064 0.137876 -0.171538 0.327653 0.122668 --0.153467 -0.465689 -0.0917447 --0.142244 -0.458217 -0.0978282 --0.137315 -0.444624 -0.0945322 --0.142546 -0.468457 -0.0929801 --0.130113 -0.470071 -0.0887414 --0.111868 -0.466578 -0.0912306 --0.129822 -0.476932 -0.0773011 --0.116192 -0.47396 -0.0799073 --0.102072 -0.471199 -0.0816089 -0.0648327 -0.288276 0.006469 -0.290523 -0.14969 0.120852 -0.286341 -0.149084 0.132395 -0.276561 -0.154417 0.131433 -0.266874 -0.144863 0.128327 -0.266123 -0.155511 0.139202 -0.254682 -0.156501 0.14847 -0.243072 -0.144724 0.147014 -0.246658 -0.132891 0.13506 -0.271484 -0.158645 0.148826 -0.262513 -0.161728 0.150556 -0.255383 -0.163883 0.160093 -0.258182 -0.154155 0.170424 -0.247063 -0.165211 0.168804 -0.236375 -0.166699 0.177267 -0.223809 -0.16596 0.182974 -0.219971 -0.167303 0.19665 -0.224247 -0.15536 0.203023 -0.206877 -0.166582 0.198203 -0.201256 -0.167208 0.210293 -0.187949 -0.165778 0.214542 -0.175727 -0.151043 0.207983 -0.205839 -0.156778 0.218275 -0.191606 -0.158183 0.226132 -0.180969 -0.154585 0.235065 -0.170507 -0.150398 0.244702 -0.167678 -0.135398 0.246604 -0.174695 -0.121433 0.234619 -0.192199 -0.123695 0.219574 -0.161002 -0.150315 0.252813 -0.149901 -0.156196 0.25282 -0.145756 -0.15618 0.238445 -0.158231 -0.157867 0.22927 -0.148019 -0.147104 0.261177 -0.14318 -0.132144 0.260892 -0.131504 -0.114456 0.261736 -0.136414 -0.145181 0.266602 -0.124033 -0.152215 0.261533 -0.128756 -0.151616 0.248741 -0.169715 -0.162656 0.235355 -0.125332 -0.145025 0.270918 -0.118124 -0.13431 0.277651 -0.122928 -0.121301 0.271952 -0.113017 -0.148351 0.260498 -0.105868 -0.137752 0.254904 -0.0970603 -0.116879 0.246491 -0.113377 -0.13188 0.244126 -0.102989 -0.14407 0.26402 -0.09993 -0.141861 0.276203 --0.208269 -0.22401 0.141633 --0.20692 -0.250284 0.132437 -0.0965268 -0.427234 -0.105996 -0.0991486 -0.439673 -0.0998914 -0.19126 -0.17619 0.0390751 -0.184715 -0.204433 0.0324441 -0.164304 -0.221583 0.0465757 -0.1444 -0.208807 0.0686986 -0.0226172 -0.0147867 0.137076 --0.142255 -0.49298 -0.0216335 -0.233691 0.0208001 -0.0385404 --0.192672 -0.315988 0.0839294 --0.202286 -0.295403 0.104035 --0.212462 -0.276423 0.111373 --0.218066 -0.285356 0.0914372 --0.230517 -0.270625 0.068595 -0.136055 0.0355608 0.0131192 -0.121886 0.0399552 0.0198872 -0.106186 0.0275039 0.0255554 -0.0928691 0.0373146 0.0446539 -0.107832 0.0113031 0.0106037 -0.0853288 0.00897116 0.0135994 -0.0648776 -0.00152148 0.00684991 -0.301901 0.1917 0.196837 -0.304498 0.181718 0.192274 -0.29757 0.171529 0.192207 -0.283611 0.170157 0.190984 -0.17589 0.332048 0.230978 -0.176274 0.318104 0.221305 -0.189786 0.308195 0.22975 -0.199452 0.291101 0.221444 -0.210647 0.278153 0.221165 -0.212403 0.290739 0.232707 -0.229872 0.295702 0.229994 -0.238851 0.283146 0.23072 -0.237981 0.260514 0.227763 -0.248013 0.243086 0.221386 -0.245976 0.271423 0.230129 -0.237201 0.240055 0.208931 -0.239318 0.292509 0.222434 -0.244312 0.28734 0.210021 -0.234086 0.289095 0.199595 -0.221914 0.298769 0.201947 -0.1778 0.322602 0.229777 -0.232474 0.299285 0.221376 -0.223334 0.304746 0.213608 -0.207337 0.311267 0.204764 -0.224642 0.304397 0.226583 -0.214619 0.302742 0.233384 -0.203769 0.313465 0.232857 -0.204968 0.319702 0.218324 -0.201735 0.298965 0.232154 --0.0492057 -0.0812756 -0.10551 --0.0710597 -0.075701 -0.12067 --0.0879497 -0.0905516 -0.13677 --0.0843511 -0.119489 -0.147588 --0.0762899 -0.059326 -0.102542 --0.0985707 -0.0477827 -0.098696 --0.118275 -0.0536394 -0.113913 --0.11441 -0.0717159 -0.128365 -0.179503 0.303256 0.0436704 -0.192013 0.290229 0.0290644 -0.17689 0.287515 0.0370015 -0.192038 0.271729 0.0156116 -0.209167 0.267708 0.00969983 -0.200133 0.255704 0.00478026 --0.247504 -0.392344 -0.224787 --0.247477 -0.405986 -0.218212 --0.243099 -0.400741 -0.206484 --0.246703 -0.405341 -0.193269 --0.249065 -0.417066 -0.193806 --0.263856 -0.417281 -0.202583 --0.253854 -0.410374 -0.185389 --0.264275 -0.407223 -0.177693 --0.272044 -0.40206 -0.166216 --0.284666 -0.409799 -0.163624 --0.28689 -0.423019 -0.170791 --0.283285 -0.428723 -0.186577 --0.301536 -0.424549 -0.197331 --0.276684 -0.426319 -0.176604 --0.270204 -0.426002 -0.18843 --0.267033 -0.421163 -0.178871 --0.28572 -0.431162 -0.176917 -0.0984334 0.0428629 0.147655 -0.0984512 0.0370662 0.158757 --0.324734 -0.278381 -0.0492733 --0.319049 -0.278035 -0.0388895 --0.314693 -0.271902 -0.0298211 --0.0814975 -0.486596 -0.0514846 --0.0716057 -0.47534 -0.0576231 --0.0807984 -0.475501 -0.0687174 --0.0782273 -0.46782 -0.0773567 --0.0794515 -0.454639 -0.076562 --0.0865219 -0.442019 -0.0638219 --0.0826697 -0.456554 -0.0896798 --0.0913604 -0.446861 -0.0969099 --0.0924184 -0.432044 -0.0753911 --0.098273 -0.416607 -0.0817744 --0.111942 -0.395187 -0.0818225 --0.0923505 -0.401008 -0.0727607 --0.110463 -0.376831 -0.0757945 --0.0955645 -0.358515 -0.0727168 --0.0748788 -0.367775 -0.053732 --0.099584 -0.336657 -0.0718408 --0.0617229 -0.346951 -0.0508932 --0.0372908 -0.340191 -0.0358611 --0.187209 -0.0514446 0.134371 --0.172275 -0.062062 0.144345 --0.181772 -0.0387772 0.12984 -0.27325 0.225257 0.167666 -0.283037 0.221467 0.169903 -0.141164 -0.00931766 -0.150578 -0.223335 -0.262546 -0.149684 -0.199958 -0.258684 -0.141852 -0.218212 -0.249649 -0.131577 --0.0730224 -0.485274 -0.0383372 --0.0765985 -0.473674 -0.025717 --0.0833487 -0.485599 -0.0202509 --0.0969784 -0.487241 -0.0157338 --0.106328 -0.475595 -0.0156679 --0.107519 -0.486132 -0.0147883 --0.117398 -0.491224 -0.0155421 --0.11866 -0.497871 -0.0255495 -0.192205 -0.0198585 -0.151725 -0.281772 0.238327 0.210562 -0.269826 0.243309 0.222088 -0.243244 0.207816 -0.00325363 -0.239729 0.221932 -0.000908316 -0.243256 0.225044 0.0142927 -0.235434 0.23543 0.00346349 -0.158954 -0.447934 -0.074517 -0.164 -0.440163 -0.0849532 -0.160645 -0.422992 -0.0839209 -0.160099 -0.41995 -0.0633914 -0.170063 -0.432118 -0.0580684 -0.170775 -0.442201 -0.0470561 -0.167356 -0.452883 -0.036848 -0.157537 -0.464023 -0.0432353 -0.154265 -0.458207 -0.0582632 -0.146921 -0.466798 -0.0291891 -0.17099 -0.445942 -0.0649823 -0.152813 -0.426396 -0.0985179 -0.142833 -0.435406 -0.0989063 -0.126573 -0.436557 -0.100225 -0.120949 -0.42189 -0.103936 -0.130685 -0.411647 -0.0938972 -0.160351 -0.460975 -0.0324222 -0.164884 -0.460693 -0.0398836 -0.167083 -0.430709 -0.0834012 -0.161089 -0.432508 -0.0931652 -0.165433 -0.424956 -0.0749657 -0.155909 -0.417913 -0.0743018 -0.149825 -0.407366 -0.067413 -0.142015 -0.399818 -0.0764637 -0.153043 -0.435416 -0.0966096 -0.148842 -0.431897 -0.101759 -0.145045 -0.424265 -0.10134 -0.147732 -0.418014 -0.0911084 -0.138977 -0.417451 -0.0965307 -0.139635 -0.411036 -0.0878314 -0.14174 -0.430413 -0.104765 -0.134784 -0.434536 -0.101822 -0.135086 -0.440748 -0.0918112 -0.13766 -0.447456 -0.0800542 -0.122938 -0.455334 -0.0698495 -0.127914 -0.428797 -0.106691 -0.135671 -0.430083 -0.106069 --0.0268041 -0.304928 0.142542 --0.0182344 -0.281255 0.151927 -0.000262015 -0.258244 0.142866 -0.0151742 -0.229959 0.128466 --0.00970849 -0.227931 0.154073 -0.0337917 -0.209923 0.115845 -0.0199495 -0.192987 0.125832 --0.154293 -0.0335699 -0.082135 --0.129631 -0.0324487 -0.0813475 --0.120097 -0.027431 -0.0586998 --0.0956922 -0.0340394 -0.0533561 --0.0814148 -0.0448428 -0.0722969 --0.0594432 -0.0515596 -0.0534184 --0.160793 -0.0482086 -0.0989707 --0.166155 -0.0307425 -0.0663998 --0.169924 -0.0270352 -0.0414151 --0.183311 -0.0375758 -0.0551581 --0.174206 -0.0274939 -0.0203147 --0.192353 -0.0397338 -0.00141151 --0.170447 -0.0249801 0.0063437 --0.211587 -0.0636911 -0.00501259 --0.0018753 -0.187141 -0.149775 -0.0183103 -0.182965 -0.142326 -0.0322217 -0.167313 -0.134641 -0.0236675 -0.146992 -0.123167 --0.00232452 -0.142332 -0.126149 -0.0375806 -0.18533 -0.140019 -0.0530379 -0.201545 -0.137283 -0.0772348 -0.20845 -0.140694 -0.0988175 -0.224384 -0.140468 -0.119251 -0.222512 -0.162731 -0.03788 -0.218276 -0.135529 -0.0772845 -0.19139 -0.155355 -0.080882 -0.225907 -0.127445 -0.0653619 -0.242778 -0.113036 -0.0731805 -0.173068 -0.155239 -0.0897045 -0.191329 -0.166141 -0.102707 -0.199007 -0.171074 -0.119058 -0.208637 -0.176942 -0.127514 -0.196293 -0.185172 -0.13998 -0.205302 -0.190755 -0.149824 -0.215626 -0.194546 -0.161245 -0.221969 -0.199237 -0.175364 -0.230409 -0.20401 -0.173584 -0.215336 -0.204853 -0.178202 -0.198362 -0.197173 -0.188397 -0.22907 -0.211289 -0.202734 -0.22056 -0.215065 -0.199745 -0.239634 -0.214151 -0.0815106 -0.18364 -0.162988 --0.172104 -0.359269 -0.00938238 --0.172319 -0.335226 -0.0164663 --0.16873 -0.368903 -0.0231312 --0.292266 -0.291505 -0.0889456 --0.288266 -0.299574 -0.0955502 --0.280983 -0.308012 -0.105167 --0.278654 -0.297571 -0.101297 --0.274336 -0.285615 -0.103446 --0.260134 -0.28158 -0.0989442 --0.257005 -0.265144 -0.10215 --0.26942 -0.305285 -0.10276 --0.257003 -0.309674 -0.100476 --0.244993 -0.306014 -0.0938734 --0.249351 -0.292113 -0.0926885 --0.25954 -0.322424 -0.104282 --0.267093 -0.332079 -0.111051 --0.283312 -0.328944 -0.119574 --0.249017 -0.331591 -0.10481 --0.232981 -0.32784 -0.100164 --0.240291 -0.342062 -0.113719 --0.229688 -0.345883 -0.126712 --0.23058 -0.352629 -0.145077 --0.21352 -0.337371 -0.127344 --0.269191 -0.344874 -0.117105 --0.208623 -0.327937 -0.112241 --0.191793 -0.321843 -0.117022 --0.180909 -0.311277 -0.104708 -0.11012 0.10505 0.0238496 -0.213679 0.221732 0.163906 --0.0357839 -0.0025294 0.108473 --0.0312254 -0.0135193 0.128152 --0.0238807 -0.033229 0.139313 --0.00300831 -0.046529 0.144036 --0.00364169 -0.0760125 0.145155 --0.0103288 -0.10643 0.141831 -0.015326 -0.129347 0.142131 --0.041062 -0.0443202 0.130625 --0.0555252 -0.0465254 0.114753 --0.0556686 -0.0325657 0.0996413 --0.0768736 -0.0422105 0.0949058 --0.0167984 0.000564353 0.123722 -0.00524698 0.0020139 0.129964 --0.0281137 -0.0861213 0.139333 --0.0785841 -0.0379469 0.0747431 --0.0762529 -0.0505618 0.114297 --0.032521 -0.108383 0.136839 --0.0633754 -0.0458183 0.101476 --0.0250298 0.00663901 0.112981 --0.0219675 0.00393164 0.0935556 --0.147404 -0.304789 -0.127071 -0.192111 0.473304 0.143665 -0.202701 0.475169 0.131787 -0.206558 0.475874 0.120017 -0.202492 0.480449 0.108775 -0.157654 -0.366957 -0.0205798 -0.26661 -0.307414 -0.281977 -0.270077 -0.295571 -0.288815 -0.283263 -0.291236 -0.297392 -0.290598 -0.279448 -0.297082 -0.304158 -0.276932 -0.29882 -0.315035 -0.2885 -0.301158 -0.307588 -0.298676 -0.297006 -0.297613 -0.307939 -0.283621 -0.293787 -0.301201 -0.295424 -0.318389 -0.297707 -0.296483 -0.328066 -0.301032 -0.289042 -0.324582 -0.309109 -0.276338 -0.33579 -0.291676 -0.2964 -0.346867 -0.288071 -0.287976 -0.353956 -0.299169 -0.28317 -0.345495 -0.307423 -0.274703 -0.352866 -0.288693 -0.277818 -0.351312 -0.284395 -0.265224 -0.355354 -0.294774 -0.257468 -0.360217 -0.304818 -0.260578 -0.35682 -0.308388 -0.269609 -0.359106 -0.312264 -0.252967 -0.356474 -0.316127 -0.245135 -0.351445 -0.319298 -0.237976 --0.26647 -0.314705 -0.199507 --0.27426 -0.329739 -0.204261 --0.290128 -0.337563 -0.206145 --0.303723 -0.332243 -0.195363 --0.303858 -0.323407 -0.176279 --0.302757 -0.349394 -0.210447 --0.304665 -0.364526 -0.223447 --0.321319 -0.375627 -0.232938 --0.285437 -0.371465 -0.224735 --0.28011 -0.37957 -0.242654 --0.314609 -0.397575 -0.24523 --0.31498 -0.344154 -0.199865 --0.330892 -0.351901 -0.189469 --0.332902 -0.388937 -0.243068 --0.348245 -0.402805 -0.233911 --0.328232 -0.404959 -0.235266 --0.273541 -0.396265 -0.240028 --0.293049 -0.39498 -0.245656 --0.355214 -0.402159 -0.217135 --0.360108 -0.415844 -0.205721 --0.346381 -0.42668 -0.201491 --0.340374 -0.436455 -0.180032 --0.360217 -0.406031 -0.193698 --0.356506 -0.391255 -0.182727 --0.356512 -0.407116 -0.177427 --0.347384 -0.421732 -0.172779 --0.348669 -0.37532 -0.17511 --0.341266 -0.408591 -0.160307 --0.332592 -0.391247 -0.153464 --0.323849 -0.407723 -0.153687 --0.349384 -0.43169 -0.189135 --0.335815 -0.43119 -0.192046 --0.324454 -0.435806 -0.181733 --0.331465 -0.433295 -0.170921 --0.314528 -0.431132 -0.168412 --0.260177 -0.397767 -0.235 --0.252953 -0.401301 -0.227678 --0.247407 -0.394723 -0.238053 --0.24372 -0.401046 -0.225417 --0.243948 -0.396615 -0.216223 --0.258003 -0.38952 -0.247437 --0.272162 -0.387844 -0.252537 --0.287147 -0.384539 -0.255755 --0.302942 -0.384129 -0.252391 --0.315505 -0.382766 -0.24459 --0.323099 -0.390444 -0.249836 --0.312399 -0.390336 -0.253745 --0.328503 -0.399215 -0.245058 --0.340635 -0.398589 -0.24271 --0.266346 -0.382624 -0.244488 --0.267321 -0.391914 -0.245578 --0.0266812 0.0695328 -0.0242573 --0.00773299 0.0681739 -0.0184911 -0.0122858 0.0669077 -0.0123781 -0.0150035 0.0767227 0.00239352 -0.0141467 0.0954062 -0.00215996 -0.0325338 0.098411 -0.00617133 -0.0450676 0.0983028 0.010086 -0.0314473 0.0730983 0.00401189 -0.0505593 0.0686309 0.00292132 -0.0698817 0.067505 0.00832925 -0.145383 0.180744 0.0984363 -0.132189 0.17376 0.0925198 -0.121241 0.164951 0.0850023 -0.133425 0.169934 0.0774819 -0.11505 0.153676 0.07879 --0.083942 -0.368893 -0.0674225 --0.0809876 -0.385027 -0.0581697 --0.0723248 -0.382058 -0.0416794 --0.00904893 0.0914446 -0.0120745 -0.00504523 0.0987359 -0.0150796 -0.0060609 0.0932522 -0.034057 -0.0248461 0.0873188 -0.0377031 -0.0363994 0.089055 -0.023789 -0.00213821 0.0914225 -0.00482177 -0.0092558 0.0863088 0.00212053 -0.106375 0.0274106 0.14138 --0.263884 -0.385451 -0.252252 -0.258755 0.24689 0.225661 -0.259085 0.23306 0.219814 -0.249971 0.25591 0.228242 --0.322841 -0.345115 -0.164492 --0.323706 -0.355326 -0.152393 --0.279169 -0.265328 -0.0439542 --0.285416 -0.267917 -0.0516616 --0.294745 -0.263175 -0.0517725 -0.23393 -0.0149701 -0.10397 -0.219738 -0.0165453 -0.112039 -0.204608 -0.00981825 -0.104246 -0.187184 -0.00954937 -0.110855 -0.228145 0.230452 0.102316 -0.214447 0.238029 0.0983297 -0.23229 0.220992 0.0943503 -0.215398 0.247623 0.105271 -0.217938 0.25177 0.117669 -0.210276 0.258003 0.111405 -0.220949 0.241286 0.103103 -0.290344 -0.337843 -0.233955 -0.276226 -0.337233 -0.22831 -0.296573 -0.339782 -0.226215 -0.227663 0.461812 0.0838481 -0.234265 0.455281 0.0718225 -0.229698 0.445794 0.0603386 -0.225781 0.470182 0.0813133 -0.214396 0.4698 0.0788369 -0.208406 0.478123 0.0862021 -0.214031 0.460896 0.0711899 -0.217085 0.451741 0.0628259 -0.219354 0.474333 0.0827819 -0.21619 0.47758 0.0903862 -0.217994 0.472178 0.097233 -0.209525 0.481852 0.0939712 -0.227208 0.456115 0.0633709 -0.234329 0.451682 0.0642008 -0.23166 0.462658 0.0759848 -0.273713 -0.249314 -0.252993 -0.274317 -0.268417 -0.267071 -0.263304 -0.282613 -0.260934 --0.240326 -0.404033 -0.2139 --0.241182 -0.408607 -0.207233 -0.243855 0.194683 0.113624 -0.235959 0.206195 0.114609 --0.329827 -0.30598 -0.098469 --0.057071 -0.0425853 0.0117122 --0.0551192 -0.0420888 0.0309046 --0.0534835 -0.0402863 0.0500454 --0.0435993 -0.0469165 0.00748095 --0.0255629 -0.0374196 0.00481763 --0.00817324 -0.0328811 -0.0061182 --0.00350439 -0.0437548 -0.0255784 --0.0349503 -0.0490115 -0.00492533 --0.0419875 -0.0536475 -0.0293419 --0.0139014 -0.0607747 -0.0378374 --0.0105205 -0.0780801 -0.0585195 --0.0335249 -0.0540562 -0.0180409 --0.0278411 -0.0595083 -0.0313207 -0.193253 0.49569 0.0858481 -0.189805 0.494474 0.0949255 -0.179559 0.491992 0.0947812 -0.19709 0.487808 0.0978913 -0.174576 0.497081 0.0879734 -0.179584 0.496616 0.0784813 -0.175502 0.48892 0.0725035 -0.169475 0.481635 0.069206 -0.164994 0.474033 0.0669237 -0.155945 0.469227 0.0714265 -0.159639 0.459314 0.0671634 -0.160451 -0.0544036 0.113535 -0.0454737 -0.0938547 0.293886 -0.0481458 -0.104952 0.292926 -0.0550885 -0.114349 0.29062 -0.0654216 -0.120788 0.289843 -0.0691263 -0.126298 0.278339 -0.0372417 -0.0943784 0.286279 -0.0377118 -0.088702 0.274623 -0.179652 -0.262284 0.0054219 -0.186087 -0.244472 0.00347757 -0.246807 -0.00615903 -0.103541 -0.242297 -0.0135206 -0.100916 -0.240802 -0.0172339 -0.108388 -0.232593 -0.0188607 -0.112191 -0.240348 -0.0194541 -0.117278 -0.238974 -0.018297 -0.127651 --0.164632 -0.397326 -0.024693 -0.0813645 -0.458079 -0.0716845 -0.302397 0.127887 0.0470846 -0.298473 0.138148 0.0455108 -0.292313 0.136477 0.0386335 -0.288054 0.130079 0.0326142 -0.28271 0.137427 0.0379629 -0.271939 0.136706 0.038053 -0.261008 0.14116 0.0404912 -0.28194 0.12422 0.0312108 -0.300876 0.126279 0.0551937 -0.295306 0.129324 0.0632115 -0.286846 0.130603 0.0706512 -0.282604 0.11846 0.0804122 -0.273418 0.134767 0.0747422 -0.296205 0.121995 0.0600976 -0.289869 0.116278 0.0576534 -0.283444 0.108666 0.0679441 -0.208186 0.435058 0.0678801 -0.218992 0.437774 0.0675136 -0.20517 0.444041 0.0615 -0.195457 0.447135 0.0675955 -0.224157 0.438328 0.0597838 -0.221367 0.446069 0.0584788 -0.168291 -0.443724 -0.076199 --0.239169 -0.248023 -0.0426243 --0.246509 -0.244281 -0.0523825 --0.245933 -0.250709 -0.0701381 --0.252213 -0.23035 -0.0598084 --0.256922 -0.212794 -0.063622 --0.26443 -0.20039 -0.048456 --0.236601 -0.248665 -0.0331221 --0.248238 -0.244973 -0.04299 --0.257005 -0.243536 -0.0363368 --0.264619 -0.253098 -0.0324677 --0.260909 -0.234911 -0.0387305 --0.270856 -0.228969 -0.0335016 --0.268106 -0.214873 -0.0367573 --0.255525 -0.250409 -0.0291707 --0.246354 -0.252152 -0.0213798 --0.25421 -0.258055 -0.0140562 --0.253371 -0.258976 0.00135493 --0.263391 -0.256528 0.0180325 --0.264412 -0.265611 -0.0106557 --0.268835 -0.263918 0.00476582 --0.24972 -0.252323 0.0327963 --0.239732 -0.259608 0.0493553 --0.242639 -0.251027 0.0706418 --0.27192 -0.270836 -0.02103 --0.264888 -0.241199 0.0366373 --0.279792 -0.22631 0.033251 --0.274206 -0.207933 0.0474852 --0.283361 -0.276288 -0.0174295 --0.267659 -0.226048 0.0482271 -0.202151 0.274483 0.19338 -0.194908 0.283204 0.198963 --0.157532 -0.273615 -0.179435 --0.176899 -0.279729 -0.184503 --0.188947 -0.290942 -0.187096 --0.192598 -0.3074 -0.18306 --0.203551 -0.297799 -0.190929 --0.222085 -0.288246 -0.191352 --0.217908 -0.303036 -0.194268 --0.355074 -0.426501 -0.195973 --0.354866 -0.423993 -0.205337 --0.355569 -0.419108 -0.214528 --0.353763 -0.412061 -0.224887 --0.346029 -0.286085 -0.1062 --0.341227 -0.288967 -0.0947058 --0.336277 -0.278132 -0.0971269 --0.328988 -0.269127 -0.105169 --0.340297 -0.292656 -0.0831052 --0.335578 -0.266756 -0.115188 --0.339311 -0.299368 -0.0917431 -0.212569 0.261061 0.18216 -0.216886 0.255854 0.175009 -0.219484 0.251322 0.162982 -0.212289 0.247584 0.170006 -0.218513 0.26322 0.154269 -0.225667 0.261532 0.178216 -0.218436 0.276817 0.178562 -0.234042 0.273996 0.185236 -0.223426 0.242038 0.154678 -0.21181 0.288948 0.181391 -0.220498 0.257755 0.18435 -0.211254 0.266563 0.187703 -0.211739 0.26954 0.199826 -0.278409 -0.209413 -0.174692 -0.233056 0.457965 0.0658843 -0.227063 0.46182 0.0682472 -0.220168 0.458063 0.0666597 --0.0481392 -0.0447802 0.0166181 -0.131516 0.0530135 0.000672445 -0.12038 0.0567042 0.000376152 -0.134766 0.046581 0.0097546 -0.0956782 -0.141364 0.26837 -0.0877085 -0.13595 0.269242 -0.0854698 -0.124959 0.261926 -0.0839071 -0.111836 0.253439 -0.0904091 -0.099649 0.239147 -0.0872053 -0.136949 0.279095 -0.085161 -0.130401 0.287023 -0.0763801 -0.13103 0.282851 --0.00884361 -0.0890856 -0.0728998 --0.00654069 -0.102279 -0.0895079 --0.0290648 -0.104665 -0.111248 --0.0100257 -0.116287 -0.107029 -0.0875054 -0.104584 0.297054 -0.191386 -0.264049 -0.175252 -0.190045 -0.262732 -0.156889 -0.204589 -0.269071 -0.178679 -0.222111 -0.273266 -0.172895 -0.189235 0.0753918 -0.0129238 -0.0782752 -0.467624 -0.0498343 -0.0759673 -0.46177 -0.0555898 -0.0772195 -0.460605 -0.0642783 -0.151932 0.323656 0.079912 -0.153175 0.313215 0.0881136 -0.161272 0.302381 0.0857396 -0.163146 0.324848 0.0718597 -0.151557 0.334415 0.0880604 -0.142886 0.334889 0.0972586 -0.140662 0.34411 0.107021 -0.133598 0.347717 0.117582 -0.131314 0.355467 0.129074 -0.127988 0.361743 0.142546 -0.123763 0.348981 0.138438 -0.119822 0.353337 0.149718 -0.116288 0.351928 0.168204 -0.226419 0.174912 -0.00671405 -0.232006 0.15884 0.00199041 -0.243933 0.169091 0.00253819 -0.237571 0.172786 -0.00612936 -0.183717 0.142245 0.147062 -0.183533 0.157982 0.153224 -0.200684 0.169917 0.154336 -0.175927 0.14817 0.154877 -0.164064 0.143191 0.159694 -0.181391 0.132744 0.149775 -0.177453 0.123521 0.157312 -0.108424 0.0602759 0.0311611 -0.101691 0.0511896 0.042029 -0.297187 0.12858 0.124591 -0.286869 0.125817 0.115842 -0.279264 0.13651 0.11071 -0.277464 0.150893 0.114839 -0.267972 0.163015 0.116989 -0.29066 -0.215317 -0.195858 --0.0439761 -0.143405 -0.149346 -0.0959309 0.0199379 0.158053 -0.0941358 0.00844127 0.16726 -0.273991 -0.158318 0.138404 -0.280108 -0.155995 0.136908 -0.28311 -0.154668 0.131232 -0.287165 -0.152142 0.126309 -0.291082 -0.145525 0.127381 -0.258354 -0.329753 -0.2515 -0.256649 -0.327468 -0.240856 -0.265642 -0.321399 -0.233195 -0.269005 -0.32965 -0.227653 -0.204877 0.287044 0.0357487 -0.289139 -0.339472 -0.226774 -0.282728 -0.335989 -0.224475 -0.283065 -0.327384 -0.221193 -0.28398 -0.316486 -0.219293 -0.289461 -0.305296 -0.210924 -0.2738 -0.310998 -0.221733 -0.2646 -0.301502 -0.221281 -0.282981 -0.339471 -0.231684 -0.275544 -0.336339 -0.239462 -0.259131 -0.2954 -0.232139 -0.281853 -0.296955 -0.202405 -0.287258 -0.287693 -0.192682 -0.301236 -0.282638 -0.194913 -0.23745 0.0270265 -0.0333549 -0.234865 0.0358956 -0.0292661 -0.240774 0.0243245 -0.0402136 -0.034599 -0.0884904 0.28182 -0.0345637 -0.0787252 0.277243 -0.0422003 -0.0728232 0.283477 -0.048607 -0.0763619 0.292696 -0.0395236 -0.0802562 0.266836 -0.0486856 -0.0788086 0.257578 -0.044992 -0.0647291 0.254151 -0.0587204 -0.0731238 0.296598 -0.068389 -0.0812067 0.300077 -0.0829644 -0.0808872 0.290453 -0.0435251 -0.0559756 0.262078 -0.20354 0.276522 0.016541 --0.0980428 -0.240155 0.197738 --0.0924965 -0.26196 0.186819 --0.109853 -0.270124 0.168775 --0.253582 -0.386742 -0.238773 --0.0267016 0.0982672 -0.0374627 -0.214024 0.433945 0.0622105 -0.204736 0.432758 0.058829 -0.201109 0.433103 0.0655996 -0.201809 0.436011 0.073894 -0.193477 0.433855 0.0682907 -0.185218 0.436354 0.0703184 -0.180836 0.436291 0.0819631 -0.191166 0.440882 0.0659291 -0.187348 0.447071 0.070626 -0.179215 0.453739 0.0726364 -0.193028 0.454826 0.0736221 -0.173421 0.440644 0.0776765 --0.147031 -0.496444 -0.028386 --0.151597 -0.495421 -0.0374969 --0.157627 -0.484775 -0.0397619 --0.140246 -0.499465 -0.0256815 --0.132401 -0.5 -0.0296698 --0.132703 -0.497498 -0.0384881 --0.126331 -0.494492 -0.0452588 --0.11646 -0.490117 -0.0524448 --0.101295 -0.487303 -0.0547567 --0.136101 -0.494007 -0.0471871 --0.13938 -0.490039 -0.0561432 --0.133381 -0.483866 -0.0661423 --0.15577 -0.492035 -0.0446482 --0.153261 -0.490282 -0.0555144 -0.197755 0.272342 0.0690149 -0.190382 0.263313 0.0560052 -0.0686632 -0.292912 -0.0237205 -0.056243 -0.29127 -0.0303266 -0.0398126 -0.298058 -0.030698 -0.0315681 -0.295788 -0.0489563 -0.043708 -0.285681 -0.061727 -0.0621136 -0.289132 -0.040881 -0.0722108 -0.295077 -0.0484755 -0.0577034 -0.286988 -0.0524253 -0.0569935 -0.281989 -0.0659264 -0.064236 -0.290328 -0.0328163 --0.0127838 0.0757233 -0.00921143 -0.0935192 0.0772038 0.0642915 -0.169714 0.302879 0.0497006 -0.163659 0.300165 0.0640716 -0.172929 0.295611 0.0434924 -0.049865 0.0913802 0.0221499 -0.0418831 0.0808731 0.013212 -0.0368549 0.0913191 0.0145569 -0.0319565 0.0968433 0.00544037 -0.310435 0.13526 0.142507 -0.026474 0.0305763 -0.129196 -0.017822 0.0292426 -0.122273 -0.0163904 0.0280919 -0.108702 -0.0350495 0.0278097 -0.132911 -0.178361 0.286185 0.0866978 -0.184473 0.288229 0.0959947 -0.0746028 -0.0119842 0.202652 -0.0770488 -0.00198153 0.201878 -0.0861829 0.00359659 0.206228 -0.0964676 0.00912576 0.20305 -0.110414 0.00821695 0.200752 -0.119478 0.0159283 0.18886 --0.0749585 -0.470198 -0.0703816 --0.0722579 -0.469101 -0.0627931 --0.0775597 -0.45771 -0.0519849 --0.0725204 -0.466379 -0.0530837 --0.0822617 -0.458336 -0.0360309 -0.11796 -0.00196684 -0.151498 -0.110489 0.008862 -0.155734 -0.119387 0.0273131 -0.141295 -0.100036 0.00317869 -0.149099 -0.0946498 0.00360487 -0.130289 -0.0996271 0.00897841 -0.108462 -0.0876406 0.00969553 -0.149119 -0.0889673 0.0239205 -0.144401 -0.103773 0.0275171 -0.140968 -0.112143 0.0407687 -0.122665 -0.128275 -0.00210722 -0.155193 -0.136944 0.00690927 -0.158099 -0.1315 0.01712 -0.150046 -0.148823 0.0111196 -0.154092 -0.137878 -0.00286061 -0.157253 -0.0812501 0.0180999 -0.148671 -0.0723702 0.0199576 -0.146504 -0.0894465 0.0177562 -0.14994 --0.244247 -0.411744 -0.197734 -0.0532101 -0.0425986 0.239458 -0.0614299 -0.034607 0.250277 -0.0718515 -0.0264935 0.244569 -0.0612036 -0.0408317 0.227838 -0.211416 0.21985 0.0506815 -0.209629 0.209187 0.0516229 -0.197715 0.200596 0.0531448 -0.210711 0.212673 0.041983 -0.209533 0.205981 0.0261421 -0.207685 0.214484 0.0320655 -0.204793 0.215907 0.019768 -0.207322 0.190012 0.0316877 -0.210424 0.206724 0.0353988 -0.209086 0.197885 0.0355421 -0.19948 0.191764 0.0420701 -0.206287 0.1798 0.0239909 -0.199532 0.162119 0.0159658 -0.0889829 0.0153312 0.187549 -0.0895058 0.0194699 0.175832 -0.0997882 0.0258376 0.180178 -0.0912633 -0.43583 -0.104962 -0.0893849 -0.44209 -0.0966632 -0.0818551 -0.438899 -0.0891003 -0.0775492 -0.435915 -0.0759439 -0.0805228 -0.426773 -0.087989 -0.0848008 -0.421093 -0.0971385 -0.0844397 -0.410175 -0.0930928 -0.0796444 -0.399052 -0.082153 -0.096306 -0.399151 -0.0928503 -0.0992866 -0.434977 -0.10667 --0.038871 -0.095203 0.134909 --0.0453136 -0.074362 0.132403 --0.0642973 -0.0816285 0.135216 -0.128958 0.371237 0.164377 -0.114324 0.368213 0.169385 -0.110394 0.358489 0.182698 -0.119359 0.382173 0.172221 -0.244566 0.0274799 0.091132 -0.236517 0.0321023 0.108593 -0.228129 0.0154777 0.110489 -0.243652 -0.0044318 0.106704 -0.215089 0.0116198 0.126655 -0.193164 -0.00203925 0.149761 -0.295901 -0.145148 0.11649 -0.181002 0.326878 0.162405 -0.192626 0.308402 0.164847 -0.180295 0.335983 0.171005 -0.215019 0.222685 -0.0085752 -0.216226 0.238327 -0.00679645 -0.204842 0.243639 -0.00108745 -0.197086 0.237151 0.00943393 -0.207737 0.21475 -0.00125832 -0.200663 0.225332 0.00994825 -0.227239 0.239308 -0.00361834 -0.210369 0.205349 -0.000451507 -0.212421 0.195338 0.00658041 -0.0965367 0.0723068 0.0490018 --0.263237 -0.382248 -0.20077 --0.334935 -0.395374 -0.246716 -0.0666968 0.0983382 0.0352739 -0.0735768 0.104043 0.0256427 -0.0854137 0.105714 0.0280147 -0.0966684 0.103408 0.0197312 --0.293219 -0.246559 0.00868918 --0.284829 -0.260543 0.00656712 -0.231694 -0.239358 -0.229883 -0.249173 -0.248287 -0.23972 --0.313753 -0.278534 -0.156686 --0.167921 -0.0222432 0.100354 --0.166557 -0.0217484 0.0804222 --0.137191 -0.0189498 0.0544508 --0.183157 -0.0297007 0.0652393 --0.193717 -0.0386652 0.0428719 --0.162262 -0.0205653 0.0563587 --0.148465 -0.0201145 0.071316 --0.122512 -0.0229086 0.0762312 --0.112808 -0.0225311 0.0535636 --0.15755 -0.0225071 0.112728 --0.13968 -0.0242143 0.109228 --0.128528 -0.0323847 0.121144 --0.137944 -0.0426964 0.133983 -0.338353 -0.331842 -0.233 -0.197716 0.0369013 -0.0158252 -0.225598 0.0269623 0.107608 -0.227611 0.0352699 0.116368 -0.216481 0.0416846 0.126455 -0.20366 0.0291575 0.127785 -0.197706 0.0139181 0.138946 -0.177004 0.0154396 0.15063 -0.196436 0.0406475 0.130552 -0.183582 0.0465791 0.137895 -0.171962 0.0340634 0.144092 -0.209838 0.0350513 0.1214 -0.122018 -0.0147207 0.202688 -0.134625 -0.010294 0.190699 -0.150345 -0.0204184 0.190322 -0.171467 -0.0443352 0.191435 -0.124137 -0.0275222 0.21069 -0.115004 -0.0324307 0.220668 -0.103488 -0.0235525 0.230568 --0.234966 -0.250492 -0.00657503 -0.230136 -0.0629922 0.010065 -0.22781 -0.0439028 0.0052695 -0.226758 -0.0758146 -0.00318988 -0.218119 -0.0755566 -0.0185995 -0.210925 -0.0881128 -0.0299059 -0.195391 -0.0835698 -0.0397083 -0.187049 -0.0970928 -0.0513544 -0.215909 -0.10623 -0.0275177 -0.221663 -0.111792 -0.00835326 -0.21689 -0.135734 -0.006698 -0.187289 -0.0668273 -0.0361071 -0.170089 -0.0549841 -0.0384156 -0.158135 -0.0292105 -0.0224101 -0.144698 -0.0631652 -0.0561794 -0.210349 -0.156804 0.00619425 -0.122465 -0.0210506 -0.020971 -0.25102 0.0827579 -0.00901225 -0.246076 0.0717907 -0.00732438 -0.248146 0.0653429 0.00439784 -0.245544 0.0538898 0.0151974 -0.255748 0.0868229 -0.000826293 --0.125725 -0.258433 -0.170214 -0.151089 -0.0268375 0.140451 -0.244155 0.0131187 -0.0533056 -0.246127 0.0105937 -0.0612737 -0.239403 0.00492409 -0.0645919 -0.228547 -0.00190445 -0.0800819 -0.236042 0.000460551 -0.073323 -0.244455 -0.00400961 -0.0816292 -0.225173 0.00380285 -0.0703884 -0.247885 -0.00234363 -0.0902777 -0.247838 0.00379159 -0.0839495 -0.243353 0.0148187 -0.0838177 -0.236031 0.0244971 -0.0791546 -0.225616 0.0291453 -0.0875646 --0.29518 -0.390079 -0.254006 --0.303646 -0.394416 -0.248506 --0.302477 -0.403327 -0.233435 --0.298023 -0.412267 -0.217588 --0.29425 -0.38151 -0.250683 --0.302074 -0.390767 -0.254186 -0.191342 0.435898 0.0636941 --0.0964353 -0.460913 -0.0144457 -0.137532 -0.0116873 0.139128 -0.343547 -0.294973 -0.292923 -0.137424 0.0947211 0.0118894 -0.147115 0.0902563 -0.00271409 -0.147874 0.0802052 -0.0226835 -0.255027 -0.310713 -0.257667 -0.257498 -0.300887 -0.266482 -0.220835 -0.0105204 -0.15317 -0.210403 -0.014095 -0.156518 -0.204562 -0.0228168 -0.148776 --0.117774 -0.20207 0.202016 -3 575 1215 1225 -3 902 137 1166 -3 2122 17 2125 -3 1333 1332 328 -3 1031 1037 1032 -3 1235 1234 736 -3 986 1231 182 -3 532 534 185 -3 534 448 185 -3 1588 1570 1587 -3 674 675 639 -3 1130 1225 242 -3 279 6 280 -3 6 283 280 -3 621 954 955 -3 235 273 275 -3 2565 1747 2566 -3 2225 2224 2223 -3 11 206 278 -3 922 93 2080 -3 530 2723 2724 -3 2144 2146 2139 -3 1374 1376 766 -3 1361 1374 766 -3 1775 525 1893 -3 2626 1649 2625 -3 2338 2265 2339 -3 992 96 990 -3 103 21 104 -3 1681 1682 31 -3 1686 1682 1681 -3 2064 2065 295 -3 747 748 464 -3 597 1084 1086 -3 1003 2191 2190 -3 2184 1003 2190 -3 214 38 215 -3 2202 2205 473 -3 2725 257 2728 -3 216 554 944 -3 554 553 944 -3 1457 1456 445 -3 986 2154 96 -3 458 1022 1021 -3 961 1949 2534 -3 625 1514 1515 -3 1786 1791 518 -3 1586 2773 2006 -3 52 1485 1486 -3 2101 52 1486 -3 2368 2374 1380 -3 2054 1499 74 -3 329 326 1762 -3 933 934 176 -3 527 569 705 -3 2004 247 2003 -3 414 1966 1967 -3 853 544 184 -3 1136 354 352 -3 1896 1895 134 -3 5 978 1108 -3 1344 1342 726 -3 80 27 1599 -3 1489 1683 175 -3 1952 1953 2619 -3 21 105 104 -3 21 103 29 -3 2180 959 1164 -3 288 2180 1164 -3 294 1940 1941 -3 1263 1865 443 -3 1755 1756 337 -3 696 1187 1185 -3 1186 696 1185 -3 1489 66 1683 -3 68 907 122 -3 260 1748 341 -3 1340 1341 451 -3 806 397 478 -3 1337 750 728 -3 1516 419 1515 -3 279 11 278 -3 277 279 278 -3 226 2120 2119 -3 258 259 76 -3 686 683 719 -3 1232 950 1230 -3 1597 1598 1978 -3 91 2073 977 -3 724 734 1233 -3 724 732 734 -3 47 281 280 -3 85 1982 2246 -3 207 154 64 -3 2000 1998 1999 -3 553 853 184 -3 76 261 1618 -3 259 261 76 -3 682 706 1504 -3 660 706 682 -3 296 17 297 -3 17 2122 297 -3 554 216 916 -3 381 383 382 -3 271 269 296 -3 269 17 296 -3 1082 656 1823 -3 115 199 196 -3 1056 1049 880 -3 1520 2574 2248 -3 212 550 238 -3 1980 1598 27 -3 364 72 365 -3 674 657 673 -3 82 168 158 -3 1133 641 1171 -3 440 1406 1411 -3 804 803 398 -3 178 2662 461 -3 159 2100 157 -3 1657 1659 1658 -3 1659 1280 1658 -3 1499 141 1498 -3 2144 2137 2146 -3 358 2137 2144 -3 567 534 531 -3 64 264 274 -3 137 365 380 -3 379 137 380 -3 117 903 902 -3 377 376 136 -3 88 2566 2567 -3 83 1299 1306 -3 1819 692 1822 -3 1642 584 710 -3 645 602 1808 -3 503 500 502 -3 1855 1263 54 -3 1706 1580 517 -3 1169 1069 1068 -3 42 1825 1828 -3 21 67 106 -3 81 80 84 -3 20 105 118 -3 20 156 105 -3 67 21 256 -3 1015 258 1016 -3 259 258 65 -3 649 890 1665 -3 2021 116 2020 -3 1146 402 1147 -3 126 1615 1618 -3 164 1894 163 -3 120 164 163 -3 2774 378 377 -3 1796 1893 525 -3 510 511 413 -3 1143 754 305 -3 1257 1024 1260 -3 866 1816 1815 -3 2685 1816 866 -3 574 1222 1221 -3 1673 1038 1672 -3 158 168 51 -3 793 1560 1561 -3 750 730 465 -3 729 730 750 -3 996 998 61 -3 280 281 11 -3 919 93 918 -3 590 2300 1322 -3 1305 1478 1482 -3 68 122 118 -3 238 537 555 -3 138 365 903 -3 1536 1562 1535 -3 264 1720 268 -3 2079 2080 93 -3 6 273 283 -3 2554 63 246 -3 236 317 319 -3 40 84 80 -3 84 40 104 -3 778 1357 1356 -3 1359 778 1356 -3 170 263 265 -3 1986 170 265 -3 21 106 105 -3 944 183 1740 -3 944 945 183 -3 1894 164 165 -3 67 249 106 -3 283 235 47 -3 63 2005 246 -3 314 235 275 -3 435 436 188 -3 1746 1747 345 -3 1161 2431 2432 -3 2459 594 1102 -3 1484 1305 1482 -3 2 1136 1137 -3 1062 2510 2511 -3 1811 219 1809 -3 1736 997 1732 -3 264 268 274 -3 439 1427 1247 -3 526 2722 530 -3 2722 2721 530 -3 118 105 106 -3 164 120 121 -3 71 131 165 -3 63 247 2005 -3 1894 165 1895 -3 2054 2052 2053 -3 2285 2432 2286 -3 254 106 249 -3 254 68 106 -3 685 661 670 -3 1498 75 2135 -3 989 180 991 -3 263 24 265 -3 2021 2020 932 -3 1724 170 1487 -3 124 1016 76 -3 277 278 64 -3 431 151 430 -3 2225 2757 2189 -3 818 407 478 -3 1499 237 74 -3 1994 1576 2611 -3 2697 904 897 -3 904 899 897 -3 377 378 135 -3 132 72 364 -3 282 109 293 -3 109 282 319 -3 224 83 1306 -3 74 381 376 -3 2054 2053 141 -3 2226 2678 2224 -3 1134 1438 1437 -3 412 814 813 -3 2624 1641 709 -3 126 1618 261 -3 999 34 991 -3 899 1991 1989 -3 98 1315 275 -3 928 1767 931 -3 144 2131 2136 -3 23 234 78 -3 603 800 707 -3 337 1490 332 -3 1490 1491 332 -3 1348 1341 727 -3 1591 16 1590 -3 547 92 549 -3 2194 2226 2189 -3 2236 2582 2581 -3 378 72 135 -3 1796 1784 1893 -3 278 206 207 -3 170 82 1487 -3 265 24 266 -3 308 153 943 -3 310 308 943 -3 569 565 177 -3 267 1987 1502 -3 236 316 317 -3 1895 165 134 -3 1898 2388 1479 -3 163 159 120 -3 1464 1468 1463 -3 756 903 117 -3 138 903 756 -3 951 1231 986 -3 987 951 986 -3 2670 700 2672 -3 445 187 446 -3 1722 154 910 -3 256 21 29 -3 84 168 81 -3 1283 2386 2684 -3 2736 79 852 -3 146 363 2143 -3 1323 1324 2271 -3 68 118 106 -3 1043 1038 1673 -3 2773 1585 1705 -3 1231 1232 1230 -3 1337 729 750 -3 922 917 93 -3 825 395 824 -3 510 389 511 -3 763 1188 2128 -3 833 831 487 -3 560 1355 179 -3 560 1354 1355 -3 899 904 1991 -3 639 1133 1138 -3 674 639 1138 -3 1250 1243 1254 -3 2715 2714 1433 -3 502 499 503 -3 1690 1213 1212 -3 1342 451 1341 -3 1397 1025 443 -3 929 928 930 -3 1855 1854 1262 -3 1854 1857 1262 -3 2733 2736 241 -3 1001 987 993 -3 2662 2708 191 -3 1470 2764 1459 -3 264 1717 1720 -3 264 1718 1717 -3 346 330 1139 -3 1137 1136 353 -3 1514 557 1515 -3 2045 115 2026 -3 143 1494 1495 -3 1487 1713 1724 -3 211 974 975 -3 319 318 109 -3 319 317 318 -3 2081 2077 209 -3 1015 123 258 -3 207 59 910 -3 1770 241 848 -3 857 222 1810 -3 546 857 1810 -3 87 343 1750 -3 621 915 622 -3 393 803 806 -3 977 976 975 -3 2016 992 934 -3 992 176 934 -3 1794 2744 2745 -3 278 207 64 -3 524 1692 1691 -3 2772 1702 2771 -3 1702 2772 1586 -3 1107 1105 1106 -3 190 2593 2594 -3 802 393 586 -3 1777 1783 1793 -3 476 816 390 -3 2159 1049 1048 -3 516 515 1576 -3 321 2057 2063 -3 476 477 509 -3 1180 773 1181 -3 553 184 945 -3 1227 1228 949 -3 1892 255 101 -3 1919 600 2258 -3 124 1014 1015 -3 273 98 275 -3 225 1612 1613 -3 48 313 314 -3 539 2728 2729 -3 2728 539 538 -3 1897 1896 468 -3 1810 1809 980 -3 794 2238 2237 -3 307 39 311 -3 39 307 320 -3 1531 1533 1538 -3 1583 1702 1582 -3 410 954 621 -3 2519 2333 2332 -3 541 854 855 -3 459 439 1249 -3 529 1511 2740 -3 1259 450 1258 -3 1677 2170 2175 -3 509 389 510 -3 1704 1703 1707 -3 1703 1583 1707 -3 1644 1645 584 -3 463 562 2660 -3 175 929 930 -3 262 1756 1755 -3 1756 262 1757 -3 380 72 378 -3 365 72 380 -3 128 2247 1985 -3 2532 135 2533 -3 128 1985 1987 -3 1121 2187 288 -3 891 1663 1377 -3 135 132 2533 -3 76 1016 258 -3 117 902 901 -3 132 135 72 -3 908 139 909 -3 125 76 1618 -3 76 125 124 -3 737 736 745 -3 30 541 543 -3 30 540 541 -3 395 826 824 -3 260 1751 1749 -3 341 1746 1753 -3 173 553 554 -3 1141 331 1139 -3 1756 1490 337 -3 2668 2667 700 -3 1751 1752 259 -3 230 1380 2377 -3 281 282 12 -3 2137 2138 2146 -3 1689 1852 1850 -3 588 1697 1083 -3 1069 1169 641 -3 1149 424 1088 -3 11 279 280 -3 919 1331 213 -3 1626 715 3 -3 2374 2368 2367 -3 178 2661 2707 -3 2094 2093 2092 -3 320 318 39 -3 318 317 39 -3 754 14 755 -3 6 272 273 -3 2172 1047 1051 -3 1058 2163 2162 -3 237 1499 142 -3 1799 523 1518 -3 591 695 781 -3 696 695 2128 -3 234 23 430 -3 800 720 683 -3 236 282 47 -3 235 236 47 -3 23 1162 2312 -3 2192 2678 2227 -3 2681 2680 832 -3 236 319 282 -3 817 390 808 -3 509 510 476 -3 1661 1474 492 -3 1278 1659 1657 -3 191 2715 2716 -3 1138 1133 1171 -3 835 840 412 -3 840 1184 412 -3 1898 1897 468 -3 138 756 754 -3 22 2069 300 -3 309 755 1018 -3 1651 586 589 -3 1147 1651 589 -3 1771 2733 241 -3 2733 1771 522 -3 379 380 378 -3 1749 1751 65 -3 1729 1728 197 -3 2092 2093 970 -3 300 2069 2068 -3 903 137 902 -3 25 332 1002 -3 236 235 315 -3 1162 78 1027 -3 1163 318 320 -3 147 429 801 -3 360 359 302 -3 2285 1172 3 -3 637 669 677 -3 754 1143 14 -3 2706 2709 418 -3 1213 575 574 -3 1215 575 1213 -3 2186 2223 2192 -3 2364 718 2365 -3 535 184 544 -3 610 1197 394 -3 1401 2331 2444 -3 839 149 837 -3 956 149 839 -3 181 330 347 -3 674 1138 657 -3 340 326 329 -3 1430 1415 1635 -3 2664 1822 692 -3 173 554 924 -3 944 553 945 -3 535 544 533 -3 1740 216 944 -3 334 90 336 -3 2475 783 2480 -3 927 1767 929 -3 1267 310 943 -3 2479 952 783 -3 1144 592 1146 -3 929 175 927 -3 323 1731 1730 -3 2280 2292 2430 -3 1811 1813 1812 -3 1733 1735 2570 -3 222 857 858 -3 346 1139 1760 -3 809 808 390 -3 292 1073 1072 -3 1113 1959 1962 -3 1765 1764 1116 -3 2285 2286 1172 -3 1961 1012 287 -3 1754 77 1752 -3 509 820 844 -3 417 839 2710 -3 1260 1024 1252 -3 1463 1468 483 -3 2734 2732 537 -3 568 742 734 -3 2060 2059 298 -3 187 1137 353 -3 1438 1414 441 -3 469 133 753 -3 901 902 360 -3 902 1166 360 -3 592 1144 811 -3 151 234 430 -3 2321 715 2322 -3 1648 1643 1642 -3 498 786 2606 -3 1501 2696 146 -3 2699 145 898 -3 373 1970 1273 -3 1229 985 182 -3 384 237 142 -3 1025 1263 443 -3 1273 1275 1282 -3 900 1989 1193 -3 381 237 383 -3 839 837 415 -3 755 753 754 -3 138 754 753 -3 2719 437 1701 -3 789 1562 1536 -3 2604 2607 1573 -3 820 509 477 -3 780 2484 2485 -3 1184 840 416 -3 2717 1433 2713 -3 437 2717 2713 -3 1216 1219 1217 -3 394 1195 612 -3 238 550 551 -3 1206 529 1207 -3 1442 1472 2637 -3 1472 190 2637 -3 1778 1794 2745 -3 1082 1078 656 -3 1709 2266 2265 -3 498 497 1 -3 1235 735 1234 -3 526 527 705 -3 624 620 558 -3 393 589 586 -3 2128 1188 696 -3 501 497 498 -3 616 1204 1205 -3 825 514 798 -3 293 2063 294 -3 215 343 87 -3 853 854 544 -3 854 853 542 -3 2769 1060 1129 -3 212 547 548 -3 121 479 119 -3 784 620 411 -3 1320 610 1321 -3 419 618 624 -3 1518 2740 528 -3 410 953 954 -3 957 2709 2705 -3 1662 1470 1661 -3 619 2484 1174 -3 2672 708 2670 -3 893 771 887 -3 1418 2622 1700 -3 2306 2307 428 -3 1348 1342 1341 -3 527 528 1355 -3 455 436 454 -3 1704 1705 1585 -3 1703 1704 1585 -3 1900 848 1901 -3 1259 1257 455 -3 401 1323 1646 -3 422 2250 1553 -3 2250 422 1524 -3 668 666 671 -3 1469 482 481 -3 1449 1662 1661 -3 622 912 410 -3 844 2681 832 -3 454 1630 453 -3 463 748 747 -3 467 1433 2717 -3 735 1021 733 -3 909 2051 140 -3 1216 524 1219 -3 840 956 416 -3 408 811 1144 -3 739 740 738 -3 449 446 447 -3 2731 536 2726 -3 576 1223 1221 -3 461 2716 192 -3 598 2665 2664 -3 1144 1146 589 -3 627 1392 1393 -3 569 177 705 -3 1390 1391 651 -3 1799 1518 240 -3 1089 606 626 -3 1776 1220 523 -3 462 461 192 -3 1505 561 560 -3 192 459 456 -3 701 581 704 -3 673 676 636 -3 1022 1349 480 -3 2722 177 2721 -3 462 178 461 -3 611 1358 1357 -3 625 624 558 -3 2322 1621 2321 -3 1630 1460 386 -3 2727 537 2726 -3 748 178 462 -3 480 733 1021 -3 983 1543 1545 -3 531 534 532 -3 778 611 1357 -3 2607 2604 2605 -3 1463 485 148 -3 567 565 566 -3 748 462 464 -3 412 816 834 -3 743 746 464 -3 566 534 567 -3 606 663 1808 -3 1770 1771 241 -3 834 835 412 -3 420 798 514 -3 1454 1662 1449 -3 2306 428 2309 -3 763 2128 591 -3 625 558 1513 -3 477 476 390 -3 817 477 390 -3 1526 1523 1528 -3 1562 1521 791 -3 551 46 552 -3 700 2670 2669 -3 497 501 518 -3 790 1562 1552 -3 1212 574 1214 -3 1588 1587 1571 -3 2736 849 241 -3 2039 2024 2038 -3 123 128 258 -3 128 123 965 -3 1726 1727 582 -3 550 46 551 -3 274 2742 64 -3 1779 1781 2755 -3 1781 1782 2755 -3 503 506 500 -3 709 1641 581 -3 1563 1589 1587 -3 505 498 515 -3 2238 2239 392 -3 807 2243 2241 -3 818 477 817 -3 1589 1565 507 -3 1736 325 1737 -3 1530 1531 1525 -3 872 1052 1051 -3 1800 2753 2752 -3 534 186 448 -3 603 1069 721 -3 2665 653 687 -3 1513 559 1512 -3 1782 520 571 -3 2473 1727 399 -3 2748 2750 2747 -3 240 1773 1799 -3 1773 1774 1799 -3 753 752 138 -3 1 497 851 -3 1070 627 1393 -3 1071 627 1070 -3 744 743 464 -3 803 397 806 -3 1086 1084 1085 -3 402 1086 1085 -3 1020 458 1021 -3 854 540 544 -3 540 533 544 -3 811 810 174 -3 729 562 563 -3 1351 562 729 -3 731 749 568 -3 749 746 568 -3 1338 564 1350 -3 1337 1338 1350 -3 455 1257 54 -3 1198 2486 426 -3 817 2244 818 -3 239 530 536 -3 242 1225 1216 -3 1582 499 938 -3 50 737 745 -3 735 724 1234 -3 540 539 533 -3 539 540 30 -3 901 1990 900 -3 223 541 855 -3 541 540 854 -3 715 1620 3 -3 212 548 550 -3 545 543 541 -3 223 545 541 -3 2456 2455 1680 -3 545 546 547 -3 223 546 545 -3 1163 110 2066 -3 212 545 547 -3 945 946 183 -3 549 548 547 -3 2748 2747 2749 -3 217 917 920 -3 173 924 925 -3 855 542 856 -3 855 854 542 -3 46 203 552 -3 1081 95 662 -3 173 542 553 -3 149 956 840 -3 1505 560 1507 -3 742 50 745 -3 2749 1710 2751 -3 561 562 452 -3 179 1509 1508 -3 1507 560 179 -3 2574 150 2248 -3 665 638 693 -3 571 1778 2754 -3 727 751 480 -3 705 177 2722 -3 885 882 765 -3 177 565 567 -3 402 1326 587 -3 556 1516 1509 -3 1690 1691 1213 -3 742 745 734 -3 725 1235 736 -3 1374 769 1375 -3 1361 769 1374 -3 1343 566 565 -3 451 1343 565 -3 707 1066 603 -3 707 632 1066 -3 1076 656 1078 -3 584 1643 1644 -3 705 2722 526 -3 1326 402 1085 -3 1148 1086 402 -3 598 652 2665 -3 2636 2635 577 -3 773 611 914 -3 611 773 772 -3 914 622 915 -3 1925 1924 757 -3 680 697 605 -3 663 646 645 -3 732 724 733 -3 833 490 831 -3 677 669 636 -3 618 617 1182 -3 617 618 419 -3 844 490 509 -3 697 680 635 -3 566 713 186 -3 1070 595 1071 -3 1825 107 57 -3 814 409 813 -3 636 672 673 -3 2349 2348 1203 -3 900 117 901 -3 1206 573 1205 -3 658 679 675 -3 2653 1821 596 -3 410 621 622 -3 2738 2737 1207 -3 649 1665 891 -3 620 0 621 -3 680 678 635 -3 674 658 675 -3 598 2664 2666 -3 1807 602 1806 -3 698 710 584 -3 710 698 1096 -3 395 798 828 -3 1365 1372 1364 -3 1371 1372 1365 -3 409 2479 827 -3 1321 610 609 -3 1151 616 1223 -3 2177 2176 1044 -3 0 915 621 -3 2664 2665 687 -3 2672 2671 580 -3 733 724 735 -3 2737 1211 2741 -3 1346 453 1345 -3 2351 1209 1204 -3 845 2676 833 -3 2323 715 1625 -3 1151 1150 616 -3 2352 2344 1201 -3 2273 2274 588 -3 615 1516 556 -3 557 1516 1515 -3 744 50 743 -3 1145 1144 589 -3 1181 1182 619 -3 1182 1181 618 -3 2546 2545 1626 -3 620 621 411 -3 1374 1375 770 -3 405 1356 1320 -3 1359 1356 405 -3 145 2144 2139 -3 1353 452 1352 -3 1469 2141 355 -3 137 379 1165 -3 784 558 620 -3 670 693 638 -3 668 671 672 -3 2753 1779 2754 -3 1069 722 721 -3 654 1187 1188 -3 637 678 680 -3 1687 1688 660 -3 813 390 816 -3 671 667 658 -3 667 679 658 -3 2350 1195 613 -3 607 646 663 -3 665 595 666 -3 638 666 668 -3 711 1096 1095 -3 769 1361 1358 -3 758 1924 1925 -3 597 1086 1087 -3 1989 900 1990 -3 1183 1184 416 -3 708 581 701 -3 1420 1407 2330 -3 637 680 685 -3 672 671 658 -3 669 670 638 -3 636 669 668 -3 669 638 668 -3 637 670 669 -3 1919 1921 600 -3 816 412 813 -3 693 628 665 -3 1082 1077 1078 -3 636 668 672 -3 666 595 667 -3 1641 1642 710 -3 884 648 881 -3 1688 707 660 -3 637 685 670 -3 145 359 2144 -3 1908 1907 895 -3 706 660 707 -3 1861 1247 1860 -3 731 568 734 -3 721 722 642 -3 606 1089 1081 -3 1352 452 1351 -3 2152 682 1504 -3 857 546 223 -3 717 1627 2545 -3 1780 1797 2750 -3 747 746 563 -3 881 694 882 -3 1133 639 1132 -3 489 829 2680 -3 1548 1537 1564 -3 965 964 252 -3 490 844 832 -3 1175 1176 1180 -3 1557 1559 793 -3 2547 2542 1628 -3 1726 1639 2009 -3 992 990 176 -3 859 222 858 -3 1667 222 859 -3 732 465 731 -3 465 730 731 -3 566 186 534 -3 357 2130 2129 -3 465 732 733 -3 1366 768 1368 -3 746 749 563 -3 731 734 732 -3 743 568 746 -3 568 743 742 -3 1579 516 1994 -3 878 2164 2165 -3 773 1176 772 -3 956 839 417 -3 50 742 743 -3 1233 736 1234 -3 751 733 480 -3 903 365 137 -3 465 733 751 -3 751 728 750 -3 465 751 750 -3 752 365 138 -3 1922 1923 758 -3 1389 2666 1392 -3 575 805 94 -3 881 762 694 -3 1442 2492 1134 -3 887 886 765 -3 406 776 1371 -3 954 417 955 -3 590 1322 1323 -3 2661 178 2659 -3 137 1165 1166 -3 773 1180 1176 -3 622 914 911 -3 439 467 1426 -3 782 591 781 -3 765 893 887 -3 413 835 834 -3 809 813 409 -3 805 823 397 -3 1356 777 1320 -3 1020 1236 1237 -3 793 1558 1557 -3 2686 2685 866 -3 965 123 964 -3 703 1096 1097 -3 2317 1019 2316 -3 1557 421 1559 -3 286 2179 232 -3 1676 2176 2177 -3 354 2133 352 -3 393 802 803 -3 407 806 478 -3 954 956 417 -3 777 2488 1320 -3 1212 1211 244 -3 2238 2580 2237 -3 813 809 390 -3 1184 1183 814 -3 835 149 840 -3 478 821 819 -3 392 2579 2580 -3 1127 2481 953 -3 819 821 820 -3 818 819 477 -3 818 478 819 -3 805 396 823 -3 819 820 477 -3 1211 573 2741 -3 397 823 478 -3 395 825 798 -3 2236 829 797 -3 831 392 487 -3 2681 489 2680 -3 798 829 828 -3 956 953 416 -3 953 956 954 -3 2128 695 591 -3 1184 814 412 -3 1943 2576 1122 -3 487 846 845 -3 1904 1903 849 -3 542 853 553 -3 16 2608 1593 -3 1048 2160 2159 -3 1586 2772 2773 -3 856 223 855 -3 978 867 1108 -3 2194 2189 2758 -3 239 536 2731 -3 2295 2296 865 -3 2170 2166 1046 -3 1138 1171 657 -3 873 1055 1035 -3 2046 2045 936 -3 2134 2145 142 -3 1167 44 360 -3 875 2296 2770 -3 2165 2164 2167 -3 1051 1052 1034 -3 914 915 773 -3 306 14 1143 -3 623 913 912 -3 2695 2694 301 -3 2353 2354 614 -3 694 762 1920 -3 1906 1907 1367 -3 1142 304 1143 -3 906 117 900 -3 756 117 906 -3 931 1767 1766 -3 754 756 906 -3 214 910 59 -3 911 912 622 -3 1129 870 1128 -3 77 337 1126 -3 917 217 916 -3 917 916 918 -3 1001 998 951 -3 1392 1818 1393 -3 1739 1740 183 -3 1075 1124 1125 -3 1414 440 1412 -3 964 963 252 -3 1494 1496 1495 -3 2708 2662 2707 -3 1881 1882 2437 -3 396 2766 822 -3 823 396 822 -3 2711 415 2708 -3 417 2710 957 -3 1008 473 1005 -3 289 1073 960 -3 71 130 131 -3 2223 1004 2222 -3 2050 339 2051 -3 1625 715 1626 -3 2520 2333 2519 -3 2407 2197 2408 -3 2197 2407 2198 -3 1226 2334 948 -3 2678 2192 2224 -3 2433 2305 2302 -3 124 1015 1016 -3 248 255 60 -3 473 1003 1005 -3 2191 1003 473 -3 2348 613 1202 -3 2036 993 2035 -3 123 907 964 -3 907 123 1015 -3 1207 2739 2738 -3 1242 1838 1839 -3 926 1681 1680 -3 1109 865 1110 -3 868 1109 1110 -3 2320 1621 2319 -3 1037 1038 1043 -3 2768 2767 1130 -3 1986 265 86 -3 1984 1986 86 -3 2107 1298 2108 -3 2377 2375 1271 -3 1380 2375 2377 -3 2195 2229 2214 -3 1294 1153 1289 -3 995 61 994 -3 1253 1856 1689 -3 2113 1299 229 -3 2423 889 2421 -3 1274 1273 1269 -3 373 1273 1274 -3 1610 1611 1154 -3 1610 471 1611 -3 2120 226 2121 -3 2535 1524 1526 -3 1375 894 770 -3 2072 204 2073 -3 2601 2604 2602 -3 2601 1575 2604 -3 981 980 979 -3 981 979 221 -3 922 425 920 -3 425 922 970 -3 2142 2145 2134 -3 2650 2145 2142 -3 970 922 220 -3 113 2062 2060 -3 2451 921 1996 -3 1996 217 2451 -3 217 920 2451 -3 57 1826 1825 -3 2168 2171 2169 -3 208 2081 214 -3 770 2258 600 -3 2258 770 883 -3 2085 975 974 -3 549 974 211 -3 335 1888 1887 -3 1334 2084 213 -3 976 548 211 -3 2079 2078 210 -3 1755 337 1754 -3 1332 213 1331 -3 869 1672 1669 -3 281 58 11 -3 58 281 1938 -3 1815 1816 219 -3 1816 979 219 -3 1812 1815 219 -3 998 1232 951 -3 1997 1996 1995 -3 2094 2096 2093 -3 2096 2094 221 -3 2098 978 971 -3 978 973 971 -3 2093 2096 971 -3 2096 2098 971 -3 2098 2096 2097 -3 1619 547 546 -3 1996 921 1995 -3 2096 221 2097 -3 1821 601 1820 -3 1769 1577 1768 -3 1581 1769 1768 -3 925 924 1997 -3 984 316 233 -3 303 2692 2068 -3 2692 300 2068 -3 2129 2130 2147 -3 1529 1525 792 -3 1530 1529 1523 -3 729 563 730 -3 2728 257 2729 -3 2673 699 2668 -3 699 2667 2668 -3 1541 1540 55 -3 1536 1540 1541 -3 1533 1531 1530 -3 348 1971 1998 -3 1523 1532 1530 -3 2250 2251 1553 -3 2251 2250 796 -3 676 657 1632 -3 1633 676 1632 -3 1527 422 983 -3 2248 150 2253 -3 2249 2248 2253 -3 2693 2689 2694 -3 2693 2691 2689 -3 22 298 299 -3 2656 697 635 -3 787 2656 635 -3 2599 1972 2597 -3 1972 1973 2597 -3 316 39 317 -3 316 984 39 -3 984 311 39 -3 1393 1818 601 -3 37 195 1729 -3 2052 2054 140 -3 1120 2218 1949 -3 989 990 96 -3 990 989 991 -3 1684 9 1682 -3 998 1001 61 -3 66 1686 1683 -3 990 930 176 -3 930 990 34 -3 930 933 176 -3 298 2059 295 -3 444 1460 1461 -3 444 2743 1460 -3 195 996 995 -3 2158 1049 2159 -3 1677 1676 1045 -3 985 180 989 -3 180 985 423 -3 333 89 1140 -3 333 1492 89 -3 695 1186 2477 -3 1186 174 2477 -3 2062 2061 2060 -3 991 34 990 -3 1699 1063 1669 -3 930 928 933 -3 33 347 346 -3 33 1000 347 -3 1030 1032 860 -3 1030 2515 1032 -3 1886 2640 2649 -3 2640 1885 2649 -3 1161 2432 2285 -3 993 987 988 -3 987 96 988 -3 143 1491 1492 -3 1493 143 1492 -3 1493 334 336 -3 734 745 1233 -3 38 1334 344 -3 927 926 1685 -3 1867 1265 1864 -3 1679 4 202 -3 2050 127 1614 -3 127 1616 1614 -3 922 920 917 -3 195 37 996 -3 1731 1736 1732 -3 1757 329 1760 -3 351 34 999 -3 327 351 999 -3 774 1179 1369 -3 1233 745 736 -3 1491 1002 332 -3 2565 88 2569 -3 323 197 99 -3 996 997 998 -3 999 991 180 -3 1000 999 180 -3 1000 327 999 -3 2140 356 2141 -3 1743 1742 324 -3 1000 180 423 -3 347 1000 423 -3 33 327 1000 -3 987 1001 951 -3 351 99 197 -3 1029 864 1030 -3 2773 1706 2006 -3 2605 2606 1574 -3 960 292 1117 -3 1073 292 960 -3 1959 1958 1114 -3 2272 1326 2274 -3 2002 249 248 -3 249 67 248 -3 2201 1006 2203 -3 2200 2210 2211 -3 1939 1935 1936 -3 1939 112 1935 -3 1533 982 1534 -3 1538 1533 1534 -3 2206 2203 2204 -3 70 124 125 -3 1014 124 70 -3 2028 994 2042 -3 2041 2028 2042 -3 2192 1005 2186 -3 1962 1960 1963 -3 1965 350 1960 -3 2187 961 2188 -3 77 1395 261 -3 41 2000 1999 -3 1014 907 1015 -3 122 907 1014 -3 70 122 1014 -3 350 1114 2213 -3 2209 2200 2204 -3 2209 2204 1006 -3 2668 700 2669 -3 141 2053 2056 -3 2321 2320 1620 -3 2321 1621 2320 -3 1010 2213 2208 -3 2299 1321 1322 -3 405 1321 2299 -3 706 707 683 -3 2301 1157 1602 -3 831 490 2679 -3 490 832 2679 -3 287 1012 1011 -3 1010 2206 2204 -3 1013 2191 473 -3 2205 1013 473 -3 2561 166 2560 -3 2201 2202 1008 -3 2203 2202 2201 -3 2369 2368 2370 -3 1896 1897 1895 -3 102 40 107 -3 2394 1283 53 -3 1283 2394 2390 -3 985 2154 986 -3 2076 2072 2073 -3 2076 208 2072 -3 308 310 1018 -3 755 308 1018 -3 308 755 14 -3 1848 1849 1251 -3 2345 1200 2346 -3 737 740 1238 -3 740 737 738 -3 1020 1021 735 -3 738 737 50 -3 50 744 738 -3 454 1259 455 -3 1337 1350 729 -3 480 1021 1022 -3 738 744 741 -3 457 738 741 -3 1841 1840 193 -3 1840 1841 1242 -3 1693 1691 1519 -3 1025 442 188 -3 1257 1256 1024 -3 453 450 1259 -3 928 931 2022 -3 931 932 2022 -3 2055 2056 2053 -3 2056 2055 1495 -3 336 1497 1496 -3 2701 1678 1673 -3 1672 2701 1673 -3 436 455 1025 -3 188 436 1025 -3 459 467 439 -3 702 1123 1093 -3 1026 842 843 -3 1093 1123 841 -3 1945 2578 1943 -3 2684 2384 371 -3 2384 2684 2386 -3 1526 1524 1527 -3 2537 2538 1173 -3 2252 796 2235 -3 1076 1125 656 -3 691 2316 2315 -3 704 842 1026 -3 342 1747 1748 -3 1747 341 1748 -3 1163 320 110 -3 190 2639 2637 -3 2065 2064 2066 -3 2079 93 919 -3 2078 2077 2082 -3 2077 91 2082 -3 1231 1230 182 -3 1678 2178 1674 -3 1048 2168 2167 -3 1676 2177 2178 -3 2177 1044 2178 -3 2500 2508 1041 -3 190 2594 2595 -3 377 136 2774 -3 874 1033 1032 -3 1710 2265 2337 -3 2336 1710 2337 -3 2158 1057 2157 -3 1057 2163 2157 -3 1236 735 1235 -3 1815 1812 1814 -3 499 1578 503 -3 2315 2316 714 -3 1620 2315 714 -3 382 1165 379 -3 1033 873 1035 -3 1033 1034 873 -3 1238 725 737 -3 861 1030 860 -3 1036 861 860 -3 1035 1036 860 -3 1938 2490 58 -3 861 1029 1030 -3 1285 2720 1488 -3 1801 1286 1274 -3 367 1801 1274 -3 937 2013 2021 -3 1673 1674 1043 -3 1415 1430 2622 -3 1430 1419 2622 -3 2161 1057 2160 -3 1037 871 1038 -3 28 1599 1597 -3 1830 28 1597 -3 2025 2044 2043 -3 2161 2162 2163 -3 2173 2172 1050 -3 2172 2173 879 -3 1095 703 2502 -3 1169 604 1170 -3 1169 1065 604 -3 971 425 970 -3 658 674 673 -3 788 604 1065 -3 631 788 1065 -3 659 2153 1019 -3 2153 2318 1019 -3 425 971 973 -3 1059 2155 2770 -3 868 1105 1107 -3 1109 868 1107 -3 35 2028 2027 -3 2028 198 2027 -3 2132 356 2131 -3 331 1756 1757 -3 2014 2032 2031 -3 2032 2015 2031 -3 1047 2171 2168 -3 1046 2166 2165 -3 1048 1049 872 -3 1834 1759 262 -3 873 1053 1055 -3 1715 155 1714 -3 2509 2508 1040 -3 2731 2726 537 -3 1705 1704 1584 -3 616 2349 1203 -3 238 543 212 -3 1047 872 1051 -3 2171 2172 879 -3 1050 1051 1034 -3 1708 1707 572 -3 2090 2089 967 -3 1681 31 1680 -3 2513 1031 2514 -3 1031 2515 2514 -3 2508 2500 1061 -3 2500 2501 1061 -3 1033 874 1034 -3 1034 1052 873 -3 1052 1053 873 -3 1052 1056 1053 -3 1055 863 1035 -3 1032 1033 860 -3 1033 1035 860 -3 1054 863 1055 -3 1049 2295 880 -3 2295 1049 876 -3 1967 1444 2491 -3 1056 1052 872 -3 1706 508 2268 -3 1580 1706 2268 -3 1814 863 1054 -3 1788 1790 1789 -3 1056 880 1053 -3 2016 2017 2033 -3 1054 1053 880 -3 2194 1011 2193 -3 2095 969 967 -3 2091 2092 970 -3 2091 969 2092 -3 2618 2616 2608 -3 2618 2607 2616 -3 2167 1046 2165 -3 2162 2161 878 -3 2644 2643 9 -3 2025 2038 2037 -3 2008 2257 2258 -3 883 2008 2258 -3 1004 2185 2187 -3 673 672 658 -3 940 1979 1976 -3 199 1886 196 -3 2769 875 2770 -3 1106 864 1029 -3 923 1106 1029 -3 2506 2508 1061 -3 640 1171 1170 -3 1171 641 1170 -3 2224 2192 2223 -3 2770 2155 1060 -3 2155 870 1060 -3 240 528 527 -3 1518 528 240 -3 220 2088 2090 -3 2088 2089 2090 -3 2035 993 988 -3 2099 2085 974 -3 2564 1742 1745 -3 2165 2166 878 -3 2013 2046 936 -3 937 2046 2013 -3 1684 1686 66 -3 631 1065 1066 -3 1067 631 1066 -3 667 1067 632 -3 1067 1066 632 -3 595 1067 667 -3 1091 2468 2466 -3 1170 641 1169 -3 1066 1068 603 -3 1066 1065 1068 -3 1170 635 640 -3 604 635 1170 -3 1934 1932 1912 -3 1928 1927 758 -3 1067 595 1070 -3 1068 1065 1169 -3 471 1612 1611 -3 1612 225 1611 -3 1067 1070 631 -3 2399 271 2400 -3 665 664 1071 -3 664 665 628 -3 2125 2126 224 -3 102 103 40 -3 712 1075 2589 -3 2588 712 2589 -3 612 399 583 -3 399 612 1150 -3 1076 1078 1074 -3 673 657 676 -3 1084 597 1078 -3 1115 1765 1116 -3 291 1115 1116 -3 653 1083 1082 -3 687 653 1082 -3 656 1824 1823 -3 1077 1082 1083 -3 1075 1125 1076 -3 1094 1093 712 -3 711 710 1096 -3 2274 2273 2272 -3 1094 702 1093 -3 584 2011 698 -3 2011 584 1645 -3 1087 1080 597 -3 1080 1087 95 -3 593 1087 1086 -3 593 1088 1087 -3 1078 1077 1084 -3 1179 1174 406 -3 1911 1394 645 -3 1911 1926 1394 -3 690 2463 1102 -3 1076 1074 2589 -3 1822 1823 688 -3 173 1104 542 -3 1091 1090 2468 -3 1099 599 1091 -3 690 1099 1091 -3 1529 1528 1523 -3 594 1098 1099 -3 1077 1085 1084 -3 2570 325 1733 -3 597 1080 1079 -3 1963 1960 1112 -3 424 655 1088 -3 1079 1080 599 -3 1080 1081 599 -3 2423 2422 757 -3 2422 2423 2421 -3 404 2422 2421 -3 760 653 759 -3 760 1083 653 -3 48 314 275 -3 1149 1088 593 -3 2589 1075 1076 -3 2588 2589 1074 -3 1085 1077 1697 -3 1146 1148 402 -3 1146 592 1148 -3 1641 711 581 -3 711 2563 581 -3 591 782 1330 -3 1146 1147 589 -3 1147 402 587 -3 1087 1088 95 -3 1088 655 95 -3 655 662 95 -3 1820 601 1819 -3 2291 2281 2365 -3 2286 2291 2365 -3 1527 982 1594 -3 962 1124 1101 -3 1203 1204 616 -3 1093 841 1075 -3 1091 599 1090 -3 599 1089 1090 -3 2469 1090 2470 -3 1103 2461 1122 -3 2458 2461 1103 -3 399 2472 2473 -3 2472 399 1151 -3 2060 2061 26 -3 794 2237 2232 -3 599 1081 1089 -3 603 721 800 -3 721 720 800 -3 2117 2114 2115 -3 2466 2468 2467 -3 689 2466 2467 -3 2468 1090 2469 -3 2467 2468 2469 -3 626 2470 1089 -3 688 1092 689 -3 1820 689 1821 -3 1099 1079 599 -3 1126 332 338 -3 332 25 338 -3 706 686 1504 -3 683 686 706 -3 702 1094 1095 -3 962 1100 1092 -3 1078 1079 1098 -3 1079 1078 597 -3 2566 1747 342 -3 1074 1098 594 -3 1074 1078 1098 -3 594 1099 1102 -3 2562 2563 711 -3 841 1124 1075 -3 1098 1079 1099 -3 1460 1630 1461 -3 1760 329 1761 -3 249 2002 2763 -3 254 249 2763 -3 1628 1629 642 -3 1628 720 1629 -3 925 1104 173 -3 1823 1824 688 -3 1125 1124 962 -3 677 1633 1634 -3 1183 815 814 -3 1503 2282 2287 -3 2288 1503 2287 -3 2717 2718 1432 -3 856 542 1104 -3 858 856 1104 -3 858 857 856 -3 858 1104 925 -3 858 925 859 -3 594 2459 2460 -3 1102 1099 690 -3 2578 1026 2576 -3 1671 1039 870 -3 1107 1106 5 -3 1108 1107 5 -3 2277 2505 2501 -3 2277 2504 2505 -3 2505 1061 2501 -3 1672 1038 1699 -3 2367 370 1475 -3 2014 936 2032 -3 521 1791 1785 -3 1814 1054 866 -3 1815 1814 866 -3 1608 1607 470 -3 1607 1608 2687 -3 1154 2687 1608 -3 1108 867 1109 -3 1107 1108 1109 -3 1030 2514 2515 -3 1281 2262 1696 -3 2262 1281 2263 -3 1825 8 107 -3 1956 1955 1117 -3 2185 2184 288 -3 1003 2184 2185 -3 2224 2225 2189 -3 2226 2224 2189 -3 88 2565 2566 -3 185 448 1887 -3 1174 780 406 -3 286 232 285 -3 1834 1753 1746 -3 116 2021 2023 -3 2021 2013 2023 -3 1954 1963 1112 -3 1833 1832 1830 -3 620 624 618 -3 2760 1004 2188 -3 799 1524 2535 -3 2210 2200 2209 -3 1116 1119 291 -3 2416 1190 1189 -3 1521 2230 1525 -3 8 102 107 -3 1118 1953 1955 -3 1954 1953 1118 -3 1007 2196 2195 -3 1116 475 1613 -3 1113 1956 1957 -3 1952 1951 1120 -3 1950 290 1111 -3 882 2007 765 -3 1955 1964 1118 -3 1445 1444 1966 -3 1008 2202 473 -3 896 2699 2700 -3 683 707 800 -3 2242 2241 2243 -3 675 1687 681 -3 639 675 681 -3 1525 1529 1530 -3 2546 1626 3 -3 2461 841 1122 -3 912 913 1127 -3 912 1127 410 -3 953 410 1127 -3 810 827 174 -3 827 2476 174 -3 2481 2480 783 -3 2481 1127 2480 -3 845 846 2482 -3 2352 2353 2357 -3 1229 182 1228 -3 182 1230 1228 -3 1069 641 722 -3 641 1133 722 -3 2320 2319 691 -3 722 1132 1131 -3 1132 722 1133 -3 361 383 1167 -3 1902 391 1901 -3 431 147 432 -3 1229 181 423 -3 2316 2318 714 -3 2318 2316 1019 -3 1131 659 1019 -3 1131 1132 659 -3 2716 459 192 -3 1136 2 354 -3 461 2662 191 -3 536 2725 2726 -3 484 485 1462 -3 484 389 485 -3 973 2452 2450 -3 2452 921 2450 -3 2141 1891 355 -3 1440 1701 437 -3 1936 1935 13 -3 1409 2520 2519 -3 355 494 1453 -3 346 1760 1761 -3 329 1758 340 -3 1847 193 1845 -3 1846 1847 1845 -3 1856 1853 1689 -3 1247 1266 1860 -3 1395 1126 338 -3 153 366 943 -3 366 1267 943 -3 1798 525 1774 -3 2696 2690 2695 -3 363 2696 2695 -3 898 2695 301 -3 94 805 804 -3 1194 111 1142 -3 633 1805 1804 -3 1737 997 1736 -3 1517 1208 1518 -3 523 1517 1518 -3 715 2321 1620 -3 582 2474 2471 -3 582 2473 2474 -3 111 304 1142 -3 303 304 2692 -3 111 1194 905 -3 1498 2135 142 -3 2136 357 2137 -3 304 306 1143 -3 152 306 304 -3 408 810 811 -3 2476 2478 2475 -3 913 2476 2475 -3 593 1148 1149 -3 592 1149 1148 -3 808 408 2241 -3 1148 593 1086 -3 594 2460 1074 -3 2459 1103 2460 -3 1558 2231 1521 -3 810 408 809 -3 809 408 808 -3 2275 2270 2272 -3 886 885 765 -3 1149 592 812 -3 592 811 812 -3 2145 385 384 -3 142 2145 384 -3 1197 610 1198 -3 2517 1776 523 -3 2724 2723 257 -3 56 2267 1709 -3 471 2183 1612 -3 227 2112 2111 -3 1608 1609 1610 -3 470 1609 1608 -3 314 313 233 -3 313 312 233 -3 1310 1309 231 -3 276 1310 1297 -3 1292 2413 2415 -3 48 372 313 -3 471 1610 1609 -3 2387 2388 161 -3 2391 2387 161 -3 225 1603 1155 -3 321 2066 2064 -3 1163 2066 321 -3 2261 472 2260 -3 1606 1154 1155 -3 1603 1602 1155 -3 1083 1697 1077 -3 2263 1281 1287 -3 1281 228 1287 -3 1611 225 1155 -3 1310 231 1297 -3 1306 1299 2112 -3 1277 1288 1289 -3 1288 1294 1289 -3 1694 1695 1279 -3 1695 1694 1281 -3 643 661 1804 -3 1382 661 643 -3 1279 1275 1656 -3 1278 1654 1655 -3 1156 1660 1292 -3 1291 1652 1653 -3 1291 1277 1289 -3 228 1288 1287 -3 228 1293 1288 -3 1970 1275 1273 -3 2347 2348 2346 -3 2291 2432 2431 -3 2432 2291 2286 -3 2013 2366 2023 -3 366 153 312 -3 2372 366 312 -3 370 2368 2369 -3 472 2261 1119 -3 1951 2297 2218 -3 2217 2297 1950 -3 2297 2217 2218 -3 1365 1366 775 -3 1166 1167 360 -3 361 1167 1166 -3 2206 2207 1009 -3 277 6 279 -3 277 1168 6 -3 444 1459 2743 -3 268 97 274 -3 821 823 822 -3 1458 1460 2743 -3 1460 1458 1457 -3 1005 2227 2193 -3 2159 1057 2158 -3 1919 2258 2257 -3 2169 1046 2765 -3 720 721 1629 -3 657 1171 1632 -3 1627 2547 1628 -3 2466 2465 1091 -3 2466 1092 2465 -3 1523 1594 1532 -3 426 2485 1182 -3 617 426 1182 -3 1196 2356 1199 -3 2356 1196 1197 -3 1181 0 618 -3 0 620 618 -3 1180 619 1175 -3 741 192 457 -3 1250 1254 1244 -3 1176 774 1177 -3 773 915 1181 -3 1636 1405 1635 -3 619 1180 1181 -3 462 192 741 -3 646 1911 645 -3 1911 646 1912 -3 771 1367 888 -3 629 1387 1910 -3 1926 629 1910 -3 1371 775 406 -3 1179 406 775 -3 1369 1179 775 -3 1440 2592 1701 -3 2590 1437 1436 -3 712 1093 1075 -3 1176 1175 774 -3 2484 619 1182 -3 2485 2484 1182 -3 2486 2487 780 -3 779 780 2487 -3 780 779 406 -3 98 272 284 -3 272 2399 284 -3 424 1187 655 -3 38 2084 1334 -3 494 493 1450 -3 1192 654 1191 -3 799 2535 1522 -3 812 1185 424 -3 1149 812 424 -3 1808 663 645 -3 812 1186 1185 -3 812 174 1186 -3 811 174 812 -3 1404 1428 2521 -3 1666 1668 861 -3 1189 654 1188 -3 2417 1189 2419 -3 884 2423 648 -3 647 1913 2397 -3 655 1187 1192 -3 2426 890 2416 -3 654 1189 1190 -3 270 2402 2401 -3 1193 906 900 -3 271 2398 97 -3 2417 2416 1189 -3 2426 2416 2417 -3 1191 2397 607 -3 654 1190 1191 -3 305 906 1193 -3 906 305 754 -3 654 1192 1187 -3 1936 108 1939 -3 1192 662 655 -3 607 662 1192 -3 662 607 663 -3 2446 1027 2448 -3 1570 2587 1587 -3 2587 1563 1587 -3 647 1914 1913 -3 1142 1143 305 -3 1193 1142 305 -3 2595 2639 190 -3 1473 2639 2595 -3 322 905 1194 -3 1194 1142 1193 -3 1196 394 1197 -3 915 0 1181 -3 2676 486 2575 -3 615 556 400 -3 1206 1205 556 -3 529 1206 556 -3 1205 400 556 -3 2211 350 2213 -3 2211 1961 350 -3 577 2471 2472 -3 576 577 2472 -3 805 397 804 -3 1195 394 1196 -3 2486 2485 426 -3 913 623 2477 -3 2636 577 94 -3 1631 719 720 -3 719 1631 2539 -3 1195 1196 613 -3 1196 1199 613 -3 616 2350 2349 -3 616 1150 2350 -3 528 2740 1511 -3 1209 1224 1210 -3 1224 2358 1210 -3 2242 407 2244 -3 83 1711 1302 -3 1300 83 1302 -3 1727 1726 579 -3 1645 579 2011 -3 1516 557 1509 -3 1221 1222 576 -3 1222 94 576 -3 613 1199 1202 -3 170 1716 263 -3 1355 528 1511 -3 1210 400 1209 -3 2767 2768 824 -3 2221 2196 2756 -3 2196 2221 2220 -3 570 1217 1219 -3 1217 570 520 -3 2739 1207 529 -3 1219 1693 1220 -3 1219 524 1693 -3 207 910 154 -3 1212 1213 574 -3 2358 1224 2357 -3 1204 400 1205 -3 1204 1209 400 -3 613 2349 2350 -3 2351 1203 2347 -3 1203 2348 2347 -3 399 1727 583 -3 501 1788 1787 -3 1971 1973 1972 -3 396 575 1130 -3 575 1225 1130 -3 2352 2357 1224 -3 1639 1726 582 -3 520 1218 1217 -3 497 518 1792 -3 2348 1202 2346 -3 242 1217 1218 -3 1208 2739 2740 -3 2739 529 2740 -3 277 2742 1168 -3 519 1785 1786 -3 1772 522 1771 -3 1218 825 242 -3 825 1218 500 -3 2023 2366 935 -3 242 1216 1217 -3 782 1327 1330 -3 56 1788 1789 -3 2631 2633 2628 -3 2631 2634 2633 -3 233 312 984 -3 1786 1785 1791 -3 1793 1784 1794 -3 570 1220 1776 -3 1223 616 573 -3 616 1205 573 -3 1776 2517 1783 -3 574 1221 1214 -3 1199 2356 2354 -3 1214 1223 573 -3 1223 1214 1221 -3 1211 1214 573 -3 419 624 625 -3 1515 419 625 -3 1150 1195 2350 -3 2455 1685 1680 -3 2286 2362 1172 -3 1695 1696 375 -3 1219 1220 570 -3 2359 615 1210 -3 2358 2359 1210 -3 1509 557 1512 -3 1236 725 1237 -3 949 946 1226 -3 183 946 949 -3 36 1232 998 -3 997 36 998 -3 874 1037 1043 -3 1226 1227 949 -3 1226 181 1227 -3 1123 702 1943 -3 740 739 1240 -3 1836 740 1240 -3 354 2 1891 -3 1229 1227 181 -3 1229 1228 1227 -3 1909 769 1358 -3 772 1909 1358 -3 923 1029 1028 -3 2178 1044 1674 -3 1232 36 950 -3 859 925 218 -3 925 1997 218 -3 924 217 1996 -3 1817 949 1230 -3 945 184 947 -3 2044 2048 2049 -3 2048 2044 2026 -3 2734 537 238 -3 1836 2496 2497 -3 1236 1020 735 -3 18 2642 2644 -3 1844 1839 1843 -3 1839 1844 1840 -3 1235 725 1236 -3 1243 1846 739 -3 1251 193 1847 -3 1366 1365 888 -3 1846 1250 1847 -3 1022 1842 1023 -3 458 1838 1242 -3 1842 1242 1841 -3 739 457 1243 -3 737 725 736 -3 2383 2386 2385 -3 2382 2383 2385 -3 1845 193 1840 -3 1255 193 1251 -3 184 535 947 -3 947 535 49 -3 535 1888 49 -3 1237 1239 1020 -3 1839 1838 1837 -3 1243 1250 1846 -3 192 456 457 -3 458 1242 1842 -3 1251 1252 1024 -3 339 2052 2051 -3 110 2065 2066 -3 1616 1617 130 -3 69 1616 130 -3 448 90 335 -3 1842 1841 1023 -3 2622 1418 2621 -3 1268 1270 367 -3 909 2050 2051 -3 2050 909 127 -3 1255 1841 193 -3 1247 1869 1868 -3 456 1254 457 -3 2552 60 2551 -3 1253 1254 456 -3 456 459 1249 -3 1249 439 1247 -3 1835 2496 1239 -3 2496 1835 2497 -3 1243 457 1254 -3 1496 1494 336 -3 2418 2419 763 -3 1277 1291 1653 -3 1909 1908 895 -3 1497 187 353 -3 2574 2230 2231 -3 1864 1865 1264 -3 1864 1265 1865 -3 440 2524 1412 -3 1254 1851 1244 -3 2308 2307 430 -3 1691 1692 1213 -3 1692 1215 1213 -3 1691 1690 1519 -3 450 1022 1258 -3 1909 1178 1908 -3 1263 1855 1262 -3 1024 1255 1251 -3 454 453 1259 -3 90 448 447 -3 448 186 447 -3 1256 1255 1024 -3 1255 1256 1023 -3 186 449 447 -3 1257 1260 54 -3 1023 1258 1022 -3 1023 1256 1258 -3 1256 1257 1258 -3 1258 1257 1259 -3 1245 1260 1252 -3 1488 2383 1285 -3 2383 1488 369 -3 1253 456 1862 -3 1861 1246 1862 -3 1367 768 1366 -3 1907 768 1367 -3 2764 1458 2743 -3 1459 2764 2743 -3 25 2052 339 -3 1263 1262 1866 -3 1519 1690 244 -3 1690 1212 244 -3 1457 386 1460 -3 1298 2110 2111 -3 1403 2327 2332 -3 2333 1403 2332 -3 2214 2219 2534 -3 2229 2219 2214 -3 2375 2380 2376 -3 140 74 908 -3 74 140 2054 -3 338 25 339 -3 520 570 571 -3 2598 250 2600 -3 1699 1038 1600 -3 1481 1479 369 -3 56 1789 2267 -3 1267 1270 1268 -3 1270 1267 366 -3 374 1267 1268 -3 433 1905 431 -3 1004 2186 2185 -3 351 1489 34 -3 1272 1801 1802 -3 2379 230 2378 -3 230 2377 2378 -3 1282 2394 53 -3 371 2683 2684 -3 1296 2107 2108 -3 1658 1293 228 -3 1314 98 285 -3 375 1282 1275 -3 1279 375 1275 -3 469 468 133 -3 1017 469 309 -3 1018 1017 309 -3 2393 1017 1018 -3 2684 2683 53 -3 2733 2732 2734 -3 551 2734 238 -3 555 537 2727 -3 2107 1296 1295 -3 1158 2107 1295 -3 1159 1291 1290 -3 1291 1289 1290 -3 1273 1282 53 -3 1269 1273 53 -3 1292 1159 2413 -3 312 153 311 -3 2125 17 1715 -3 2119 2120 1304 -3 2401 271 296 -3 1288 1277 1287 -3 2122 2125 2124 -3 375 1279 1695 -3 1656 1657 1279 -3 1656 1278 1657 -3 373 1969 1970 -3 424 1185 1187 -3 716 1623 642 -3 1131 716 642 -3 288 2184 2181 -3 2184 2182 2181 -3 1360 778 1359 -3 369 2384 2383 -3 2028 35 994 -3 370 1300 1319 -3 158 2100 2101 -3 227 2118 1306 -3 828 489 2682 -3 52 160 1478 -3 1305 52 1478 -3 1036 2429 1666 -3 1270 2378 367 -3 2378 1270 2379 -3 2730 2735 533 -3 1271 2375 2376 -3 1802 1271 1803 -3 1271 2376 1803 -3 1152 1307 1309 -3 1307 231 1309 -3 315 314 233 -3 231 1307 1295 -3 2111 2114 227 -3 1802 1803 1272 -3 2560 166 2549 -3 1158 2104 2106 -3 1158 2103 2104 -3 1153 1290 1289 -3 984 312 311 -3 1710 1709 2265 -3 1309 1311 470 -3 1153 1294 1295 -3 289 1336 2121 -3 2384 2386 2383 -3 1336 297 2121 -3 1300 229 1299 -3 83 1300 1299 -3 1607 1309 470 -3 48 1297 1313 -3 2272 1325 2275 -3 1325 2272 2273 -3 1422 1423 1399 -3 1114 1115 291 -3 1603 2301 1602 -3 1605 1308 1152 -3 1012 2211 2210 -3 371 2384 2391 -3 1116 1764 475 -3 1306 2112 227 -3 1280 1293 1658 -3 1603 225 1317 -3 1156 1603 1317 -3 475 1764 1763 -3 162 1476 1475 -3 2685 972 1816 -3 978 972 2685 -3 2114 2111 1301 -3 1315 1314 276 -3 1315 1297 275 -3 275 1297 48 -3 1315 276 1297 -3 2076 91 2077 -3 91 2076 2073 -3 2221 2760 2188 -3 1319 1302 1483 -3 369 1479 2387 -3 2384 369 2387 -3 160 52 159 -3 469 753 309 -3 1989 1991 322 -3 543 555 30 -3 555 2727 30 -3 189 1881 2437 -3 2396 1191 1190 -3 1009 2203 2206 -3 2106 2107 1158 -3 2107 2106 1298 -3 1290 2410 2413 -3 367 1802 1801 -3 1153 1295 1308 -3 1308 1295 1307 -3 1308 1307 1152 -3 47 280 283 -3 372 48 1313 -3 230 2370 1380 -3 1610 1154 1608 -3 1311 1309 1310 -3 235 314 315 -3 283 273 235 -3 2110 1301 2111 -3 98 1314 1315 -3 1311 276 1312 -3 276 1311 1310 -3 1660 1280 1659 -3 1312 1314 285 -3 1314 1312 276 -3 1301 2116 2115 -3 2115 2123 1303 -3 2123 1763 1303 -3 1010 2208 2206 -3 2410 2415 2413 -3 2651 2650 2142 -3 2412 2411 1153 -3 966 1155 1602 -3 1304 2120 2122 -3 1602 1157 1601 -3 2301 1292 2415 -3 1316 2409 2411 -3 2756 2196 2757 -3 647 2397 2396 -3 1357 777 1356 -3 1357 767 777 -3 911 778 623 -3 912 911 623 -3 611 911 914 -3 587 1650 1651 -3 805 575 396 -3 2478 783 2475 -3 952 2479 815 -3 405 1320 1321 -3 782 781 405 -3 2261 1013 1119 -3 1360 623 778 -3 1116 472 1119 -3 1322 1321 609 -3 1042 1129 2498 -3 1042 2769 1129 -3 2372 2379 366 -3 2379 1270 366 -3 2084 919 213 -3 2012 2673 1725 -3 1323 1322 609 -3 782 2299 2300 -3 1064 1600 871 -3 2274 1697 588 -3 2505 2506 1061 -3 1324 587 2270 -3 2400 284 2399 -3 2402 284 2400 -3 2074 977 2073 -3 977 2074 203 -3 2270 2269 1324 -3 404 2417 2418 -3 2417 2419 2418 -3 2563 2562 842 -3 2396 2397 1191 -3 1819 1822 688 -3 1330 1327 1328 -3 761 1330 1328 -3 1801 2264 1286 -3 2373 1475 1476 -3 918 1331 919 -3 1331 918 216 -3 1732 1730 1731 -3 2215 1950 1111 -3 77 1126 1395 -3 1757 1760 331 -3 1760 1139 331 -3 324 323 1744 -3 296 1336 270 -3 297 1336 296 -3 1038 871 1600 -3 1538 1534 1539 -3 728 1338 1337 -3 1340 1338 728 -3 1338 1340 1339 -3 1339 564 1338 -3 1339 569 564 -3 569 1339 565 -3 1346 1344 726 -3 564 1351 1350 -3 1351 564 1352 -3 451 1339 1340 -3 1339 451 565 -3 2168 2169 2765 -3 1349 1348 727 -3 450 1346 726 -3 955 957 411 -3 508 2267 2268 -3 453 1346 450 -3 1341 1347 727 -3 449 186 713 -3 1345 449 713 -3 1347 751 727 -3 451 1342 1343 -3 1349 727 480 -3 1343 713 566 -3 1344 713 1343 -3 713 1344 1345 -3 746 747 464 -3 463 2660 748 -3 2717 1432 467 -3 1344 1343 1342 -3 1347 728 751 -3 2660 178 748 -3 2659 178 2660 -3 1340 728 1347 -3 1340 1347 1341 -3 386 1345 453 -3 1345 1344 1346 -3 726 1342 1348 -3 726 1348 1349 -3 726 1349 450 -3 741 464 462 -3 741 744 464 -3 450 1349 1022 -3 1350 1351 729 -3 452 1353 1354 -3 1353 527 1354 -3 781 1360 1359 -3 411 2705 784 -3 569 1352 564 -3 1006 2201 2198 -3 497 1792 850 -3 887 888 403 -3 419 1516 615 -3 522 1772 526 -3 569 1353 1352 -3 452 1354 560 -3 561 452 560 -3 463 747 563 -3 776 406 779 -3 2443 1423 1422 -3 1379 885 886 -3 1362 1363 767 -3 1394 1910 644 -3 1910 1387 644 -3 1361 766 1362 -3 1576 1994 516 -3 590 1323 2271 -3 1357 1358 767 -3 1358 1361 767 -3 1407 1420 1400 -3 911 611 778 -3 611 772 1358 -3 1927 1922 758 -3 2331 1406 1402 -3 767 1361 1362 -3 888 1365 1364 -3 892 1363 1362 -3 1363 892 403 -3 1364 1363 403 -3 888 1364 403 -3 1177 772 1176 -3 766 892 1362 -3 774 1369 1370 -3 1177 774 1370 -3 1370 1368 768 -3 1178 1370 768 -3 1178 1177 1370 -3 1366 1368 775 -3 772 1177 1178 -3 2174 879 2173 -3 2174 2175 879 -3 2282 1161 2283 -3 2279 2282 1503 -3 1369 775 1368 -3 1370 1369 1368 -3 781 1359 405 -3 767 1363 1373 -3 709 708 2629 -3 1650 587 1324 -3 1646 608 1643 -3 1733 323 324 -3 1363 1364 1372 -3 2299 1322 2300 -3 767 1373 777 -3 1373 776 777 -3 1326 1697 2274 -3 1373 1363 1372 -3 1371 776 1372 -3 776 1373 1372 -3 1360 2477 623 -3 770 600 1376 -3 1374 770 1376 -3 1378 764 1379 -3 764 885 1379 -3 600 1377 1376 -3 1376 1377 766 -3 1234 724 1233 -3 1377 1378 766 -3 696 1186 695 -3 600 891 1377 -3 1028 1668 218 -3 554 217 924 -3 1378 1379 892 -3 766 1378 892 -3 1993 886 887 -3 1381 628 693 -3 670 1381 693 -3 1381 670 661 -3 1914 630 1915 -3 630 1916 1915 -3 1428 1417 1429 -3 1941 293 294 -3 1382 628 1381 -3 661 1382 1381 -3 2508 2509 1041 -3 2509 2512 1041 -3 980 219 979 -3 1809 219 980 -3 664 1388 1071 -3 651 1387 1390 -3 645 1384 602 -3 1072 1765 1115 -3 629 1390 1387 -3 629 1933 1390 -3 1870 1871 1265 -3 1874 1871 1870 -3 643 1804 1805 -3 2524 1410 2525 -3 644 1386 1385 -3 644 1387 1386 -3 628 1385 664 -3 644 1385 1383 -3 1383 1385 1382 -3 1385 628 1382 -3 1383 1382 643 -3 2523 1411 2333 -3 651 1388 1386 -3 1387 651 1386 -3 1934 1931 1932 -3 1931 650 1932 -3 2654 2653 596 -3 2654 2656 2653 -3 1615 1616 69 -3 1761 33 346 -3 644 1383 1384 -3 1929 1928 1925 -3 1391 1929 652 -3 598 1391 652 -3 598 1389 1391 -3 1388 1389 627 -3 1071 1388 627 -3 601 1818 1819 -3 1393 788 631 -3 1393 601 788 -3 290 1950 2298 -3 1388 664 1386 -3 1910 1394 1926 -3 1388 651 1389 -3 1407 2329 2330 -3 629 1926 1912 -3 1926 1911 1912 -3 638 665 666 -3 1389 651 1391 -3 440 1402 1406 -3 1666 862 1667 -3 862 1666 2429 -3 2672 700 2671 -3 92 1619 981 -3 1392 627 1389 -3 1394 644 1384 -3 1923 762 881 -3 1669 1672 1699 -3 200 2641 199 -3 126 261 1395 -3 260 1754 1752 -3 2123 475 1763 -3 475 2123 2116 -3 1263 1866 1865 -3 1496 353 75 -3 1497 353 1496 -3 1916 1922 1927 -3 1432 1431 1426 -3 1476 2720 1285 -3 2720 1476 1477 -3 2141 356 1891 -3 1441 1442 1134 -3 909 139 127 -3 838 837 1473 -3 306 152 307 -3 25 1002 2053 -3 189 1878 1881 -3 2328 2329 1407 -3 1436 441 2623 -3 2130 2131 356 -3 1411 1403 2333 -3 383 384 44 -3 1040 2513 1105 -3 1617 1616 127 -3 2449 1421 2528 -3 1410 2521 1428 -3 2521 1410 2520 -3 1811 862 1813 -3 1411 1406 1403 -3 1918 2259 1920 -3 630 1918 1920 -3 920 425 2450 -3 1439 2331 1402 -3 2331 1439 2444 -3 10 1413 2525 -3 1413 1412 2525 -3 1425 1424 460 -3 381 74 237 -3 1868 1869 1248 -3 2327 1408 2522 -3 1414 1413 441 -3 1418 1436 2623 -3 2701 1672 869 -3 1635 1415 1636 -3 2399 2398 271 -3 1409 2521 2520 -3 1416 1417 1404 -3 2314 2308 2311 -3 2330 1401 1420 -3 147 430 2307 -3 147 431 430 -3 1404 1417 1428 -3 353 1136 352 -3 1165 361 1166 -3 2332 2327 1409 -3 237 384 383 -3 2331 1401 2330 -3 188 442 1414 -3 349 74 376 -3 753 133 752 -3 133 1883 752 -3 1424 438 1405 -3 438 1424 1425 -3 2055 1002 143 -3 2053 1002 2055 -3 1879 2441 442 -3 579 583 1727 -3 2131 2130 2136 -3 1636 1415 1429 -3 1437 1441 1134 -3 1401 2443 1422 -3 439 1426 1427 -3 1426 1425 1427 -3 2260 2191 1013 -3 2443 2444 2442 -3 1807 1808 602 -3 1431 438 1426 -3 1431 1430 438 -3 2717 437 2718 -3 2714 2713 1433 -3 364 365 1883 -3 2525 2529 10 -3 2525 1410 2529 -3 1878 1880 1396 -3 838 2595 1435 -3 1879 189 2439 -3 460 1424 1421 -3 1881 1877 1398 -3 1877 1881 1878 -3 1434 2713 2714 -3 356 2140 2130 -3 1880 443 1396 -3 1397 443 1880 -3 442 1025 1397 -3 467 1432 1426 -3 1399 1423 1875 -3 1867 1870 1265 -3 1870 1867 1868 -3 1427 1425 460 -3 1248 1876 1875 -3 444 1461 436 -3 1426 438 1425 -3 2524 2525 1412 -3 2530 1947 1946 -3 1947 2530 703 -3 1807 633 626 -3 2133 2132 144 -3 2404 286 2403 -3 108 1936 1937 -3 1419 1701 1700 -3 600 1921 891 -3 1700 1701 1135 -3 1417 1636 1429 -3 1406 2331 2330 -3 2620 2623 1413 -3 1913 1914 1915 -3 714 2290 2284 -3 2290 714 2318 -3 1703 2771 1702 -3 1405 1636 1417 -3 1416 1405 1417 -3 1919 1918 649 -3 2259 1918 1919 -3 2716 467 459 -3 66 351 197 -3 351 66 1489 -3 132 73 2533 -3 1438 435 188 -3 1459 435 1471 -3 441 1437 1438 -3 2652 44 385 -3 2444 1439 2442 -3 265 266 1502 -3 266 267 1502 -3 1500 2302 2305 -3 2440 2439 1402 -3 1437 441 1436 -3 1447 491 1451 -3 188 1414 1438 -3 1134 435 1438 -3 2593 1440 2594 -3 2201 1008 2198 -3 2651 2136 358 -3 2652 2651 358 -3 2492 2491 434 -3 1977 1596 1597 -3 2637 2638 1442 -3 434 1474 1470 -3 1450 493 1448 -3 2 493 494 -3 1445 491 1447 -3 492 1445 1447 -3 2595 838 1473 -3 2639 2638 2637 -3 2639 1443 2638 -3 413 491 1446 -3 413 511 491 -3 1446 836 413 -3 413 836 835 -3 414 836 1446 -3 491 1445 1446 -3 1447 1448 492 -3 1448 1449 492 -3 445 1455 1137 -3 485 389 486 -3 926 1680 1685 -3 388 1447 1451 -3 2598 2600 253 -3 1472 2592 2593 -3 190 1472 2593 -3 493 1449 1448 -3 485 486 1902 -3 1448 388 1450 -3 1454 1455 1456 -3 1134 2492 1471 -3 1451 511 484 -3 511 1451 491 -3 511 389 484 -3 1447 388 1448 -3 482 1467 481 -3 1966 1444 1967 -3 1451 484 1452 -3 1454 1449 493 -3 388 1453 1450 -3 388 1452 1453 -3 2485 2486 780 -3 1456 387 1454 -3 1455 2 1137 -3 551 79 2734 -3 552 79 551 -3 1906 1367 771 -3 1456 1455 445 -3 1902 486 2483 -3 780 1174 2484 -3 1458 387 1456 -3 1457 1458 1456 -3 1459 444 435 -3 1452 1462 483 -3 1452 484 1462 -3 1457 445 446 -3 521 1785 1773 -3 1324 2269 2271 -3 2269 590 2271 -3 1471 1470 1459 -3 1134 1471 435 -3 1470 1471 434 -3 2766 826 822 -3 2744 1784 1795 -3 939 2746 2748 -3 1794 1784 2744 -3 1452 483 1453 -3 1462 1463 483 -3 1469 355 1468 -3 482 1469 1468 -3 485 1463 1462 -3 619 2583 1175 -3 1174 2583 619 -3 830 829 2236 -3 2680 829 830 -3 392 2580 2238 -3 1466 1467 482 -3 942 1984 2245 -3 2255 1445 492 -3 1561 2238 794 -3 1465 431 432 -3 433 431 1465 -3 1466 432 1467 -3 60 2552 2005 -3 2552 246 2005 -3 1465 1464 148 -3 1455 493 2 -3 255 1892 60 -3 415 837 838 -3 355 1453 483 -3 1468 355 483 -3 482 1468 1464 -3 2133 2142 2134 -3 476 834 816 -3 476 413 834 -3 476 510 413 -3 1284 2374 2367 -3 2168 2765 2167 -3 2765 1046 2167 -3 2141 481 2140 -3 1469 481 2141 -3 1664 889 764 -3 2689 301 2694 -3 493 1455 1454 -3 2638 2492 1442 -3 2492 2638 2491 -3 2491 2638 1443 -3 836 149 835 -3 414 837 836 -3 414 1473 837 -3 837 149 836 -3 1712 171 1713 -3 2398 1168 97 -3 1168 2398 272 -3 1305 1485 52 -3 2265 2266 2339 -3 1476 162 1477 -3 66 197 1684 -3 2368 1380 2370 -3 370 2367 2368 -3 1302 1484 1483 -3 1319 162 1475 -3 1475 370 1319 -3 1902 2483 391 -3 2395 1017 2393 -3 19 1898 1479 -3 2389 161 2388 -3 1478 1479 1481 -3 1478 160 1479 -3 2723 531 257 -3 2750 1797 56 -3 930 34 175 -3 34 1489 175 -3 2369 2370 372 -3 1482 1483 1484 -3 1482 162 1483 -3 1716 1714 155 -3 1717 1716 155 -3 1305 1484 1485 -3 1484 172 1485 -3 2730 2729 532 -3 2303 1501 146 -3 2303 2302 1501 -3 147 2307 2306 -3 1566 1544 512 -3 1501 1500 113 -3 220 2080 2088 -3 2726 2725 538 -3 2392 2395 2393 -3 1477 1480 1488 -3 172 1486 1485 -3 1486 172 1487 -3 1017 2389 469 -3 161 2389 1017 -3 1712 1487 172 -3 1487 1712 1713 -3 1480 1481 1488 -3 2727 538 30 -3 538 539 30 -3 2727 2726 538 -3 536 2724 2725 -3 2724 257 2725 -3 1493 1492 334 -3 1858 1246 1859 -3 1025 54 1263 -3 1493 336 1494 -3 143 1493 1494 -3 2306 2309 2305 -3 2309 1500 2305 -3 26 2058 2059 -3 140 908 909 -3 1879 1878 189 -3 430 23 2308 -3 1498 1495 75 -3 90 1497 336 -3 234 151 852 -3 447 187 1497 -3 90 447 1497 -3 1025 455 54 -3 1495 1498 2056 -3 1498 141 2056 -3 2136 2130 357 -3 2308 2314 428 -3 2138 114 362 -3 771 888 887 -3 2294 2295 865 -3 2294 880 2295 -3 2322 2323 1622 -3 2686 865 1109 -3 463 563 562 -3 1506 1513 558 -3 1513 1506 559 -3 466 561 1505 -3 559 1505 1507 -3 1355 1511 179 -3 523 1220 1517 -3 2129 2148 114 -3 2704 2703 2658 -3 2705 2704 784 -3 2657 1506 2658 -3 1506 784 2658 -3 1440 2713 1434 -3 2594 1440 1434 -3 2660 466 2659 -3 556 1510 529 -3 1509 1510 556 -3 1509 179 1510 -3 1508 1512 559 -3 1509 1512 1508 -3 2703 466 2658 -3 2703 2659 466 -3 1726 2009 579 -3 579 2009 2010 -3 1222 574 575 -3 2707 418 2708 -3 2705 785 2704 -3 557 1514 1512 -3 2435 2434 847 -3 1786 501 1787 -3 1510 1511 529 -3 179 1511 1510 -3 530 2721 2723 -3 2721 531 2723 -3 536 530 2724 -3 730 749 731 -3 730 563 749 -3 625 1513 1514 -3 1514 1513 1512 -3 2129 2147 2148 -3 452 562 1351 -3 1207 2741 1206 -3 2251 420 1553 -3 295 2058 2057 -3 806 2243 807 -3 393 806 807 -3 269 268 2149 -3 486 389 2575 -3 389 509 2575 -3 1561 488 2238 -3 408 1145 2240 -3 408 1144 1145 -3 144 2132 2131 -3 787 1821 2653 -3 1445 2255 1444 -3 1554 1559 421 -3 792 1520 1522 -3 2708 1435 191 -3 1545 1542 55 -3 1542 1541 55 -3 2222 2756 2761 -3 1546 1541 1542 -3 1536 1541 1546 -3 2231 2230 1521 -3 2691 905 2689 -3 2515 1031 1032 -3 2325 1627 723 -3 1528 792 1522 -3 583 1645 1644 -3 1527 1524 422 -3 303 152 304 -3 2690 2694 2695 -3 2697 301 2688 -3 1164 1121 288 -3 2677 1121 1164 -3 1532 982 1533 -3 1530 1532 1533 -3 434 2491 1474 -3 2057 2058 294 -3 299 298 295 -3 791 1525 1531 -3 2541 1631 2542 -3 1555 1570 1588 -3 1572 1555 1588 -3 1999 253 2600 -3 421 1551 1550 -3 2585 2584 1568 -3 2584 2585 1567 -3 789 1536 1569 -3 704 1026 1945 -3 982 983 1534 -3 2059 2058 295 -3 2541 2540 1631 -3 2064 2057 321 -3 1531 1539 1535 -3 1539 1531 1538 -3 2057 2064 295 -3 1937 1942 108 -3 1406 2329 1403 -3 2329 2328 1403 -3 282 281 47 -3 1723 215 87 -3 1546 1548 1569 -3 1551 789 1549 -3 2670 1948 2669 -3 1562 791 1535 -3 1587 1589 1571 -3 1543 1544 513 -3 1552 1557 790 -3 1097 578 1947 -3 578 1097 1725 -3 1547 1548 1564 -3 1548 1547 1549 -3 1560 793 1559 -3 1622 2324 1623 -3 2324 723 1623 -3 498 1 1556 -3 1546 1537 1548 -3 677 678 637 -3 677 1634 678 -3 513 1537 1543 -3 1942 12 293 -3 293 12 282 -3 1540 1539 55 -3 1543 1542 1545 -3 1543 1537 1542 -3 1545 55 1534 -3 1539 1534 55 -3 798 420 797 -3 422 1544 983 -3 512 1553 420 -3 1557 1558 790 -3 1937 1938 12 -3 421 1552 1551 -3 131 132 134 -3 1544 1543 983 -3 1054 880 2294 -3 1534 983 1545 -3 1540 1535 1539 -3 1536 1535 1540 -3 843 842 2588 -3 1565 1564 513 -3 1537 1546 1542 -3 512 420 514 -3 1567 1565 1563 -3 1565 1567 1564 -3 1591 2610 16 -3 1556 1 1560 -3 498 1556 786 -3 1946 1948 701 -3 2323 2324 1622 -3 321 2063 109 -3 1552 421 1557 -3 1579 1580 516 -3 1569 1536 1546 -3 1162 23 78 -3 2587 2584 1563 -3 2587 1568 2584 -3 1551 1552 789 -3 1171 640 1632 -3 422 1553 1544 -3 1558 1521 790 -3 2254 795 2234 -3 1120 1951 2218 -3 512 1544 1553 -3 1549 789 1569 -3 2609 16 2610 -3 2281 2364 2365 -3 234 552 78 -3 1121 961 2187 -3 1554 1550 1555 -3 2460 2577 843 -3 499 2614 1578 -3 2614 499 1768 -3 494 1450 1453 -3 482 1464 1466 -3 794 2232 1558 -3 796 2252 2251 -3 793 794 1558 -3 504 2601 2602 -3 2613 504 2602 -3 488 2239 2238 -3 1905 151 431 -3 1 1561 1560 -3 1 851 1561 -3 1561 851 488 -3 2063 2057 294 -3 1552 1562 789 -3 790 1521 1562 -3 506 1591 1592 -3 1550 1551 2586 -3 2586 2585 1568 -3 1570 1568 2587 -3 1550 1568 1555 -3 1555 1574 786 -3 1569 1548 1549 -3 2603 2613 2602 -3 1574 2606 786 -3 787 788 1821 -3 1554 421 1550 -3 1565 1566 507 -3 1566 1565 513 -3 1544 1566 513 -3 843 2588 1074 -3 2586 1568 1550 -3 982 1532 1594 -3 281 12 1938 -3 1567 1563 2584 -3 1570 1555 1568 -3 2491 1444 1474 -3 1555 1572 1574 -3 976 211 975 -3 2604 1575 2605 -3 507 1591 1590 -3 46 976 203 -3 516 505 515 -3 1573 2618 2608 -3 1582 1581 499 -3 684 2289 2288 -3 951 1232 1231 -3 1575 515 2605 -3 1790 505 1789 -3 2319 2317 691 -3 2287 2282 2283 -3 1779 2753 1800 -3 572 1779 1800 -3 206 205 59 -3 1577 2612 2614 -3 1768 1577 2614 -3 2611 2601 504 -3 69 129 125 -3 1583 1582 938 -3 2742 277 64 -3 1580 505 516 -3 218 1995 923 -3 1581 1768 499 -3 1708 2341 1707 -3 2659 785 2661 -3 1772 240 526 -3 240 527 526 -3 2170 1045 2166 -3 1064 871 2505 -3 238 555 543 -3 1230 949 1228 -3 876 2158 2157 -3 1809 1810 222 -3 862 1809 222 -3 979 972 2097 -3 972 2098 2097 -3 972 978 2098 -3 1817 1739 183 -3 2287 2283 2284 -3 1050 2172 1051 -3 2164 2160 2167 -3 2160 1048 2167 -3 848 1900 1899 -3 2357 2353 614 -3 2342 2102 51 -3 2342 156 2102 -3 183 949 1817 -3 578 1948 1947 -3 1620 714 3 -3 446 2326 1457 -3 2671 2674 580 -3 1709 2747 56 -3 1121 2677 1120 -3 517 1580 1579 -3 1585 2772 2771 -3 1300 1302 1319 -3 1707 1583 572 -3 1789 505 2268 -3 1781 1583 938 -3 543 545 212 -3 1200 1224 1209 -3 1563 1565 1589 -3 507 1566 1592 -3 1590 1589 507 -3 1571 1589 1590 -3 786 1554 1555 -3 507 1592 1591 -3 1554 786 1556 -3 604 788 787 -3 1554 1556 1559 -3 1556 1560 1559 -3 1588 1571 1593 -3 1590 1593 1571 -3 1590 16 1593 -3 2217 2216 1949 -3 1527 1594 1526 -3 1594 1523 1526 -3 2400 2401 2402 -3 2324 2323 2325 -3 1132 639 2150 -3 1978 1598 940 -3 1598 1979 940 -3 1980 1979 1598 -3 926 1683 1681 -3 1838 458 1020 -3 1156 1292 2301 -3 940 1976 1975 -3 1598 1599 27 -3 869 1669 1670 -3 2597 1595 2599 -3 2643 31 1682 -3 1828 1825 1826 -3 1829 28 1830 -3 1113 1957 1958 -3 1963 1954 1118 -3 2559 42 1831 -3 1599 1598 1597 -3 101 2550 2549 -3 2264 1287 1286 -3 1801 1272 2264 -3 1277 1286 1287 -3 1294 1158 1295 -3 1611 1155 1154 -3 2194 2227 2226 -3 206 58 205 -3 58 2490 205 -3 966 1605 1606 -3 966 1606 1155 -3 2087 2086 968 -3 2086 974 968 -3 2084 2083 919 -3 311 153 307 -3 958 471 1609 -3 232 958 1609 -3 1013 2207 1119 -3 1604 966 1601 -3 966 1602 1601 -3 2227 1005 2192 -3 1607 1152 1309 -3 1152 1607 1605 -3 1606 1605 1607 -3 143 1002 1491 -3 1609 1312 232 -3 470 1312 1609 -3 1311 1312 470 -3 1013 2261 2260 -3 1617 127 139 -3 2008 883 2007 -3 503 2610 506 -3 1614 1616 1615 -3 125 1615 69 -3 1615 125 1618 -3 1764 1072 1763 -3 2774 136 379 -3 694 2008 882 -3 2257 2008 694 -3 2015 2033 2031 -3 73 1617 139 -3 73 132 131 -3 2214 2534 2216 -3 2534 1949 2216 -3 130 73 131 -3 130 1617 73 -3 129 69 130 -3 71 129 130 -3 2002 248 2003 -3 2669 1948 578 -3 2668 2669 578 -3 126 1614 1615 -3 217 554 916 -3 318 321 109 -3 141 1499 2054 -3 2534 2219 2228 -3 961 2534 2228 -3 2284 2283 3 -3 689 1092 2466 -3 2465 690 1091 -3 1634 1633 640 -3 677 676 1633 -3 676 677 636 -3 1632 640 1633 -3 472 1612 2260 -3 2544 2545 2546 -3 2363 2544 2546 -3 723 1628 642 -3 721 642 1629 -3 1630 386 453 -3 1624 1623 716 -3 1102 2463 2462 -3 2458 1102 2462 -3 1173 2543 2363 -3 962 1101 1100 -3 1101 2462 1100 -3 963 68 254 -3 722 1131 642 -3 1623 723 642 -3 1103 2459 2458 -3 2459 1102 2458 -3 1461 1630 454 -3 2579 830 2236 -3 2067 299 2065 -3 2070 2068 110 -3 2068 2070 303 -3 2067 2065 110 -3 2691 300 2692 -3 2446 45 2445 -3 152 2070 320 -3 320 2070 110 -3 111 2691 2692 -3 2362 2363 1172 -3 678 1634 640 -3 1621 1622 1623 -3 2183 2260 1612 -3 2257 2259 1919 -3 2257 694 2259 -3 438 1635 1405 -3 2667 2671 700 -3 1920 2259 694 -3 1413 1414 1412 -3 1635 438 1430 -3 1435 1434 2714 -3 1324 1323 401 -3 1650 1324 401 -3 1145 393 807 -3 711 1641 710 -3 1291 1159 1652 -3 502 938 499 -3 401 1646 1648 -3 1649 401 1648 -3 1647 608 1646 -3 2559 62 167 -3 2634 802 2626 -3 1647 1646 1323 -3 1648 1646 1643 -3 608 612 583 -3 1644 608 583 -3 1642 1643 584 -3 585 2634 2626 -3 2627 2629 2632 -3 585 2627 2632 -3 586 1649 2626 -3 802 586 2626 -3 1647 394 612 -3 612 608 1647 -3 394 1647 609 -3 2489 1036 1035 -3 613 2348 2349 -3 609 1647 1323 -3 401 1649 1650 -3 1654 1656 1276 -3 1649 586 1651 -3 1649 1651 1650 -3 2564 1735 1742 -3 2408 1011 2407 -3 1277 1653 1286 -3 1275 1276 1656 -3 1159 1654 1652 -3 1652 1654 1276 -3 157 479 120 -3 159 157 120 -3 479 121 120 -3 2101 82 158 -3 1293 1294 1288 -3 1660 1659 1278 -3 1899 496 1770 -3 1654 1278 1656 -3 1655 1660 1278 -3 1970 1969 1275 -3 1329 588 760 -3 2100 159 2101 -3 159 52 2101 -3 1486 82 2101 -3 2117 227 2114 -3 1294 2103 1158 -3 1662 1454 387 -3 1474 1661 1470 -3 2124 1304 2122 -3 1660 1655 1292 -3 1470 1662 387 -3 1663 1378 1377 -3 1378 1663 764 -3 2425 2422 759 -3 2425 757 2422 -3 1664 891 1665 -3 891 1664 1663 -3 1664 764 1663 -3 2421 2417 404 -3 889 884 764 -3 2417 2421 2426 -3 1667 1668 1666 -3 1668 1667 859 -3 1814 1812 863 -3 1666 861 1036 -3 2686 1109 867 -3 1029 861 1028 -3 1668 859 218 -3 1371 1365 775 -3 861 1668 1028 -3 1677 2175 2174 -3 2176 1677 2174 -3 1670 1669 1039 -3 1044 1675 1674 -3 1675 1043 1674 -3 958 232 2179 -3 46 550 976 -3 1675 1050 1034 -3 1845 1241 1846 -3 2162 1671 870 -3 872 1049 1056 -3 1671 1670 1039 -3 2295 1059 2296 -3 1059 2295 876 -3 1095 2502 702 -3 1722 1721 154 -3 874 1675 1034 -3 2174 2173 1044 -3 2176 2174 1044 -3 1122 1123 1943 -3 1037 874 1032 -3 1045 1670 1671 -3 874 1043 1675 -3 1834 262 1753 -3 1670 1676 869 -3 1670 1045 1676 -3 2335 946 947 -3 946 945 947 -3 201 4 1679 -3 1673 1678 1674 -3 2018 116 2023 -3 519 1774 2702 -3 662 663 606 -3 2664 692 2666 -3 1890 90 334 -3 1687 660 681 -3 1729 197 323 -3 2568 2567 343 -3 387 1458 2764 -3 2643 1682 9 -3 200 199 201 -3 2343 200 201 -3 119 118 122 -3 250 941 85 -3 250 1976 941 -3 1999 1998 253 -3 681 660 682 -3 2647 195 2648 -3 2008 2007 882 -3 175 1683 926 -3 927 175 926 -3 1686 1684 1682 -3 32 2035 2034 -3 32 2036 2035 -3 1614 1698 2050 -3 1595 2598 2599 -3 1683 1686 1681 -3 1866 1262 1873 -3 1262 1858 1873 -3 2017 2023 935 -3 1848 1250 1244 -3 1400 2527 1408 -3 1923 881 648 -3 679 632 1688 -3 679 667 632 -3 679 1688 1687 -3 675 679 1687 -3 603 1068 1069 -3 1688 632 707 -3 1852 1252 1850 -3 1882 2442 2437 -3 1861 1860 1246 -3 1693 1519 1517 -3 1519 1208 1517 -3 1220 1693 1517 -3 524 1691 1693 -3 1772 1771 521 -3 938 1782 1781 -3 1696 1318 375 -3 1657 1694 1279 -3 1657 228 1694 -3 1658 228 1657 -3 1281 1694 228 -3 1696 1695 1281 -3 1059 876 2156 -3 1326 1085 1697 -3 1096 703 1095 -3 1698 338 339 -3 1698 1395 338 -3 126 1395 1698 -3 126 1698 1614 -3 1706 1705 508 -3 1300 370 2369 -3 1436 1700 1135 -3 1700 1436 1418 -3 1582 1702 1586 -3 1581 1582 1586 -3 2007 893 765 -3 883 893 2007 -3 1418 2623 2620 -3 952 815 1183 -3 1817 950 1739 -3 2269 2275 1325 -3 2275 2269 2270 -3 1679 202 2455 -3 1790 1788 501 -3 1583 1703 1702 -3 379 378 2774 -3 2738 244 2737 -3 1711 172 1484 -3 1302 1711 1484 -3 826 2127 822 -3 2490 7 205 -3 2733 2734 79 -3 2474 2473 2472 -3 2471 2474 2472 -3 833 2575 490 -3 833 2676 2575 -3 1214 1211 1212 -3 849 2736 852 -3 2729 531 532 -3 2341 1584 1704 -3 1707 2341 1704 -3 2266 1584 2339 -3 2341 2340 1584 -3 2280 2430 2293 -3 156 2100 2102 -3 2100 158 2102 -3 171 83 224 -3 171 1711 83 -3 243 822 2127 -3 821 822 243 -3 172 1711 1712 -3 82 1486 1487 -3 1732 997 996 -3 1721 1719 154 -3 1713 171 1714 -3 1719 64 154 -3 171 1712 1711 -3 1476 1285 2381 -3 701 2670 708 -3 82 170 169 -3 272 98 273 -3 17 269 1715 -3 823 821 478 -3 155 1720 1717 -3 2448 1027 2075 -3 2447 2446 2448 -3 1717 263 1716 -3 263 1717 1718 -3 1719 1718 264 -3 1719 24 1718 -3 64 1719 264 -3 24 263 1718 -3 2075 7 2448 -3 2447 2448 7 -3 209 2083 2084 -3 38 209 2084 -3 266 1721 1723 -3 1721 266 24 -3 266 1723 87 -3 2645 2644 9 -3 1722 214 215 -3 1721 1722 1723 -3 1758 1759 340 -3 1714 1724 1713 -3 1724 1714 1716 -3 1716 170 1724 -3 1097 698 1725 -3 2675 1637 1638 -3 1637 1639 582 -3 576 1151 1223 -3 576 2472 1151 -3 994 2036 2042 -3 2010 1639 699 -3 2010 2009 1639 -3 1639 1640 699 -3 1578 2603 503 -3 2170 1677 1045 -3 2172 2171 1047 -3 2038 2024 2037 -3 261 259 1752 -3 820 243 844 -3 1729 1730 37 -3 323 1730 1729 -3 325 1333 328 -3 1333 325 1734 -3 1736 1731 325 -3 1733 1731 323 -3 1744 323 99 -3 2230 2574 1520 -3 792 2230 1520 -3 2564 2565 2573 -3 2565 2564 345 -3 325 1738 1737 -3 1743 326 340 -3 93 917 918 -3 260 341 1754 -3 1743 324 1744 -3 36 1738 1739 -3 1730 1732 37 -3 37 1732 996 -3 38 344 215 -3 325 328 1738 -3 1733 324 1735 -3 1126 337 332 -3 2573 2565 2569 -3 1741 1331 216 -3 1041 2512 875 -3 213 1333 1334 -3 36 997 1737 -3 340 1745 1742 -3 345 1745 1746 -3 1741 1332 1331 -3 2746 2750 2748 -3 2750 2746 1780 -3 1737 1738 36 -3 1070 1393 631 -3 1392 692 1818 -3 1891 2 494 -3 1889 335 1890 -3 355 1891 494 -3 36 1739 950 -3 1741 328 1332 -3 1072 226 1763 -3 1739 1738 328 -3 1740 1739 328 -3 1741 1740 328 -3 916 216 918 -3 216 1740 1741 -3 2142 144 2651 -3 1742 1743 340 -3 326 1743 1744 -3 99 326 1744 -3 327 326 99 -3 1754 337 77 -3 1734 325 2570 -3 2569 88 2571 -3 2570 2569 2571 -3 1750 343 2567 -3 342 1750 2567 -3 460 1421 1399 -3 1751 259 65 -3 260 1749 1748 -3 342 1748 1749 -3 1749 65 1750 -3 1750 342 1749 -3 2431 2430 2291 -3 327 99 351 -3 77 261 1752 -3 260 1752 1751 -3 341 1753 1754 -3 1753 1755 1754 -3 347 330 346 -3 1758 329 1757 -3 331 1141 1490 -3 1756 331 1490 -3 1141 89 1490 -3 1491 1490 89 -3 1492 1491 89 -3 1141 1140 89 -3 1753 262 1755 -3 948 330 181 -3 948 1140 330 -3 1850 1849 1244 -3 1850 1252 1849 -3 2020 2030 2029 -3 2030 2019 2029 -3 2018 2023 2017 -3 262 1759 1758 -3 262 1758 1757 -3 948 333 1140 -3 33 1762 327 -3 1762 326 327 -3 1762 33 1761 -3 329 1762 1761 -3 1613 475 2116 -3 1072 1073 226 -3 472 1116 1613 -3 40 103 104 -3 1115 292 1072 -3 4 931 1766 -3 1765 1072 1764 -3 1612 472 1613 -3 4 1766 202 -3 927 1685 202 -3 1766 927 202 -3 129 70 125 -3 931 4 932 -3 929 1767 928 -3 121 70 129 -3 1777 571 570 -3 549 92 967 -3 517 1769 1581 -3 1586 517 1581 -3 1769 517 1579 -3 1776 1777 570 -3 2658 784 2704 -3 2662 178 2707 -3 242 2768 1130 -3 496 521 1770 -3 1327 590 2269 -3 1353 569 527 -3 1773 1772 521 -3 1772 1773 240 -3 163 1894 19 -3 1774 525 1775 -3 1710 2747 1709 -3 530 239 526 -3 1786 518 501 -3 1208 1519 2738 -3 571 1777 1778 -3 2751 1710 2336 -3 1787 1788 1798 -3 2752 2749 2751 -3 1800 2752 2751 -3 571 2754 2755 -3 610 394 609 -3 502 500 1218 -3 938 502 1782 -3 1327 2269 1325 -3 2657 2658 466 -3 1776 1783 1777 -3 1505 2657 466 -3 1518 1208 2740 -3 1775 2517 523 -3 523 1799 1775 -3 382 383 361 -3 1211 2737 244 -3 1774 1775 1799 -3 1786 1787 519 -3 1789 2268 2267 -3 2336 2337 1708 -3 505 1790 498 -3 1580 2268 505 -3 501 498 1790 -3 502 520 1782 -3 521 496 1791 -3 1705 1706 2773 -3 1791 1792 518 -3 1791 496 1792 -3 2333 2520 2523 -3 2517 1775 2516 -3 2517 2516 1783 -3 2519 2332 1409 -3 2738 1519 244 -3 313 2371 2372 -3 2266 1709 2267 -3 1780 1795 1796 -3 2523 1410 2524 -3 1793 1794 1778 -3 1777 1793 1778 -3 1798 1774 519 -3 1787 1798 519 -3 1795 1784 1796 -3 56 1797 1788 -3 526 239 522 -3 525 1797 1796 -3 1796 1797 1780 -3 2752 2753 939 -3 1788 1797 1798 -3 525 1798 1797 -3 313 372 2371 -3 2262 2263 1272 -3 1272 1803 2262 -3 1274 1269 367 -3 372 2370 2371 -3 661 605 1804 -3 661 685 605 -3 2419 1188 763 -3 2611 1576 2601 -3 1804 605 633 -3 605 634 633 -3 633 1806 1805 -3 643 1805 1806 -3 643 1806 602 -3 1384 643 602 -3 688 962 1092 -3 1824 962 688 -3 1383 643 1384 -3 606 1808 1807 -3 1125 1824 656 -3 1809 862 1811 -3 708 2672 2630 -3 652 759 2665 -3 652 1925 757 -3 699 1640 2667 -3 980 1619 1810 -3 1810 1619 546 -3 104 2342 51 -3 218 923 1028 -3 219 1811 1812 -3 1816 972 979 -3 1054 1055 1053 -3 2327 2328 1407 -3 474 2182 2190 -3 1230 950 1817 -3 2289 1503 2288 -3 1508 559 1507 -3 1082 1823 687 -3 42 8 1825 -3 1080 95 1081 -3 689 1820 688 -3 1819 688 1820 -3 94 577 576 -3 634 605 697 -3 2656 634 697 -3 634 2656 2654 -3 664 1385 1386 -3 601 1821 788 -3 962 1824 1125 -3 1955 1952 1117 -3 1827 80 1599 -3 1979 941 1976 -3 80 57 40 -3 1827 57 80 -3 1831 1829 1832 -3 1831 62 2559 -3 1745 1834 1746 -3 1826 1827 28 -3 1827 1599 28 -3 292 1115 1957 -3 1827 1826 57 -3 1832 62 1831 -3 42 1828 1829 -3 1828 28 1829 -3 1828 1826 28 -3 1415 2621 1429 -3 2622 2621 1415 -3 1759 1745 340 -3 1745 1759 1834 -3 1888 1889 49 -3 1595 1975 1976 -3 57 107 40 -3 42 1829 1831 -3 100 2561 2560 -3 8 2561 100 -3 2550 100 2560 -3 102 29 103 -3 1832 1833 43 -3 78 203 1027 -3 2335 49 948 -3 2334 2335 948 -3 946 2335 2334 -3 1838 1020 1239 -3 1837 1838 1239 -3 535 1887 1888 -3 738 457 739 -3 1839 1840 1242 -3 458 1842 1022 -3 1844 1241 1845 -3 1844 1843 1241 -3 1841 1255 1023 -3 1229 423 985 -3 2494 1240 2493 -3 1240 1843 2493 -3 1836 1240 2494 -3 2494 2493 1837 -3 1849 1848 1244 -3 1847 1848 1251 -3 1245 1855 1260 -3 1247 1427 1869 -3 1844 1845 1840 -3 1843 1240 739 -3 1241 1843 739 -3 739 1846 1241 -3 1245 1252 1852 -3 78 552 203 -3 2020 2022 932 -3 347 423 181 -3 2139 2146 362 -3 2146 2138 362 -3 1849 1252 1251 -3 1851 1850 1244 -3 1851 1689 1850 -3 1254 1253 1851 -3 1253 1689 1851 -3 1847 1250 1848 -3 1260 1855 54 -3 1245 1853 1854 -3 1853 1245 1852 -3 1855 1245 1854 -3 1869 1876 1248 -3 333 334 1492 -3 1139 1140 1141 -3 330 1140 1139 -3 1253 1862 1261 -3 1862 1246 1261 -3 1857 1854 1853 -3 1856 1857 1853 -3 1852 1689 1853 -3 1261 1857 1856 -3 1246 1858 1261 -3 1858 1857 1261 -3 1266 1247 1868 -3 1253 1261 1856 -3 1864 1264 1863 -3 769 1909 895 -3 1858 1859 1873 -3 25 2053 2052 -3 1859 1246 1860 -3 1249 1862 456 -3 1249 1861 1862 -3 895 1907 1906 -3 436 1461 454 -3 1266 1868 1867 -3 1863 1859 1860 -3 1266 1863 1860 -3 1864 1863 1867 -3 1266 1867 1863 -3 1866 1264 1865 -3 1859 1863 1264 -3 349 139 908 -3 1868 1248 1870 -3 2051 2052 140 -3 1872 1396 443 -3 1872 1865 1265 -3 1865 1872 443 -3 1872 1265 1871 -3 1871 1396 1872 -3 1264 1866 1873 -3 1873 1859 1264 -3 1858 1262 1857 -3 1875 1876 1399 -3 1248 1874 1870 -3 1876 460 1399 -3 460 1876 1869 -3 2440 2441 1879 -3 1874 1877 1871 -3 1398 1877 1874 -3 1877 1396 1871 -3 318 1163 321 -3 1495 2055 143 -3 1398 1874 1875 -3 1874 1248 1875 -3 1879 2439 2440 -3 1875 1423 1882 -3 1398 1875 1882 -3 1879 1397 1880 -3 1878 1879 1880 -3 1869 1427 460 -3 442 1397 1879 -3 1878 1396 1877 -3 45 2447 1935 -3 2438 189 2437 -3 1881 1398 1882 -3 1883 365 752 -3 133 1884 1883 -3 1884 364 1883 -3 134 364 1884 -3 198 2043 2049 -3 1698 339 2050 -3 2498 2499 1042 -3 1128 877 2498 -3 877 2499 2498 -3 960 2677 1164 -3 2641 2640 1886 -3 1885 194 35 -3 2027 198 2048 -3 2040 2024 2039 -3 214 1722 910 -3 539 2730 533 -3 857 223 856 -3 1887 448 335 -3 948 49 1889 -3 333 948 1889 -3 335 1889 1888 -3 1890 333 1889 -3 334 333 1890 -3 335 90 1890 -3 1895 1897 19 -3 101 2549 2548 -3 532 2735 2730 -3 1006 2204 2203 -3 164 71 165 -3 121 71 164 -3 119 70 121 -3 121 129 71 -3 479 20 119 -3 134 1884 1896 -3 1884 468 1896 -3 468 1884 133 -3 19 1894 1895 -3 1423 2442 1882 -3 1423 2443 2442 -3 177 531 2721 -3 531 177 567 -3 160 19 1479 -3 134 132 364 -3 1488 1481 369 -3 1770 848 1899 -3 850 495 851 -3 1903 241 849 -3 241 1903 848 -3 1901 848 1903 -3 1900 1901 391 -3 1902 1904 433 -3 1902 1901 1904 -3 86 1985 2245 -3 86 1987 1985 -3 148 485 1902 -3 497 850 851 -3 495 488 851 -3 2648 2646 2647 -3 1903 1904 1901 -3 1904 1905 433 -3 793 1561 794 -3 849 1905 1904 -3 849 151 1905 -3 849 852 151 -3 1137 187 445 -3 883 894 893 -3 893 894 771 -3 894 1906 771 -3 769 895 1375 -3 894 895 1906 -3 1375 895 894 -3 1178 768 1908 -3 1908 768 1907 -3 1249 1247 1861 -3 1909 772 1178 -3 1488 2720 1477 -3 191 2716 461 -3 1929 650 1928 -3 1933 650 1929 -3 1394 1384 645 -3 2424 763 591 -3 2341 1708 2340 -3 649 630 1914 -3 630 649 1918 -3 758 1925 1928 -3 671 666 667 -3 2260 474 2191 -3 649 891 1921 -3 1924 1923 648 -3 630 1920 1917 -3 1921 1919 649 -3 474 2190 2191 -3 2262 1318 1696 -3 764 884 885 -3 884 881 882 -3 885 884 882 -3 762 1917 1920 -3 934 2018 2017 -3 1026 2577 2576 -3 1916 1917 1922 -3 1917 762 1922 -3 630 1917 1916 -3 814 815 409 -3 1188 1187 696 -3 1923 1922 762 -3 2019 116 2018 -3 1700 2622 1419 -3 1923 1924 758 -3 1913 1934 1912 -3 1916 1927 1930 -3 1927 1928 1930 -3 1931 1930 650 -3 1930 1928 650 -3 1390 1933 1391 -3 1916 1930 1931 -3 1915 1916 1931 -3 1407 1400 1408 -3 2528 2527 1400 -3 1933 1929 1391 -3 595 665 1071 -3 1931 1934 1915 -3 1932 1933 629 -3 1932 629 1912 -3 1913 1912 646 -3 112 45 1935 -3 1932 650 1933 -3 2621 1418 2620 -3 1913 1915 1934 -3 1936 13 1937 -3 1937 13 1938 -3 1967 2491 1443 -3 1473 1967 1443 -3 108 1941 1940 -3 1942 293 1941 -3 108 1942 1941 -3 1940 1939 108 -3 2247 2246 1985 -3 2278 1064 2503 -3 1527 983 982 -3 1944 1943 702 -3 1944 1945 1943 -3 2670 701 1948 -3 2503 2276 2278 -3 1335 289 959 -3 959 289 960 -3 2533 139 2532 -3 2222 2761 2225 -3 1625 2325 2323 -3 703 1097 1947 -3 1946 1947 1948 -3 2576 2577 1122 -3 1062 2511 1128 -3 1944 1946 1945 -3 1946 704 1945 -3 1172 2363 2546 -3 635 678 640 -3 701 704 1946 -3 449 2326 446 -3 1955 1953 1952 -3 1164 959 960 -3 1120 1949 1121 -3 1949 961 1121 -3 1117 1952 1120 -3 1115 1114 1958 -3 292 1956 1117 -3 2186 1004 2223 -3 2197 1005 2193 -3 1012 2199 1011 -3 1113 1964 1956 -3 2221 2188 2220 -3 1111 290 1954 -3 1953 1954 290 -3 2144 359 358 -3 359 2652 358 -3 1114 350 1959 -3 1111 1954 1112 -3 1962 1965 1960 -3 1965 1962 1959 -3 1956 292 1957 -3 2226 2227 2678 -3 1964 1113 1962 -3 1964 1962 1963 -3 1956 1964 1955 -3 1965 1959 350 -3 42 15 8 -3 1959 1113 1958 -3 1112 1960 1961 -3 2561 15 2558 -3 1960 350 1961 -3 166 2558 2557 -3 2561 2558 166 -3 8 15 2561 -3 1963 1118 1964 -3 388 1451 1452 -3 414 1446 1966 -3 1445 1966 1446 -3 1652 1276 1968 -3 1653 1652 1968 -3 1201 1199 2353 -3 1201 1202 1199 -3 1969 1276 1275 -3 373 1653 1969 -3 1653 373 1286 -3 1653 1968 1969 -3 1968 1276 1969 -3 2557 2554 2553 -3 1286 373 1274 -3 348 63 2556 -3 1971 348 2556 -3 31 200 2343 -3 2457 31 2343 -3 2596 1595 2597 -3 62 1832 43 -3 681 2150 639 -3 1977 940 1975 -3 1977 1974 1596 -3 1977 1975 1974 -3 1596 43 1833 -3 2640 2641 18 -3 1596 1974 1973 -3 1975 1595 1974 -3 1596 1973 43 -3 1980 941 1979 -3 1982 1981 942 -3 1981 1982 1980 -3 1978 940 1977 -3 1597 1978 1977 -3 1833 1597 1596 -3 1830 1597 1833 -3 2247 85 2246 -3 942 2246 1982 -3 2245 2246 942 -3 2648 195 995 -3 18 1885 2640 -3 1885 18 2645 -3 169 1986 1984 -3 1983 169 1984 -3 420 2251 2252 -3 685 680 605 -3 1991 904 322 -3 1194 1193 322 -3 1193 1989 322 -3 80 81 1981 -3 80 1981 27 -3 1981 1980 27 -3 1982 941 1980 -3 2247 128 965 -3 85 941 1982 -3 1988 65 258 -3 2697 2698 301 -3 897 2698 2697 -3 1988 258 128 -3 1981 1983 942 -3 1981 81 1983 -3 965 85 2247 -3 1984 942 1983 -3 2233 795 2254 -3 169 81 168 -3 81 169 1983 -3 265 1502 86 -3 1502 1987 86 -3 169 170 1986 -3 1988 128 1987 -3 197 1728 1684 -3 1750 65 1988 -3 267 1750 1988 -3 87 1750 267 -3 267 1988 1987 -3 796 2250 2249 -3 799 2249 2250 -3 1992 1379 886 -3 588 1329 2273 -3 2270 587 1326 -3 1573 2608 2609 -3 1328 1327 1325 -3 1992 892 1379 -3 2611 2615 1994 -3 887 403 1993 -3 892 1992 403 -3 1992 1993 403 -3 2298 2619 290 -3 2619 2298 1951 -3 218 1997 1995 -3 1106 2453 5 -3 1996 1997 924 -3 1766 1767 927 -3 2454 1215 1692 -3 2454 1216 1225 -3 1215 2454 1225 -3 524 2454 1692 -3 2454 524 1216 -3 963 251 252 -3 964 907 963 -3 348 1998 2000 -3 247 348 2001 -3 63 348 247 -3 348 2000 2001 -3 2001 2000 41 -3 2001 2003 247 -3 1619 92 547 -3 2006 517 1586 -3 2005 2004 60 -3 247 2004 2005 -3 2004 248 60 -3 122 70 119 -3 248 2004 2003 -3 517 2006 1706 -3 2772 1585 2773 -3 2033 2017 935 -3 1367 1366 888 -3 2528 1416 2527 -3 2019 2018 934 -3 243 820 821 -3 1807 1806 633 -3 2345 2346 1202 -3 2631 2628 1638 -3 2016 2033 2034 -3 877 2500 2499 -3 923 2453 1106 -3 1040 2508 2506 -3 196 2027 2048 -3 1885 2027 196 -3 2048 198 2049 -3 2026 115 196 -3 35 2027 1885 -3 2044 2045 2026 -3 2045 2044 2025 -3 199 2047 201 -3 2016 934 2017 -3 2034 2033 2015 -3 2043 2038 2025 -3 2043 2039 2038 -3 96 987 986 -3 96 992 988 -3 1081 662 606 -3 933 2019 934 -3 2021 932 937 -3 2020 116 2030 -3 116 2019 2030 -3 2041 198 2028 -3 1679 2343 201 -3 1679 2457 2343 -3 2040 2039 2041 -3 61 993 994 -3 2042 2036 32 -3 202 1685 2455 -3 1699 2278 1063 -3 1998 1971 1972 -3 2044 2049 2043 -3 2041 2039 198 -3 1042 2500 1041 -3 2048 2026 196 -3 2029 2022 2020 -3 2022 2029 933 -3 2029 2019 933 -3 1503 1504 686 -3 2279 1503 686 -3 933 928 2022 -3 2014 2031 935 -3 2031 2033 935 -3 2035 2016 2034 -3 2035 988 2016 -3 2513 2507 1031 -3 2161 2160 2164 -3 2013 936 2014 -3 988 992 2016 -3 2511 877 1128 -3 1110 2296 875 -3 2512 1110 875 -3 2040 2042 32 -3 2015 2032 2024 -3 2032 2037 2024 -3 2032 936 2037 -3 2502 1944 702 -3 1946 1944 2530 -3 2451 920 2450 -3 994 993 2036 -3 2015 2040 32 -3 32 2034 2015 -3 2040 2015 2024 -3 993 61 1001 -3 2037 936 2025 -3 2046 115 2045 -3 198 2039 2043 -3 868 1110 2512 -3 2042 2040 2041 -3 936 2045 2025 -3 1039 1063 1062 -3 1039 1062 870 -3 1669 1063 1039 -3 1601 1157 2409 -3 1045 1671 2166 -3 4 201 2047 -3 937 2047 2046 -3 2046 2047 115 -3 287 2758 2759 -3 878 2161 2164 -3 199 115 2047 -3 932 2047 937 -3 4 2047 932 -3 2309 2313 1500 -3 428 2313 2309 -3 2310 1500 2313 -3 26 1940 2058 -3 1940 294 2058 -3 2308 23 2311 -3 791 1531 1535 -3 113 2060 298 -3 2060 26 2059 -3 26 2061 1940 -3 1940 2061 1939 -3 2061 112 1939 -3 2062 112 2061 -3 427 112 2062 -3 112 427 45 -3 113 1500 2310 -3 2310 2062 113 -3 307 152 320 -3 2069 22 299 -3 299 295 2065 -3 2068 2069 2067 -3 2069 299 2067 -3 2068 2067 110 -3 343 215 344 -3 975 91 977 -3 2085 91 975 -3 205 204 2071 -3 479 157 20 -3 59 208 214 -3 208 59 2071 -3 22 1501 298 -3 1501 113 298 -3 152 303 2070 -3 2082 210 2078 -3 210 2082 2086 -3 2083 2079 919 -3 203 2074 1027 -3 204 2074 2073 -3 2075 2074 204 -3 1027 2074 2075 -3 2446 2445 1162 -3 2071 59 205 -3 2075 204 205 -3 7 2075 205 -3 24 1719 1721 -3 1422 1421 2449 -3 1421 1422 1399 -3 2080 2079 210 -3 214 2081 38 -3 549 211 548 -3 2081 209 38 -3 220 922 2080 -3 2078 2079 2083 -3 208 2077 2081 -3 2076 2077 208 -3 220 2091 970 -3 2078 2083 209 -3 2077 2078 209 -3 203 976 977 -3 157 156 20 -3 156 157 2100 -3 204 2072 2071 -3 2085 2082 91 -3 1317 1280 1156 -3 210 2086 2087 -3 2087 2088 2080 -3 2088 2087 968 -3 2087 2080 210 -3 549 967 2089 -3 968 549 2089 -3 968 2089 2088 -3 969 2090 967 -3 969 2091 2090 -3 2091 220 2090 -3 970 2093 971 -3 2072 208 2071 -3 206 59 207 -3 2095 92 981 -3 2097 221 979 -3 967 92 2095 -3 969 2094 2092 -3 11 58 206 -3 2095 981 221 -3 980 981 1619 -3 221 2094 2095 -3 969 2095 2094 -3 2686 2294 865 -3 2085 2099 2082 -3 2086 2099 974 -3 2082 2099 2086 -3 1317 225 2105 -3 1293 2103 1294 -3 2102 158 51 -3 2103 1293 1280 -3 2104 2103 1280 -3 1280 1317 2104 -3 1317 2105 2104 -3 1296 231 1295 -3 1297 2109 1313 -3 2108 229 2109 -3 1296 2108 2109 -3 2113 2112 1299 -3 2124 2125 224 -3 2110 1298 2106 -3 2105 2110 2106 -3 2108 2113 229 -3 1298 2112 2113 -3 2111 2112 1298 -3 1297 1296 2109 -3 231 1296 1297 -3 2106 2104 2105 -3 2105 1301 2110 -3 2123 2115 2116 -3 232 1312 285 -3 1298 2113 2108 -3 2124 2118 1304 -3 2118 2124 1306 -3 84 51 168 -3 227 2117 2118 -3 1301 2115 2114 -3 226 1073 2121 -3 1303 2119 2117 -3 2119 2118 2117 -3 2119 1304 2118 -3 1156 1280 1660 -3 1303 226 2119 -3 1303 1763 226 -3 2105 225 2116 -3 2105 2116 1301 -3 225 1613 2116 -3 1073 289 2121 -3 1336 289 1335 -3 2117 2115 1303 -3 297 2120 2121 -3 297 2122 2120 -3 2126 1715 171 -3 224 1306 2124 -3 82 169 168 -3 2125 1715 2126 -3 1714 171 1715 -3 171 224 2126 -3 2682 489 2681 -3 828 2682 826 -3 395 828 826 -3 826 2682 2127 -3 487 845 833 -3 1467 2148 481 -3 2148 2147 481 -3 2143 2139 362 -3 2135 2134 142 -3 787 2653 2656 -3 2502 2530 1944 -3 2502 703 2530 -3 302 359 896 -3 2700 302 896 -3 2537 1173 2362 -3 1891 2132 354 -3 1891 356 2132 -3 352 2134 2135 -3 354 2132 2133 -3 1508 1507 179 -3 2650 2651 385 -3 2133 2134 352 -3 2524 1411 2523 -3 2320 691 2315 -3 357 2129 2138 -3 1499 1498 142 -3 147 2306 429 -3 353 352 2135 -3 357 2138 2137 -3 2129 114 2138 -3 898 145 363 -3 2147 2130 2140 -3 155 2149 1720 -3 2149 268 1720 -3 300 2691 2693 -3 2149 155 1715 -3 2142 2133 144 -3 385 44 384 -3 2688 904 2697 -3 411 621 955 -3 2143 363 2139 -3 363 145 2139 -3 114 801 362 -3 242 825 2768 -3 2143 2304 146 -3 626 606 1807 -3 2689 905 2688 -3 301 2689 2688 -3 1528 1529 792 -3 2140 481 2147 -3 1715 269 2149 -3 268 269 97 -3 1132 2150 2151 -3 2150 681 2151 -3 659 1132 2151 -3 2152 1504 684 -3 2153 2152 684 -3 691 2317 2316 -3 2151 681 682 -3 2152 2151 682 -3 2283 2285 3 -3 1161 2285 2283 -3 714 2284 3 -3 659 2152 2153 -3 659 2151 2152 -3 1621 1623 1624 -3 1019 716 1131 -3 985 989 2154 -3 96 2154 989 -3 182 985 986 -3 2157 2156 876 -3 1060 870 1129 -3 293 109 2063 -3 1058 870 2155 -3 867 2685 2686 -3 2215 2216 2217 -3 287 1011 2194 -3 1059 2156 2155 -3 878 1671 2162 -3 2158 876 1049 -3 2166 1671 878 -3 872 1047 1048 -3 2160 1057 2159 -3 1110 865 2296 -3 2162 870 1058 -3 2169 2171 879 -3 2168 1048 1047 -3 2175 2169 879 -3 2175 2170 2169 -3 2176 1676 1677 -3 2170 1046 2169 -3 2279 2293 2282 -3 2293 1161 2282 -3 1044 2173 1675 -3 2173 1050 1675 -3 1201 2345 1202 -3 1204 1203 2351 -3 1201 2344 2345 -3 1318 2262 1803 -3 1678 1676 2178 -3 1676 1678 869 -3 2179 286 959 -3 286 1335 959 -3 2181 958 2180 -3 2180 958 2179 -3 959 2180 2179 -3 2222 1004 2760 -3 1898 19 1897 -3 2220 2188 2228 -3 2759 1961 287 -3 2266 2267 508 -3 2181 2182 958 -3 958 2183 471 -3 958 2182 2183 -3 2182 474 2183 -3 2698 898 301 -3 1420 2449 1400 -3 2449 2528 1400 -3 894 883 770 -3 2186 1005 1003 -3 2185 2186 1003 -3 2202 1009 2205 -3 966 1604 1605 -3 1008 1005 2197 -3 2188 1004 2187 -3 2184 2190 2182 -3 711 1094 2562 -3 711 1095 1094 -3 2181 2180 288 -3 2187 2185 288 -3 2195 2220 2229 -3 2198 1008 2197 -3 2230 792 1525 -3 2407 2406 2198 -3 1112 2759 1007 -3 2579 831 2679 -3 830 2579 2679 -3 1009 1013 2205 -3 2208 2207 2206 -3 2211 2213 2212 -3 2213 1114 2208 -3 2550 2560 2549 -3 2200 2212 2204 -3 2200 2211 2212 -3 2548 2549 245 -3 1316 2414 1604 -3 2412 1153 1308 -3 2549 166 245 -3 291 1119 2207 -3 2416 2427 1190 -3 1013 1009 2207 -3 291 2208 1114 -3 2208 291 2207 -3 1655 1159 1292 -3 2002 2003 2001 -3 1654 1159 1655 -3 2210 2209 2199 -3 1012 2210 2199 -3 1010 2204 2212 -3 2213 1010 2212 -3 8 100 102 -3 1012 1961 2211 -3 2298 2297 1951 -3 2298 1950 2297 -3 2215 2217 1950 -3 843 2577 1026 -3 296 270 2401 -3 1111 2195 2214 -3 2194 2193 2227 -3 1111 2214 2215 -3 2183 474 2260 -3 353 2135 75 -3 2757 2758 2189 -3 1111 1112 1007 -3 2195 1111 1007 -3 2214 2216 2215 -3 1336 1335 270 -3 2218 2217 1949 -3 1830 1832 1829 -3 2641 1886 199 -3 2232 2231 1558 -3 2222 2225 2223 -3 97 269 271 -3 18 2641 2642 -3 1335 286 2404 -3 961 2228 2188 -3 1520 799 1522 -3 2583 1179 774 -3 2232 2233 150 -3 795 2582 2234 -3 2581 2582 795 -3 2253 2254 2234 -3 1465 432 1466 -3 1465 1466 1464 -3 2243 806 407 -3 2583 1174 1179 -3 2679 2680 830 -3 148 1902 433 -3 2237 795 2233 -3 2233 2254 150 -3 2234 2235 796 -3 2249 2234 796 -3 1175 2583 774 -3 393 1145 589 -3 2467 2469 596 -3 2579 2581 2580 -3 2579 2236 2581 -3 1473 414 1967 -3 148 433 1465 -3 2581 795 2580 -3 392 2239 487 -3 828 829 489 -3 2239 488 495 -3 487 2239 495 -3 846 487 495 -3 2241 2242 808 -3 817 808 2242 -3 2243 407 2242 -3 2240 807 2241 -3 2241 408 2240 -3 2242 2244 817 -3 807 2240 1145 -3 797 2252 2235 -3 266 87 267 -3 2245 1984 86 -3 1524 799 2250 -3 2648 995 35 -3 194 2648 35 -3 2245 1985 2246 -3 1520 2248 799 -3 118 119 20 -3 2254 2253 150 -3 150 2231 2232 -3 1821 2467 596 -3 2579 392 831 -3 2576 1943 2578 -3 2237 2233 2232 -3 797 420 2252 -3 2249 2253 2234 -3 2255 1474 1444 -3 492 1474 2255 -3 2220 2228 2256 -3 2229 2220 2256 -3 2219 2256 2228 -3 2229 2256 2219 -3 2155 2156 1058 -3 2157 1058 2156 -3 272 6 1168 -3 285 98 2403 -3 2402 2404 2403 -3 2404 2402 270 -3 2198 2406 2405 -3 1006 2198 2405 -3 2535 1526 1522 -3 284 2402 2403 -3 2163 1058 2157 -3 1272 2263 2264 -3 2263 1287 2264 -3 572 1583 1781 -3 2340 2339 1584 -3 2340 2338 2339 -3 1708 2338 2340 -3 1584 2266 1705 -3 2266 508 1705 -3 533 2735 535 -3 2735 1887 535 -3 1993 1992 886 -3 2270 1326 2272 -3 405 2299 782 -3 704 2563 842 -3 2273 1328 1325 -3 1329 1328 2273 -3 761 1328 1329 -3 1063 2276 1062 -3 2507 1040 2506 -3 2507 2513 1040 -3 2500 877 2501 -3 2277 877 2511 -3 2510 2277 2511 -3 2503 1064 2504 -3 2365 718 2286 -3 2278 1600 1064 -3 2278 1699 1600 -3 718 2362 2286 -3 2366 2014 935 -3 2322 715 2323 -3 2284 2288 2287 -3 684 2290 2153 -3 1403 2328 2327 -3 2290 2288 2284 -3 382 361 1165 -3 1504 2289 684 -3 719 1160 686 -3 684 2288 2290 -3 1504 1503 2289 -3 1154 1606 2687 -3 1606 1607 2687 -3 2319 1621 1624 -3 2315 1620 2320 -3 2280 1160 2292 -3 2279 1160 2280 -3 686 1160 2279 -3 2279 2280 2293 -3 2011 579 2010 -3 2012 2011 2010 -3 2671 1640 2674 -3 1725 698 2012 -3 698 2011 2012 -3 698 1097 1096 -3 500 514 825 -3 2291 2292 2281 -3 2292 1160 2281 -3 2430 2431 2293 -3 2431 1161 2293 -3 2300 1327 782 -3 2300 590 1327 -3 2411 2410 1290 -3 2411 2409 2410 -3 1290 2413 1159 -3 1603 1156 2301 -3 1521 1525 791 -3 801 2303 2304 -3 801 429 2303 -3 146 2304 2303 -3 2304 2143 362 -3 801 2304 362 -3 2434 496 2436 -3 2062 2310 427 -3 2314 2310 2313 -3 2314 427 2310 -3 1500 1501 2302 -3 2311 23 2312 -3 427 2311 2312 -3 429 2306 2305 -3 428 2307 2308 -3 428 2314 2313 -3 2311 427 2314 -3 1496 75 1495 -3 45 427 2312 -3 2446 2447 45 -3 1027 2446 1162 -3 2449 1420 1422 -3 1420 1401 1422 -3 2529 1410 1428 -3 870 1062 1128 -3 2317 716 1019 -3 1624 716 2317 -3 2322 1622 1621 -3 2318 2153 2290 -3 2319 1624 2317 -3 1410 2523 2520 -3 1578 2613 2603 -3 513 1564 1537 -3 1173 2363 2362 -3 386 1457 2326 -3 2002 2001 41 -3 2763 2002 41 -3 2366 2013 2014 -3 2541 2542 717 -3 2544 2541 717 -3 2434 1792 496 -3 850 1792 2434 -3 2303 429 2433 -3 626 633 2655 -3 2324 2325 723 -3 1625 1627 2325 -3 2327 1407 1408 -3 386 449 1345 -3 386 2326 449 -3 2329 1406 2330 -3 2445 2312 1162 -3 2312 2445 45 -3 136 382 379 -3 136 381 382 -3 1836 2494 2495 -3 2496 1836 2495 -3 1226 946 2334 -3 2496 2495 1239 -3 136 376 381 -3 572 2336 1708 -3 2335 947 49 -3 506 1592 514 -3 514 500 506 -3 2195 2196 2220 -3 2344 1200 2345 -3 572 1800 2336 -3 287 2194 2758 -3 531 2729 257 -3 2750 56 2747 -3 2337 2338 1708 -3 2337 2265 2338 -3 2725 2728 538 -3 447 446 187 -3 1667 862 222 -3 156 2342 105 -3 51 84 104 -3 2607 2605 1572 -3 2342 104 105 -3 2462 2463 1100 -3 1971 43 1973 -3 2641 200 2642 -3 2752 939 2749 -3 1795 2746 2745 -3 2745 2744 1795 -3 2344 1224 1200 -3 2344 2352 1224 -3 1209 2347 1200 -3 2347 2346 1200 -3 615 400 1210 -3 2347 1209 2351 -3 2745 2746 939 -3 1778 2745 939 -3 2545 1625 1626 -3 2352 1201 2353 -3 617 615 2359 -3 617 419 615 -3 2356 2355 2354 -3 426 2355 2356 -3 2354 2353 1199 -3 777 779 2488 -3 779 777 776 -3 426 617 2355 -3 1197 426 2356 -3 426 1197 1198 -3 2355 617 2359 -3 2359 2358 614 -3 2357 614 2358 -3 2355 2359 614 -3 2354 2355 614 -3 718 2364 2536 -3 2537 718 2536 -3 2360 2539 2538 -3 2360 719 2539 -3 2360 2538 2537 -3 2536 2360 2537 -3 718 2537 2362 -3 717 2545 2544 -3 719 2360 2361 -3 719 2361 1160 -3 2364 2281 1160 -3 2361 2364 1160 -3 1313 2369 372 -3 2109 229 1313 -3 229 2369 1313 -3 1300 2369 229 -3 315 316 236 -3 2370 230 2371 -3 2367 1475 2373 -3 312 313 2372 -3 1284 2367 2373 -3 315 233 316 -3 2381 2373 1476 -3 2382 1285 2383 -3 2374 2375 1380 -3 1284 2375 2374 -3 2378 1802 367 -3 2378 1271 1802 -3 2381 2380 1284 -3 1284 2373 2381 -3 1803 2376 1318 -3 2379 2372 230 -3 2371 230 2372 -3 2375 1284 2380 -3 310 1267 374 -3 2392 310 374 -3 2389 468 469 -3 468 2389 1898 -3 2378 2377 1271 -3 2380 368 2376 -3 1479 2388 2387 -3 2380 2382 368 -3 2381 2382 2380 -3 1283 2385 2386 -3 162 1480 1477 -3 2388 1898 2389 -3 2381 1285 2382 -3 2382 2385 368 -3 2384 2387 2391 -3 19 160 163 -3 163 160 159 -3 1478 1481 1480 -3 2392 2391 2395 -3 310 2392 2393 -3 368 2390 1318 -3 2376 368 1318 -3 2390 368 2385 -3 2385 1283 2390 -3 1480 162 1482 -3 1478 1480 1482 -3 1483 162 1319 -3 1283 2684 53 -3 2397 646 607 -3 646 2397 1913 -3 375 2390 2394 -3 375 1318 2390 -3 371 2391 2392 -3 310 2393 1018 -3 375 2394 1282 -3 2391 161 2395 -3 79 2736 2733 -3 1925 652 1929 -3 1665 890 2426 -3 2399 272 2398 -3 98 284 2403 -3 271 2401 2400 -3 2405 2199 2209 -3 1006 2405 2209 -3 2403 286 285 -3 1011 2406 2407 -3 2406 1011 2199 -3 2406 2199 2405 -3 2193 1011 2408 -3 2408 2197 2193 -3 1153 2411 1290 -3 1308 1605 2414 -3 1308 2414 2412 -3 2412 2414 1316 -3 1605 1604 2414 -3 1009 2202 2203 -3 1316 1601 2409 -3 1604 1601 1316 -3 1157 2410 2409 -3 2411 2412 1316 -3 1157 2415 2410 -3 2415 1157 2301 -3 2427 2396 1190 -3 2426 2421 1665 -3 2421 889 1665 -3 2422 404 759 -3 2424 591 1330 -3 1665 889 1664 -3 761 2420 2424 -3 2420 2418 2424 -3 761 1329 2420 -3 652 2425 759 -3 652 757 2425 -3 1189 1188 2419 -3 2418 2420 404 -3 760 759 2420 -3 759 404 2420 -3 760 588 1083 -3 1329 760 2420 -3 2424 1330 761 -3 1914 2428 649 -3 2423 884 889 -3 2418 763 2424 -3 890 2427 2416 -3 2427 2428 2396 -3 2427 890 2428 -3 2428 647 2396 -3 1914 647 2428 -3 1924 2423 757 -3 890 649 2428 -3 648 2423 1924 -3 862 2429 1813 -3 2291 2430 2292 -3 429 2305 2433 -3 2433 2302 2303 -3 2439 1439 1402 -3 2435 847 495 -3 847 846 495 -3 850 2435 495 -3 2482 2483 845 -3 486 845 2483 -3 2436 1900 847 -3 2434 2436 847 -3 1130 2767 396 -3 2767 2766 396 -3 850 2434 2435 -3 1899 1900 2436 -3 496 1899 2436 -3 391 2483 2482 -3 440 2440 1402 -3 440 2441 2440 -3 440 1414 2441 -3 189 2438 2439 -3 2438 1439 2439 -3 1414 442 2441 -3 1401 2444 2443 -3 1439 2438 2442 -3 2438 2437 2442 -3 2447 7 13 -3 1935 2447 13 -3 2566 342 2567 -3 425 973 2450 -3 996 61 995 -3 921 2451 2450 -3 2453 923 1995 -3 2453 1995 921 -3 2452 2453 921 -3 2452 5 2453 -3 973 5 2452 -3 978 5 973 -3 1103 1122 2577 -3 1972 253 1998 -3 2456 2457 1679 -3 2456 31 2457 -3 2455 2456 1679 -3 250 85 252 -3 907 68 963 -3 1026 2578 1945 -3 843 1074 2460 -3 2542 720 1628 -3 2461 1101 841 -3 1101 1124 841 -3 1100 2463 2464 -3 690 2464 2463 -3 2458 1101 2461 -3 2458 2462 1101 -3 2464 1092 1100 -3 2470 2654 596 -3 2469 2470 596 -3 626 2655 2470 -3 690 2465 2464 -3 2465 1092 2464 -3 1821 689 2467 -3 1222 575 94 -3 2470 1090 1089 -3 799 2248 2249 -3 2361 2536 2364 -3 2471 1637 582 -3 1127 913 2475 -3 952 1183 416 -3 2476 913 2477 -3 1057 2161 2163 -3 952 416 953 -3 2481 952 953 -3 520 502 1218 -3 1637 1640 1639 -3 577 1637 2471 -3 1638 1637 577 -3 1727 2473 582 -3 2477 174 2476 -3 810 409 827 -3 2540 2762 1173 -3 2538 2540 1173 -3 1173 2762 2543 -3 2762 2541 2543 -3 1781 1779 572 -3 952 2481 783 -3 809 409 810 -3 815 2479 409 -3 1360 695 2477 -3 695 1360 781 -3 2478 2476 827 -3 2479 2478 827 -3 2478 2479 783 -3 2767 824 2766 -3 2480 1127 2475 -3 847 391 2482 -3 1900 391 847 -3 2482 846 847 -3 1320 2488 610 -3 2710 418 2709 -3 957 2710 2709 -3 785 2712 2706 -3 2712 2709 2706 -3 2709 2712 2705 -3 610 2488 1198 -3 1813 2429 2489 -3 1035 863 2489 -3 1198 2487 2486 -3 863 1813 2489 -3 1813 863 1812 -3 2488 2487 1198 -3 2488 779 2487 -3 2429 1036 2489 -3 2609 2608 16 -3 7 2490 13 -3 13 2490 1938 -3 2493 1839 1837 -3 1974 1595 2596 -3 252 85 965 -3 2596 1973 1974 -3 2493 1843 1839 -3 2495 2494 1837 -3 1237 1835 1239 -3 948 181 1226 -3 2495 1837 1239 -3 2497 1238 740 -3 2646 2645 9 -3 2497 740 1836 -3 1128 2498 1129 -3 2770 2296 1059 -3 2769 2770 1060 -3 877 2277 2501 -3 2504 1064 2505 -3 868 2512 2509 -3 2499 2500 1042 -3 1105 864 1106 -3 2506 2505 871 -3 2507 871 1037 -3 2506 871 2507 -3 1031 2507 1037 -3 1105 2509 1040 -3 2509 1105 868 -3 2276 1063 2278 -3 2510 2276 2503 -3 2504 2510 2503 -3 2504 2277 2510 -3 875 1042 1041 -3 2276 2510 1062 -3 864 2513 2514 -3 2513 864 1105 -3 864 2514 1030 -3 1775 1893 2516 -3 2738 2739 1208 -3 1778 2753 2754 -3 2518 1893 1784 -3 2518 2516 1893 -3 1793 2518 1784 -3 1793 1783 2518 -3 1783 2516 2518 -3 1167 383 44 -3 1990 302 899 -3 901 302 1990 -3 360 302 901 -3 1409 2522 2521 -3 2521 2522 1404 -3 1989 1990 899 -3 2327 2522 1409 -3 440 1411 2524 -3 1424 1405 2528 -3 2528 1405 1416 -3 1421 1424 2528 -3 2522 1408 2526 -3 2522 2526 1404 -3 2526 1416 1404 -3 2526 2527 1416 -3 2526 1408 2527 -3 1678 2701 869 -3 376 377 349 -3 377 2531 349 -3 165 131 134 -3 377 135 2531 -3 135 2532 2531 -3 2532 349 2531 -3 139 349 2532 -3 73 139 2533 -3 74 349 908 -3 270 1335 2404 -3 2552 2551 245 -3 2360 2536 2361 -3 720 2542 1631 -3 251 254 2763 -3 841 1123 1122 -3 251 2763 41 -3 41 1999 251 -3 1631 2540 2539 -3 2540 2538 2539 -3 963 254 251 -3 2541 2544 2543 -3 2543 2544 2363 -3 2547 1627 717 -3 723 1627 1628 -3 1627 1625 2545 -3 1172 2546 3 -3 719 683 720 -3 2542 2547 717 -3 1892 101 2548 -3 29 100 2550 -3 100 29 102 -3 345 2564 1745 -3 256 2550 101 -3 256 29 2550 -3 60 1892 2551 -3 245 2553 2552 -3 2553 246 2552 -3 245 2551 2548 -3 2551 1892 2548 -3 167 2557 2558 -3 167 2554 2557 -3 67 255 248 -3 2556 63 2555 -3 2554 246 2553 -3 167 62 2555 -3 167 2555 2554 -3 2555 63 2554 -3 255 256 101 -3 67 256 255 -3 2557 2553 245 -3 166 2557 245 -3 2556 62 43 -3 1971 2556 43 -3 1680 31 2456 -3 62 2556 2555 -3 2558 2559 167 -3 2559 2558 15 -3 15 42 2559 -3 2562 1094 712 -3 842 2562 712 -3 2574 2231 150 -3 587 1651 1147 -3 2563 704 581 -3 2565 345 1747 -3 1735 324 1742 -3 2570 2571 1734 -3 2568 2571 88 -3 2571 2568 2572 -3 1735 2564 2573 -3 341 1747 1746 -3 88 2567 2568 -3 343 344 2568 -3 2569 1735 2573 -3 2569 2570 1735 -3 1734 2571 2572 -3 1333 213 1332 -3 432 147 801 -3 1731 1733 325 -3 1333 1734 2572 -3 2572 1334 1333 -3 1334 2572 344 -3 344 2572 2568 -3 432 801 114 -3 432 114 1467 -3 1467 114 2148 -3 2575 509 490 -3 1464 1463 148 -3 2235 2582 2236 -3 1103 2577 2460 -3 243 2682 2681 -3 844 243 2681 -3 2582 2235 2234 -3 2236 797 2235 -3 2237 2580 795 -3 2244 407 818 -3 1942 1937 12 -3 1549 1547 2586 -3 2586 1547 2585 -3 2585 1547 1567 -3 1547 1564 1567 -3 2586 1551 1549 -3 2588 842 712 -3 2590 1436 1135 -3 2590 2591 1441 -3 1472 1441 2591 -3 1472 1442 1441 -3 2593 2592 1440 -3 2590 1135 2591 -3 1701 2592 1135 -3 2591 1135 2592 -3 1437 2590 1441 -3 1472 2591 2592 -3 308 306 307 -3 2595 1434 1435 -3 2492 434 1471 -3 2595 2594 1434 -3 2600 251 1999 -3 2597 1973 2596 -3 2598 1595 1976 -3 251 250 252 -3 2600 250 251 -3 2598 1976 250 -3 1972 2599 253 -3 2599 2598 253 -3 550 548 976 -3 2617 1588 1593 -3 1591 506 2610 -3 2602 2604 1573 -3 974 549 968 -3 1572 2605 1574 -3 2602 1573 2603 -3 2612 2613 2614 -3 2613 1578 2614 -3 79 552 234 -3 852 79 234 -3 2605 515 2606 -3 2609 2603 1573 -3 2609 2610 2603 -3 515 498 2606 -3 2603 2610 503 -3 2616 1593 2608 -3 2612 2611 504 -3 2612 2615 2611 -3 1952 2619 1951 -3 2607 1572 2616 -3 1576 515 1575 -3 1576 1575 2601 -3 2613 2612 504 -3 2612 1577 2615 -3 1577 1994 2615 -3 1577 1579 1994 -3 1769 1579 1577 -3 1573 2607 2618 -3 2619 1953 290 -3 2616 2617 1593 -3 1572 2617 2616 -3 2617 1572 1588 -3 2620 1413 10 -3 2621 2620 10 -3 1429 2621 10 -3 10 2529 1429 -3 2529 1428 1429 -3 441 1413 2623 -3 585 2626 2625 -3 1642 1641 2624 -3 1648 1642 2624 -3 585 2625 2627 -3 608 1644 1643 -3 2625 1649 1648 -3 708 709 581 -3 398 2634 2631 -3 2635 2631 1638 -3 2629 2627 709 -3 2630 2628 2629 -3 2627 2624 709 -3 2627 2625 2624 -3 1648 2624 2625 -3 2629 708 2630 -3 2635 1638 577 -3 2628 2630 580 -3 398 803 802 -3 2633 2634 585 -3 2633 585 2632 -3 398 2631 2635 -3 2632 2629 2628 -3 2633 2632 2628 -3 398 802 2634 -3 1151 399 1150 -3 1195 1150 612 -3 2636 94 804 -3 845 486 2676 -3 2639 1473 1443 -3 215 1723 1722 -3 2643 2644 2642 -3 2145 2650 385 -3 200 31 2643 -3 200 2643 2642 -3 2645 18 2644 -3 1886 2649 196 -3 2647 2646 9 -3 1684 1728 2647 -3 1728 195 2647 -3 2645 194 1885 -3 1684 2647 9 -3 995 994 35 -3 2646 2648 194 -3 2645 2646 194 -3 1728 1729 195 -3 2649 1885 196 -3 2136 2137 358 -3 2651 2652 385 -3 144 2136 2651 -3 359 44 2652 -3 360 44 359 -3 604 787 635 -3 2655 2654 2470 -3 2655 634 2654 -3 2655 633 634 -3 1526 1528 1522 -3 785 2703 2704 -3 785 2659 2703 -3 559 2657 1505 -3 1506 2657 559 -3 2707 2706 418 -3 1433 467 2715 -3 2706 2661 785 -3 466 2660 561 -3 561 2660 562 -3 558 784 1506 -3 1835 1237 2663 -3 2663 1237 725 -3 725 1238 2663 -3 1835 1238 2497 -3 1835 2663 1238 -3 2664 687 1822 -3 687 1823 1822 -3 692 1819 1818 -3 692 1392 2666 -3 1389 598 2666 -3 2630 2672 580 -3 1191 607 1192 -3 653 2665 759 -3 512 1592 1566 -3 797 829 798 -3 1592 512 514 -3 397 803 804 -3 2012 2010 699 -3 2668 578 2673 -3 1640 2671 2667 -3 2012 699 2673 -3 578 1725 2673 -3 2760 2756 2222 -3 1638 2628 2675 -3 2675 2628 580 -3 580 2674 2675 -3 398 2636 804 -3 398 2635 2636 -3 1637 2675 2674 -3 2674 1640 1637 -3 1645 583 579 -3 1120 2677 1117 -3 2756 2760 2221 -3 1957 1115 1958 -3 1117 2677 960 -3 2682 243 2127 -3 832 2680 2679 -3 2392 374 2683 -3 371 2392 2683 -3 53 2683 1269 -3 367 1269 1268 -3 1269 374 1268 -3 374 1269 2683 -3 867 978 2685 -3 1054 2686 866 -3 1054 2294 2686 -3 905 904 2688 -3 905 322 904 -3 146 2696 363 -3 2691 111 905 -3 2690 2693 2694 -3 896 145 2699 -3 2690 300 2693 -3 2690 22 300 -3 363 2695 898 -3 2690 2696 22 -3 2692 304 111 -3 2699 898 2698 -3 2698 897 2699 -3 1501 22 2696 -3 359 145 896 -3 2700 2699 897 -3 899 2700 897 -3 302 2700 899 -3 2702 1774 1773 -3 1785 2702 1773 -3 2702 1785 519 -3 435 444 436 -3 2713 1440 437 -3 521 1771 1770 -3 527 1355 1354 -3 2715 467 2716 -3 2710 839 2711 -3 1800 2751 2336 -3 957 2705 411 -3 2707 2661 2706 -3 417 957 955 -3 2705 2712 785 -3 2708 415 1435 -3 415 838 1435 -3 1795 1780 2746 -3 2711 2708 418 -3 2710 2711 418 -3 492 1449 1661 -3 839 415 2711 -3 191 1435 2715 -3 2715 1435 2714 -3 1431 2719 1430 -3 1430 2719 1419 -3 1431 2718 2719 -3 2718 1431 1432 -3 2718 437 2719 -3 1701 1419 2719 -3 753 755 309 -3 14 306 308 -3 1017 2395 161 -3 308 307 153 -3 539 2729 2730 -3 2732 2731 537 -3 239 2731 2732 -3 2735 185 1887 -3 532 185 2735 -3 2733 522 239 -3 2733 239 2732 -3 97 2742 274 -3 2742 97 1168 -3 2741 573 1206 -3 1207 2737 2741 -3 939 2748 2749 -3 2753 1778 939 -3 2749 2747 1710 -3 1782 571 2755 -3 1779 2755 2754 -3 2541 2762 2540 -3 2196 2758 2757 -3 1007 2758 2196 -3 2758 1007 2759 -3 2759 1112 1961 -3 2225 2761 2757 -3 2756 2757 2761 -3 387 2764 1470 -3 2766 824 826 -3 825 824 2768 -3 1703 1585 2771 -3 1042 875 2769 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + compas_cgal/data/elephant.off at main · compas-dev/compas_cgal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ Skip to content + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + +
+ Open in github.dev + Open in a new github.dev tab + Open in codespace + + + + + + + + + + + + + + + + + + +

Files

Latest commit

 

History

History
8337 lines (8335 loc) · 178 KB

elephant.off

File metadata and controls

8337 lines (8335 loc) · 178 KB
+
+ + + + +
+ +
+ +
+
+ +
+ +
+

Footer

+ + + + +
+
+ + + + + © 2025 GitHub, Inc. + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + diff --git a/docs/_images/cgal_boolean_split.png b/docs/_images/cgal_boolean_split.png deleted file mode 100644 index 04458e5a..00000000 Binary files a/docs/_images/cgal_boolean_split.png and /dev/null differ diff --git a/docs/_images/cgal_boolean_union.png b/docs/_images/cgal_boolean_union.png deleted file mode 100644 index 087174f2..00000000 Binary files a/docs/_images/cgal_boolean_union.png and /dev/null differ diff --git a/docs/_images/cgal_dev_guide_overview_0.drawio b/docs/_images/cgal_dev_guide_overview_0.drawio new file mode 100644 index 00000000..c087107d --- /dev/null +++ b/docs/_images/cgal_dev_guide_overview_0.drawio @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_images/cgal_dev_guide_overview_0.drawio.png b/docs/_images/cgal_dev_guide_overview_0.drawio.png new file mode 100644 index 00000000..65c0749d Binary files /dev/null and b/docs/_images/cgal_dev_guide_overview_0.drawio.png differ diff --git a/docs/_images/cgal_intersections.png b/docs/_images/cgal_intersections.png deleted file mode 100644 index d6a92624..00000000 Binary files a/docs/_images/cgal_intersections.png and /dev/null differ diff --git a/docs/_images/cgal_pointset_normal_estimation.png b/docs/_images/cgal_pointset_normal_estimation.png deleted file mode 100644 index bd544437..00000000 Binary files a/docs/_images/cgal_pointset_normal_estimation.png and /dev/null differ diff --git a/docs/_images/cgal_pointset_reduction.png b/docs/_images/cgal_pointset_reduction.png deleted file mode 100644 index 2f4afa13..00000000 Binary files a/docs/_images/cgal_pointset_reduction.png and /dev/null differ diff --git a/docs/_images/cgal_pointset_smoothing.png b/docs/_images/cgal_pointset_smoothing.png deleted file mode 100644 index 13b52d33..00000000 Binary files a/docs/_images/cgal_pointset_smoothing.png and /dev/null differ diff --git a/docs/_images/cgal_pointsets_outliers.png b/docs/_images/cgal_pointsets_outliers.png deleted file mode 100644 index 5252cd66..00000000 Binary files a/docs/_images/cgal_pointsets_outliers.png and /dev/null differ diff --git a/docs/_images/cgal_polygonal_surface_reconstruction_ransac.png b/docs/_images/cgal_polygonal_surface_reconstruction_ransac.png deleted file mode 100644 index e9d5e251..00000000 Binary files a/docs/_images/cgal_polygonal_surface_reconstruction_ransac.png and /dev/null differ diff --git a/docs/_images/cgal_reconstruction.png b/docs/_images/cgal_reconstruction.png deleted file mode 100644 index ed0312f5..00000000 Binary files a/docs/_images/cgal_reconstruction.png and /dev/null differ diff --git a/docs/_images/cgal_remeshing.png b/docs/_images/cgal_remeshing.png deleted file mode 100644 index d12a88c9..00000000 Binary files a/docs/_images/cgal_remeshing.png and /dev/null differ diff --git a/docs/_images/cgal_skeletonization.png b/docs/_images/cgal_skeletonization.png deleted file mode 100644 index b3ebe9af..00000000 Binary files a/docs/_images/cgal_skeletonization.png and /dev/null differ diff --git a/docs/_images/cgal_slicer.png b/docs/_images/cgal_slicer.png deleted file mode 100644 index 253215e4..00000000 Binary files a/docs/_images/cgal_slicer.png and /dev/null differ diff --git a/docs/_images/cgal_straight_skeleton_2.png b/docs/_images/cgal_straight_skeleton_2.png deleted file mode 100644 index f56860dd..00000000 Binary files a/docs/_images/cgal_straight_skeleton_2.png and /dev/null differ diff --git a/docs/_images/cgal_straight_skeleton_2_holes.png b/docs/_images/cgal_straight_skeleton_2_holes.png deleted file mode 100644 index b535e924..00000000 Binary files a/docs/_images/cgal_straight_skeleton_2_holes.png and /dev/null differ diff --git a/docs/_images/cgal_straight_skeleton_2_offset.png b/docs/_images/cgal_straight_skeleton_2_offset.png deleted file mode 100644 index 0868a50b..00000000 Binary files a/docs/_images/cgal_straight_skeleton_2_offset.png and /dev/null differ diff --git a/docs/_images/cgal_straight_skeleton_2_offset_weighted.png b/docs/_images/cgal_straight_skeleton_2_offset_weighted.png deleted file mode 100644 index f9ffa8b7..00000000 Binary files a/docs/_images/cgal_straight_skeleton_2_offset_weighted.png and /dev/null differ diff --git a/docs/_images/cgal_subd.png b/docs/_images/cgal_subd.png deleted file mode 100644 index b0d89c39..00000000 Binary files a/docs/_images/cgal_subd.png and /dev/null differ diff --git a/docs/_images/cgal_subd_loop.png b/docs/_images/cgal_subd_loop.png deleted file mode 100644 index 8def9c3b..00000000 Binary files a/docs/_images/cgal_subd_loop.png and /dev/null differ diff --git a/docs/_images/cgal_triangulation.png b/docs/_images/cgal_triangulation.png deleted file mode 100644 index ffa9e1e6..00000000 Binary files a/docs/_images/cgal_triangulation.png and /dev/null differ diff --git a/docs/_images/example_booleans.png b/docs/_images/example_booleans.png new file mode 100644 index 00000000..7ae165a6 Binary files /dev/null and b/docs/_images/example_booleans.png differ diff --git a/docs/_images/example_intersections.png b/docs/_images/example_intersections.png new file mode 100644 index 00000000..95290820 Binary files /dev/null and b/docs/_images/example_intersections.png differ diff --git a/docs/_images/example_meshing.png b/docs/_images/example_meshing.png new file mode 100644 index 00000000..6a71534a Binary files /dev/null and b/docs/_images/example_meshing.png differ diff --git a/docs/_images/example_reconstruction_pointset_normal_estimation.png b/docs/_images/example_reconstruction_pointset_normal_estimation.png new file mode 100644 index 00000000..3f287602 Binary files /dev/null and b/docs/_images/example_reconstruction_pointset_normal_estimation.png differ diff --git a/docs/_images/example_reconstruction_pointset_outlier_removal.png b/docs/_images/example_reconstruction_pointset_outlier_removal.png new file mode 100644 index 00000000..75db19b4 Binary files /dev/null and b/docs/_images/example_reconstruction_pointset_outlier_removal.png differ diff --git a/docs/_images/example_reconstruction_pointset_reduction.png b/docs/_images/example_reconstruction_pointset_reduction.png new file mode 100644 index 00000000..9ff1c47e Binary files /dev/null and b/docs/_images/example_reconstruction_pointset_reduction.png differ diff --git a/docs/_images/example_reconstruction_pointset_smoothing.png b/docs/_images/example_reconstruction_pointset_smoothing.png new file mode 100644 index 00000000..d002be1d Binary files /dev/null and b/docs/_images/example_reconstruction_pointset_smoothing.png differ diff --git a/docs/_images/example_reconstruction_poisson_surface_reconstruction.png b/docs/_images/example_reconstruction_poisson_surface_reconstruction.png new file mode 100644 index 00000000..4372fb3b Binary files /dev/null and b/docs/_images/example_reconstruction_poisson_surface_reconstruction.png differ diff --git a/docs/_images/example_skeletonization.png b/docs/_images/example_skeletonization.png new file mode 100644 index 00000000..4174f51e Binary files /dev/null and b/docs/_images/example_skeletonization.png differ diff --git a/docs/_images/example_slicer.png b/docs/_images/example_slicer.png new file mode 100644 index 00000000..f60d4343 Binary files /dev/null and b/docs/_images/example_slicer.png differ diff --git a/docs/_images/example_straight_skeleton_2_interior_straight_skeleton.png b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton.png new file mode 100644 index 00000000..1c6b3935 Binary files /dev/null and b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton.png differ diff --git a/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.png b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.png new file mode 100644 index 00000000..8d76f032 Binary files /dev/null and b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.png differ diff --git a/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.png b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.png new file mode 100644 index 00000000..c2007c2b Binary files /dev/null and b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.png differ diff --git a/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_with_holes.png b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_with_holes.png new file mode 100644 index 00000000..fac4b2dd Binary files /dev/null and b/docs/_images/example_straight_skeleton_2_interior_straight_skeleton_with_holes.png differ diff --git a/docs/_images/example_subdivision.png b/docs/_images/example_subdivision.png new file mode 100644 index 00000000..50a89207 Binary files /dev/null and b/docs/_images/example_subdivision.png differ diff --git a/docs/_images/example_triangulation.png b/docs/_images/example_triangulation.png new file mode 100644 index 00000000..a86b1423 Binary files /dev/null and b/docs/_images/example_triangulation.png differ diff --git a/docs/_images/windows_frame.png b/docs/_images/windows_frame.png new file mode 100644 index 00000000..f861623e Binary files /dev/null and b/docs/_images/windows_frame.png differ diff --git a/docs/conf.py b/docs/conf.py index 85c9b38f..9f49a23c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,6 @@ # -- Extension configuration ------------------------------------------------ extensions = sphinx_compas2_theme.default_extensions -extensions.remove("sphinx.ext.linkcode") # numpydoc options diff --git a/docs/devguide.rst b/docs/devguide.rst index 2ec3a497..a2982863 100644 --- a/docs/devguide.rst +++ b/docs/devguide.rst @@ -4,153 +4,14 @@ Development Guide ******************************************************************************** -Environment -=========== - -To set up a local development environment, start by cloning the :mod:`compas_cgal` repo. - -.. code-block:: bash - - git clone https://github.com/compas-dev/compas_cgal - -Then, create an environment with all requirements using the provided environment file. -The requirements are slightly different on Windows... - -.. tab-set:: - - .. tab-item:: OSX - :selected: - - .. code-block:: bash - - cd compas_cgal - conda env create -f env_osx.yml - - .. tab-item:: Windows - - .. code-block:: bash - - cd compas_cgal - conda env create -f env_win.yml - -Note, that this will immediately also build :mod:`compas_cgal` for your system. - - -Build -===== - -To manually build the extension modules, -for example after making changes to one of the extension modules or adding a new one, -simply run the ``setup.py`` file trough a local source install. - -.. code-block:: bash - - pip install -e . - - -Extension modules -================= - -To control which extension modules are built, -modify the list of extension modules in the ``setup.py`` file, -and the corresponding declaration and initialisation of modules in ``src/compas_cgal.cpp``. -``src/compas_cgal.cpp`` is the entry point for all extension modules. -``src/compas.cpp`` defines common functionality for all extension modules. -They should not be removed. - -.. code-block:: python - - # setup.py - - ext_modules = [ - Extension( - 'compas_cgal._cgal', - sorted([ - 'src/compas_cgal.cpp', - 'src/compas.cpp', - 'src/meshing.cpp', - 'src/booleans.cpp', - 'src/slicer.cpp', - 'src/intersections.cpp', - 'src/measure.cpp', - 'src/triangulations.cpp', - 'src/subdivision.cpp', - ]), - include_dirs=[ - './include', - get_eigen_include(), - get_pybind_include() - ], - library_dirs=[ - get_library_dirs(), - ], - libraries=['mpfr', 'gmp'], - language='c++' - ), - ] - -.. code-block:: cpp - - // src/compas_cgal.cpp - - #include - #include - - - // here all modules of "_cgal" are declared. - void init_meshing(pybind11::module&); - void init_booleans(pybind11::module&); - void init_slicer(pybind11::module&); - void init_intersections(pybind11::module&); - void init_measure(pybind11::module&); - void init_triangulations(pybind11::module&); - void init_subdivision(pybind11::module&); - - - // the first parameter here ("_cgal") will be the name of the "so" or "pyd" file that will be produced by PyBind11 - // which is the entry point from where all other modules will be accessible. - PYBIND11_MODULE(_cgal, m) { - m.doc() = ""; - - // register Result as a Python class - pybind11::class_(m, "Result") - .def_readonly("vertices", &compas::Result::vertices) - .def_readonly("faces", &compas::Result::faces); - - // here all modules of "_cgal" are initializied. - init_meshing(m); - init_booleans(m); - init_slicer(m); - init_intersections(m); - init_measure(m); - init_triangulations(m); - init_subdivision(m); - } - - -Example -======= - -Coming soon! - - -Include Path -============ - -To avoid having VS Code complain about missing includes, add the following to ``.vscode/c_cpp_properties.json``. - -.. code-block:: json - - { - "configurations": [ - { - "includePath": [ - "${workspaceFolder}/**", - "/path/to/(mini)conda/envs/cgal-dev/include", - "/path/to/(mini)conda/envs/cgal-dev/include/python3.9", - "/path/to/(mini)conda/envs/cgal-dev/include/eigen3" - ] - } - } - -Replace ``/path/to/(mini)conda`` with the actual path to your (mini)conda installation. +.. toctree:: + :maxdepth: 1 + + devguide/overview + devguide/compiler + devguide/conda_environment + devguide/contribute + devguide/cmake_configuration + devguide/types + devguide/style + \ No newline at end of file diff --git a/docs/devguide/cmake_configuration.rst b/docs/devguide/cmake_configuration.rst new file mode 100644 index 00000000..96242e8d --- /dev/null +++ b/docs/devguide/cmake_configuration.rst @@ -0,0 +1,128 @@ +******************************************************************************** +CMake Configuration +******************************************************************************** + +This project uses CMake with scikit-build-core and nanobind for building Python extensions. + +Core Settings +============= + +.. code-block:: cmake + + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + +External Dependencies +===================== + +We use CMake's ExternalProject to manage external dependencies (CGAL, Boost, Eigen) as header-only libraries. This approach: + +1. Downloads dependencies at configure time +2. Extracts them to the ``external`` directory +3. Sets them up as header-only libraries +4. Requires no system-wide installation + +Configuration +------------- + +.. code-block:: cmake + + # Define source directories + set(EXTERNAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external") + set(CGAL_SOURCE_DIR "${EXTERNAL_DIR}/cgal") + set(BOOST_SOURCE_DIR "${EXTERNAL_DIR}/boost") + set(EIGEN_SOURCE_DIR "${EXTERNAL_DIR}/eigen") + + # Create target for all downloads + add_custom_target(external_downloads ALL) + +Example: CGAL Setup +------------------- + +.. code-block:: cmake + + if(NOT EXISTS "${CGAL_SOURCE_DIR}") + message(STATUS "Downloading CGAL...") + ExternalProject_Add( + cgal_download + URL https://github.com/CGAL/cgal/releases/download/v6.0.1/CGAL-6.0.1.zip + SOURCE_DIR "${CGAL_SOURCE_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + ) + add_dependencies(external_downloads cgal_download) + endif() + +Key Components +-------------- + +* ``SOURCE_DIR``: Where to extract the downloaded files +* Empty ``CONFIGURE_COMMAND``, ``BUILD_COMMAND``, ``INSTALL_COMMAND``: Treat as header-only +* ``LOG_DOWNLOAD ON``: Enable download progress logging +* ``add_dependencies``: Ensure downloads complete before building + +Include Directories +------------------- + +After download, headers are made available through: + +.. code-block:: cmake + + include_directories( + ${CGAL_INCLUDE_DIR} + ${BOOST_INCLUDE_DIR} + ${EIGEN_INCLUDE_DIR} + ) + +Build Flags +----------- + +Dependencies are configured as header-only libraries with specific compiler flags: + +.. code-block:: cmake + + add_definitions( + -DCGAL_HEADER_ONLY + -DBOOST_ALL_NO_LIB + -DBOOST_ALL_DYN_LINK=0 + -DCGAL_DISABLE_GMP + -DCGAL_USE_BOOST_MP + ) + + # Platform-specific flags + if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + endif() + set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON) + +This setup ensures: +* No compilation of external libraries needed +* Consistent headers across different platforms +* Simplified dependency management +* Reproducible builds + +Precompiled Headers +=================== + +We use precompiled headers to improve build times. The configuration is optimized for template-heavy code: + +.. code-block:: cmake + + # Enhanced PCH configuration + set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON) # Improve template compilation + set(CMAKE_PCH_WARN_INVALID ON) # Warn about invalid PCH usage + + # Configure PCH for the extension + target_precompile_headers(compas_cgal_ext + PRIVATE + src/compas.h + ) + +Note: When adding new headers that are frequently included, consider adding them to the precompiled header ``src/compas.h`` to further improve build times. Common headers to precompile: + +* STL containers (vector, string) +* CGAL core headers +* Boost headers used by CGAL +* Eigen matrix types \ No newline at end of file diff --git a/docs/devguide/compiler.rst b/docs/devguide/compiler.rst new file mode 100644 index 00000000..e6080947 --- /dev/null +++ b/docs/devguide/compiler.rst @@ -0,0 +1,45 @@ +******************************************************************************** +Compiler Requirements +******************************************************************************** + +Before installing COMPAS CGAL, you need to ensure you have the appropriate C++ compiler setup for your operating system: + +Windows +------- + +* Install Visual Studio Build Tools (2022 or newer) +* During installation, select "Desktop development with C++" +* No additional PATH settings are required as CMake will automatically find the compiler + +macOS +----- + +* Install Xcode Command Line Tools: + + .. code-block:: bash + + xcode-select --install + +* The clang compiler will be automatically available after installation + +Linux +----- + +* Install GCC and related build tools. On Ubuntu/Debian: + + .. code-block:: bash + + sudo apt-get update + sudo apt-get install build-essential + +* On RHEL/Fedora: + + .. code-block:: bash + + sudo dnf groupinstall "Development Tools" + +* Alternatively, when using conda, you can install the C++ compiler through conda: + + .. code-block:: bash + + conda install gxx_linux-64 diff --git a/docs/devguide/conda_environment.rst b/docs/devguide/conda_environment.rst new file mode 100644 index 00000000..35eb8b17 --- /dev/null +++ b/docs/devguide/conda_environment.rst @@ -0,0 +1,24 @@ +******************************************************************************** +Conda Environment +******************************************************************************** + +There are two ways to set up the development environment: + +Using environment.yml (recommended) +----------------------------------- + +.. code-block:: bash + + conda env create -f environment.yml + conda activate compas_cgal + +Manual setup +------------ + +.. code-block:: bash + + conda create -n compas_cgal python=3.9 compas -c conda-forge cmake -y + pip install -r requirements-dev.txt + pip install --no-build-isolation -ve . -Ceditable.rebuild=true + +Both methods will create and configure the same development environment. \ No newline at end of file diff --git a/docs/devguide/contribute.rst b/docs/devguide/contribute.rst new file mode 100644 index 00000000..89479962 --- /dev/null +++ b/docs/devguide/contribute.rst @@ -0,0 +1,191 @@ +******************************************************************************** +Contribute +******************************************************************************** + +Getting Started +=============== + +Fork the repository to your GitHub account and clone it: + +.. code-block:: bash + + git clone https://github.com//compas_cgal.git + cd compas_cgal + +Create and switch to a development branch: + +.. code-block:: bash + + git branch + git checkout + + +Binding Process +=============== + +C++ Binding +----------- + +Create new files in the ``src`` folder: + +.. code-block:: bash + + cd src + touch new_module.cpp + touch new_module.h + +Define new methods declarations in ``src/new_module.h``: + +.. code-block:: cpp + + #pragma once + + #include "compas.h" + + // Your method declarations here + +Implement the functions and add the nanobind module registration: ``src/new_module.cpp``: + +.. code-block:: cpp + + #include "new_module" + + // Your method definitions here + + void init_new_module(nb::module_& m) { + auto submodule = m.def_submodule("new_module"); + + submodule.def( + "python_function_name", + &cpp_function_name, + "description", + "my_argument1"_a, + "my_argument2"_a, + ); + } + +Add the submodule registration to ``src/compas_cgal.cpp``: + +.. code-block:: cpp + + #include "compas.h" + + ... + init_new_module(m); + ... + + NB_MODULE(compas_cgal_ext, m) { + ... + init_new_module(m); + } + +Rebuild the project with: + +.. code-block:: bash + + pip install --no-build-isolation -ve . -Ceditable.rebuild=true + + +.. note:: + It is advisable to include all the headers from 3rd-party libraries to the precompiled header ``src/compas.h`` so that your compilation time decreases. + +Python Binding +-------------- + +Add the new python submodule in ``src/compas_cgal/__init__.py``: + +.. code-block:: python + + __all_plugins__ = [ + ... + "compas_cgal.new_module", + ] + +Implement the submodule in ``src/compas_cgal/new_module.py``: + +.. code-block:: python + + from compas_cgal.compas_cgal_ext import new_module + + def new_function(): + ... + result_from_cplusplus = new_module.python_function_name() + ... + + +After creating new source files, you must rebuild the project with: + +.. code-block:: bash + + pip install --no-build-isolation -ve . + + + +Document, Test, and Format +========================== + +Documentation +------------- + +Document your scripts with a screenshot in ``docs/examples``. Documentation can be build with: + +.. code-block:: bash + + invoke docs + + +Scripts should be profiled for performance checks: + +.. code-block:: bash + + pip install line_profiler + kernprof -l -v -r + +Add a description of the changes in ``CHANGELOG.md``. + +.. code-block:: markdown + + ## [1.0.1] 2025-03-06 + + ### Added + + * Nanobind integration. + + ### Changed + + ### Removed + +Testing +------- + +Write tests in the ``tests`` folder and run with pytest. As a bare minimum add a simplest possible test, this will help you run all the tests to know if everything is working. + +.. code-block:: bash + + invoke test + + +Formatting +---------- + +Run the formatter to ensure consistent code style: + +.. code-block:: bash + + invoke format + invoke lint + + + +GitHub Pull Request +=================== + +Push the changes to your forked repository: + +.. code-block:: bash + + git add --all + git commit -m "commit message" + git push origin + +Afterwards there should be a green button on GitHub to open a pull request. Check if all the GitHub tasks run successfully. Lastly, as for a review of your code, assign a reviewer at the top left corner of the pull request and wait for the review and make the necessary changes. One of the reviewers will merge your pull request. \ No newline at end of file diff --git a/docs/devguide/overview.rst b/docs/devguide/overview.rst new file mode 100644 index 00000000..3835a0e4 --- /dev/null +++ b/docs/devguide/overview.rst @@ -0,0 +1,53 @@ +******************************************************************************** +Overview +******************************************************************************** + +`CGAL `_ integration uses `nanobind `_ for Python bindings for COMPAS. The contribution guide is found in the "Contribute" section, which explains how to create a pull-request and implement new methods in both C++ and Python. Additional sections explain technical aspects of the repository that are useful during the binding process. The workflow is as follows: + +.. figure:: /_images/cgal_dev_guide_overview_0.drawio.png + :figclass: figure + :class: figure-img img-fluid + + + + +File and Folder Structure +------------------------- + +The compas_cgal package contains numerous files and folders. For a simpler reference, check out the minimal `nanobind binding example `_. Comparing it with compas_cgal helps understand the additional features provided by the COMPAS framework. For basic CGAL bindings, you'll mainly work with the ``src`` folder, ``tests``, and ``scripts``. The ``CMakeLists.txt`` is pre-configured to automatically include any new C++ source files, so you typically won't need to modify it. + +Source Code +^^^^^^^^^^^ +* ``src/`` - C++ backend code +* ``src/compas_cgal/`` - Python frontend code + +Build & Dependencies +^^^^^^^^^^^^^^^^^^^^ +* ``build/`` - Distributables e.g. for PyPi package +* ``external/`` - External C++ dependencies, downloaded via CMake ExternalProject module +* ``CMakeLists.txt`` - C++ project configuration +* ``pyproject.toml`` - Python project configuration (pip install -e .) +* ``requirements.txt`` - Runtime requirements (pip install -r requirements.txt) +* ``requirements-dev.txt`` - Development requirement (pip install -r requirements-dev.txt) +* ``tasks.py`` - Development tasks (invoke test, invoke docs, invoke format, invoke lint) + +Tests & Examples +^^^^^^^^^^^^^^^^ +* ``scripts/`` - Example files +* ``tests/`` - Test files + +Documentation +^^^^^^^^^^^^^ +* ``docs/`` - Source code documentation +* ``dist/`` - Documentation build output + +Data & Temporary Files +^^^^^^^^^^^^^^^^^^^^^^ +* ``data/`` - Data sets (.json, .ply, .stl) +* ``temp/`` - Temporary files + +Project Info +^^^^^^^^^^^^ +* ``README.md`` - Project overview +* ``CHANGELOG.md`` - Feature changelog +* ``LICENSE`` - License information \ No newline at end of file diff --git a/docs/devguide/style.rst b/docs/devguide/style.rst new file mode 100644 index 00000000..f3819d38 --- /dev/null +++ b/docs/devguide/style.rst @@ -0,0 +1,129 @@ +******************************************************************************** +Style +******************************************************************************** + +Python Code +=========== + +Use the compas style guide from the offcial documentation `documentation `_. + + +C++ Code +======== + +* Functions: ``snake_case`` +* Variables (local and public): ``snake_case`` +* Private members: ``_snake_case`` (prefix with _) +* Static members: ``s_snake_case`` (prefix with s and _) +* Constants: ``SNAKE_UPPER_CASE`` +* Class/Struct names: ``UpperCamelCase`` + +Namespaces +---------- + +.. code-block:: cpp + + // Do not use "using namespace std", specify namespace explicitly + std::vector points; + +Functions +--------- + +.. code-block:: cpp + + // Next line bracket style + void compute_boolean_union() + { + /* content */ + } + +Structures +---------- + +.. code-block:: cpp + + // Structure name uses UpperCamelCase + struct MeshData + { + // Structure attribute uses snake_case + const char* file_name; + }; + + +Classes +------- + +.. code-block:: cpp + + // Class name uses UpperCamelCase + class BooleanSolver + { + public: + BooleanSolver(const double& tolerance); + virtual ~BooleanSolver(); + + // Member functions use snake_case + void compute_intersection() + { + // Local variable uses snake_case + double tolerance = 0.001; + } + + // Field indicator to separate functions and attributes + public: + int result_count; // Public member uses snake_case + + private: + void validate_mesh(); // Private function uses snake_case + double _tolerance; // Private member uses _snake_case + static int s_meshes; // Static member uses s_snake_case + const int MAX_COUNT = 100; // Constant uses SNAKE_UPPER_CASE + }; + +Docstrings +========== + +Use Doxygen-style comments with the following format: + +Functions and Methods +--------------------- + +.. code-block:: cpp + + /** + * @brief Short description of function + * @param[in] param1 Description of input parameter + * @param[out] param2 Description of output parameter + * @return Description of return value + * @throws Description of potential exceptions + */ + double compute_intersection(const Mesh& mesh1, Mesh& result); + +Classes +------- + +.. code-block:: cpp + + /** + * @brief Short description of class + * @details Longer description if needed + */ + class BooleanSolver { + public: + /** + * @brief Constructor description + * @param tolerance Mesh tolerance value + */ + BooleanSolver(double tolerance); + }; + +Member Variables +---------------- + +.. code-block:: cpp + + class BooleanSolver { + private: + double _tolerance; //!< Brief description of member variable + int s_meshes; //!< Use //!< for single-line member documentation + }; \ No newline at end of file diff --git a/docs/devguide/types.rst b/docs/devguide/types.rst new file mode 100644 index 00000000..ec01866c --- /dev/null +++ b/docs/devguide/types.rst @@ -0,0 +1,157 @@ +******************************************************************************** +Types +******************************************************************************** + +Type Conversion +=============== + +Matching C++/Python types often takes the most of the time and requires careful attention. When implementing C++/Python bindings, follow these key patterns from the existing files or implement your own. If there are specific types you want to implement, review the `nanobind tests `_ . Ask questions in discussion section for nanobind typing or follow previous issues. Current implementation provides examples for the following types: + + +* C++: + * Use ``Eigen::Ref`` for matrix parameters, e.g. to transfer mesh vertex coordinates. + * Return complex data as ``std::tuple`` types. + * Use ``std::vector`` for list copies otherwise use ``const std::vector &``. + * Use Eigen Matrix types in vectors ``const std::vector> &`` instead of reference type ``const std::vector> &``. + +* Python: + * Use ``float64`` for vertices and ``int32`` for faces in numpy arrays + * Enforce row-major (C-contiguous) order for matrices + + + +Type Conversion Patterns +======================== + +When implementing C++/Python bindings, follow these established patterns: + +Matrix Operations +----------------- + +Use ``Eigen::Ref`` for efficient matrix passing: + +.. code-block:: cpp + + void my_function(const Eigen::Ref& vertices, + const Eigen::Ref& faces); + +Return complex mesh data as tuples: + +.. code-block:: cpp + + return std::tuple my_function(); + +Enforce proper numpy array types using float64 and int32 in C-contiguous order: + +.. code-block:: python + + import numpy as np + from compas_cgal.compas_cgal_ext import my_submodule + + # Convert mesh vertices and faces to proper numpy arrays + vertices1 = np.asarray(mesh1.vertices, dtype=np.float64) + faces1 = np.asarray(mesh1.faces, dtype=np.int32) + + # Pass to C++ function + V, F = my_submodule.my_function(vertices1, faces1) + + + +Vector Types +------------ + +For list data, choose between ``std::vector`` for value copies, ``const std::vector&`` for references, and ``std::vector>`` for matrix vectors. + +Bind vector types explicitly: + +.. code-block:: cpp + + // In module initialization + nb::bind_vector>(m, "VectorDouble"); + +Access in Python: + +.. code-block:: python + + # Get vector result + vector_result = my_function() + # Access elements by index + x, y, z = vector_result[0], vector_result[1], vector_result[2] + +Follow existing patterns: ``booleans.cpp``: Matrix and tuple handling, ``measure.cpp``: Array type validation, ``reconstruction.cpp``: Complex data structures, ``skeletonization.cpp``: Point cloud data, ``slicer.cpp``: Geometry validation + +Type Conversion Best Practices +============================== + +When implementing new functionality: + +* Matrix Operations: + + .. code-block:: cpp + + // GOOD: Use Eigen::Ref for matrix parameters + void my_function(Eigen::Ref vertices); + + // BAD: Don't use raw matrices + void my_function(Eigen::MatrixXd vertices); + +* Return Types: + + .. code-block:: cpp + + // GOOD: Return complex data as tuples + std::tuple my_mesh_operation(); + + // BAD: Don't use output parameters + void my_mesh_operation(RowMatrixXd& out_vertices); + +* Vector Handling: + + .. code-block:: cpp + + // GOOD: Use const references for input vectors + void my_function(const std::vector& input); + + // GOOD: Return vectors by value + std::vector MyOperation(); + + // BAD: Don't use non-const references + void my_function(std::vector& input); + +* Matrix Vectors: + + .. code-block:: cpp + + // GOOD: Use Matrix types in vectors + std::vector> points; + + // BAD: Don't use Ref types in vectors + std::vector> points; + +* Python Integration: + + .. code-block:: python + + # GOOD: Enforce proper types + vertices = np.array(points, dtype=np.float64) + faces = np.array(indices, dtype=np.int32) + + # BAD: Don't rely on automatic conversion + vertices = points # type not enforced + faces = indices # type not enforced + +* Error Handling: + + .. code-block:: cpp + + // GOOD: Validate and throw with clear messages + if (!IsMeshClosed(vertices, faces)) { + throw std::runtime_error( + "Mesh must be closed for boolean operations" + ); + } + + // BAD: Don't silently handle errors + if (!IsMeshClosed(vertices, faces)) { + return std::make_tuple(vertices, faces); // original input + } diff --git a/docs/examples/__temp/booleans_rhino.py b/docs/examples/__temp/booleans_rhino.py deleted file mode 100644 index ba7868d0..00000000 --- a/docs/examples/__temp/booleans_rhino.py +++ /dev/null @@ -1,55 +0,0 @@ -from compas.geometry import Point -from compas.geometry import Box -from compas.geometry import Sphere -from compas.datastructures import Mesh - -from compas_rhino.artists import MeshArtist - -from compas.rpc import Proxy - -proxy = Proxy() - -# ============================================================================== -# Make a box and a sphere -# ============================================================================== - -box = Box.from_width_height_depth(2, 2, 2) -box = Mesh.from_shape(box) -box.quads_to_triangles() - -A = box.to_vertices_and_faces() - -sphere = Sphere(Point(1, 1, 1), 1) -sphere = Mesh.from_shape(sphere, u=30, v=30) -sphere.quads_to_triangles() - -B = sphere.to_vertices_and_faces() - -# ============================================================================== -# Remesh the sphere -# ============================================================================== - -proxy.package = 'compas_cgal.meshing' - -B = proxy.remesh(B, 0.3, 100) - -# ============================================================================== -# Compute the boolean mesh -# ============================================================================== - -proxy.package = 'compas_cgal.booleans' - -V, F = proxy.boolean_union(A, B) - -mesh = Mesh.from_vertices_and_faces(V, F) - -# ============================================================================== -# Visualize -# ============================================================================== - -artist = MeshArtist(mesh, layer="CGAL::Booleans::Union") -artist.clear_layer() - -artist.draw_faces(join_faces=True) - -artist.redraw() diff --git a/docs/examples/__temp/booleans_rhino.rst b/docs/examples/__temp/booleans_rhino.rst deleted file mode 100644 index 43661205..00000000 --- a/docs/examples/__temp/booleans_rhino.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Boolean operations in Rhino -******************************************************************************** - -.. figure:: /_images/cgal_boolean_union-rhino.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: booleans_rhino.py - :language: python diff --git a/docs/examples/__temp/intersections_rhino.py b/docs/examples/__temp/intersections_rhino.py deleted file mode 100644 index 9b0996da..00000000 --- a/docs/examples/__temp/intersections_rhino.py +++ /dev/null @@ -1,85 +0,0 @@ -from compas.datastructures import Mesh -from compas.geometry import Box -from compas.geometry import Point -from compas.geometry import Polyline -from compas.geometry import Sphere - -# @me add restart option to init -from compas.rpc import Proxy -from compas.utilities import hex_to_rgb -from compas_rhino.artists import MeshArtist -from compas_rhino.artists import PointArtist -from compas_rhino.artists import PolylineArtist - -proxy = Proxy() - -# ============================================================================== -# Make a box and a sphere -# ============================================================================== - -box = Box.from_width_height_depth(2, 2, 2) -box = Mesh.from_shape(box) -box.quads_to_triangles() - -A = box.to_vertices_and_faces() - -sphere = Sphere(Point(1, 1, 1), 1) -sphere = Mesh.from_shape(sphere, u=30, v=30) -sphere.quads_to_triangles() - -B = sphere.to_vertices_and_faces() - -# ============================================================================== -# Remesh the sphere -# ============================================================================== - -proxy.package = "compas_cgal.meshing" - -B = proxy.remesh(B, 0.3, 10) - -# ============================================================================== -# Compute the intersections -# ============================================================================== - -proxy.package = "compas_cgal.intersections" - -pointsets = proxy.intersection_mesh_mesh(A, B) - -# ============================================================================== -# Process output -# ============================================================================== - -polylines = [] -for points in pointsets: - points = [Point(*point) for point in points] - polyline = Polyline(points) - polylines.append(polyline) - -# ============================================================================== -# Visualize -# ============================================================================== - -meshartist = MeshArtist(None) - -meshartist.mesh = Mesh.from_vertices_and_faces(*A) -meshartist.layer = "CGAL::Intersections::A" -meshartist.clear_layer() -meshartist.draw_faces(join_faces=True, color=hex_to_rgb("#222222")) - -meshartist.mesh = Mesh.from_vertices_and_faces(*B) -meshartist.layer = "CGAL::Intersections::B" -meshartist.clear_layer() -meshartist.draw_faces(join_faces=True, color=hex_to_rgb("#888888")) - -polylineartist = PolylineArtist(None, layer="CGAL::Intersections::Polylines") -polylineartist.clear_layer() -pointartist = PointArtist(None, layer="CGAL::Intersections::Points") -pointartist.clear_layer() - -for polyline in polylines: - polylineartist.primitive = polyline - polylineartist.color = hex_to_rgb("#ffffff") - polylineartist.draw() - PointArtist.draw_collection(polyline.points, color=(255, 0, 0), layer="CGAL::Intersections::Points") - -polylineartist.redraw() diff --git a/docs/examples/__temp/remeshing_rhino.py b/docs/examples/__temp/remeshing_rhino.py deleted file mode 100644 index 3dcfb3ff..00000000 --- a/docs/examples/__temp/remeshing_rhino.py +++ /dev/null @@ -1,54 +0,0 @@ -import math -import compas - -from compas.geometry import scale_vector -from compas.geometry import Vector -from compas.geometry import Rotation -from compas.geometry import Translation -from compas.geometry import Scale -from compas.datastructures import Mesh - -from compas_rhino.artists import MeshArtist - -from compas.rpc import Proxy - -meshing = Proxy('compas_cgal.meshing') - -# ============================================================================== -# Get the bunny and construct a mesh -# ============================================================================== - -bunny = Mesh.from_ply(compas.get('bunny.ply')) - -bunny.cull_vertices() - -# ============================================================================== -# Move the bunny to the origin and rotate it upright. -# ============================================================================== - -vector = scale_vector(bunny.centroid(), -1) -T = Translation.from_vector(vector) -S = Scale.from_factors([100, 100, 100]) -R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90)) - -bunny.transform(R * S * T) - -# ============================================================================== -# Remesh -# ============================================================================== - -num_edges = bunny.number_of_edges() -length = sum(bunny.edge_length(*edge) for edge in bunny.edges()) / num_edges - -V, F = meshing.remesh(bunny.to_vertices_and_faces(), 2 * length, 10) - -bunny = Mesh.from_vertices_and_faces(V, F) - -# ============================================================================== -# Visualize -# ============================================================================== - -artist = MeshArtist(bunny, layer="CGAL::Remesh::Bunny") -artist.clear_layer() -artist.draw_faces(join_faces=True) -artist.redraw() diff --git a/docs/examples/__temp/slicer_rhino.py b/docs/examples/__temp/slicer_rhino.py deleted file mode 100644 index 3cc76b39..00000000 --- a/docs/examples/__temp/slicer_rhino.py +++ /dev/null @@ -1,93 +0,0 @@ -import math -import compas - -from compas.geometry import scale_vector -from compas.geometry import Point -from compas.geometry import Vector -from compas.geometry import Plane -from compas.geometry import Polyline -from compas.geometry import Rotation -from compas.geometry import Translation -from compas.geometry import Scale -from compas.datastructures import Mesh - -from compas_rhino.artists import MeshArtist -from compas_rhino.artists import PolylineArtist - -from compas.rpc import Proxy - -slicer = Proxy('compas_cgal.slicer') - -# ============================================================================== -# Get the bunny and construct a mesh -# ============================================================================== - -bunny = Mesh.from_ply(compas.get('bunny.ply')) - -# ============================================================================== -# Move the bunny to the origin and rotate it upright. -# ============================================================================== - -vector = Vector(* bunny.centroid()) -T = Translation.from_vector(vector.scaled(-1)) -S = Scale.from_factors([100, 100, 100]) -R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90)) - -bunny.transform(R * S * T) - -# ============================================================================== -# Create planes -# ============================================================================== - -# replace by planes along a curve - -bbox = bunny.bounding_box() - -x, y, z = zip(*bbox) -xmin, xmax = min(x), max(x) -N = 50 -dx = (xmax - xmin) / N - -normal = Vector(1, 0, 0) -planes = [] -for i in range(N): - x = xmin + i * dx - plane = Plane(Point(x, 0, 0), normal) - planes.append(plane) - -# ============================================================================== -# Slice -# ============================================================================== - -pointsets = slicer.slice_mesh( - bunny.to_vertices_and_faces(), - planes) - -# ============================================================================== -# Process output -# ============================================================================== - -polylines = [] -for points in pointsets: - points = [Point(*point) for point in points] # otherwise Polygon throws an error - polyline = Polyline(points) - polylines.append(polyline) - -# ============================================================================== -# Visualize -# ============================================================================== - -meshartist = MeshArtist(bunny, layer="CGAL::Slicer::Bunny") -meshartist.clear_layer() -meshartist.draw_faces(join_faces=True, color=(255, 200, 200)) -meshartist.redraw() - -# this is very slow - -polylineartist = PolylineArtist(None, layer="CGAL::Slicer::Slices") -polylineartist.clear_layer() -for polyline in polylines: - polylineartist.primitive = polyline - polylineartist.color = (255, 0, 0) - polylineartist.draw() -polylineartist.redraw() diff --git a/docs/examples/__temp/slicer_rhino.rst b/docs/examples/__temp/slicer_rhino.rst deleted file mode 100644 index 6ec1353a..00000000 --- a/docs/examples/__temp/slicer_rhino.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Mesh slicing in Rhino -******************************************************************************** - -.. figure:: /_images/cgal_slicer-rhino.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: slicer_rhino.py - :language: python diff --git a/docs/examples/booleans.py b/docs/examples/booleans.py deleted file mode 100644 index 2fc125ee..00000000 --- a/docs/examples/booleans.py +++ /dev/null @@ -1,44 +0,0 @@ -from compas.geometry import Box -from compas.geometry import Polyhedron -from compas.geometry import Sphere -from compas_cgal.booleans import boolean_union_mesh_mesh -from compas_cgal.meshing import mesh_remesh -from compas_viewer import Viewer - -# ============================================================================== -# Make a box and a sphere -# ============================================================================== - -box = Box(2) -A = box.to_vertices_and_faces(triangulated=True) - -sphere = Sphere(1, point=[1, 1, 1]) -B = sphere.to_vertices_and_faces(u=64, v=64, triangulated=True) - -# ============================================================================== -# Remesh the sphere -# ============================================================================== - -B = mesh_remesh(B, 0.3, 50) - -# ============================================================================== -# Compute the boolean mesh -# ============================================================================== - -V, F = boolean_union_mesh_mesh(A, B) -shape = Polyhedron(V.tolist(), F.tolist()) # revise the Shape API - -# temp solution until viewer supports polyhedrons -shape = shape.to_mesh() - -# ============================================================================== -# Visualize -# ============================================================================== - -viewer = Viewer() - -viewer.renderer.camera.target = [0, 0, 0] -viewer.renderer.camera.position = [4, -6, 3] - -viewer.scene.add(shape, lineswidth=1, show_points=False) -viewer.show() diff --git a/docs/examples/booleans.rst b/docs/examples/booleans.rst deleted file mode 100644 index 92635ac2..00000000 --- a/docs/examples/booleans.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Boolean operations -******************************************************************************** - -.. figure:: /_images/cgal_boolean_union.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: booleans.py - :language: python diff --git a/docs/examples/example_booleans.py b/docs/examples/example_booleans.py new file mode 100644 index 00000000..03f657b9 --- /dev/null +++ b/docs/examples/example_booleans.py @@ -0,0 +1,95 @@ +from compas.geometry import Box +from compas.geometry import Polyhedron +from compas.geometry import Sphere +from compas.datastructures import Mesh +from compas_cgal.booleans import ( + boolean_difference_mesh_mesh, + boolean_intersection_mesh_mesh, + boolean_union_mesh_mesh, + split_mesh_mesh, +) +from compas_cgal.meshing import mesh_remesh +from compas_viewer import Viewer +from compas.geometry import Translation +from line_profiler import profile + + +def input(): + """Create an input for the boolean methods.""" + box = Box(2) + A = box.to_vertices_and_faces(triangulated=True) + sphere = Sphere(1, point=[1, 1, 1]) + B = sphere.to_vertices_and_faces(u=64, v=64, triangulated=True) + B = mesh_remesh(B, 0.3, 50) + return A, B + + +def boolean_difference(): + """Compute the boolean difference of two triangle meshes.""" + A, B = input() + V, F = boolean_difference_mesh_mesh(A, B) + shape = Polyhedron(V.tolist(), F.tolist()) + shape = shape.to_mesh() + + return shape + + +def boolean_intersection(): + """Compute the boolean intersection of two triangle meshes.""" + A, B = input() + V, F = boolean_intersection_mesh_mesh(A, B) + shape = Polyhedron(V.tolist(), F.tolist()) + shape = shape.to_mesh() + + return shape + + +def boolean_union(): + """Compute the boolean union of two triangle meshes.""" + A, B = input() + V, F = boolean_union_mesh_mesh(A, B) + shape = Polyhedron(V.tolist(), F.tolist()) + shape = shape.to_mesh() + + return shape + + +def split(): + """Compute the mesh split of two triangle meshes.""" + A, B = input() + V, F = split_mesh_mesh(A, B) + mesh = Mesh.from_vertices_and_faces(V, F) + return mesh.exploded() + + +@profile +def main(): + """Compute the boolean difference, intersection, union, and split of two triangle meshes.""" + difference = boolean_difference() + intersection = boolean_intersection() + union = boolean_union() + splits = split() + return difference, intersection, union, splits + + +difference, intersection, union, splits = main() + + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +difference.transform(Translation.from_vector([-6, 0, 0])) +intersection.transform(Translation.from_vector([-2, 0, 0])) +union.transform(Translation.from_vector([2, 0, 0])) +for m in splits: + m.transform(Translation.from_vector([6, 0, 0])) + +viewer.scene.add(difference, lineswidth=1, show_points=False) +viewer.scene.add(intersection, lineswidth=1, show_points=False) +viewer.scene.add(union, lineswidth=1, show_points=False) +viewer.scene.add(splits, lineswidth=1, show_points=False) + +viewer.show() diff --git a/docs/examples/example_booleans.rst b/docs/examples/example_booleans.rst new file mode 100644 index 00000000..7ce128fc --- /dev/null +++ b/docs/examples/example_booleans.rst @@ -0,0 +1,18 @@ +Boolean Operations +================== + +This example demonstrates how to perform boolean operations between two triangle meshes using COMPAS CGAL. + +The following operations are demonstrated: + +* Boolean difference +* Boolean intersection +* Boolean union +* Mesh splitting + +.. figure:: /_images/example_booleans.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_booleans.py + :language: python diff --git a/docs/examples/example_intersections.py b/docs/examples/example_intersections.py new file mode 100644 index 00000000..7930a275 --- /dev/null +++ b/docs/examples/example_intersections.py @@ -0,0 +1,72 @@ +import profile +from compas.datastructures import Mesh +from compas.geometry import Box +from compas.geometry import Point +from compas.geometry import Polyline +from compas.geometry import Sphere +from compas_cgal.intersections import intersection_mesh_mesh +from compas_viewer import Viewer +from line_profiler import profile + + +@profile +def main(): + # ============================================================================== + # Make a box and a sphere + # ============================================================================== + + box = Box(2) + A = box.to_vertices_and_faces(triangulated=True) + + sphere = Sphere(1, point=[1, 1, 1]) + B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) + + # ============================================================================== + # Compute the intersections + # ============================================================================== + + pointsets = intersection_mesh_mesh(A, B) + + # ============================================================================== + # Process output + # ============================================================================== + + polylines = [] + for points in pointsets: + points = [Point(*point) for point in points] + polyline = Polyline(points) + polylines.append(polyline) + + return A, B, polylines + + +A, B, polylines = main() + +# ============================================================================= +# Visualize +# ============================================================================== + +viewer = Viewer() + +viewer.renderer.camera.target = [0, 0, 0] +viewer.renderer.camera.position = [4, -6, 3] + +viewer.scene.add(Mesh.from_vertices_and_faces(*A), facecolor=(1.0, 0.0, 0.0), show_points=False) +viewer.scene.add( + Mesh.from_vertices_and_faces(*B), + facecolor=(0.0, 1.0, 0.0), + show_points=False, + opacity=0.3, +) + +for polyline in polylines: + viewer.scene.add( + polyline, + linecolor=(0.0, 0.0, 1.0), + lineswidth=3, + pointcolor=(0.0, 0.0, 1.0), + pointsize=20, + show_points=True, + ) + +viewer.show() diff --git a/docs/examples/example_intersections.rst b/docs/examples/example_intersections.rst new file mode 100644 index 00000000..91999e21 --- /dev/null +++ b/docs/examples/example_intersections.rst @@ -0,0 +1,17 @@ +Mesh Intersections +===================== + +This example demonstrates how to compute intersections between two triangle meshes using COMPAS CGAL. + +Key Features: + +* Computing intersection curves between meshes +* Visualizing intersection results +* Handling multiple intersection curves + +.. figure:: /_images/example_intersections.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_intersections.py + :language: python diff --git a/docs/examples/example_measure.py b/docs/examples/example_measure.py new file mode 100644 index 00000000..454bfaa1 --- /dev/null +++ b/docs/examples/example_measure.py @@ -0,0 +1,39 @@ +from compas.geometry import Box +from compas_cgal.measure import ( + mesh_area, + mesh_volume, + mesh_centroid, +) +from compas.datastructures import Mesh +from line_profiler import profile + + +@profile +def main(mesh): + """Mesh measurement methods.""" + area = mesh_area(mesh) + volume = mesh_volume(mesh) + centroid = mesh_centroid(mesh) + return area, volume, centroid + + +# ============================================================================== +# Input geometry +# ============================================================================== + +box = Box(1) +v, f = box.to_vertices_and_faces() +mesh = Mesh.from_vertices_and_faces(v, f) +mesh.quads_to_triangles() +V, F = mesh.to_vertices_and_faces() + + +# ============================================================================== +# Compute +# ============================================================================== + +result = main((V, F)) + +print("Area:", result[0]) +print("Volume:", result[1]) +print("Centroid:", result[2]) diff --git a/docs/examples/example_measure.rst b/docs/examples/example_measure.rst new file mode 100644 index 00000000..06a3e281 --- /dev/null +++ b/docs/examples/example_measure.rst @@ -0,0 +1,14 @@ +Mesh Measurements +================= + +This example demonstrates how to compute basic geometric measurements of a mesh using COMPAS CGAL. + +Key Features: + +* Computing mesh surface area +* Computing mesh volume +* Computing mesh centroid +* Working with triangulated meshes + +.. literalinclude:: example_measure.py + :language: python diff --git a/docs/examples/example_meshing.py b/docs/examples/example_meshing.py new file mode 100644 index 00000000..1905fe03 --- /dev/null +++ b/docs/examples/example_meshing.py @@ -0,0 +1,58 @@ +from compas.datastructures import Mesh + +from compas.geometry import scale_vector +from compas.geometry import Translation +from compas.geometry import Rotation +from compas.geometry import Scale +from compas.geometry import Vector +from compas.geometry import transform_points_numpy +from pathlib import Path +import math +from line_profiler import profile +from compas_viewer import Viewer +from compas_cgal.meshing import mesh_remesh + + +@profile +def main(): + """Remesh a bunny mesh that is loaded from .ply file.""" + + FILE = Path(__file__).parent.parent.parent / "data" / "Bunny.ply" + bunny = Mesh.from_ply(FILE) + bunny.remove_unused_vertices() + + centroid = bunny.vertex_point(0) + + vector = scale_vector(centroid, -1) + + T = Translation.from_vector(vector) + S = Scale.from_factors([100, 100, 100]) + R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90)) + + bunny.transform(R * S * T) + V0, F0 = bunny.to_vertices_and_faces() + V1 = transform_points_numpy(V0, R * S * T) + + V1, F1 = mesh_remesh((V0, F0), 0.3, 10) + V1 = transform_points_numpy(V1, Translation.from_vector([20, 0, 0])) + mesh = Mesh.from_vertices_and_faces(V1, F1) + + return bunny, mesh + + +bunny, mesh = main() + + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer(width=1600, height=900) + +viewer.renderer.camera.target = [0, 0, 0] +viewer.renderer.camera.position = [0, -25, 10] + +viewer.scene.add(bunny, show_points=False) +viewer.scene.add(mesh, show_points=False) + +viewer.show() diff --git a/docs/examples/example_meshing.rst b/docs/examples/example_meshing.rst new file mode 100644 index 00000000..37c23805 --- /dev/null +++ b/docs/examples/example_meshing.rst @@ -0,0 +1,18 @@ +Mesh Remeshing +============== + +This example demonstrates how to remesh a triangle mesh using COMPAS CGAL. + +Key Features: + +* Loading PLY mesh files +* Mesh transformation and centering +* Remeshing with target edge length +* Side-by-side visualization of original and remeshed models + +.. figure:: /_images/example_meshing.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_meshing.py + :language: python diff --git a/docs/examples/example_nanobind_types.py b/docs/examples/example_nanobind_types.py new file mode 100644 index 00000000..57fa363f --- /dev/null +++ b/docs/examples/example_nanobind_types.py @@ -0,0 +1,37 @@ +from compas_cgal.compas_cgal_ext import scale_matrix, create_matrix, add +import numpy as np +from line_profiler import profile + + +@profile +def add_binding(a: int = 1, b: int = 2) -> int: + """Add two numbers in C++.""" + result = add(a, b) + return result + + +@profile +def scale_matrix_binding(mat: np.ndarray) -> None: + """Scale a NumPy matrix in-place using C++ and transfer to Python.""" + mat.flags.writeable = True + scale_matrix(mat) + return mat + + +@profile +def create_matrix_binding() -> np.ndarray: + """Create a NumPy matrix in C++ and transfer to Python.""" + mat: np.ndarray = create_matrix() + return mat + + +m = np.array( + [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]], + dtype=np.float64, + order="C", # C-style, row-major order to match Eigen::RowMajor +) + +print("Sum of 1 and 2 is:\n", add_binding(1, 2), "\n") +print("Input matrix:\n", m) +print("Scaled matrix:\n", scale_matrix_binding(m), "\n") +print("Created matrix:\n", create_matrix_binding(), "\n") diff --git a/docs/examples/example_nanobind_types.rst b/docs/examples/example_nanobind_types.rst new file mode 100644 index 00000000..c8a44e56 --- /dev/null +++ b/docs/examples/example_nanobind_types.rst @@ -0,0 +1,23 @@ +Type Conversion with Nanobind +============================= + +This example demonstrates proper type conversion patterns between C++ and Python using nanobind in COMPAS CGAL. + +Key Features: + +* Basic type conversion (integers) +* Matrix passing with Eigen::Ref (in-place modification) +* Matrix creation in C++ and transfer to Python +* Row-major order matrix handling +* Proper numpy array type handling (float64) + +.. note:: + This example follows the type conversion guidelines: + + * Uses row-major order for matrix operations + * Handles numpy arrays with proper data types + * Demonstrates both input and output matrix conversions + * Shows in-place matrix modification patterns + +.. literalinclude:: example_nanobind_types.py + :language: python diff --git a/docs/examples/example_reconstruction_pointset_normal_estimation.py b/docs/examples/example_reconstruction_pointset_normal_estimation.py new file mode 100644 index 00000000..829853b9 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_normal_estimation.py @@ -0,0 +1,60 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas.geometry import Line +from compas_cgal.reconstruction import pointset_normal_estimation +from compas_cgal.reconstruction import pointset_reduction +from compas_viewer import Viewer +from compas_viewer.scene import Collection +from compas_viewer.config import Config +from line_profiler import profile + + +@profile +def reconstruction_pointset_normal_estimation(): + # ============================================================================== + # Input geometry + # ============================================================================== + + FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" + + cloud = Pointcloud.from_ply(FILE) + reduced_cloud = Pointcloud(pointset_reduction(cloud, 10)) + points, vectors = pointset_normal_estimation(reduced_cloud, 16, True) + + # ============================================================================== + # Compute + # ============================================================================== + + lines = [] + line_scale = 10 + + for p, v in zip(points, vectors): + line = Line( + [p[0], p[1], p[2]], + [ + p[0] + v[0] * line_scale, + p[1] + v[1] * line_scale, + p[2] + v[2] * line_scale, + ], + ) + lines.append(line) + + return lines + + +lines = reconstruction_pointset_normal_estimation() + +# ============================================================================== +# Visualize +# ============================================================================== + +config = Config() +config.camera.target = [600, 500, 200] +config.camera.position = [600, -1000, 1500] +config.camera.scale = 100 +config.renderer.gridsize = (20000, 20, 20000, 20) + +viewer = Viewer(config=config) +viewer.scene.add(Collection(lines)) +viewer.show() diff --git a/docs/examples/example_reconstruction_pointset_normal_estimation.rst b/docs/examples/example_reconstruction_pointset_normal_estimation.rst new file mode 100644 index 00000000..22447906 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_normal_estimation.rst @@ -0,0 +1,18 @@ +Point Cloud Normal Estimation +============================= + +This example demonstrates how to estimate normals from a point cloud using COMPAS CGAL. + +Key Features: + +* Loading point clouds from PLY files +* Point cloud density reduction +* Normal estimation using k-nearest neighbors +* Visualization of point normals as scaled lines + +.. figure:: /_images/example_reconstruction_pointset_normal_estimation.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_reconstruction_pointset_normal_estimation.py + :language: python diff --git a/docs/examples/example_reconstruction_pointset_outlier_removal.py b/docs/examples/example_reconstruction_pointset_outlier_removal.py new file mode 100644 index 00000000..1aa7e884 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_outlier_removal.py @@ -0,0 +1,49 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas_cgal.reconstruction import pointset_outlier_removal +from compas_viewer import Viewer +from compas_viewer.config import Config +from line_profiler import profile + + +@profile +def reconstruction_pointset_outlier_removal(): + """Remove outliers from a point set.""" + + # ============================================================================== + # Input geometry + # ============================================================================== + + FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" + c1 = Pointcloud.from_ply(FILE) + + # ============================================================================== + # Compute + # ============================================================================== + + points = pointset_outlier_removal(c1, 30, 2.0) + c2 = Pointcloud(points) + c3 = c1.difference(c2) + + return c2, c3 + + +c_outlier_removal_0, c_outlier_removal_1 = reconstruction_pointset_outlier_removal() + +# ============================================================================== +# Visualize +# ============================================================================== + +config = Config() +config.camera.target = [600, 500, 200] +config.camera.position = [600, -1000, 1500] +config.camera.scale = 100 +config.renderer.gridsize = (20000, 20, 20000, 20) + +viewer = Viewer(config=config) + +viewer.scene.add(c_outlier_removal_0, pointcolor=(0.0, 0.0, 0.0)) +viewer.scene.add(c_outlier_removal_1, pointcolor=(1.0, 0.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_reconstruction_pointset_outlier_removal.rst b/docs/examples/example_reconstruction_pointset_outlier_removal.rst new file mode 100644 index 00000000..7ee0460e --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_outlier_removal.rst @@ -0,0 +1,18 @@ +Point Cloud Outlier Removal +=========================== + +This example demonstrates how to remove outliers from a point cloud using COMPAS CGAL. + +Key Features: + +* Loading point clouds from PLY files +* Removing outliers based on neighborhood analysis +* Visualization of inliers and outliers with different colors +* Point cloud difference computation + +.. figure:: /_images/example_reconstruction_pointset_outlier_removal.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_reconstruction_pointset_outlier_removal.py + :language: python diff --git a/docs/examples/example_reconstruction_pointset_reduction.py b/docs/examples/example_reconstruction_pointset_reduction.py new file mode 100644 index 00000000..1779c480 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_reduction.py @@ -0,0 +1,39 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas.geometry import Translation +from compas.geometry import transform_points_numpy +from compas_cgal.reconstruction import pointset_reduction +from compas_viewer import Viewer +from compas_viewer.config import Config +from line_profiler import profile + + +@profile +def reconstruction_pointset_reduction(): + """Reduce the number of points in a point set.""" + + FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" + c = Pointcloud.from_ply(FILE) + + points = Pointcloud(pointset_reduction(c, 50)) + transform_points_numpy(points, Translation.from_vector([-1000, 0, 0])) + + c.transform(Translation.from_vector([-1000, 0, 0])) + + return c, points + + +c_reduction_0, c_reduction_1 = reconstruction_pointset_reduction() + +config = Config() +config.camera.target = [100, 500, 200] +config.camera.position = [100, -1500, 2000] +config.camera.scale = 100 +config.renderer.gridsize = (20000, 20, 20000, 20) + +viewer = Viewer(config=config) +viewer.scene.add(c_reduction_0, pointcolor=(0.0, 0.0, 0.0)) +viewer.scene.add(c_reduction_1, pointcolor=(1.0, 0.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_reconstruction_pointset_reduction.rst b/docs/examples/example_reconstruction_pointset_reduction.rst new file mode 100644 index 00000000..6f77f864 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_reduction.rst @@ -0,0 +1,17 @@ +Point Cloud Reduction +===================== + +This example demonstrates how to reduce the density of a point cloud using COMPAS CGAL. + +Key Features: + +* Loading point clouds from PLY files +* Point cloud density reduction with specified percentage +* Side-by-side visualization of original and reduced point clouds + +.. figure:: /_images/example_reconstruction_pointset_reduction.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_reconstruction_pointset_reduction.py + :language: python diff --git a/docs/examples/example_reconstruction_pointset_smoothing.py b/docs/examples/example_reconstruction_pointset_smoothing.py new file mode 100644 index 00000000..734974d0 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_smoothing.py @@ -0,0 +1,39 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas.geometry import Translation +from compas_cgal.reconstruction import pointset_smoothing +from compas.geometry import transform_points_numpy +from compas_viewer import Viewer +from compas_viewer.config import Config +from line_profiler import profile + + +@profile +def reconstruction_pointset_smoothing(): + """Smooth a point set.""" + + ply_file_path = Path(__file__).parent.parent.parent / "data" / "box.ply" + original_points = Pointcloud.from_ply(ply_file_path) + + smoothed_points = pointset_smoothing(original_points, 1000, 3) + + return Pointcloud(original_points), Pointcloud(smoothed_points) + + +c_smoothing_0, c_smoothing_1 = reconstruction_pointset_smoothing() + +# ============================================================================== +# Visualize +# ============================================================================== + +config = Config() +config.camera.target = [0, 5000, -10000] +config.camera.position = [0, -10000, 30000] +config.camera.scale = 1000 +config.renderer.gridsize = (20000, 20, 20000, 20) + +viewer = Viewer(config=config) +viewer.scene.add(c_smoothing_0, pointcolor=(0.0, 0.0, 0.0)) +viewer.scene.add(c_smoothing_1, pointcolor=(1.0, 0.0, 0.0)) +viewer.show() diff --git a/docs/examples/example_reconstruction_pointset_smoothing.rst b/docs/examples/example_reconstruction_pointset_smoothing.rst new file mode 100644 index 00000000..053dbe60 --- /dev/null +++ b/docs/examples/example_reconstruction_pointset_smoothing.rst @@ -0,0 +1,17 @@ +Point Cloud Smoothing +===================== + +This example demonstrates how to smooth a point cloud using COMPAS CGAL. + +Key Features: + +* Loading point clouds from PLY files +* Point cloud smoothing with specified iterations and neighborhood size +* Side-by-side visualization of original and smoothed point clouds + +.. figure:: /_images/example_reconstruction_pointset_smoothing.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_reconstruction_pointset_smoothing.py + :language: python diff --git a/docs/examples/example_reconstruction_poisson_surface_reconstruction.py b/docs/examples/example_reconstruction_poisson_surface_reconstruction.py new file mode 100644 index 00000000..19c947bf --- /dev/null +++ b/docs/examples/example_reconstruction_poisson_surface_reconstruction.py @@ -0,0 +1,47 @@ +import math +from pathlib import Path + +from compas.datastructures import Mesh +from compas.geometry import Pointcloud +from compas.geometry import Rotation +from compas.geometry import Scale +from compas_cgal.reconstruction import poisson_surface_reconstruction +from compas_viewer import Viewer +from line_profiler import profile + + +@profile +def reconstruction_poisson_surface_reconstruction(): + FILE = Path(__file__).parent.parent.parent / "data" / "oni.xyz" + + points = [] + normals = [] + with open(FILE, "r") as f: + for line in f: + x, y, z, nx, ny, nz = line.strip().split() + points.append([float(x), float(y), float(z)]) + normals.append([float(nx), float(ny), float(nz)]) + + V, F = poisson_surface_reconstruction(points, normals) + mesh = Mesh.from_vertices_and_faces(V, F) + + c = Pointcloud(V) + + return c, mesh + + +points, mesh = reconstruction_poisson_surface_reconstruction() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +viewer.renderer.camera.target = [0, 0, 0] +viewer.renderer.camera.position = [0, -0.2, 2.0] + +viewer.scene.add(points, pointsize=10, pointcolor=(255, 0, 0)) +viewer.scene.add(mesh, show_points=False) + +viewer.show() diff --git a/docs/examples/example_reconstruction_poisson_surface_reconstruction.rst b/docs/examples/example_reconstruction_poisson_surface_reconstruction.rst new file mode 100644 index 00000000..7efe3fd9 --- /dev/null +++ b/docs/examples/example_reconstruction_poisson_surface_reconstruction.rst @@ -0,0 +1,18 @@ +Poisson Surface Reconstruction +============================== + +This example demonstrates how to perform Poisson surface reconstruction from a point cloud with normals using COMPAS CGAL. + +Key Features: + +* Loading point clouds with normals from XYZ files +* Poisson surface reconstruction to create a mesh +* Mesh transformation and visualization +* Side-by-side display of input points and reconstructed mesh + +.. figure:: /_images/example_reconstruction_poisson_surface_reconstruction.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_reconstruction_poisson_surface_reconstruction.py + :language: python diff --git a/docs/examples/example_skeletonization.py b/docs/examples/example_skeletonization.py new file mode 100644 index 00000000..74878514 --- /dev/null +++ b/docs/examples/example_skeletonization.py @@ -0,0 +1,57 @@ +import math +from pathlib import Path + +from compas.datastructures import Mesh +from compas.geometry import Polyline +from compas.geometry import Rotation +from compas.geometry import Scale +from compas.geometry import Translation +from compas_cgal.skeletonization import mesh_skeleton +from line_profiler import profile + +from compas_viewer import Viewer + + +@profile +def main(): + """Skeletonize a mesh.""" + + input_file = Path(__file__).parent.parent.parent / "data" / "elephant.off" + + rotation_x = Rotation.from_axis_and_angle([1, 0, 0], math.radians(60)) + rotation_y = Rotation.from_axis_and_angle([0, 1, 0], math.radians(5)) + rotation = rotation_y * rotation_x + scale = Scale.from_factors([5, 5, 5]) + translation = Translation.from_vector([0, 0, 2]) + + mesh = Mesh.from_off(input_file).transformed(translation * rotation * scale) + mesh = mesh.subdivided("loop") + + vertices, faces = mesh.to_vertices_and_faces() + + skeleton_edges = mesh_skeleton((vertices, faces)) + + polylines = [] + for start_point, end_point in skeleton_edges: + polyline = Polyline([start_point, end_point]) + polylines.append(polyline) + + return mesh, polylines + + +mesh, polylines = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() +viewer.renderer.camera.target = [0, 0, 1.5] +viewer.renderer.camera.position = [-5, -5, 1.5] + +viewer.scene.add(mesh, opacity=0.5, show_points=False) + +for polyline in polylines: + viewer.scene.add(polyline, linewidth=5, show_points=True) + +viewer.show() diff --git a/docs/examples/example_skeletonization.rst b/docs/examples/example_skeletonization.rst new file mode 100644 index 00000000..0ba747e3 --- /dev/null +++ b/docs/examples/example_skeletonization.rst @@ -0,0 +1,18 @@ +Mesh Skeletonization +==================== + +This example demonstrates how to compute the geometric skeleton of a triangle mesh using COMPAS CGAL. + +Key Features: + +* Loading and transforming OFF mesh files +* Mesh subdivision using Loop scheme +* Skeleton computation using mean curvature flow +* Visualization of mesh and skeleton polylines + +.. figure:: /_images/example_skeletonization.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_skeletonization.py + :language: python diff --git a/docs/examples/example_slicer.py b/docs/examples/example_slicer.py new file mode 100644 index 00000000..3ee7755d --- /dev/null +++ b/docs/examples/example_slicer.py @@ -0,0 +1,61 @@ +import math +from pathlib import Path + +from compas.datastructures import Mesh +from compas.geometry import Plane, Point, Vector +from compas.geometry import Polyline +from compas_cgal.slicer import slice_mesh +import numpy as np +from line_profiler import profile +from compas_viewer import Viewer +from compas_viewer.config import Config + + +@profile +def main(): + # Get Mesh from STL + FILE = Path(__file__).parent.parent.parent / "data" / "3DBenchy.stl" + benchy = Mesh.from_stl(FILE) + + V, F = benchy.to_vertices_and_faces() + + # Get Slice planes from the bounding box + bbox = benchy.aabb() + normal = Vector(0, 0, 1) + planes = [] + for i in np.linspace(bbox.zmin, bbox.zmax, 50): + plane = Plane(Point(0, 0, i), normal) + planes.append(plane) + + # Slice + slicer_polylines = slice_mesh((V, F), planes) + + # Convert edges to polylines + polylines = [] + for polyline in slicer_polylines: + points = [] + for point in polyline: + points.append(Point(*point)) + polylines.append(Polyline(points)) + + return benchy, polylines + + +mesh, polylines = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +config = Config() +config.camera.target = [0, 100, 0] +config.camera.position = [0, -75, 50] +config.camera.scale = 10 + +viewer = Viewer(config=config) + +viewer.scene.add(mesh, opacity=0.5, show_lines=False, show_points=False) +for polyline in polylines: + viewer.scene.add(polyline, linewidth=2, show_points=False) + +viewer.show() diff --git a/docs/examples/example_slicer.rst b/docs/examples/example_slicer.rst new file mode 100644 index 00000000..f662b4b1 --- /dev/null +++ b/docs/examples/example_slicer.rst @@ -0,0 +1,19 @@ +Mesh Slicing +============ + +This example demonstrates how to slice a mesh with multiple planes using COMPAS CGAL. + +Key Features: + +* Loading STL mesh files +* Creating multiple slice planes from bounding box +* Slicing mesh with multiple planes +* Converting slice results to polylines +* Visualization with semi-transparent mesh and slice curves + +.. figure:: /_images/example_slicer.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_slicer.py + :language: python diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.py b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.py new file mode 100644 index 00000000..b33182d6 --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.py @@ -0,0 +1,48 @@ +from compas.geometry import Polygon +from compas.geometry import Point +from compas_viewer import Viewer +from compas_cgal.straight_skeleton_2 import interior_straight_skeleton +from line_profiler import profile + + +@profile +def main(): + """Compute the interior straight skeleton of a polygon.""" + + points = [ + Point(-1.91, 3.59, 0.0), + Point(-5.53, -5.22, 0.0), + Point(-0.39, -1.98, 0.0), + Point(2.98, -5.51, 0.0), + Point(4.83, -2.02, 0.0), + Point(9.70, -3.63, 0.0), + Point(12.23, 1.25, 0.0), + Point(3.42, 0.66, 0.0), + Point(2.92, 4.03, 0.0), + Point(-1.91, 3.59, 0.0), + ] + polygon = Polygon(points) + + graph = interior_straight_skeleton(polygon) + + return graph + + +graph = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +for edge in graph.edges(): + line = graph.edge_line(edge) + if graph.edge_attribute(edge, "inner_bisector"): + viewer.scene.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2) + elif graph.edge_attribute(edge, "bisector"): + viewer.scene.add(line, linecolor=(0.0, 0.0, 1.0)) + else: + viewer.scene.add(line, linecolor=(0.0, 0.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.rst b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.rst new file mode 100644 index 00000000..8d396646 --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton.rst @@ -0,0 +1,17 @@ +Interior Straight Skeleton +========================== + +This example demonstrates how to compute the interior straight skeleton of a polygon using COMPAS CGAL. + +Key Features: + +* Computing straight skeleton from polygon points +* Visualization of inner bisectors and edges +* Color-coded display of different edge types + +.. figure:: /_images/example_straight_skeleton_2_interior_straight_skeleton.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_straight_skeleton_2_interior_straight_skeleton.py + :language: python diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py new file mode 100644 index 00000000..177da27e --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py @@ -0,0 +1,49 @@ +from compas.geometry import Polygon +from compas_viewer import Viewer + +from compas_cgal.straight_skeleton_2 import offset_polygon +from line_profiler import profile + + +@profile +def main(): + """Create offset polygons.""" + + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + polygon = Polygon(points) + offset = 1.5 + + offset_polygon_inner = offset_polygon(points, offset) + offset_polygon_outer = offset_polygon(points, -offset) + + return offset_polygon_inner, offset_polygon_outer, polygon + + +offset_polygon_inner, offset_polygon_outer, polygon = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() +viewer.scene.add(polygon) +viewer.config.renderer.show_grid = False + +for opolygon in offset_polygon_inner: + viewer.scene.add(opolygon, linecolor=(1.0, 0.0, 0.0), facecolor=(1.0, 1.0, 1.0, 0.0)) + +for opolygon in offset_polygon_outer: + viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.rst b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.rst new file mode 100644 index 00000000..a7c0370e --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.rst @@ -0,0 +1,17 @@ +Polygon Offsetting +================== + +This example demonstrates how to create offset polygons (inward and outward) using COMPAS CGAL. + +Key Features: + +* Creating inner and outer polygon offsets +* Handling complex polygon shapes +* Color-coded visualization of original and offset polygons + +.. figure:: /_images/example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py + :language: python diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.py b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.py new file mode 100644 index 00000000..e2ca0848 --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.py @@ -0,0 +1,47 @@ +from compas.geometry import Polygon +from compas_viewer import Viewer + +from compas_cgal.straight_skeleton_2 import weighted_offset_polygon +from line_profiler import profile + + +@profile +def main(): + """Create weighted offset polygons.""" + + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + polygon = Polygon(points) + + distances = [0.1, 0.3, 0.6, 0.1, 0.7, 0.5, 0.2, 0.4, 0.8, 0.2] + weights = [1.0 / d for d in distances] + offset = 1.0 + offset_polygons_outer = weighted_offset_polygon(points, -offset, weights) + + return offset_polygons_outer, polygon + + +offset_polygons_outer, polygon = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() +viewer.scene.add(polygon) +viewer.config.renderer.show_grid = False + +for opolygon in offset_polygons_outer: + viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.rst b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.rst new file mode 100644 index 00000000..c62d03ff --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.rst @@ -0,0 +1,17 @@ +Weighted Polygon Offsetting +=========================== + +This example demonstrates how to create weighted offset polygons using COMPAS CGAL. + +Key Features: + +* Creating weighted polygon offsets +* Specifying different weights for each edge +* Visualization of original and weighted offset polygons + +.. figure:: /_images/example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygon.py + :language: python diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.py b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.py new file mode 100644 index 00000000..ec5b2538 --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.py @@ -0,0 +1,57 @@ +from compas.geometry import Polygon +from compas.geometry import Point +from compas_viewer import Viewer +from compas_cgal.straight_skeleton_2 import interior_straight_skeleton_with_holes +from line_profiler import profile + + +@profile +def main(): + """Compute the interior straight skeleton of a polygon with holes.""" + + # Outer boundary + points_boundary = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + boundary = Polygon(points_boundary) + + # Inner holes + holes = [ + [(0.42, 0.88, 0.0), (1.1, -1.0, 0.0), (-1.97, -0.93, 0.0), (-1.25, 1.82, 0.0)], + [(4.25, -0.64, 0.0), (2.9, -3.03, 0.0), (2.12, -2.16, 0.0), (2.89, -0.36, 0.0)], + [(10.6, 0.29, 0.0), (9.48, -1.54, 0.0), (5.48, -1.26, 0.0), (5.98, -0.04, 0.0)], + ] + holes = [Polygon(hole) for hole in holes] + + graph = interior_straight_skeleton_with_holes(boundary, holes) + + return graph + + +graph = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +for edge in graph.edges(): + line = graph.edge_line(edge) + if graph.edge_attribute(edge, "inner_bisector"): + viewer.scene.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2) + elif graph.edge_attribute(edge, "bisector"): + viewer.scene.add(line, linecolor=(0.0, 0.0, 1.0)) + else: + viewer.scene.add(line, linecolor=(0.0, 0.0, 0.0)) + +viewer.show() diff --git a/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.rst b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.rst new file mode 100644 index 00000000..d53be5dd --- /dev/null +++ b/docs/examples/example_straight_skeleton_2_interior_straight_skeleton_with_holes.rst @@ -0,0 +1,18 @@ +Straight Skeleton with Holes +============================ + +This example demonstrates how to compute the straight skeleton of a polygon with holes using COMPAS CGAL. + +Key Features: + +* Computing straight skeleton for polygons with holes +* Handling multiple interior holes +* Color-coded visualization of different edge types +* Distinction between inner bisectors and regular edges + +.. figure:: /_images/example_straight_skeleton_2_interior_straight_skeleton_with_holes.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_straight_skeleton_2_interior_straight_skeleton_with_holes.py + :language: python diff --git a/docs/examples/example_subdivision.py b/docs/examples/example_subdivision.py new file mode 100644 index 00000000..438cabdd --- /dev/null +++ b/docs/examples/example_subdivision.py @@ -0,0 +1,70 @@ +from compas.geometry import Box +from compas.geometry import Polyhedron +from compas.geometry import Translation +from compas_cgal.subdivision import ( + mesh_subdivide_catmull_clark, + mesh_subdivide_loop, + mesh_subdivide_sqrt3, +) +from compas_viewer import Viewer +from line_profiler import profile + + +@profile +def main(): + """Subdivide a mesh using different subdivision algorithms.""" + + # ============================================================================== + # Input geometry + # ============================================================================== + + box = Box.from_diagonal(([0, 0, 0], [1, 1, 1])) + + V0, F0 = box.to_vertices_and_faces(triangulated=False) + V1, F1 = box.to_vertices_and_faces(triangulated=False) + V2, F2 = box.to_vertices_and_faces(triangulated=True) + V3, F3 = box.to_vertices_and_faces(triangulated=True) + + # ============================================================================== + # Compute + # ============================================================================== + + V4, F4 = mesh_subdivide_catmull_clark((V1, F1), 3) + V5, F5 = mesh_subdivide_loop((V2, F2), 3) + V6, F6 = mesh_subdivide_sqrt3((V3, F3), 3) + + S2 = Polyhedron(V0, F0) + S4 = Polyhedron(V4.tolist(), F4.tolist()) + S6 = Polyhedron(V5.tolist(), F5.tolist()) + S8 = Polyhedron(V6.tolist(), F6.tolist()) + + S2.transform(Translation.from_vector([1, 0, 0])) + S4.transform(Translation.from_vector([2, 0, 0])) + S6.transform(Translation.from_vector([3, 0, 0])) + S8.transform(Translation.from_vector([4, 0, 0])) + + # ============================================================================== + # Visualize + # ============================================================================== + + return S2, S4, S6, S8 + + +S2, S4, S6, S8 = main() + + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +viewer.renderer.camera.target = [3, 1, 0.5] +viewer.renderer.camera.position = [5, -2, 0.5] + +viewer.scene.add(S2.to_mesh(), show_points=False) +viewer.scene.add(S4.to_mesh(), show_points=False) +viewer.scene.add(S6.to_mesh(), show_points=False) +viewer.scene.add(S8.to_mesh(), show_points=False) + +viewer.show() diff --git a/docs/examples/example_subdivision.rst b/docs/examples/example_subdivision.rst new file mode 100644 index 00000000..d8ca85e2 --- /dev/null +++ b/docs/examples/example_subdivision.rst @@ -0,0 +1,18 @@ +Mesh Subdivision +================ + +This example demonstrates different mesh subdivision schemes available in COMPAS CGAL. + +Key Features: + +* Catmull-Clark subdivision for quad meshes +* Loop subdivision for triangle meshes +* √3 (sqrt3) subdivision for triangle meshes +* Side-by-side comparison of different subdivision methods + +.. figure:: /_images/example_subdivision.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_subdivision.py + :language: python diff --git a/docs/examples/example_triangulation.py b/docs/examples/example_triangulation.py new file mode 100644 index 00000000..dc46569b --- /dev/null +++ b/docs/examples/example_triangulation.py @@ -0,0 +1,58 @@ +from compas.datastructures import Mesh +from compas.geometry import Polygon +from compas.geometry import Translation +from compas_cgal.triangulation import conforming_delaunay_triangulation +from compas_cgal.triangulation import refined_delaunay_mesh +from compas_viewer import Viewer +from line_profiler import profile + + +@profile +def main(): + """Triangulate a mesh with holes.""" + + boundary = Polygon.from_sides_and_radius_xy(64, 4) + + hole = Polygon.from_sides_and_radius_xy(128, 1) + + hole1 = hole.transformed(Translation.from_vector([2, 0, 0])) + hole2 = hole.transformed(Translation.from_vector([-2, 0, 0])) + hole3 = hole.transformed(Translation.from_vector([0, 2, 0])) + hole4 = hole.transformed(Translation.from_vector([0, -2, 0])) + + holes = [hole1, hole2, hole3, hole4] + + V, F = conforming_delaunay_triangulation( + boundary, + holes=holes, + ) + + cdt = Mesh.from_vertices_and_faces(V, F) + + V, F = refined_delaunay_mesh( + boundary, + holes=holes, + maxlength=0.5, + is_optimized=True, + ) + + rdm = Mesh.from_vertices_and_faces(V, F) + + return cdt, rdm + + +cdt, rdm = main() + +# ============================================================================== +# Visualize +# ============================================================================== + +viewer = Viewer() + +viewer.renderer.camera.target = [0, 0, 0] +viewer.renderer.camera.position = [0, -1, 13] + +viewer.scene.add(cdt.transformed(Translation.from_vector([-5, 0, 0]))) +viewer.scene.add(rdm.transformed(Translation.from_vector([+5, 0, 0]))) + +viewer.show() diff --git a/docs/examples/example_triangulation.rst b/docs/examples/example_triangulation.rst new file mode 100644 index 00000000..219cd295 --- /dev/null +++ b/docs/examples/example_triangulation.rst @@ -0,0 +1,18 @@ +Mesh Triangulation +================== + +This example demonstrates how to perform constrained and refined Delaunay triangulation using COMPAS CGAL. + +Key Features: + +* Creating polygonal boundary and holes +* Conforming Delaunay triangulation +* Refined Delaunay meshing with size constraints +* Side-by-side visualization of different triangulation methods + +.. figure:: /_images/example_triangulation.png + :figclass: figure + :class: figure-img img-fluid + +.. literalinclude:: example_triangulation.py + :language: python diff --git a/docs/examples/intersections.py b/docs/examples/intersections.py deleted file mode 100644 index bf675d66..00000000 --- a/docs/examples/intersections.py +++ /dev/null @@ -1,57 +0,0 @@ -from compas.datastructures import Mesh -from compas.geometry import Box -from compas.geometry import Point -from compas.geometry import Polyline -from compas.geometry import Sphere -from compas_cgal.intersections import intersection_mesh_mesh -from compas_viewer import Viewer - -# ============================================================================== -# Make a box and a sphere -# ============================================================================== - -box = Box(2) -A = box.to_vertices_and_faces(triangulated=True) - -sphere = Sphere(1, point=[1, 1, 1]) -B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) - -# ============================================================================== -# Compute the intersections -# ============================================================================== - -pointsets = intersection_mesh_mesh(A, B) - -# ============================================================================== -# Process output -# ============================================================================== - -polylines = [] -for points in pointsets: - points = [Point(*point) for point in points] - polyline = Polyline(points) - polylines.append(polyline) - -# ============================================================================== -# Visualize -# ============================================================================== - -viewer = Viewer() - -viewer.renderer.camera.target = [0, 0, 0] -viewer.renderer.camera.position = [4, -6, 3] - -viewer.scene.add(Mesh.from_vertices_and_faces(*A), facecolor=(1.0, 0.0, 0.0), show_points=False) -viewer.scene.add(Mesh.from_vertices_and_faces(*B), facecolor=(0.0, 1.0, 0.0), show_points=False, opacity=0.3) - -for polyline in polylines: - viewer.scene.add( - polyline, - linecolor=(0.0, 0.0, 1.0), - lineswidth=3, - pointcolor=(0.0, 0.0, 1.0), - pointsize=20, - show_points=True, - ) - -viewer.show() diff --git a/docs/examples/intersections.rst b/docs/examples/intersections.rst deleted file mode 100644 index e5eb9135..00000000 --- a/docs/examples/intersections.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Mesh intersections -******************************************************************************** - -.. figure:: /_images/cgal_intersections.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: intersections.py - :language: python diff --git a/docs/examples/mesh_split.py b/docs/examples/mesh_split.py deleted file mode 100644 index 2f0ccd4d..00000000 --- a/docs/examples/mesh_split.py +++ /dev/null @@ -1,45 +0,0 @@ -from compas.colors import Color -from compas.datastructures import Mesh -from compas.geometry import Box -from compas.geometry import Sphere -from compas_cgal.booleans import split_mesh_mesh -from compas_viewer import Viewer - -# ============================================================================== -# Make a box and a sphere -# ============================================================================== - -box = Box(2) -A = box.to_vertices_and_faces(triangulated=True) - -sphere = Sphere(1, point=[1, 1, 1]) -B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) - -# ============================================================================== -# Compute the mesh split -# ============================================================================== - -V, F = split_mesh_mesh(A, B) - -mesh = Mesh.from_vertices_and_faces(V, F) - -# ============================================================================== -# Seperate disjoint faces and visualize -# ============================================================================== - -viewer = Viewer() - -viewer.renderer.camera.target = [0, 0, 0] -viewer.renderer.camera.position = [4, -6, 3] - -for color, vertices in zip([Color.blue(), Color.pink()], mesh.connected_vertices()): - faces = [] - for indices in F: - if all(index in vertices for index in indices): - faces.append(indices) - mesh = Mesh.from_vertices_and_faces(V, faces) - mesh.remove_unused_vertices() - - viewer.scene.add(mesh, facecolor=color, show_points=False) - -viewer.show() diff --git a/docs/examples/mesh_split.rst b/docs/examples/mesh_split.rst deleted file mode 100644 index 8b086a4d..00000000 --- a/docs/examples/mesh_split.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Mesh Split -******************************************************************************** - -.. figure:: /_images/cgal_boolean_split.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: mesh_split.py - :language: python diff --git a/docs/examples/pointset_reduction.py b/docs/examples/pointset_reduction.py deleted file mode 100644 index 5f872eee..00000000 --- a/docs/examples/pointset_reduction.py +++ /dev/null @@ -1,44 +0,0 @@ -from pathlib import Path - -from compas.colors import Color -from compas.geometry import Point -from compas.geometry import Pointcloud -from compas.geometry import Translation -from compas_cgal.reconstruction import pointset_reduction -from compas_viewer import Viewer - -# Define the file path for the point cloud data -FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" - -# Load the original point cloud -original_points = Pointcloud.from_ply(FILE) - -# Create a copy of the point cloud for processing -cloud = Pointcloud.from_ply(FILE) - -# Translate the original point cloud -cloud.transform(Translation.from_vector([-1000, 0, 0])) - -# Apply point set reduction to the translated point cloud -points = pointset_reduction(cloud, 50) -print(f"Original points: {len(cloud)}, Reduced points: {len(points)}") - -# ============================================================================= -# Viz -# ============================================================================= - -# Initialize the COMPAS viewer -viewer = Viewer() - -# viewer.ui.window.viewport.view3d.camera.position = [...] -# viewer.ui.window.viewport.view3d.camera.target = [...] - -# viewer.scene.add(Pointcloud(points)) -# viewer.scene.add(Pointcloud(original_points)) - -for x, y, z in points: - viewer.scene.add(Point(x, y, z).scaled(1e-3, 1e-3, 1e-3)) - -# viewer.renderer.camera.zoom_extents() - -viewer.show() diff --git a/docs/examples/pointset_reduction.rst b/docs/examples/pointset_reduction.rst deleted file mode 100644 index f88e163d..00000000 --- a/docs/examples/pointset_reduction.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Pointset Reduction -******************************************************************************** - -.. figure:: /_images/cgal_pointset_reduction.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: pointset_reduction.py - :language: python diff --git a/docs/examples/pointsets.py b/docs/examples/pointsets.py deleted file mode 100644 index 59d44ca5..00000000 --- a/docs/examples/pointsets.py +++ /dev/null @@ -1,29 +0,0 @@ -from pathlib import Path - -from compas.geometry import Pointcloud -from compas_cgal.reconstruction import pointset_outlier_removal -from compas_viewer import Viewer - -FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" - -cloud1 = Pointcloud.from_ply(FILE) - -points = pointset_outlier_removal(cloud1, 30, 2.0) - -cloud2 = Pointcloud(points) -could3 = cloud1.difference(cloud2) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer() - -# viewer.renderer.camera.scale = 1000 -# viewer.view.grid.cell_size = 1000 - -viewer.scene.add(cloud2) -viewer.scene.add(could3, pointcolor=(1.0, 0.0, 0.0)) - -# viewer.renderer.camera.zoom_extents() -viewer.show() diff --git a/docs/examples/pointsets.rst b/docs/examples/pointsets.rst deleted file mode 100644 index f8fe8915..00000000 --- a/docs/examples/pointsets.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Pointset Outlier Removal -******************************************************************************** - -.. figure:: /_images/cgal_pointsets_outliers.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: pointsets.py - :language: python diff --git a/docs/examples/pointsets_normals.py b/docs/examples/pointsets_normals.py deleted file mode 100644 index 59e6afcd..00000000 --- a/docs/examples/pointsets_normals.py +++ /dev/null @@ -1,59 +0,0 @@ -from pathlib import Path - -from compas.colors import Color -from compas.geometry import Line -from compas.geometry import Point -from compas.geometry import Pointcloud -from compas_cgal.reconstruction import pointset_normal_estimation -from compas_viewer import Viewer - -# from compas_view2.collections import Collection - -# Define the file path for the point cloud data -FILE = Path(__file__).parent.parent.parent / "data" / "forked_branch_1.ply" - -# Load the point cloud data from the PLY file -cloud = Pointcloud.from_ply(FILE) - -# Estimate normals for the point cloud -points, vectors = pointset_normal_estimation(cloud, 16, True) -print(f"Original points: {len(cloud)}, Points with normals: {len(points)}, Vectors: {len(vectors)}") - -# Create lines and properties for visualizing normals -lines = [] -line_properties = [] -line_scale = 25 - -# Iterate over points and vectors to create lines and color properties -for p, v in zip(points, vectors): - lines.append( - Line( - Point(p[0], p[1], p[2]), - Point(p[0] + v[0] * line_scale, p[1] + v[1] * line_scale, p[2] + v[2] * line_scale), - ) - ) - - # Normalize vector components to be in the range [0, 1] for color representation - r = (v[0] + 1) * 0.5 - g = (v[1] + 1) * 0.5 - b = (v[2] + 1) * 0.5 - - # Store line color properties - line_properties.append({"linecolor": Color(r, g, b)}) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer() - -# viewer.renderer.camera.scale = 1000 -# viewer.view.grid.cell_size = 1000 - -# line_collection = Collection(lines, line_properties) -# viewer.scene.add(Pointcloud(points)) -# viewer.scene.add(line_collection) - -# viewer.renderer.camera.zoom_extents() - -viewer.show() diff --git a/docs/examples/pointsets_normals.rst b/docs/examples/pointsets_normals.rst deleted file mode 100644 index b21c02d8..00000000 --- a/docs/examples/pointsets_normals.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Pointset Normal Estimation -******************************************************************************** - -.. figure:: /_images/cgal_pointset_normal_estimation.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: pointsets_normals.py - :language: python diff --git a/docs/examples/pointsets_smoothing.py b/docs/examples/pointsets_smoothing.py deleted file mode 100644 index f1fc0031..00000000 --- a/docs/examples/pointsets_smoothing.py +++ /dev/null @@ -1,40 +0,0 @@ -from pathlib import Path - -from compas.geometry import Pointcloud -from compas.geometry import Translation -from compas_cgal.reconstruction import pointset_smoothing -from compas_viewer import Viewer - -# Define the path to the PLY file -ply_file_path = Path(__file__).parent.parent.parent / "data" / "box.ply" - -# Load the original point cloud and translate it -original_points = Pointcloud.from_ply(ply_file_path) -original_points.transform(Translation.from_vector([-10000, 0, 0])) - -# Load another copy of the point cloud for comparison and translate it in the opposite direction -transformed_points = Pointcloud.from_ply(ply_file_path) -transformed_points.transform(Translation.from_vector([10000, 0, 0])) - -# Apply point set smoothing to the transformed point cloud -smoothed_points = pointset_smoothing(transformed_points, 1000, 3) - -# Create Pointcloud objects for visualization -cloud_original = Pointcloud(original_points) -cloud_transformed = Pointcloud(smoothed_points) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer() - -# viewer.renderer.camera.scale = 1000 -# viewer.view.grid.cell_size = 1000 - -# viewer.scene.add(cloud_original) -# viewer.scene.add(cloud_transformed) - -# viewer.renderer.camera.zoom_extents() - -viewer.show() diff --git a/docs/examples/pointsets_smoothing.rst b/docs/examples/pointsets_smoothing.rst deleted file mode 100644 index 35a635e2..00000000 --- a/docs/examples/pointsets_smoothing.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Pointset Smoothing -******************************************************************************** - -.. figure:: /_images/cgal_pointset_smoothing.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: pointsets_smoothing.py - :language: python diff --git a/docs/examples/polygonal_poinset_reconstruction.py b/docs/examples/polygonal_poinset_reconstruction.py deleted file mode 100644 index 03b8874e..00000000 --- a/docs/examples/polygonal_poinset_reconstruction.py +++ /dev/null @@ -1,75 +0,0 @@ -from pathlib import Path - -from compas.colors import Color -from compas.geometry import Line -from compas.geometry import Point -from compas.geometry import Pointcloud -from compas.geometry import Polygon -from compas_cgal.polygonal_surface_reconstruction import polygonal_surface_reconstruction_ransac -from compas_cgal.reconstruction import pointset_normal_estimation -from compas_viewer import Viewer - -# Define the file path for the point cloud data -FILE = Path(__file__).parent.parent.parent / "data" / "two_intersecting_boxes.ply" - -# Load the point cloud data from the PLY file -cloud = Pointcloud.from_ply(FILE) - -# Estimate normals for the point cloud -points, vectors = pointset_normal_estimation(cloud, 4, True) -vertices, faces = polygonal_surface_reconstruction_ransac(points, vectors) - -# Create polygons from vertices and faces -polygons = [] -for face in faces: - polygon = [] - for i in face: - polygon.append(Point(*vertices[i])) - polygons.append(Polygon(polygon)) - - -# Create lines and properties for visualizing normals -lines = [] -line_properties = [] -line_scale = 25 - -# Iterate over points and vectors to create lines and color properties -for p, v in zip(points, vectors): - # Create lines - lines.append( - Line( - Point(p[0], p[1], p[2]), - Point( - p[0] + v[0] * line_scale, - p[1] + v[1] * line_scale, - p[2] + v[2] * line_scale, - ), - ) - ) - - # Normalize vector components to be in the range [0, 1] for color representation - r = (v[0] + 1) * 0.5 - g = (v[1] + 1) * 0.5 - b = (v[2] + 1) * 0.5 - - # Store line color properties - line_properties.append({"linecolor": Color(r, g, b)}) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer() - -# viewer.renderer.camera.position = [5, -4, 2] -# viewer.renderer.camera.target = [0, 1, 0]) - -# viewer.scene.add(Pointcloud(points)) - -# line_collection = Collection(lines, line_properties) -# viewer.scene.add(line_collection) - -for polygon in polygons: - viewer.scene.add(polygon, linewidth=2) - -viewer.show() diff --git a/docs/examples/polygonal_poinset_reconstruction.rst b/docs/examples/polygonal_poinset_reconstruction.rst deleted file mode 100644 index 466c4e68..00000000 --- a/docs/examples/polygonal_poinset_reconstruction.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Polygonal PointSet Reconstruction -******************************************************************************** - -.. figure:: /_images/cgal_polygonal_surface_reconstruction_ransac.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: polygonal_poinset_reconstruction.py - :language: python diff --git a/docs/examples/reconstruction.py b/docs/examples/reconstruction.py deleted file mode 100644 index cc4b077f..00000000 --- a/docs/examples/reconstruction.py +++ /dev/null @@ -1,44 +0,0 @@ -import math -from pathlib import Path - -from compas.datastructures import Mesh -from compas.geometry import Pointcloud -from compas.geometry import Rotation -from compas.geometry import Scale -from compas_cgal.reconstruction import poisson_surface_reconstruction -from compas_viewer import Viewer - -FILE = Path(__file__).parent.parent.parent / "data" / "oni.xyz" - -points = [] -normals = [] -with open(FILE, "r") as f: - for line in f: - x, y, z, nx, ny, nz = line.strip().split() - points.append([float(x), float(y), float(z)]) - normals.append([float(nx), float(ny), float(nz)]) - -V, F = poisson_surface_reconstruction(points, normals) -mesh = Mesh.from_vertices_and_faces(V, F) - -R = Rotation.from_axis_and_angle([1, 0, 0], math.radians(90)) -S = Scale.from_factors([5, 5, 5]) -T = R * S - -cloud = Pointcloud(V) -cloud.transform(T) -mesh.transform(T) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer(width=1600, height=900) - -# viewer.renderer.camera.position = [-5, -5, 1.5] -# viewer.renderer.camera.target = [0, 0, 1.5]) - -viewer.scene.add(mesh, show_points=False) -# viewer.scene.add(cloud) - -viewer.show() diff --git a/docs/examples/reconstruction.rst b/docs/examples/reconstruction.rst deleted file mode 100644 index a6ae6de1..00000000 --- a/docs/examples/reconstruction.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Poisson Surface Reconstruction -******************************************************************************** - -.. figure:: /_images/cgal_reconstruction.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: reconstruction.py - :language: python diff --git a/docs/examples/remeshing.py b/docs/examples/remeshing.py deleted file mode 100644 index 9c032741..00000000 --- a/docs/examples/remeshing.py +++ /dev/null @@ -1,55 +0,0 @@ -import math -from pathlib import Path - -from compas.datastructures import Mesh -from compas.geometry import Rotation -from compas.geometry import Scale -from compas.geometry import Translation -from compas.geometry import Vector -from compas.geometry import scale_vector -from compas_cgal.meshing import mesh_remesh -from compas_viewer import Viewer - -FILE = Path(__file__).parent.parent.parent / "data" / "Bunny.ply" - -# ============================================================================== -# Get the bunny and construct a mesh -# ============================================================================== - -bunny = Mesh.from_ply(FILE) - -bunny.remove_unused_vertices() - -# ============================================================================== -# Move the bunny to the origin and rotate it upright. -# ============================================================================== - -vector = scale_vector(bunny.centroid(), -1) - -T = Translation.from_vector(vector) -S = Scale.from_factors([100, 100, 100]) -R = Rotation.from_axis_and_angle(Vector(1, 0, 0), math.radians(90)) - -bunny.transform(R * S * T) - -# ============================================================================== -# Remesh -# ============================================================================== - -V, F = mesh_remesh(bunny.to_vertices_and_faces(), 0.3, 10) - -mesh = Mesh.from_vertices_and_faces(V, F) - -# ============================================================================== -# Visualize -# ============================================================================== - -viewer = Viewer(width=1600, height=900) - -viewer.renderer.camera.target = [0, 0, 0] -viewer.renderer.camera.position = [0, -25, 10] - -viewer.scene.add(bunny.transformed(Translation.from_vector([-10, 0, 0])), show_points=False) -viewer.scene.add(mesh.transformed(Translation.from_vector([+10, 0, 0])), show_points=False) - -viewer.show() diff --git a/docs/examples/remeshing.rst b/docs/examples/remeshing.rst deleted file mode 100644 index d1d3ecb2..00000000 --- a/docs/examples/remeshing.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Remeshing -******************************************************************************** - -.. figure:: /_images/cgal_remeshing.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: remeshing.py - :language: python diff --git a/docs/examples/skeletonization.py b/docs/examples/skeletonization.py deleted file mode 100644 index aaa57d57..00000000 --- a/docs/examples/skeletonization.py +++ /dev/null @@ -1,48 +0,0 @@ -import math -from pathlib import Path - -from compas.datastructures import Mesh -from compas.geometry import Polyline -from compas.geometry import Rotation -from compas.geometry import Scale -from compas.geometry import Translation -from compas_cgal.skeletonization import mesh_skeleton -from compas_cgal.subdivision import mesh_subdivide_sqrt3 - -# from compas_view2.objects import Collection -from compas_viewer import Viewer - -FILE = Path(__file__).parent.parent.parent / "data" / "elephant.off" - -Rx = Rotation.from_axis_and_angle([1, 0, 0], math.radians(60)) -Ry = Rotation.from_axis_and_angle([0, 1, 0], math.radians(5)) -R = Ry * Rx -S = Scale.from_factors([5, 5, 5]) -T = Translation.from_vector([0, 0, 2]) -mesh = Mesh.from_off(FILE).transformed(T * R * S) - -V, F = mesh.to_vertices_and_faces() -V, F = mesh_subdivide_sqrt3((V, F), k=2) - -pointsets = mesh_skeleton((V, F)) - -polylines = [] -for points in pointsets: - polyline = Polyline(points) - polylines.append(polyline) - -# ============================================================================= -# Viz -# ============================================================================= - -viewer = Viewer(width=1600, height=900) - -viewer.renderer.camera.target = [0, 0, 1.5] -viewer.renderer.camera.position = [-5, -5, 1.5] - -viewer.scene.add(mesh, opacity=0.5, show_points=False) - -for polyline in polylines: - viewer.scene.add(polyline, lineswidth=5, show_points=False) - -viewer.show() diff --git a/docs/examples/skeletonization.rst b/docs/examples/skeletonization.rst deleted file mode 100644 index 5dd77de9..00000000 --- a/docs/examples/skeletonization.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Triangle Mesh Skeletonization -******************************************************************************** - -.. figure:: /_images/cgal_skeletonization.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: skeletonization.py - :language: python diff --git a/docs/examples/slicer.py b/docs/examples/slicer.py deleted file mode 100644 index 1fb745e7..00000000 --- a/docs/examples/slicer.py +++ /dev/null @@ -1,67 +0,0 @@ -from pathlib import Path - -import numpy as np -from compas.colors import Color -from compas.datastructures import Mesh -from compas.geometry import Plane -from compas.geometry import Point -from compas.geometry import Polyline -from compas.geometry import Vector -from compas_cgal.slicer import slice_mesh_planes -from compas_viewer import Viewer - -FILE = Path(__file__).parent.parent.parent / "data" / "3DBenchy.stl" - -# ============================================================================== -# Get benchy and construct a mesh -# ============================================================================== - -benchy = Mesh.from_stl(FILE) - -# ============================================================================== -# Create planes -# ============================================================================== - -# replace by planes along a curve - -bbox = benchy.aabb() - -normal = Vector(0, 0, 1) -planes = [] -for i in np.linspace(bbox.zmin, bbox.zmax, 50): - plane = Plane(Point(0, 0, i), normal) - planes.append(plane) - -# ============================================================================== -# Slice -# ============================================================================== - -M = benchy.to_vertices_and_faces() - -pointsets = slice_mesh_planes(M, planes) - -# ============================================================================== -# Process output -# ============================================================================== - -polylines = [] -for points in pointsets: - points = [Point(*point) for point in points] - polyline = Polyline(points) - polylines.append(polyline) - -# ============================================================================== -# Visualize -# ============================================================================== - -viewer = Viewer(width=1600, height=900) - -viewer.renderer.camera.target = [0, 0, 20] -viewer.renderer.camera.position = [-40, -70, 40] - -viewer.scene.add(benchy, show_lines=False, show_points=False, opacity=0.5) - -for polyline in polylines: - viewer.scene.add(polyline, linecolor=Color.grey(), lineswidth=2, show_points=False) - -viewer.show() diff --git a/docs/examples/slicer.rst b/docs/examples/slicer.rst deleted file mode 100644 index c9c87810..00000000 --- a/docs/examples/slicer.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Mesh slicing -******************************************************************************** - -.. figure:: /_images/cgal_slicer.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: slicer.py - :language: python diff --git a/docs/examples/straight_skeleton_2.py b/docs/examples/straight_skeleton_2.py deleted file mode 100644 index c3d2180e..00000000 --- a/docs/examples/straight_skeleton_2.py +++ /dev/null @@ -1,34 +0,0 @@ -from compas_cgal.straight_skeleton_2 import interior_straight_skeleton -from compas_viewer import Viewer - -points = [ - (-1.91, 3.59, 0.0), - (-5.53, -5.22, 0.0), - (-0.39, -1.98, 0.0), - (2.98, -5.51, 0.0), - (4.83, -2.02, 0.0), - (9.70, -3.63, 0.0), - (12.23, 1.25, 0.0), - (3.42, 0.66, 0.0), - (2.92, 4.03, 0.0), - (-1.91, 3.59, 0.0), -] - - -graph = interior_straight_skeleton(points) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) -for edge in graph.edges(): - line = graph.edge_line(edge) - if graph.edge_attribute(edge, "inner_bisector"): - print(edge, "inner_bisector") - viewer.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2) - elif graph.edge_attribute(edge, "bisector"): - viewer.add(line, linecolor=(0.0, 0.0, 1.0)) - else: - viewer.add(line, linecolor=(0.0, 0.0, 0.0)) -viewer.show() diff --git a/docs/examples/straight_skeleton_2.rst b/docs/examples/straight_skeleton_2.rst deleted file mode 100644 index 9d33bbbc..00000000 --- a/docs/examples/straight_skeleton_2.rst +++ /dev/null @@ -1,38 +0,0 @@ -******************************************************************************** -2D Straight Skeleton -******************************************************************************** - -.. figure:: /_images/cgal_straight_skeleton_2.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: straight_skeleton_2.py - :language: python - - -.. figure:: /_images/cgal_straight_skeleton_2_holes.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: straight_skeleton_2_holes.py - :language: python - - -.. figure:: /_images/cgal_straight_skeleton_2_offset.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: straight_skeleton_2_offset.py - :language: python - - -.. figure:: /_images/cgal_straight_skeleton_2_offset_weighted.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: straight_skeleton_2_offset_weighted.py - :language: python diff --git a/docs/examples/straight_skeleton_2_holes.py b/docs/examples/straight_skeleton_2_holes.py deleted file mode 100644 index 2e3637ce..00000000 --- a/docs/examples/straight_skeleton_2_holes.py +++ /dev/null @@ -1,44 +0,0 @@ -from compas.geometry import Polygon -from compas_viewer import Viewer - -from compas_cgal.straight_skeleton_2 import interior_straight_skeleton_with_holes - -points = [ - (-1.91, 3.59, 0.0), - (-5.53, -5.22, 0.0), - (-0.39, -1.98, 0.0), - (2.98, -5.51, 0.0), - (4.83, -2.02, 0.0), - (9.70, -3.63, 0.0), - (12.23, 1.25, 0.0), - (3.42, 0.66, 0.0), - (2.92, 4.03, 0.0), - (-1.91, 3.59, 0.0), -] - -holes = [ - [(0.42, 0.88, 0.0), (1.1, -1.0, 0.0), (-1.97, -0.93, 0.0), (-1.25, 1.82, 0.0)], - [(4.25, -0.64, 0.0), (2.9, -3.03, 0.0), (2.12, -2.16, 0.0), (2.89, -0.36, 0.0)], - [(10.6, 0.29, 0.0), (9.48, -1.54, 0.0), (5.48, -1.26, 0.0), (5.98, -0.04, 0.0)], -] - - -polygon = Polygon(points) -holes = [Polygon(hole) for hole in holes] -graph = interior_straight_skeleton_with_holes(polygon, holes) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) - -for edge in graph.edges(): - line = graph.edge_line(edge) - if graph.edge_attribute(edge, "inner_bisector"): - viewer.scene.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2) - elif graph.edge_attribute(edge, "bisector"): - viewer.scene.add(line, linecolor=(0.0, 0.0, 1.0)) - else: - viewer.scene.add(line, linecolor=(0.0, 0.0, 0.0)) -viewer.show() diff --git a/docs/examples/straight_skeleton_2_offset.py b/docs/examples/straight_skeleton_2_offset.py deleted file mode 100644 index f6b30068..00000000 --- a/docs/examples/straight_skeleton_2_offset.py +++ /dev/null @@ -1,37 +0,0 @@ -from compas.geometry import Polygon -from compas_viewer import Viewer - -from compas_cgal.straight_skeleton_2 import offset_polygon - -points = [ - (-1.91, 3.59, 0.0), - (-5.53, -5.22, 0.0), - (-0.39, -1.98, 0.0), - (2.98, -5.51, 0.0), - (4.83, -2.02, 0.0), - (9.70, -3.63, 0.0), - (12.23, 1.25, 0.0), - (3.42, 0.66, 0.0), - (2.92, 4.03, 0.0), - (-1.91, 3.59, 0.0), -] -polygon = Polygon(points) -offset = 1.5 - -offset_polygon_inner = offset_polygon(points, offset) -offset_polygon_outer = offset_polygon(points, -offset) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) -viewer.scene.add(polygon) -viewer.config.renderer.show_grid = False - -for opolygon in offset_polygon_inner: - viewer.scene.add(opolygon, linecolor=(1.0, 0.0, 0.0), facecolor=(1.0, 1.0, 1.0, 0.0)) -for opolygon in offset_polygon_outer: - viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0)) - -viewer.show() diff --git a/docs/examples/straight_skeleton_2_offset_weighted.py b/docs/examples/straight_skeleton_2_offset_weighted.py deleted file mode 100644 index 09f92737..00000000 --- a/docs/examples/straight_skeleton_2_offset_weighted.py +++ /dev/null @@ -1,37 +0,0 @@ -from compas.geometry import Polygon -from compas_viewer import Viewer - -from compas_cgal.straight_skeleton_2 import weighted_offset_polygons - -points = [ - (-1.91, 3.59, 0.0), - (-5.53, -5.22, 0.0), - (-0.39, -1.98, 0.0), - (2.98, -5.51, 0.0), - (4.83, -2.02, 0.0), - (9.70, -3.63, 0.0), - (12.23, 1.25, 0.0), - (3.42, 0.66, 0.0), - (2.92, 4.03, 0.0), - (-1.91, 3.59, 0.0), -] -polygon = Polygon(points) - - -distances = [0.1, 0.3, 0.6, 0.1, 0.7, 0.5, 0.2, 0.4, 0.8, 0.2] -weights = [1.0 / d for d in distances] -offset = 1.0 -offset_polygons_outer = weighted_offset_polygons(points, -offset, weights) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) -viewer.scene.add(polygon) -viewer.config.renderer.show_grid = False - -for opolygon in offset_polygons_outer: - viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0)) - -viewer.show() diff --git a/docs/examples/subd.py b/docs/examples/subd.py deleted file mode 100644 index 47ced846..00000000 --- a/docs/examples/subd.py +++ /dev/null @@ -1,45 +0,0 @@ -from compas.geometry import Box -from compas.geometry import Polyhedron -from compas.geometry import Translation -from compas_cgal.subdivision import mesh_subdivide_sqrt3 as mesh_subdivide -from compas_viewer import Viewer - -# ============================================================================== -# Input -# ============================================================================== - -box = Box.from_diagonal(([0, 0, 0], [1, 1, 1])) - -V, F = box.to_vertices_and_faces(triangulated=True) - -V2, F2 = mesh_subdivide((V, F), 2) -V4, F4 = mesh_subdivide((V, F), 4) -V6, F6 = mesh_subdivide((V, F), 6) -V8, F8 = mesh_subdivide((V, F), 8) - -S2 = Polyhedron(V2.tolist(), F2.tolist()) -S4 = Polyhedron(V4.tolist(), F4.tolist()) -S6 = Polyhedron(V6.tolist(), F6.tolist()) -S8 = Polyhedron(V8.tolist(), F8.tolist()) - -S2.transform(Translation.from_vector([1, 0, 0])) -S4.transform(Translation.from_vector([2, 0, 0])) -S6.transform(Translation.from_vector([3, 0, 0])) -S8.transform(Translation.from_vector([4, 0, 0])) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) - -viewer.renderer.camera.target = [3, 1, 0.5] -viewer.renderer.camera.position = [5, -2, 0.5] - -viewer.scene.add(box.to_mesh(), show_points=False) -viewer.scene.add(S2.to_mesh(), show_points=False) -viewer.scene.add(S4.to_mesh(), show_points=False) -viewer.scene.add(S6.to_mesh(), show_points=False) -viewer.scene.add(S8.to_mesh(), show_points=False) - -viewer.show() diff --git a/docs/examples/subd.rst b/docs/examples/subd.rst deleted file mode 100644 index 36e3366c..00000000 --- a/docs/examples/subd.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Mesh Subdivision -******************************************************************************** - -.. figure:: /_images/cgal_subd_loop.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: subd.py - :language: python diff --git a/docs/examples/triangulation.py b/docs/examples/triangulation.py deleted file mode 100644 index 2026d5f1..00000000 --- a/docs/examples/triangulation.py +++ /dev/null @@ -1,55 +0,0 @@ -from compas.datastructures import Mesh -from compas.geometry import Polygon -from compas.geometry import Translation -from compas_cgal.triangulation import conforming_delaunay_triangulation -from compas_cgal.triangulation import refined_delaunay_mesh -from compas_viewer import Viewer - -# ============================================================================== -# Constraints -# ============================================================================== - -boundary = Polygon.from_sides_and_radius_xy(64, 4) - -hole = Polygon.from_sides_and_radius_xy(128, 1) - -hole1 = hole.transformed(Translation.from_vector([2, 0, 0])) -hole2 = hole.transformed(Translation.from_vector([-2, 0, 0])) -hole3 = hole.transformed(Translation.from_vector([0, 2, 0])) -hole4 = hole.transformed(Translation.from_vector([0, -2, 0])) - -holes = [hole1, hole2, hole3, hole4] - -# ============================================================================== -# Triangulation -# ============================================================================== - -V, F = conforming_delaunay_triangulation( - boundary, - holes=holes, -) - -cdt = Mesh.from_vertices_and_faces(V, F) - -V, F = refined_delaunay_mesh( - boundary, - holes=holes, - maxlength=0.5, - is_optimized=True, -) - -rdm = Mesh.from_vertices_and_faces(V, F) - -# ============================================================================== -# Viz -# ============================================================================== - -viewer = Viewer(width=1600, height=900) - -viewer.renderer.camera.target = [0, 0, 0] -viewer.renderer.camera.position = [0, -1, 13] - -viewer.scene.add(cdt.transformed(Translation.from_vector([-5, 0, 0]))) -viewer.scene.add(rdm.transformed(Translation.from_vector([+5, 0, 0]))) - -viewer.show() diff --git a/docs/examples/triangulation.rst b/docs/examples/triangulation.rst deleted file mode 100644 index 78518a20..00000000 --- a/docs/examples/triangulation.rst +++ /dev/null @@ -1,11 +0,0 @@ -******************************************************************************** -Triangulation -******************************************************************************** - -.. figure:: /_images/cgal_triangulation.png - :figclass: figure - :class: figure-img img-fluid - - -.. literalinclude:: triangulation.py - :language: python diff --git a/env_linux.yml b/env_linux.yml deleted file mode 100644 index 7b9b08f2..00000000 --- a/env_linux.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: cgal-dev -channels: - - conda-forge -dependencies: - - python >=3.9 - - pip >=19.0 - - gmp - - mpfr - - scip =9.1 - - boost-cpp - - eigen =3.3 - - cgal-cpp - - pybind11 - - compas >=2.4 - - pip: - - -e .[dev] diff --git a/env_osx.yml b/env_osx.yml deleted file mode 100644 index 7e759e35..00000000 --- a/env_osx.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: cgal-dev2 -channels: - - conda-forge -dependencies: - - python >=3.9 - - pip >=19.0 - - gmp - - mpfr - - scip =9.1 - - boost-cpp - - eigen - - cgal-cpp >=6.0 - - pybind11 - - compas >=2.4 - - pip: - - -e .[dev] diff --git a/env_win.yml b/env_win.yml deleted file mode 100644 index ce108a9b..00000000 --- a/env_win.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: cgal-dev -channels: - - conda-forge -dependencies: - - python >=3.9 - - pip >=19.0 - - mpir - - mpfr - - scip =9.0.0 - - boost-cpp - - eigen =3.3 - - cgal-cpp - - pybind11 - - compas >=2.4 - - pip: - - -e .[dev] diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..75324b2d --- /dev/null +++ b/environment.yml @@ -0,0 +1,12 @@ +name: compas_cgal +channels: + - conda-forge + - defaults +dependencies: + - python=3.9 + - compas + - cmake + - pip + - pip: + - -r requirements-dev.txt + - -e . \ No newline at end of file diff --git a/include/compas.h b/include/compas.h deleted file mode 100644 index 4d47117c..00000000 --- a/include/compas.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef COMPAS_H -#define COMPAS_H - -#include -#include - -#include - -#include -#include -#include -#include -#include - - -namespace compas -{ - using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; - using Point = Kernel::Point_3; - using Vector = Kernel::Vector_3; - using Polyline = std::vector; - using Polylines = std::list; - using Polyhedron = CGAL::Polyhedron_3; - using Mesh = CGAL::Surface_mesh; - using RowMatrixXd = Eigen::Matrix; - using RowMatrixXi = Eigen::Matrix; - using Edge = std::tuple, std::vector>; - using Edges = std::list; - - Polyhedron polyhedron_from_vertices_and_faces(const RowMatrixXd &V, const RowMatrixXi &F); - - Mesh mesh_from_vertices_and_faces(const RowMatrixXd &V, const RowMatrixXi &F); - - Mesh ngon_from_vertices_and_faces(const RowMatrixXd &V, const std::vector> &faces); - - std::tuple< RowMatrixXd, RowMatrixXi> mesh_to_vertices_and_faces(const Mesh &mesh); - - std::tuple< RowMatrixXd, RowMatrixXi> quadmesh_to_vertices_and_faces(const Mesh &mesh); - - std::vector< RowMatrixXd> polylines_to_lists_of_points(Polylines polylines); - - std::tuple< RowMatrixXd, RowMatrixXi> polyhedron_to_vertices_and_faces(Polyhedron polyhedron); -} - -#endif /* COMPAS_H */ diff --git a/pyproject.toml b/pyproject.toml index c722df1c..3f55547f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,67 +1,39 @@ [build-system] -requires = ["setuptools>=66", "pybind11>=2.6.1"] -build-backend = "setuptools.build_meta" - -# ============================================================================ -# project info -# ============================================================================ +requires = ["scikit-build-core >=0.10", "nanobind >=1.3.2"] +build-backend = "scikit_build_core.build" [project] name = "compas_cgal" -dynamic = ["version", "dependencies", "optional-dependencies"] -description = "COMPAS friedly bindings for the CGAL library." -keywords = [] +version = "1.0.1" +description = "CGAL wrapper for COMPAS." +readme = "README.md" +requires-python = ">=3.8" authors = [ { name = "tom van mele", email = "tom.v.mele@gmail.com" }, - { name = "Petras Vestartas", email = "vestartas@arch.ethz.ch" }, + { name = "Petras Vestartas", email = "petrasvestartas@gmail.com" }, ] -license = { file = "LICENSE" } -readme = "README.md" -requires-python = ">=3.9" classifiers = [ - "Development Status :: 5 - Production/Stable", - "Topic :: Scientific/Engineering", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: CPython", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", ] [project.urls] -Homepage = "https://compas-dev.github.io/compas_cgal" -Repository = "https://github.com/compas-dev/compas_cgal" - -# ============================================================================ -# setuptools config -# ============================================================================ - -[tool.setuptools] -package-dir = { "" = "src" } -include-package-data = true -zip-safe = false - -[tool.setuptools.dynamic] -version = { attr = "compas_cgal.__version__" } -dependencies = { file = "requirements.txt" } -optional-dependencies = { dev = { file = "requirements-dev.txt" } } - -[tool.setuptools.packages.find] -where = ["src"] - -[tool.setuptools.package-data] - -# ============================================================================ -# replace pytest.ini -# ============================================================================ +Homepage = "https://github.com/compas-dev/compas_cgal" [tool.pytest.ini_options] minversion = "6.0" -testpaths = ["tests", "src/compas_cgal"] +testpaths = ["tests"] python_files = ["test_*.py", "*_test.py", "test.py"] +norecursedirs = [ + "external/*", + "build/*", + "dist/*", + "*.egg-info", + ".git", + ".tox", + ".env", + ".pytest_cache", + ".ruff_cache" +] addopts = [ "-ra", "--strict-markers", @@ -77,12 +49,43 @@ doctest_optionflags = [ "NUMBER", ] -# ============================================================================ -# replace bumpversion.cfg -# ============================================================================ +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "build-system.requires" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Build stable ABI wheels for CPython 3.12+ +wheel.py-api = "cp312" + +# Configure CMake +cmake.version = ">=3.15" +cmake.build-type = "Release" + +# Set CMake args for verbose output +cmake.args = ["-DCMAKE_VERBOSE_MAKEFILE=ON"] + +[tool.scikit-build.cmake.define] +CMAKE_POLICY_DEFAULT_CMP0135 = "NEW" + +[tool.cibuildwheel] +# Necessary to see build output from the actual compilation +build-verbosity = 1 + +# Run pytest to ensure that the package was correctly built +test-command = "pytest {project}/tests" +test-requires = "pytest" + +# Don't test Python 3.8 wheels on macOS/arm64 +test-skip="cp38-macosx_*:arm64" + +# Needed for full C++17 support +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "10.14" [tool.bumpversion] -current_version = "0.7.2" +current_version = "1.0.1" message = "Bump version to {new_version}" commit = true tag = true @@ -100,7 +103,6 @@ replace = "[{new_version}] {now:%Y-%m-%d}" # ============================================================================ # replace setup.cfg # ============================================================================ - [tool.black] line-length = 179 @@ -128,4 +130,4 @@ max-doc-length = 179 [tool.ruff.format] docstring-code-format = true -docstring-code-line-length = "dynamic" +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 044b3ffd..8cc95bc4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,9 @@ +compas +pytest>=7.0.0 +pytest-cov>=4.0.0 +line_profiler>=4.0.0 +nanobind>=1.3.2 +scikit-build-core[pyproject]>=0.10 attrs >=17.4 black ==24.3.0 bump-my-version @@ -7,4 +13,4 @@ invoke >=0.14 ruff sphinx_compas2_theme twine -wheel +wheel \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ba29bc73..ac5b8b20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -compas >=2.0 +line_profiler +compas_viewer \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 60bb44f3..00000000 --- a/setup.py +++ /dev/null @@ -1,147 +0,0 @@ -import io -import os -import tempfile - -import setuptools -from setuptools import Extension -from setuptools import setup -from setuptools.command.build_ext import build_ext - -here = os.path.abspath(os.path.dirname(__file__)) - - -def read(*names, **kwargs): - return io.open(os.path.join(here, *names), encoding=kwargs.get("encoding", "utf8")).read() - - -requirements = read("requirements.txt").split("\n") -optional_requirements = {} - - -conda_prefix = os.getenv("CONDA_PREFIX") - -windows = os.name == "nt" - - -def get_pybind_include(): - if windows: - return os.path.join(conda_prefix, "Library", "include") - return os.path.join(conda_prefix, "include") - - -def get_eigen_include(): - if windows: - return os.path.join(conda_prefix, "Library", "include", "eigen3") - return os.path.join(conda_prefix, "include", "eigen3") - - -def get_library_dirs(): - if windows: - return os.path.join(conda_prefix, "Library", "lib") - return os.path.join(conda_prefix, "lib") - - -def get_scip_library(): - if windows: - return "libscip" - return "scip" - - -ext_modules = [ - Extension( - "compas_cgal._cgal", - sorted( - [ - "src/compas_cgal.cpp", - "src/compas.cpp", - "src/meshing.cpp", - "src/booleans.cpp", - "src/slicer.cpp", - "src/intersections.cpp", - "src/measure.cpp", - "src/subdivision.cpp", - "src/triangulations.cpp", - "src/skeletonization.cpp", - "src/reconstruction.cpp", - "src/polygonal_surface_reconstruction.cpp", - "src/straight_skeleton_2.cpp", - ] - ), - include_dirs=["./include", get_eigen_include(), get_pybind_include()], - library_dirs=[get_library_dirs()], - libraries=["mpfr", "gmp", get_scip_library()], - language="c++", - ), -] - - -# cf http://bugs.python.org/issue26689 -def has_flag(compiler, flagname): - """Return a boolean indicating whether a flag name is supported on - the specified compiler. - """ - with tempfile.NamedTemporaryFile("w", suffix=".cpp", delete=False) as f: - f.write("int main (int argc, char **argv) { return 0; }") - fname = f.name - try: - compiler.compile([fname], extra_postargs=[flagname]) - except setuptools.distutils.errors.CompileError: - return False - finally: - try: - os.remove(fname) - except OSError: - pass - return True - - -def cpp_flag(compiler): - """Return the -std=c++[11/14/17/20] compiler flag. - - The newer version is prefered over c++11 (when it is available). - """ - # flags = ["-std=c++20", "-std=c++17", "-std=c++14", "-std=c++11"] - flags = ["-std=c++20"] - - for flag in flags: - if has_flag(compiler, flag): - return flag - - raise RuntimeError("Unsupported compiler -- at least C++11 support " "is needed!") - - -class BuildExt(build_ext): - """A custom build extension for adding compiler-specific options.""" - - c_opts = { - "msvc": ["/EHsc", "/std:c++20"], - "unix": [], - } - l_opts = { - "msvc": [], - "unix": [], - } - - def build_extensions(self): - ct = self.compiler.compiler_type - opts = self.c_opts.get(ct, []) - link_opts = self.l_opts.get(ct, []) - if ct == "unix": - opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version()) - opts.append(cpp_flag(self.compiler)) - if has_flag(self.compiler, "-fvisibility=hidden"): - opts.append("-fvisibility=hidden") - opts.append("-DCGAL_DEBUG=1") - for ext in self.extensions: - ext.define_macros = [("VERSION_INFO", '"{}"'.format(self.distribution.get_version()))] - ext.extra_compile_args = opts - ext.extra_link_args = link_opts - build_ext.build_extensions(self) - - -setup( - name="compas_cgal", - packages=["compas_cgal"], - ext_modules=ext_modules, - cmdclass={"build_ext": BuildExt}, -) diff --git a/src/booleans.cpp b/src/booleans.cpp index bc1e7531..2d8505d6 100644 --- a/src/booleans.cpp +++ b/src/booleans.cpp @@ -1,118 +1,107 @@ #include "booleans.h" -#include -#include - -namespace PMP = CGAL::Polygon_mesh_processing; std::tuple pmp_boolean_union( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB) + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b) { - compas::Mesh A = compas::mesh_from_vertices_and_faces(VA, FA); - compas::Mesh B = compas::mesh_from_vertices_and_faces(VB, FB); - compas::Mesh C; - - PMP::corefine_and_compute_union(A, B, C); - - // Result + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); + compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b); + compas::Mesh mesh_out; - std::tuple R = compas::mesh_to_vertices_and_faces(C); + CGAL::Polygon_mesh_processing::corefine_and_compute_union(mesh_a, mesh_b, mesh_out); - return R; + std::tuple result = compas::mesh_to_vertices_and_faces(mesh_out); + return result; }; std::tuple pmp_boolean_difference( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB) + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b) { - compas::Mesh A = compas::mesh_from_vertices_and_faces(VA, FA); - compas::Mesh B = compas::mesh_from_vertices_and_faces(VB, FB); - compas::Mesh C; - - PMP::corefine_and_compute_difference(A, B, C); - - // Result + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); + compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b); + compas::Mesh mesh_out; - std::tuple R = compas::mesh_to_vertices_and_faces(C); + CGAL::Polygon_mesh_processing::corefine_and_compute_difference(mesh_a, mesh_b, mesh_out); - return R; + std::tuple result = compas::mesh_to_vertices_and_faces(mesh_out); + return result; }; std::tuple pmp_boolean_intersection( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB) + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b) { - compas::Mesh A = compas::mesh_from_vertices_and_faces(VA, FA); - compas::Mesh B = compas::mesh_from_vertices_and_faces(VB, FB); - compas::Mesh C; + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); + compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b); + compas::Mesh mesh_out; - PMP::corefine_and_compute_intersection(A, B, C); + CGAL::Polygon_mesh_processing::corefine_and_compute_intersection(mesh_a, mesh_b, mesh_out); - // Result - - std::tuple R = compas::mesh_to_vertices_and_faces(C); - - return R; + std::tuple result = compas::mesh_to_vertices_and_faces(mesh_out); + return result; }; std::tuple pmp_split( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB) + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b) { - compas::Mesh A = compas::mesh_from_vertices_and_faces(VA, FA); - compas::Mesh B = compas::mesh_from_vertices_and_faces(VB, FB); - PMP::split(A, B); - - std::tuple R = compas::mesh_to_vertices_and_faces(A); + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); + compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b); + CGAL::Polygon_mesh_processing::split(mesh_a, mesh_b); - return R; + std::tuple result = compas::mesh_to_vertices_and_faces(mesh_a); + return result; }; -void init_booleans(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("booleans"); +void init_booleans(nb::module_& m) { + auto submodule = m.def_submodule("booleans"); submodule.def( "boolean_union", &pmp_boolean_union, - pybind11::arg("VA").noconvert(), - pybind11::arg("FA").noconvert(), - pybind11::arg("VB").noconvert(), - pybind11::arg("FB").noconvert()); + "Boolean Union from triangular mesh vertices and faces.", + "VA"_a, + "FA"_a, + "VB"_a, + "FB"_a); submodule.def( "boolean_difference", &pmp_boolean_difference, - pybind11::arg("VA").noconvert(), - pybind11::arg("FA").noconvert(), - pybind11::arg("VB").noconvert(), - pybind11::arg("FB").noconvert()); + "Boolean Difference from triangular mesh vertices and faces.", + "VA"_a, + "FA"_a, + "VB"_a, + "FB"_a); submodule.def( "boolean_intersection", &pmp_boolean_intersection, - pybind11::arg("VA").noconvert(), - pybind11::arg("FA").noconvert(), - pybind11::arg("VB").noconvert(), - pybind11::arg("FB").noconvert()); + "Boolean Intersection from triangular mesh vertices and faces.", + "VA"_a, + "FA"_a, + "VB"_a, + "FB"_a); submodule.def( "split", &pmp_split, - pybind11::arg("VA").noconvert(), - pybind11::arg("FA").noconvert(), - pybind11::arg("VB").noconvert(), - pybind11::arg("FB").noconvert()); -}; + "Boolean Split from triangular mesh vertices and faces.", + "VA"_a, + "FA"_a, + "VB"_a, + "FB"_a); +} \ No newline at end of file diff --git a/src/booleans.h b/src/booleans.h index bb9f5ecc..e2387173 100644 --- a/src/booleans.h +++ b/src/booleans.h @@ -1,39 +1,76 @@ -#ifndef COMPAS_BOOLEANS_H -#define COMPAS_BOOLEANS_H - -#include +#pragma once +#include "compas.h" +/** + * Compute the boolean union of two triangle meshes. + * + * @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order + * @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order + * @param vertices_b Vertices of mesh B as Px3 matrix in row-major order + * @param faces_b Faces of mesh B as Qx3 matrix of vertex indices in row-major order + * @return Tuple containing: + * - Resulting vertices as Rx3 matrix + * - Resulting faces as Sx3 matrix of vertex indices + */ std::tuple pmp_boolean_union( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB); - + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b); +/** + * Compute the boolean difference (A - B) of two triangle meshes. + * + * @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order + * @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order + * @param vertices_b Vertices of mesh B as Px3 matrix in row-major order + * @param faces_b Faces of mesh B as Qx3 matrix of vertex indices in row-major order + * @return Tuple containing: + * - Resulting vertices as Rx3 matrix + * - Resulting faces as Sx3 matrix of vertex indices + */ std::tuple pmp_boolean_difference( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB); - + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b); +/** + * Compute the boolean intersection of two triangle meshes. + * + * @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order + * @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order + * @param vertices_b Vertices of mesh B as Px3 matrix in row-major order + * @param faces_b Faces of mesh B as Qx3 matrix of vertex indices in row-major order + * @return Tuple containing: + * - Resulting vertices as Rx3 matrix + * - Resulting faces as Sx3 matrix of vertex indices + */ std::tuple pmp_boolean_intersection( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB); - + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b); +/** + * Clip mesh A using mesh B as a clipping volume. + * Points of mesh A that lie inside mesh B are removed. + * + * @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order + * @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order + * @param vertices_b Vertices of clipping mesh B as Px3 matrix in row-major order + * @param faces_b Faces of clipping mesh B as Qx3 matrix of vertex indices in row-major order + * @return Tuple containing: + * - Resulting vertices as Rx3 matrix + * - Resulting faces as Sx3 matrix of vertex indices + */ std::tuple pmp_clip( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB); - - -#endif /* COMPAS_BOOLEANS_H */ + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b); diff --git a/src/compas.cpp b/src/compas.cpp deleted file mode 100644 index 289bece9..00000000 --- a/src/compas.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include -#include - -template -class Build_polyhedron : public CGAL::Modifier_base -{ -private: - const compas::RowMatrixXd &m_vertices; - const compas::RowMatrixXi &m_faces; - -public: - Build_polyhedron(const compas::RowMatrixXd &V, const compas::RowMatrixXi &F) : m_vertices(V), m_faces(F) - { - } - - void operator()(HDS &hds) - { - typedef typename HDS::Vertex Vertex; - typedef typename Vertex::Point Point; - - CGAL::Polyhedron_incremental_builder_3 Builder(hds, true); - - Builder.begin_surface(m_vertices.rows(), m_faces.rows()); - - for (int i = 0; i < m_vertices.rows(); i++) - { - Builder.add_vertex(Point(m_vertices(i, 0), m_vertices(i, 1), m_vertices(i, 2))); - } - - for (int i = 0; i < m_faces.rows(); i++) - { - Builder.begin_facet(); - for (int j = 0; j < m_faces.cols(); j++) - { - Builder.add_vertex_to_facet(m_faces(i, j)); - } - Builder.end_facet(); - } - - Builder.end_surface(); - } -}; - -/** - * @brief Construct a Polyhedron_3 from vertices and faces. - * - * @param V vx3 matrix of vertex coordinates. - * @param F fx3 matrix of face vertex indices. - * @return compas::Polyhedron - * - * @todo: add support for Ngon faces. - */ -compas::Polyhedron compas::polyhedron_from_vertices_and_faces( - const compas::RowMatrixXd &V, - const compas::RowMatrixXi &F) -{ - compas::Polyhedron polyhedron; - Build_polyhedron build(V, F); - polyhedron.delegate(build); - return polyhedron; -} - -/** - * @brief Construct a Surface_mesh from vertices and faces. - * - * @param V vx3 matrix of vertex coordinates. - * @param F fx3 matrix of face vertex indices. - * @return compas::Mesh - * - * @todo: change name to trimesh_from_vertices_and_faces. - * @todo: check that all faces are triangles. - * @todo: add error message if not all faces are triangles. - * - */ -compas::Mesh compas::mesh_from_vertices_and_faces( - const compas::RowMatrixXd &V, - const compas::RowMatrixXi &F) -{ - int v = V.rows(); - int f = F.rows(); - - compas::Mesh mesh; - std::vector index_descriptor(v); - - for (int i = 0; i < v; i++) - { - index_descriptor[i] = mesh.add_vertex(compas::Kernel::Point_3(V(i, 0), V(i, 1), V(i, 2))); - } - - compas::Mesh::Vertex_index a; - compas::Mesh::Vertex_index b; - compas::Mesh::Vertex_index c; - - for (int i = 0; i < f; i++) - { - a = index_descriptor[F(i, 0)]; - b = index_descriptor[F(i, 1)]; - c = index_descriptor[F(i, 2)]; - mesh.add_face(a, b, c); - } - - return mesh; -} - -/** - * @brief Constrcut a Surface_mesh from vertices and faces. - * - * @param V nx3 matrix of vertex coordinates. - * @param faces list of list of vertex indices. - * @return compas::Mesh - * - * @todo: rename to mesh_from_vertices_and_faces. - */ -compas::Mesh compas::ngon_from_vertices_and_faces( - const compas::RowMatrixXd &V, - const std::vector> &faces) -{ - int v = V.rows(); - - compas::Mesh mesh; - std::vector index_descriptor(v); - - for (int i = 0; i < v; i++) - { - index_descriptor[i] = mesh.add_vertex(compas::Kernel::Point_3(V(i, 0), V(i, 1), V(i, 2))); - } - - for (std::size_t i = 0; i < faces.size(); i++) - { - std::vector face; - - for (std::size_t j = 0; j < faces[i].size(); j++) - { - face.push_back(index_descriptor[faces[i][j]]); - } - - mesh.add_face(face); - } - - return mesh; -} - -/** - * @brief Convert a Surface_mesh to vertices and faces. - * - * @param mesh A Surface_mesh containing only triangles. - * @return std::tuple - * - * @todo: rename to trimesh_to_vertices_and_faces. - * @todo: check that all faces are triangles. - * @todo: add error message if not all faces are triangles. - * - */ -std::tuple -compas::mesh_to_vertices_and_faces( - const compas::Mesh &mesh) -{ - std::size_t v = mesh.number_of_vertices(); - std::size_t f = mesh.number_of_faces(); - - compas::RowMatrixXd V(v, 3); - compas::RowMatrixXi F(f, 3); - - compas::Mesh::Property_map location = mesh.points(); - - for (compas::Mesh::Vertex_index vd : mesh.vertices()) - { - V(vd, 0) = (double)location[vd][0]; - V(vd, 1) = (double)location[vd][1]; - V(vd, 2) = (double)location[vd][2]; - } - - for (compas::Mesh::Face_index fd : mesh.faces()) - { - int i = 0; - for (compas::Mesh::Vertex_index vd : vertices_around_face(mesh.halfedge(fd), mesh)) - { - F(fd, i) = (int)vd; - i++; - } - } - - std::tuple result = std::make_tuple(V, F); - return result; -} - -/** - * @brief Convert a Surface_mesh to vertices and faces. - * - * @param mesh A Surface_mesh containing only quads. - * @return std::tuple - * - * @todo: check that all faces are quads. - * @todo: add error message if not all faces are quads. - * - */ -std::tuple -compas::quadmesh_to_vertices_and_faces( - const compas::Mesh &mesh) -{ - std::size_t v = mesh.number_of_vertices(); - std::size_t f = mesh.number_of_faces(); - - compas::RowMatrixXd V(v, 3); - compas::RowMatrixXi F(f, 4); - - compas::Mesh::Property_map vertex_location = mesh.points(); - - for (compas::Mesh::Vertex_index vd : mesh.vertices()) - { - V(vd, 0) = (double)vertex_location[vd][0]; - V(vd, 1) = (double)vertex_location[vd][1]; - V(vd, 2) = (double)vertex_location[vd][2]; - } - - for (compas::Mesh::Face_index fd : mesh.faces()) - { - int i = 0; - for (compas::Mesh::Vertex_index vd : vertices_around_face(mesh.halfedge(fd), mesh)) - { - F(fd, i) = (int)vd; - i++; - } - } - - std::tuple result = std::make_tuple(V, F); - return result; -} - -/** - * @brief Convert a list of polylines to a list of list of point. - * - * @param polylines - * @return std::vector - */ -std::vector -compas::polylines_to_lists_of_points( - compas::Polylines polylines) -{ - std::vector pointsets; - - for (auto i = polylines.begin(); i != polylines.end(); i++) - { - const compas::Polyline &poly = *i; - std::size_t n = poly.size(); - compas::RowMatrixXd points(n, 3); - - for (int j = 0; j < n; j++) - { - points(j, 0) = (double)poly[j].x(); - points(j, 1) = (double)poly[j].y(); - points(j, 2) = (double)poly[j].z(); - } - - pointsets.push_back(points); - } - - return pointsets; -} - -/** - * @brief Convert a Polyhedron_3 to vertices and faces. - * - * @param polyhedron - * @return std::tuple - * - * @todo: add support for Ngon faces. - */ -std::tuple -compas::polyhedron_to_vertices_and_faces( - compas::Polyhedron polyhedron) -{ - std::size_t v = polyhedron.size_of_vertices(); - std::size_t f = polyhedron.size_of_facets(); - - compas::RowMatrixXd V(v, 3); - compas::RowMatrixXi F(f, 3); - - std::size_t i = 0; - - for (compas::Polyhedron::Vertex_handle vh : polyhedron.vertex_handles()) - { - V(i, 0) = (double)vh->point().x(); - V(i, 1) = (double)vh->point().y(); - V(i, 2) = (double)vh->point().z(); - - vh->id() = i; - - i++; - } - - i = 0; - - for (compas::Polyhedron::Facet_handle fh : polyhedron.facet_handles()) - { - std::size_t j = 0; - - compas::Polyhedron::Halfedge_handle start = fh->halfedge(), h = start; - do - { - F(i, j) = h->vertex()->id(); - h = h->next(); - j++; - - } while (h != start); - - i++; - } - - std::tuple result = std::make_tuple(V, F); - return result; -} diff --git a/src/compas.h b/src/compas.h new file mode 100644 index 00000000..df972add --- /dev/null +++ b/src/compas.h @@ -0,0 +1,370 @@ +#pragma once + +// This file contains headers that are compiled once. +// It helps to have faster compilation times during development. +// This file is referenced in CMakeLists PCH section. + +// STD +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Nanobind +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; // enables syntax for annotating function and arguments + +// Boost Multiprecision +#include +#include + + +// Eigen +#include +#include +#include + +// CGAL +#include +#include +#include +#include +#include + +// CGAL remesh +#include +#include + +// CGAL measure +#include +#include + +// CGAL boolean +#include +#include + +// CGAL intersection +#include + +// CGAL reconstruction +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CGAL skeletonization +#include +#include + +// CGAL slicer +#include + +// CGAL subdivision +#include + +// CGAL triangulation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CGAL straight skeleton 2 +#include +#include +#include +#include +#include +#include +#include + + +namespace compas +{ + using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; + using Point = Kernel::Point_3; + using Vector = Kernel::Vector_3; + using Polyline = std::vector; + using Polylines = std::list; + using Polyhedron = CGAL::Polyhedron_3; + using Mesh = CGAL::Surface_mesh; + using RowMatrixXd = Eigen::Matrix; + using RowMatrixXi = Eigen::Matrix; + + template + class Build_polyhedron : public CGAL::Modifier_base + { + private: + const RowMatrixXd &m_vertices; + const RowMatrixXi &m_faces; + + public: + Build_polyhedron(const RowMatrixXd &V, const RowMatrixXi &F) : m_vertices(V), m_faces(F) {} + + void operator()(HDS &hds) + { + typedef typename HDS::Vertex Vertex; + typedef typename Vertex::Point Point; + + CGAL::Polyhedron_incremental_builder_3 Builder(hds, true); + Builder.begin_surface(m_vertices.rows(), m_faces.rows()); + + for (int i = 0; i < m_vertices.rows(); i++) + { + Builder.add_vertex(Point(m_vertices(i, 0), m_vertices(i, 1), m_vertices(i, 2))); + } + + for (int i = 0; i < m_faces.rows(); i++) + { + Builder.begin_facet(); + for (int j = 0; j < m_faces.cols(); j++) + { + Builder.add_vertex_to_facet(m_faces(i, j)); + } + Builder.end_facet(); + } + + Builder.end_surface(); + } + }; + + inline Polyhedron polyhedron_from_vertices_and_faces(const RowMatrixXd &V, const RowMatrixXi &F) + { + Polyhedron polyhedron; + Build_polyhedron build(V, F); + polyhedron.delegate(build); + return polyhedron; + } + + inline Mesh mesh_from_vertices_and_faces(const RowMatrixXd &V, const RowMatrixXi &F) + { + int v = V.rows(); + int f = F.rows(); + + Mesh mesh; + std::vector index_descriptor; + index_descriptor.reserve(v); + + for (int i = 0; i < v; i++) + { + auto point = Kernel::Point_3(V(i, 0), V(i, 1), V(i, 2)); + index_descriptor.push_back(mesh.add_vertex(std::move(point))); + } + + mesh.reserve(v, f * 3, f); + + for (int i = 0; i < f; i++) + { + auto a = index_descriptor[F(i, 0)]; + auto b = index_descriptor[F(i, 1)]; + auto c = index_descriptor[F(i, 2)]; + mesh.add_face(a, b, c); + } + + return mesh; + } + + inline Mesh ngon_from_vertices_and_faces(const RowMatrixXd &V, const std::vector> &faces) + { + int v = V.rows(); + Mesh mesh; + std::vector index_descriptor(v); + + for (int i = 0; i < v; i++) + { + index_descriptor[i] = mesh.add_vertex(Kernel::Point_3(V(i, 0), V(i, 1), V(i, 2))); + } + + for (std::size_t i = 0; i < faces.size(); i++) + { + std::vector face; + for (std::size_t j = 0; j < faces[i].size(); j++) + { + face.push_back(index_descriptor[faces[i][j]]); + } + mesh.add_face(face); + } + + return mesh; + } + + /** + * @brief Convert a Surface_mesh to vertices and faces matrices + * @param mesh A Surface_mesh containing only triangles + * @return Tuple containing vertices and faces matrices (row-major) + */ + inline std::tuple mesh_to_vertices_and_faces(const Mesh& mesh) + { + std::size_t v = mesh.number_of_vertices(); + std::size_t f = mesh.number_of_faces(); + + RowMatrixXd V(v, 3); + RowMatrixXi F(f, 3); + + Mesh::Property_map location = mesh.points(); + + for (Mesh::Vertex_index vd : mesh.vertices()) { + std::size_t v_idx = vd.idx(); + V(v_idx, 0) = CGAL::to_double(location[vd][0]); + V(v_idx, 1) = CGAL::to_double(location[vd][1]); + V(v_idx, 2) = CGAL::to_double(location[vd][2]); + } + + for (Mesh::Face_index fd : mesh.faces()) { + std::size_t f_idx = fd.idx(); + int i = 0; + for (Mesh::Vertex_index vd : vertices_around_face(mesh.halfedge(fd), mesh)) { + F(f_idx, i) = static_cast(vd.idx()); + i++; + } + } + + return std::make_tuple(std::move(V), std::move(F)); + } + + /** + * @brief Convert a Surface_mesh to vertices and quad faces matrices + * @param mesh A Surface_mesh containing only quads + * @return Tuple containing vertices and faces matrices (row-major) + */ + inline std::tuple quadmesh_to_vertices_and_faces(const Mesh& mesh) + { + std::size_t v = mesh.number_of_vertices(); + std::size_t f = mesh.number_of_faces(); + + RowMatrixXd V(v, 3); + RowMatrixXi F(f, 4); + + Mesh::Property_map vertex_location = mesh.points(); + + for (Mesh::Vertex_index vd : mesh.vertices()) { + std::size_t v_idx = vd.idx(); + V(v_idx, 0) = CGAL::to_double(vertex_location[vd][0]); + V(v_idx, 1) = CGAL::to_double(vertex_location[vd][1]); + V(v_idx, 2) = CGAL::to_double(vertex_location[vd][2]); + } + + for (Mesh::Face_index fd : mesh.faces()) { + std::size_t f_idx = fd.idx(); + int i = 0; + for (Mesh::Vertex_index vd : vertices_around_face(mesh.halfedge(fd), mesh)) { + F(f_idx, i) = static_cast(vd.idx()); + i++; + } + } + + return std::make_tuple(std::move(V), std::move(F)); + } + + /** + * @brief Convert polylines to list of point matrices + * @param polylines List of polylines + * @return Vector of matrices containing point coordinates + */ + inline std::vector polylines_to_lists_of_points(Polylines polylines) + { + std::vector pointsets; + + for (auto i = polylines.begin(); i != polylines.end(); i++) + { + const Polyline &poly = *i; + std::size_t n = poly.size(); + RowMatrixXd points(n, 3); + + for (int j = 0; j < n; j++) + { + points(j, 0) = (double)poly[j].x(); + points(j, 1) = (double)poly[j].y(); + points(j, 2) = (double)poly[j].z(); + } + + pointsets.push_back(points); + } + + return pointsets; + } + + /** + * @brief Convert a CGAL Polyhedron to vertices and faces matrices + * @param polyhedron Input polyhedron + * @return Tuple containing vertices and faces matrices (row-major) + */ + inline std::tuple polyhedron_to_vertices_and_faces(Polyhedron polyhedron) + { + std::size_t v = polyhedron.size_of_vertices(); + std::size_t f = polyhedron.size_of_facets(); + + RowMatrixXd V(v, 3); + RowMatrixXi F(f, 3); + + std::unordered_map vertex_id_map; + int i = 0; + + for (Polyhedron::Vertex_const_iterator it = polyhedron.vertices_begin(); it != polyhedron.vertices_end(); ++it, ++i) + { + vertex_id_map[it] = i; + V(i, 0) = CGAL::to_double(it->point().x()); + V(i, 1) = CGAL::to_double(it->point().y()); + V(i, 2) = CGAL::to_double(it->point().z()); + } + + i = 0; + for (Polyhedron::Facet_const_iterator it = polyhedron.facets_begin(); it != polyhedron.facets_end(); ++it, ++i) + { + int j = 0; + Polyhedron::Halfedge_around_facet_const_circulator jt = it->facet_begin(); + do + { + F(i, j) = vertex_id_map[jt->vertex()]; + ++j; + } while (++jt != it->facet_begin()); + } + + return std::make_tuple(std::move(V), std::move(F)); + } + +} // namespace compas \ No newline at end of file diff --git a/src/compas_cgal.cpp b/src/compas_cgal.cpp index 03179f87..382fed94 100644 --- a/src/compas_cgal.cpp +++ b/src/compas_cgal.cpp @@ -1,35 +1,63 @@ -#include -#include - -// here all modules of "_cgal" are declared. -void init_meshing(pybind11::module &); -void init_booleans(pybind11::module &); -void init_slicer(pybind11::module &); -void init_intersections(pybind11::module &); -void init_measure(pybind11::module &); -void init_triangulations(pybind11::module &); -void init_subdivision(pybind11::module &); -void init_skeletonization(pybind11::module &); -void init_reconstruction(pybind11::module &); -void init_polygonal_surface_reconstruction(pybind11::module &); -void init_straight_skeleton_2(pybind11::module &); - -// the first parameter here ("_cgal") will be the name of the "so" or "pyd" file that will be produced by PyBind11 -// which is the entry point from where all other modules will be accessible. -PYBIND11_MODULE(_cgal, m) -{ - m.doc() = ""; - - // here all modules of "_cgal" are initializied. +#include "compas.h" +#include "nanobind_types.h" + +// Forward declarations +void init_meshing(nb::module_ &); +void init_measure(nb::module_ &); +void init_booleans(nb::module_ &); +void init_intersections(nb::module_ &); +void init_reconstruction(nb::module_ &); +void init_skeletonization(nb::module_ &); +void init_slicer(nb::module_ &); +void init_subdivision(nb::module_ &); +void init_triangulation(nb::module_ &); +void init_straight_skeleton_2(nb::module_ &); + +/** + * @brief Module initialization function for COMPAS CGAL extension + * + * @param m Nanobind module instance + * @details Initializes the Python module by defining functions and their bindings. + * Each function is exposed to Python with appropriate documentation. + */ +NB_MODULE(compas_cgal_ext, m) { + m.doc() = "CGAL via Nanobind says hello to COMPAS!"; + + m.def("add", + [](int a, int b) { return a + b; }, + "Add two integers together", + "a"_a, "b"_a); + + m.def("scale_matrix", + &scale_matrix, + "Scale an Eigen Matrix in-place by multiplying all elements by 2", + "mat"_a); + + m.def("create_matrix", + &create_matrix, + "Create and return a new 3x3 matrix with predefined values"); + init_meshing(m); + + nb::bind_vector>(m, "VectorDouble"); // Be aware that both Pybind11 and Nanobind makes copy for vectors + init_measure(m); + init_booleans(m); - init_slicer(m); + + nb::bind_vector>(m, "VectorRowMatrixXd"); // Be aware that both Pybind11 and Nanobind makes copy for vectors init_intersections(m); - init_measure(m); - init_triangulations(m); - init_subdivision(m); - init_skeletonization(m); + init_reconstruction(m); - init_polygonal_surface_reconstruction(m); + + init_skeletonization(m); + + init_slicer(m); + + nb::bind_vector>(m, "VectorInt"); + nb::bind_vector>>(m, "VectorVectorInt"); + init_subdivision(m); + + init_triangulation(m); + init_straight_skeleton_2(m); } diff --git a/src/compas_cgal/__init__.py b/src/compas_cgal/__init__.py index c53b6341..0b1babce 100644 --- a/src/compas_cgal/__init__.py +++ b/src/compas_cgal/__init__.py @@ -1,11 +1,11 @@ import os -__author__ = ["tom van mele"] +__author__ = ["tom van mele", "petras vestartas"] __copyright__ = "Block Research Group - ETH Zurich" __license__ = "MIT License" -__email__ = "van.mele@arch.ethz.ch" -__version__ = "0.7.2" +__email__ = ["van.mele@arch.ethz.ch", "vestartas@arch.ethz.ch"] +__version__ = "1.0.1" HERE = os.path.dirname(__file__) @@ -20,8 +20,10 @@ "compas_cgal.intersections", "compas_cgal.meshing", "compas_cgal.measure", - "compas_cgal.slicer", + "compas_cgal.reconstruction", "compas_cgal.triangulation", + "compas_cgal.slicer", + "compas_cgal.subdivision", ] __all__ = ["HOME", "DATA", "DOCS", "TEMP"] diff --git a/src/compas_cgal/booleans.py b/src/compas_cgal/booleans.py index 9ab224ff..6d696186 100644 --- a/src/compas_cgal/booleans.py +++ b/src/compas_cgal/booleans.py @@ -3,7 +3,7 @@ import numpy as np from compas.plugins import plugin -from compas_cgal._cgal import booleans +from compas_cgal.compas_cgal_ext import booleans from .types import VerticesFaces from .types import VerticesFacesNumpy diff --git a/src/compas_cgal/intersections.py b/src/compas_cgal/intersections.py index ac292fdd..ebb99dde 100644 --- a/src/compas_cgal/intersections.py +++ b/src/compas_cgal/intersections.py @@ -1,7 +1,8 @@ import numpy as np from compas.plugins import plugin -from compas_cgal._cgal import intersections +from compas_cgal.compas_cgal_ext import VectorRowMatrixXd +from compas_cgal.compas_cgal_ext import intersections from .types import PolylinesNumpy from .types import VerticesFaces @@ -43,11 +44,11 @@ def intersection_mesh_mesh( """ VA, FA = A VB, FB = B - VA = np.asarray(VA, dtype=np.float64) - FA = np.asarray(FA, dtype=np.int32) - VB = np.asarray(VB, dtype=np.float64) - FB = np.asarray(FB, dtype=np.int32) + VA = np.asarray(VA, dtype=np.float64, order="C") + FA = np.asarray(FA, dtype=np.int32, order="C") + VB = np.asarray(VB, dtype=np.float64, order="C") + FB = np.asarray(FB, dtype=np.int32, order="C") - pointsets = intersections.intersection_mesh_mesh(VA, FA, VB, FB) + pointsets: VectorRowMatrixXd = intersections.intersection_mesh_mesh(VA, FA, VB, FB) return pointsets diff --git a/src/compas_cgal/measure.py b/src/compas_cgal/measure.py index 7f65c632..f1806d45 100644 --- a/src/compas_cgal/measure.py +++ b/src/compas_cgal/measure.py @@ -1,7 +1,9 @@ import numpy as np +from compas.geometry import Point from compas.plugins import plugin -from compas_cgal._cgal import measure +from compas_cgal.compas_cgal_ext import VectorDouble +from compas_cgal.compas_cgal_ext import measure from .types import VerticesFaces @@ -21,8 +23,8 @@ def mesh_area(mesh: VerticesFaces) -> float: """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return measure.area(V, F) @@ -53,8 +55,8 @@ def mesh_volume(mesh: VerticesFaces) -> float: """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return measure.volume(V, F) @@ -73,6 +75,8 @@ def mesh_centroid(mesh: VerticesFaces) -> list[float]: """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) - return measure.centroid(V, F) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") + vector_of_double: VectorDouble = measure.centroid(V, F) + point = Point(*vector_of_double) + return point diff --git a/src/compas_cgal/meshing.py b/src/compas_cgal/meshing.py index f936a7f2..4553d753 100644 --- a/src/compas_cgal/meshing.py +++ b/src/compas_cgal/meshing.py @@ -1,7 +1,7 @@ import numpy as np from compas.plugins import plugin -from compas_cgal._cgal import meshing +from compas_cgal.compas_cgal_ext import meshing from .types import VerticesFaces from .types import VerticesFacesNumpy @@ -49,6 +49,6 @@ def mesh_remesh( """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return meshing.remesh(V, F, target_edge_length, number_of_iterations) diff --git a/src/compas_cgal/polygonal_surface_reconstruction.py b/src/compas_cgal/polygonal_surface_reconstruction.py deleted file mode 100644 index 0a9711e6..00000000 --- a/src/compas_cgal/polygonal_surface_reconstruction.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Union - -import numpy as np -from compas.geometry import Point -from compas.geometry import Vector - -from compas_cgal._cgal import polygonal_surface_reconstruction - -from .types import FloatNx3 - - -def polygonal_surface_reconstruction_ransac( - points: Union[list[Point], FloatNx3], - normals: Union[list[Vector], FloatNx3], -): - """Reconstruct a surface from a point cloud using the Poisson surface reconstruction algorithm. - - Parameters - ---------- - points : list of :class:`compas.geometry.Point` or :class:`numpy.ndarray` - The points of the point cloud. - normals : list of :class:`compas.geometry.Vector` or :class:`numpy.ndarray` - The normals of the point cloud. - - Returns - ------- - tuple of :class:`numpy.ndarray` - The vertices and faces of the reconstructed surface. - - """ - - P = np.asarray(points, dtype=np.float64) - N = np.asarray(normals, dtype=np.float64) - return polygonal_surface_reconstruction.polygonal_surface_reconstruction_ransac(P, N) diff --git a/src/compas_cgal/reconstruction.py b/src/compas_cgal/reconstruction.py index fee137ac..f79694dc 100644 --- a/src/compas_cgal/reconstruction.py +++ b/src/compas_cgal/reconstruction.py @@ -5,7 +5,7 @@ from compas.geometry import Point from compas.geometry import Vector -from compas_cgal._cgal import reconstruction +from compas_cgal.compas_cgal_ext import reconstruction from .types import FloatNx3 from .types import IntNx3 @@ -29,11 +29,46 @@ def poisson_surface_reconstruction( tuple of :class:`numpy.ndarray` The vertices and faces of the reconstructed surface. + Raises + ------ + ValueError + If points or normals are not 3D + If number of points and normals don't match + If less than 3 points are provided + If points are not well-distributed for reconstruction + RuntimeError + If the reconstruction algorithm fails + + Notes + ----- + The Poisson surface reconstruction algorithm requires: + 1. A sufficiently dense point cloud + 2. Well-oriented normals + 3. Points distributed across a meaningful surface """ - - P = np.asarray(points, dtype=np.float64) - N = np.asarray(normals, dtype=np.float64) - return reconstruction.poisson_surface_reconstruction(P, N) + # Convert input to numpy arrays with proper type and memory layout + P = np.asarray(points, dtype=np.float64, order="C") + N = np.asarray(normals, dtype=np.float64, order="C") + + # Input validation + if P.ndim != 2 or P.shape[1] != 3: + raise ValueError("Points must be an Nx3 array") + if N.ndim != 2 or N.shape[1] != 3: + raise ValueError("Normals must be an Nx3 array") + if P.shape[0] != N.shape[0]: + raise ValueError("Number of points and normals must match") + if len(P) < 3: + raise ValueError("At least 3 points are required") + + # Ensure normals are unit vectors + norms = np.linalg.norm(N, axis=1) + if not np.allclose(norms, 1.0): + N = N / norms[:, np.newaxis] + + try: + return reconstruction.poisson_surface_reconstruction(P, N) + except RuntimeError as e: + raise RuntimeError(f"Poisson surface reconstruction failed: {str(e)}") def pointset_outlier_removal( @@ -58,7 +93,7 @@ def pointset_outlier_removal( The points of the point cloud without outliers. """ - P = np.asarray(points, dtype=np.float64) + P = np.asarray(points, dtype=np.float64, order="C") return reconstruction.pointset_outlier_removal(P, nnnbrs, radius) @@ -81,7 +116,7 @@ def pointset_reduction( The vectors of the point cloud. """ - P = np.asarray(points, dtype=np.float64) + P = np.asarray(points, dtype=np.float64, order="C") return reconstruction.pointset_reduction(P, spacing) @@ -105,7 +140,7 @@ def pointset_smoothing( The vectors of the point cloud. """ - P = np.asarray(points, dtype=np.float64) + P = np.asarray(points, dtype=np.float64, order="C") return reconstruction.pointset_smoothing(P, neighbors, iterations) @@ -131,5 +166,5 @@ def pointset_normal_estimation( The vectors of the point cloud. """ - P = np.asarray(points, dtype=np.float64) + P = np.asarray(points, dtype=np.float64, order="C") return reconstruction.pointset_normal_estimation(P, neighbors, erase) diff --git a/src/compas_cgal/skeletonization.py b/src/compas_cgal/skeletonization.py index 401ed90d..a1238a02 100644 --- a/src/compas_cgal/skeletonization.py +++ b/src/compas_cgal/skeletonization.py @@ -1,26 +1,58 @@ import numpy as np +from compas.plugins import plugin -from compas_cgal._cgal import skeletonization +from compas_cgal.compas_cgal_ext import skeletonization -from .types import PolylinesNumpy +from .types import PolylinesNumpySkeleton from .types import VerticesFaces -def mesh_skeleton(mesh: VerticesFaces) -> PolylinesNumpy: - """Compute the skeleton of a closed triangle mesh. +@plugin(category="mesh") +def mesh_skeleton(mesh: VerticesFaces) -> PolylinesNumpySkeleton: + """Compute the geometric skeleton of a triangle mesh using mean curvature flow. Parameters ---------- - mesh : :attr:`compas_cgal.types.VerticesFaces` - The mesh to skeletonize. + mesh : VerticesFaces + A tuple containing: + * vertices: Nx3 array of vertex coordinates + * faces: Mx3 array of vertex indices Returns ------- - :attr:`compas_cgal.types.PolylinesNumpy` - The skeleton of the mesh. + PolylinesNumpySkeleton + List of polylines representing the skeleton edges. + Each polyline is a tuple of start and end point coordinates. + Raises + ------ + TypeError + If the input mesh is not a tuple of vertices and faces. + ValueError + If the vertices array is not Nx3. + If the faces array is not Mx3. + If the face indices are out of range. + If the mesh is not manifold and closed. + RuntimeError + If the mesh contraction fails to converge. + + Notes + ----- + The input mesh must be manifold and closed. + The skeleton is computed using mean curvature flow. """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) - return skeletonization.mesh_skeleton(V, F) + V = np.asarray(V, dtype=np.float64, order="C") # Ensure C-contiguous + F = np.asarray(F, dtype=np.int32, order="C") # Ensure C-contiguous + + # Get start and end points as flattened vectors + start_points, end_points = skeletonization.mesh_skeleton(V, F) + + # Convert flattened vectors to list of point coordinates + edges = [] + for i in range(0, len(start_points), 3): + start = [start_points[i], start_points[i + 1], start_points[i + 2]] + end = [end_points[i], end_points[i + 1], end_points[i + 2]] + edges.append((start, end)) + + return edges diff --git a/src/compas_cgal/slicer.py b/src/compas_cgal/slicer.py index ee8cd429..38195fca 100644 --- a/src/compas_cgal/slicer.py +++ b/src/compas_cgal/slicer.py @@ -1,15 +1,15 @@ import numpy as np +from compas.geometry import Plane from compas.plugins import plugin -from compas_cgal._cgal import slicer +from compas_cgal.compas_cgal_ext import slicer -from .types import Planes from .types import PolylinesNumpy from .types import VerticesFaces @plugin(category="trimesh", pluggable_name="trimesh_slice") -def slice_mesh_planes(mesh: VerticesFaces, planes: Planes) -> PolylinesNumpy: +def slice_mesh_planes(mesh: VerticesFaces, planes: list[Plane]) -> PolylinesNumpy: """Slice a mesh by a list of planes. Parameters @@ -41,10 +41,10 @@ def slice_mesh_planes(mesh: VerticesFaces, planes: Planes) -> PolylinesNumpy: """ vertices, faces = mesh points, normals = zip(*planes) - V = np.asarray(vertices, dtype=np.float64) - F = np.asarray(faces, dtype=np.int32) - P = np.array(points, dtype=np.float64) - N = np.array(normals, dtype=np.float64) + V = np.asarray(vertices, dtype=np.float64, order="C") + F = np.asarray(faces, dtype=np.int32, order="C") + P = np.array(points, dtype=np.float64, order="C") + N = np.array(normals, dtype=np.float64, order="C") pointsets = slicer.slice_mesh(V, F, P, N) return pointsets diff --git a/src/compas_cgal/straight_skeleton_2.py b/src/compas_cgal/straight_skeleton_2.py index 88cf24d6..04e11266 100644 --- a/src/compas_cgal/straight_skeleton_2.py +++ b/src/compas_cgal/straight_skeleton_2.py @@ -7,7 +7,7 @@ from compas.geometry import normal_polygon from compas.tolerance import TOL -from compas_cgal._cgal import straight_skeleton_2 +from compas_cgal.compas_cgal_ext import straight_skeleton_2 from .types import IntNx1 from .types import IntNx2 @@ -72,7 +72,7 @@ def interior_straight_skeleton(points, as_graph=True) -> Union[Graph, Tuple[Vert normal = normal_polygon(points, True) if not TOL.is_allclose(normal, [0, 0, 1]): raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal)) - V = np.asarray(points, dtype=np.float64) + V = np.asarray(points, dtype=np.float64, order="C") points, indices, edges, edge_types = straight_skeleton_2.create_interior_straight_skeleton(V) if as_graph: return graph_from_skeleton_data(points, indices, edges, edge_types) @@ -106,7 +106,7 @@ def interior_straight_skeleton_with_holes(points, holes, as_graph=True) -> Union normal = normal_polygon(points, True) if not TOL.is_allclose(normal, [0, 0, 1]): raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal)) - V = np.asarray(points, dtype=np.float64) + V = np.asarray(points, dtype=np.float64, order="C") H = [] for i, hole in enumerate(holes): @@ -146,7 +146,7 @@ def offset_polygon(points, offset) -> list[Polygon]: normal = normal_polygon(points, True) if not TOL.is_allclose(normal, [0, 0, 1]): raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal)) - V = np.asarray(points, dtype=np.float64) + V = np.asarray(points, dtype=np.float64, order="C") offset = float(offset) if offset < 0: # outside offset_polygons = straight_skeleton_2.create_offset_polygons_2_outer(V, abs(offset))[1:] # first one is box @@ -182,7 +182,7 @@ def offset_polygon_with_holes(points, holes, offset) -> list[Tuple[Polygon, list normal = normal_polygon(points, True) if not TOL.is_allclose(normal, [0, 0, 1]): raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal)) - V = np.asarray(points, dtype=np.float64) + V = np.asarray(points, dtype=np.float64, order="C") H = [] for i, hole in enumerate(holes): @@ -190,7 +190,7 @@ def offset_polygon_with_holes(points, holes, offset) -> list[Tuple[Polygon, list normal_hole = normal_polygon(points, True) if not TOL.is_allclose(normal_hole, [0, 0, -1]): raise ValueError("The normal of the hole should be [0, 0, -1]. The normal of the provided {}-th hole is {}".format(i, normal_hole)) - hole = np.asarray(points, dtype=np.float64) + hole = np.asarray(points, dtype=np.float64, order="C") H.append(hole) if offset < 0: # outside @@ -237,13 +237,16 @@ def weighted_offset_polygon(points, offset, weights) -> list[Polygon]: if not TOL.is_allclose(normal, [0, 0, 1]): raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal)) - V = np.asarray(points, dtype=np.float64) + V = np.asarray(points, dtype=np.float64, order="C") offset = float(offset) - W = np.asarray(weights, dtype=np.float64) + # Reshape weights to be a column vector (n x 1) + W = np.asarray(weights, dtype=np.float64, order="C").reshape(-1, 1) if W.shape[0] != V.shape[0]: raise ValueError("The number of weights should be equal to the number of points %d != %d." % (W.shape[0], V.shape[0])) if offset < 0: - offset_polygons = straight_skeleton_2.create_weighted_offset_polygons_2_outer(V, abs(offset), W)[1:] + offset_polygons = straight_skeleton_2.create_weighted_offset_polygons_2_outer(V, abs(offset), W) + # Return all except the first polygon which is the bounding box + return [Polygon(points.tolist()) for points in offset_polygons[1:]] else: offset_polygons = straight_skeleton_2.create_weighted_offset_polygons_2_inner(V, offset, W) - return [Polygon(points.tolist()) for points in offset_polygons] + return [Polygon(points.tolist()) for points in offset_polygons] diff --git a/src/compas_cgal/subdivision.py b/src/compas_cgal/subdivision.py index e4f6dadb..7ed3d4f7 100644 --- a/src/compas_cgal/subdivision.py +++ b/src/compas_cgal/subdivision.py @@ -1,6 +1,6 @@ import numpy as np -from compas_cgal._cgal import subdivision +from compas_cgal.compas_cgal_ext import subdivision from .types import VerticesFaces from .types import VerticesFacesNumpy @@ -33,8 +33,8 @@ def mesh_subdivide_catmull_clark(mesh: VerticesFaces, k=1) -> VerticesFacesNumpy """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return subdivision.subd_catmullclark(V, F, k) @@ -54,8 +54,8 @@ def mesh_subdivide_loop(mesh: VerticesFaces, k=1) -> VerticesFacesNumpy: """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return subdivision.subd_loop(V, F, k) @@ -75,6 +75,6 @@ def mesh_subdivide_sqrt3(mesh: VerticesFaces, k=1) -> VerticesFacesNumpy: """ V, F = mesh - V = np.asarray(V, dtype=np.float64) - F = np.asarray(F, dtype=np.int32) + V = np.asarray(V, dtype=np.float64, order="C") + F = np.asarray(F, dtype=np.int32, order="C") return subdivision.subd_sqrt3(V, F, k) diff --git a/src/compas_cgal/triangulation.py b/src/compas_cgal/triangulation.py index 5c6c267a..9eca1edd 100644 --- a/src/compas_cgal/triangulation.py +++ b/src/compas_cgal/triangulation.py @@ -2,13 +2,13 @@ from compas.geometry import Point from compas.plugins import plugin -from compas_cgal._cgal import triangulations +from compas_cgal.compas_cgal_ext import VectorRowMatrixXd +from compas_cgal.compas_cgal_ext import triangulation +from compas_cgal.types import FacesNumpy +from compas_cgal.types import VerticesFacesNumpy -from .types import FacesNumpy -from .types import VerticesFacesNumpy - -@plugin(category="traingulation", requires=["compas_cgal"]) +@plugin(category="triangulation", requires=["compas_cgal"]) def delaunay_triangulation(points: list[Point]) -> FacesNumpy: """Construct a Delaunay triangulation from a set of points. @@ -34,8 +34,8 @@ def delaunay_triangulation(points: list[Point]) -> FacesNumpy: >>> mesh = Mesh.from_vertices_and_faces(points, triangles) """ - vertices = np.asarray(points, dtype=np.float64) - return triangulations.delaunay_triangulation(vertices) + vertices = np.asarray(points, dtype=np.float64, order="C") + return triangulation.delaunay_triangulation(vertices) @plugin(category="triangulation", requires=["compas_cgal"]) @@ -66,19 +66,27 @@ def constrained_delaunay_triangulation( boundary = np.asarray(boundary, dtype=np.float64) points = points or [] - points = np.asarray(points, dtype=np.float64) + points = np.asarray(points, dtype=np.float64, order="C") + holes_vector = VectorRowMatrixXd() if holes: - holes = [np.asarray(hole, dtype=np.float64) for hole in holes] - else: - holes = [] + for hole in holes: + hole_array = np.asarray(hole, dtype=np.float64, order="C") + holes_vector.append(hole_array) + curves_vector = VectorRowMatrixXd() if curves: - curves = [np.asarray(curve, dtype=np.float64) for curve in curves] - else: - curves = [] + for curve in curves: + curve_array = np.asarray(curve, dtype=np.float64, order="C") + curves_vector.append(curve_array) - return triangulations.constrained_delaunay_triangulation(boundary, points, holes, curves, is_conforming=False) + return triangulation.constrained_delaunay_triangulation( + boundary, # numpy array + points, # numpy array + holes_vector, # VectorRowMatrixXd + curves_vector, # VectorRowMatrixXd + False, # is_conforming=False + ) @plugin(category="triangulation", requires=["compas_cgal"]) @@ -106,22 +114,42 @@ def conforming_delaunay_triangulation( :attr:`compas_cgal.types.VerticesFacesNumpy` """ - boundary = np.asarray(boundary, dtype=np.float64) + # Convert boundary to numpy array + boundary = np.asarray(boundary, dtype=np.float64, order="C") - points = points or [] - points = np.asarray(points, dtype=np.float64) + # Handle points parameter + if points is None: + points = np.zeros((0, 3), dtype=np.float64, order="C") + else: + points = np.asarray(points, dtype=np.float64, order="C") + # Convert holes to numpy arrays and create vector + holes_vector = VectorRowMatrixXd() if holes: - holes = [np.asarray(hole, dtype=np.float64) for hole in holes] - else: - holes = [] + for hole in holes: + if len(hole) < 3: + continue + hole_array = np.asarray(hole, dtype=np.float64, order="C") + holes_vector.append(hole_array) + + # Create empty vector for curves + curves_vector = VectorRowMatrixXd() if curves: - curves = [np.asarray(curve, dtype=np.float64) for curve in curves] - else: - curves = [] + for curve in curves: + curve_array = np.asarray(curve, dtype=np.float64, order="C") + curves_vector.append(curve_array) - return triangulations.constrained_delaunay_triangulation(boundary, points, holes, curves, is_conforming=True) + # Call C++ function with all required arguments + result = triangulation.constrained_delaunay_triangulation( + boundary, # numpy array + points, # numpy array + holes_vector, # VectorRefRowMatrixXd + curves_vector, # VectorRefRowMatrixXd + True, # is_conforming=True + ) + + return result def refined_delaunay_mesh( @@ -158,21 +186,38 @@ def refined_delaunay_mesh( .. [1] https://doc.cgal.org/latest/Mesh_2/index.html#secMesh_2_meshes """ - boundary = np.asarray(boundary, dtype=np.float64) + boundary = np.asarray(boundary, dtype=np.float64, order="C") - points = points or [] - points = np.asarray(points, dtype=np.float64) + # Handle points parameter + if points is None: + points = np.zeros((0, 2), dtype=np.float64, order="C") # 2D points for triangulation + else: + points = np.asarray(points, dtype=np.float64, order="C") + # Create vectors for holes and curves with proper type conversion + holes_vector = VectorRowMatrixXd() if holes: - holes = [np.asarray(hole, dtype=np.float64) for hole in holes] - else: - holes = [] + for hole in holes: + if len(hole) < 3: + continue + hole_array = np.asarray(hole, dtype=np.float64, order="C") + holes_vector.append(hole_array) + curves_vector = VectorRowMatrixXd() if curves: - curves = [np.asarray(curve, dtype=np.float64) for curve in curves] - else: - curves = [] + for curve in curves: + curve_array = np.asarray(curve, dtype=np.float64, order="C") + curves_vector.append(curve_array) maxlength = maxlength or 0.0 - return triangulations.refined_delaunay_mesh(boundary, points, holes, curves, maxlength=maxlength, is_optimized=is_optimized) + # Call C++ function with proper type conversion and parameter order + return triangulation.refined_delaunay_mesh( + boundary, # B: numpy array (Nx2) + points, # P: numpy array (Mx2) + holes_vector, # holes: VectorRowMatrixXd + curves_vector, # curves: VectorRowMatrixXd + 0.0, # minangle: float + maxlength, # maxlength: float + is_optimized, # is_optimized: bool + ) diff --git a/src/compas_cgal/types.py b/src/compas_cgal/types.py index ff5f6770..26f48dc7 100644 --- a/src/compas_cgal/types.py +++ b/src/compas_cgal/types.py @@ -39,6 +39,9 @@ PolylinesNumpy = List[FloatNx3] """A list of polylines, with each polyline represented as a Nx3 array of spatial coordinates.""" +PolylinesNumpySkeleton = List[Tuple[List[float], List[float]]] +"""A list of polylines, where each polyline is represented by a tuple of start and end point coordinates.""" + Planes = Union[ Sequence[compas.geometry.Plane], Sequence[Tuple[compas.geometry.Point, compas.geometry.Vector]], diff --git a/src/intersections.cpp b/src/intersections.cpp index f5ce86a1..4beb7d83 100644 --- a/src/intersections.cpp +++ b/src/intersections.cpp @@ -1,36 +1,31 @@ #include "intersections.h" -#include - -namespace PMP = CGAL::Polygon_mesh_processing; std::vector pmp_intersection_mesh_mesh( - Eigen::Ref &VA, - Eigen::Ref &FA, - Eigen::Ref &VB, - Eigen::Ref &FB) + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b) { - compas::Mesh A = compas::mesh_from_vertices_and_faces(VA, FA); - compas::Mesh B = compas::mesh_from_vertices_and_faces(VB, FB); + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); + compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b); compas::Polylines polylines; - - PMP::surface_intersection(A, B, std::back_inserter(polylines)); + CGAL::Polygon_mesh_processing::surface_intersection(mesh_a, mesh_b, std::back_inserter(polylines)); std::vector result = compas::polylines_to_lists_of_points(polylines); - return result; -}; +} -void init_intersections(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("intersections"); +void init_intersections(nb::module_& m) { + auto submodule = m.def_submodule("intersections"); submodule.def( "intersection_mesh_mesh", &pmp_intersection_mesh_mesh, - pybind11::arg("VA").noconvert(), - pybind11::arg("FA").noconvert(), - pybind11::arg("VB").noconvert(), - pybind11::arg("FB").noconvert()); -} + "Compute intersection polylines between two triangle meshes.", + "vertices_a"_a, + "faces_a"_a, + "vertices_b"_a, + "faces_b"_a); +} \ No newline at end of file diff --git a/src/intersections.h b/src/intersections.h index 745f95e9..825f7d5e 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -1,15 +1,21 @@ -#ifndef COMPAS_INTERSECTIONS_H -#define COMPAS_INTERSECTIONS_H - -#include +#pragma once +#include "compas.h" +/** + * Compute intersection between two triangle meshes. + * + * @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order (float64) + * @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order (int32) + * @param vertices_b Vertices of mesh B as Px3 matrix in row-major order (float64) + * @param faces_b Faces of mesh B as Qx3 matrix of vertex indices in row-major order (int32) + * @return std::vector containing: + * - List of intersection polylines, each as Rx3 matrix of points (float64) + * @note Input meshes must be manifold and closed + */ std::vector pmp_intersection_mesh_mesh( - Eigen::Ref & VA, - Eigen::Ref & FA, - Eigen::Ref & VB, - Eigen::Ref & FB); - - -#endif /* COMPAS_INTERSECTIONS_H */ + Eigen::Ref vertices_a, + Eigen::Ref faces_a, + Eigen::Ref vertices_b, + Eigen::Ref faces_b); \ No newline at end of file diff --git a/src/measure.cpp b/src/measure.cpp index 7120e6f4..840d5e43 100644 --- a/src/measure.cpp +++ b/src/measure.cpp @@ -1,64 +1,56 @@ #include "measure.h" -#include -#include - -namespace PMP = CGAL::Polygon_mesh_processing; double pmp_area( - Eigen::Ref &V, - Eigen::Ref &F) + Eigen::Ref vertices, + Eigen::Ref faces) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - double area = PMP::area(mesh); - + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); + double area = CGAL::Polygon_mesh_processing::area(mesh); return area; -}; +} double pmp_volume( - Eigen::Ref &V, - Eigen::Ref &F) + Eigen::Ref vertices, + Eigen::Ref faces) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - double volume = PMP::volume(mesh); - + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); + double volume = CGAL::Polygon_mesh_processing::volume(mesh); return volume; -}; +} std::vector pmp_centroid( - Eigen::Ref &V, - Eigen::Ref &F) + Eigen::Ref vertices, + Eigen::Ref faces) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - compas::Kernel::Point_3 centroid = PMP::centroid(mesh); - + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); + compas::Kernel::Point_3 centroid = CGAL::Polygon_mesh_processing::centroid(mesh); return std::vector{centroid.x(), centroid.y(), centroid.z()}; -}; +} -void init_measure(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("measure"); +void init_measure(nb::module_& m) { + auto submodule = m.def_submodule("measure"); submodule.def( "area", &pmp_area, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert()); + "Calculate the surface area of a mesh", + "vertices"_a, + "faces"_a); submodule.def( "volume", &pmp_volume, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert()); + "Calculate the volume enclosed by a mesh", + "vertices"_a, + "faces"_a); submodule.def( "centroid", &pmp_centroid, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert()); -}; + "Calculate the centroid of a mesh", + "vertices"_a, + "faces"_a); +} \ No newline at end of file diff --git a/src/measure.h b/src/measure.h index c5ede763..649aeb04 100644 --- a/src/measure.h +++ b/src/measure.h @@ -1,21 +1,39 @@ -#ifndef COMPAS_MEASURE_H -#define COMPAS_MEASURE_H +#pragma once -#include +#include "compas.h" +/** + * @brief Computes the surface area of a triangle mesh. + * + * @param vertices Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces Matrix of face indices as Mx3 matrix in row-major order (int32) + * @return double The total surface area of the mesh + */ double pmp_area( - Eigen::Ref &V, - Eigen::Ref &F); + Eigen::Ref vertices, + Eigen::Ref faces); +/** + * @brief Computes the volume of a closed triangle mesh. + * + * @param vertices Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces Matrix of face indices as Mx3 matrix in row-major order (int32) + * @return double The volume enclosed by the mesh. Returns 0 if the mesh is not closed. + */ double pmp_volume( - Eigen::Ref &V, - Eigen::Ref &F); + Eigen::Ref vertices, + Eigen::Ref faces); +/** + * @brief Computes the centroid (center of mass) of a triangle mesh. + * + * @param vertices Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces Matrix of face indices as Mx3 matrix in row-major order (int32) + * @return std::vector A vector containing the x,y,z coordinates of the mesh centroid + */ std::vector pmp_centroid( - Eigen::Ref &V, - Eigen::Ref &F); - -#endif /* COMPAS_MEASURE_H */ + Eigen::Ref vertices, + Eigen::Ref faces); diff --git a/src/meshing.cpp b/src/meshing.cpp index 1153669e..d774ce55 100644 --- a/src/meshing.cpp +++ b/src/meshing.cpp @@ -1,53 +1,42 @@ #include "meshing.h" -#include -#include - - -namespace PMP = CGAL::Polygon_mesh_processing; - std::tuple pmp_remesh( - Eigen::Ref & V, - Eigen::Ref & F, + Eigen::Ref vertices_a, + Eigen::Ref faces_a, double target_edge_length, unsigned int number_of_iterations, bool do_project) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - // protect sharp features + // Convert input matrices to CGAL mesh + compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a); - // typedef boost::property_map::type EIFMap; - // EIFMap eif = get(CGAL::edge_is_feature, mesh); - // PMP::detect_sharp_edges(mesh, 60, eif); - - PMP::isotropic_remeshing( - faces(mesh), + // Perform isotropic remeshing + CGAL::Polygon_mesh_processing::isotropic_remeshing( + faces(mesh_a), target_edge_length, - mesh, - PMP::parameters::number_of_iterations(number_of_iterations).do_project(do_project)); - - mesh.collect_garbage(); - - // Result - // compas::Result R = compas::result_from_mesh(mesh); - std::tuple R = compas::mesh_to_vertices_and_faces(mesh); + mesh_a, + CGAL::Polygon_mesh_processing::parameters::number_of_iterations(number_of_iterations) + .do_project(do_project)); - return R; -}; + // Clean up the mesh + mesh_a.collect_garbage(); + // Convert back to matrices - compiler will use RVO automatically + return compas::mesh_to_vertices_and_faces(mesh_a); +} -void init_meshing(pybind11::module & m) { - pybind11::module submodule = m.def_submodule("meshing"); +void init_meshing(nb::module_& m) { + auto submodule = m.def_submodule("meshing"); submodule.def( "remesh", &pmp_remesh, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert(), - pybind11::arg("target_edge_length"), - pybind11::arg("number_of_iterations") = 10, - pybind11::arg("do_project") = true + "Remesh a triangular mesh to achieve a target edge length", + "vertices_a"_a, + "faces_a"_a, + "target_edge_length"_a, + "number_of_iterations"_a = 10, + "do_project"_a = true ); -}; +} diff --git a/src/meshing.h b/src/meshing.h index fff0c082..a008cbc9 100644 --- a/src/meshing.h +++ b/src/meshing.h @@ -1,16 +1,62 @@ -#ifndef COMPAS_MESHING_H -#define COMPAS_MESHING_H +/** + * @file meshing.h + * @brief Header file for mesh processing operations using CGAL + */ -#include +#pragma once +#include "compas.h" +namespace compas { + + /** + * @brief Calculate the surface area of a mesh + * @param vertices Matrix of vertex coordinates + * @param faces Matrix of face indices + * @return Surface area of the mesh + */ + double pmp_area( + Eigen::Ref& vertices, + Eigen::Ref& faces); + + /** + * @brief Calculate the volume enclosed by a mesh + * @param vertices Matrix of vertex coordinates + * @param faces Matrix of face indices + * @return Volume enclosed by the mesh + */ + double pmp_volume( + Eigen::Ref& vertices, + Eigen::Ref& faces); + + /** + * @brief Calculate the centroid of a mesh + * @param vertices Matrix of vertex coordinates + * @param faces Matrix of face indices + * @return Coordinates of the mesh centroid + */ + std::vector pmp_centroid( + Eigen::Ref& vertices, + Eigen::Ref& faces); + +} // namespace compas + +/** + * @brief Remesh a triangle mesh with target edge length. + * + * @param vertices_a Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces_a Matrix of face indices as Mx3 matrix in row-major order (int32) + * @param target_edge_length Desired length for mesh edges + * @param number_of_iterations Number of remeshing iterations + * @param do_project Whether to project vertices onto the input surface + * @return std::tuple containing: + * - New vertices as Rx3 matrix (float64) + * - New faces as Sx3 matrix (int32) + */ std::tuple pmp_remesh( - Eigen::Ref & V, - Eigen::Ref & F, + Eigen::Ref vertices_a, + Eigen::Ref faces_a, double target_edge_length, unsigned int number_of_iterations = 10, bool do_project = true); - - -#endif /* COMPAS_MESHING_H */ diff --git a/src/nanobind_types.h b/src/nanobind_types.h new file mode 100644 index 00000000..df10bae6 --- /dev/null +++ b/src/nanobind_types.h @@ -0,0 +1,21 @@ +#pragma once + +#include "compas.h" + +// Example functions for nanobind demonstration +// This file is used to quickly test C++ dummy methods with new types that haven't been tested before. +// It helps isolate problems for debugging when you want to focus on type conversion rather than implementation details. +// Only use these methods when C++ and Python input/output types match exactly. +// Also just use inline methods so that you do not have to declare cpp declarations. + +inline void scale_matrix(Eigen::Ref mat) { + mat *= 2.0; +} + +inline compas::RowMatrixXd create_matrix() { + compas::RowMatrixXd mat(3, 3); + mat << 1.1, 2.2, 3.3, + 4.4, 5.5, 6.6, + 7.7, 8.8, 9.9; + return mat; +} \ No newline at end of file diff --git a/src/polygonal_surface_reconstruction.cpp b/src/polygonal_surface_reconstruction.cpp deleted file mode 100644 index 5597c823..00000000 --- a/src/polygonal_surface_reconstruction.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "polygonal_surface_reconstruction.h" - -#include -#include -#include -#include -#include - -#define CGAL_USE_SCIP - -#include -typedef CGAL::SCIP_mixed_integer_program_traits MIP_Solver; - -#include -#include - -typedef boost::tuple PNI; -typedef std::vector Point_vector; -typedef CGAL::Nth_of_tuple_property_map<0, PNI> Point_map; -typedef CGAL::Nth_of_tuple_property_map<1, PNI> Normal_map; -typedef CGAL::Nth_of_tuple_property_map<2, PNI> Plane_index_map; - -typedef CGAL::Shape_detection::Efficient_RANSAC_traits Traits; -typedef CGAL::Shape_detection::Efficient_RANSAC Efficient_ransac; -typedef CGAL::Shape_detection::Plane Plane; -typedef CGAL::Shape_detection::Point_to_shape_index_map Point_to_shape_index_map; -typedef CGAL::Polygonal_surface_reconstruction Polygonal_surface_reconstruction; -typedef CGAL::Surface_mesh Surface_mesh; - -// Function for polygonal surface reconstruction using RANSAC -std::tuple>> -polygonal_surface_reconstruction_ransac( - Eigen::Ref &P, - Eigen::Ref &N) -{ - // Convert Eigen matrix to a vector of CGAL points. - int p = P.rows(); - Point_vector points; - - for (int i = 0; i < p; i++) - { - points.push_back(boost::tuple(compas::Point(P.row(i)[0], P.row(i)[1], P.row(i)[2]), - compas::Vector(N.row(i)[0], N.row(i)[1], N.row(i)[2]), -1)); - } - - std::cout << "Done. " << points.size() << " points loaded." << std::endl; - - // Efficient RANSAC for plane extraction - Efficient_ransac ransac; - ransac.set_input(points); - ransac.add_shape_factory(); - std::cout << "Extracting planes..."; - ransac.detect(); - Efficient_ransac::Plane_range planes = ransac.planes(); - std::size_t num_planes = planes.size(); - std::cout << " Done. " << num_planes << " planes extracted. " << std::endl; - - // Assign plane index to each point - Point_to_shape_index_map shape_index_map(points, planes); - for (std::size_t i = 0; i < points.size(); ++i) - { - int plane_index = get(shape_index_map, i); - points[i].get<2>() = plane_index; - } - - std::cout << "Generating candidate faces..."; - // Polygonal surface reconstruction - Polygonal_surface_reconstruction algo( - points, - Point_map(), - Normal_map(), - Plane_index_map()); - - Surface_mesh model; - std::cout << "Reconstructing..."; - - // Reconstruct surface using mixed-integer programming solver - if (!algo.reconstruct(model)) - { - std::cout << " Failed:..."; - std::cerr << algo.error_message() << std::endl; - }else{ - std::cout << " Reconstruction is finished. " << std::endl; - } - - Surface_mesh candidate_faces; - algo.output_candidate_faces(candidate_faces); - candidate_faces = model; - - // Extract vertices and faces for result - std::size_t v = candidate_faces.number_of_vertices(); - std::size_t f = candidate_faces.number_of_faces(); - - compas::RowMatrixXd V(v, 3); - std::vector> F; - - compas::Mesh::Property_map location = candidate_faces.points(); - - for (compas::Mesh::Vertex_index vd : candidate_faces.vertices()) - { - V(vd, 0) = (double)location[vd][0]; - V(vd, 1) = (double)location[vd][1]; - V(vd, 2) = (double)location[vd][2]; - } - - for (compas::Mesh::Face_index fd : candidate_faces.faces()) - { - std::vector fv; - int i = 0; - for (compas::Mesh::Vertex_index vd : vertices_around_face(candidate_faces.halfedge(fd), candidate_faces)) - { - fv.emplace_back((int)vd); - } - F.emplace_back(fv); - } - - std::tuple>> result = std::make_tuple(V, F); - - return result; -} - -// Pybind11 initialization function -void init_polygonal_surface_reconstruction(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("polygonal_surface_reconstruction"); - - submodule.def( - "polygonal_surface_reconstruction_ransac", - &polygonal_surface_reconstruction_ransac, - pybind11::arg("P").noconvert(), - pybind11::arg("N").noconvert()); -}; diff --git a/src/polygonal_surface_reconstruction.h b/src/polygonal_surface_reconstruction.h deleted file mode 100644 index aa56d3f0..00000000 --- a/src/polygonal_surface_reconstruction.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef COMPAS_POLYGONAL_SURFACE_RECONSTRUCTION_H -#define COMPAS_POLYGONAL_SURFACE_RECONSTRUCTION_H - -#include - -std::tuple>> -polygonal_surface_reconstruction_ransac( - Eigen::Ref &P, - Eigen::Ref &N); - -#endif /* COMPAS_POLYGONAL_SURFACE_RECONSTRUCTION_H */ diff --git a/src/reconstruction.cpp b/src/reconstruction.cpp index b9144d89..ea223a93 100644 --- a/src/reconstruction.cpp +++ b/src/reconstruction.cpp @@ -1,55 +1,32 @@ #include "reconstruction.h" -#include -#include - -#include - -#include -#include -#include -#include -#include // defines std::pair -#include - -#include - -#include -#include typedef std::pair PointVectorPair; typedef CGAL::Point_set_3 PointSet; -typedef CGAL::Parallel_if_available_tag Concurrency_tag; - -/** - * @brief Perform Poisson surface reconstruction on an oriented pointcloud with normals. - * - * @param P The points of the cloud. - * @param N The normals of the points. - * @return std::tuple - */ +typedef CGAL::Parallel_if_available_tag ConcurrencyTag; + std::tuple poisson_surface_reconstruction( - Eigen::Ref &P, - Eigen::Ref &N) + Eigen::Ref points, + Eigen::Ref normals) { compas::Polyhedron mesh; - std::vector points; + std::vector points_with_normals; - for (int i = 0; i < P.rows(); i++) + for (int i = 0; i < points.rows(); i++) { - points.push_back(PointVectorPair( - compas::Point(P(i, 0), P(i, 1), P(i, 2)), - compas::Vector(N(i, 0), N(i, 1), N(i, 2)))); + points_with_normals.push_back(PointVectorPair( + compas::Point(points(i, 0), points(i, 1), points(i, 2)), + compas::Vector(normals(i, 0), normals(i, 1), normals(i, 2)))); } double average_spacing = CGAL::compute_average_spacing( - points, + points_with_normals, 6, CGAL::parameters::point_map(CGAL::First_of_pair_property_map())); CGAL::poisson_surface_reconstruction_delaunay( - points.begin(), - points.end(), + points_with_normals.begin(), + points_with_normals.end(), CGAL::First_of_pair_property_map(), CGAL::Second_of_pair_property_map(), mesh, @@ -58,73 +35,48 @@ poisson_surface_reconstruction( return compas::polyhedron_to_vertices_and_faces(mesh); } -/** - * @brief Remove outliers from a pointcloud. - * - * @param P The points of the cloud. - * @param nnnbrs The number of nearest neighbors to consider. - * @param radius The radius of the sphere to consider as a multiplication factor of the average spacing. - * @return compas::RowMatrixXd - * - */ + compas::RowMatrixXd pointset_outlier_removal( - Eigen::Ref &P, - int nnnbrs, + Eigen::Ref points, + int num_neighbors, double radius) { - int p = P.rows(); - std::vector points; + int p = points.rows(); + std::vector points_vector; for (int i = 0; i < p; i++) { - points.push_back(compas::Point(P(i, 0), P(i, 1), P(i, 2))); + points_vector.push_back(compas::Point(points(i, 0), points(i, 1), points(i, 2))); } - const double average_spacing = CGAL::compute_average_spacing(points, nnnbrs); + const double average_spacing = CGAL::compute_average_spacing(points_vector, num_neighbors); std::vector::iterator to_remove = CGAL::remove_outliers( - points, - nnnbrs, + points_vector, + num_neighbors, CGAL::parameters::threshold_percent(100.).threshold_distance(radius * average_spacing)); - // std::vector outliers; - // for (it; it != points.end(); ++it) - // { - // std::size_t index = std::distance(points.begin(), it); - // outliers.push_back(index); - // } - - // return outliers; + points_vector.erase(to_remove, points_vector.end()); + std::vector(points_vector).swap(points_vector); - points.erase(to_remove, points.end()); - std::vector(points).swap(points); - - std::size_t s = points.size(); + std::size_t s = points_vector.size(); compas::RowMatrixXd result(s, 3); for (int i = 0; i < s; i++) { - result(i, 0) = (double)points[i].x(); - result(i, 1) = (double)points[i].y(); - result(i, 2) = (double)points[i].z(); + result(i, 0) = (double)points_vector[i].x(); + result(i, 1) = (double)points_vector[i].y(); + result(i, 2) = (double)points_vector[i].z(); } return result; } -/** - * @brief Reduce number of points using hierarcy simplification. - * https://doc.cgal.org/latest/Point_set_processing_3/Point_set_processing_3_2hierarchy_simplification_example_8cpp-example.html - * - * @param P The points of the cloud. - * @param spacing The scale for the average spacing. - * @return compas::RowMatrixXd - * - */ + compas::RowMatrixXd pointset_reduction( - Eigen::Ref &P, + Eigen::Ref points, double spacing ) { @@ -132,107 +84,88 @@ pointset_reduction( // ====================================================================== // Convert Eigen matrix to a vector of cgal points. // ====================================================================== - int p = P.rows(); - std::vector points; + int p = points.rows(); + std::vector points_vector; for (int i = 0; i < p; i++) - points.emplace_back(P.row(i)[0], P.row(i)[1], P.row(i)[2]); + points_vector.emplace_back(points.row(i)[0], points.row(i)[1], points.row(i)[2]); // ====================================================================== // Simplification by voxelization. // ====================================================================== double cell_size = spacing; - auto iterator_to_first_to_remove = CGAL::grid_simplify_point_set(points, cell_size); - points.erase(iterator_to_first_to_remove, points.end()); + auto iterator_to_first_to_remove = CGAL::grid_simplify_point_set(points_vector, cell_size); + points_vector.erase(iterator_to_first_to_remove, points_vector.end()); // Optional: after erase(), shrink_to_fit to trim excess capacity - points.shrink_to_fit(); + points_vector.shrink_to_fit(); // ====================================================================== // Convert the vector of cgal points to an Eigen matrix. // ====================================================================== - std::size_t s = points.size(); + std::size_t s = points_vector.size(); compas::RowMatrixXd smoothed_eigen_points(s, 3); for (std::size_t i = 0; i < s; i++) - smoothed_eigen_points.row(i) << static_cast(points[i].x()), static_cast(points[i].y()), static_cast(points[i].z()); + smoothed_eigen_points.row(i) << static_cast(points_vector[i].x()), static_cast(points_vector[i].y()), static_cast(points_vector[i].z()); return smoothed_eigen_points; } -/** - * @brief Smooth a pointcloud by number of neighbors and iterations using jet_smoothing algorithm. - * https://doc.cgal.org/4.3/Point_set_processing_3/Point_set_processing_3_2jet_smoothing_example_8cpp-example.html - * - * @param P The points of the cloud. - * @param neighbors The number of nearest neighbors to consider. - * @param iterations The number of iterations. - * @return compas::RowMatrixXd - * - */ + compas::RowMatrixXd pointset_smoothing( - Eigen::Ref &P, - int neighbors, - int iterations) + Eigen::Ref points, + int num_neighbors, + int num_iterations) { // ====================================================================== // Convert Eigen matrix to a vector of cgal points. // ====================================================================== - int p = P.rows(); - std::vector points; + int p = points.rows(); + std::vector points_vector; for (int i = 0; i < p; i++) - points.emplace_back(P.row(i)[0], P.row(i)[1], P.row(i)[2]); + points_vector.emplace_back(points.row(i)[0], points.row(i)[1], points.row(i)[2]); // ====================================================================== // Smooth the points, default is 24. // ====================================================================== - for (int i = 0; i < iterations; i++) - CGAL::jet_smooth_point_set(points, neighbors); + for (int i = 0; i < num_iterations; i++) + CGAL::jet_smooth_point_set(points_vector, num_neighbors); // ====================================================================== // Convert the vector of cgal points to an Eigen matrix // ====================================================================== - std::size_t s = points.size(); + std::size_t s = points_vector.size(); compas::RowMatrixXd smoothed_eigen_points(s, 3); for (std::size_t i = 0; i < s; i++) - smoothed_eigen_points.row(i) << static_cast(points[i].x()), static_cast(points[i].y()), static_cast(points[i].z()); + smoothed_eigen_points.row(i) << static_cast(points_vector[i].x()), static_cast(points_vector[i].y()), static_cast(points_vector[i].z()); return smoothed_eigen_points; } -/** - * @brief Estimate pointcloud normals and oriented them. - * https://doc.cgal.org/latest/Point_set_processing_3/Point_set_processing_3_2normals_example_8cpp-example.html - * - * @param P The points of the cloud. - * @param neighbors The number of nearest neighbors to consider. - * @param erase Erase points that are not oriented properly. - * @return std::tuple - * - */ std::tuple pointset_normal_estimation( - Eigen::Ref &P, - int neighbors, - bool erase) + Eigen::Ref points, + int num_neighbors, + bool erase_unoriented) { // ====================================================================== // Convert Eigen matrix to CGAL point vector pair. // ====================================================================== - int p = P.rows(); - std::list points; + int p = points.rows(); + std::list points_with_normals; for (int i = 0; i < p; i++) - points.emplace_back(PointVectorPair( - compas::Point(P(i, 0), P(i, 1), P(i, 2)), + points_with_normals.emplace_back(PointVectorPair( + compas::Point(points(i, 0), points(i, 1), points(i, 2)), compas::Vector(0, 0, 0) )); @@ -242,24 +175,24 @@ pointset_normal_estimation( // Case 1 - the radius is precomputed by the average spacing. // Case 2 - the normals are estimated with a fixed number of neighbors. // =========================================================================== - if ( neighbors < 1 ) { + if ( num_neighbors < 1 ) { // First compute a spacing using the K parameter double spacing - = CGAL::compute_average_spacing - (points, neighbors, + = CGAL::compute_average_spacing + (points_with_normals, num_neighbors, CGAL::parameters::point_map(CGAL::First_of_pair_property_map())); // Then, estimate normals with a fixed radius - CGAL::pca_estimate_normals (points,0, // when using a neighborhood radius, K=0 means no limit on the number of neighbors returns + CGAL::pca_estimate_normals (points_with_normals,0, // when using a neighborhood radius, K=0 means no limit on the number of neighbors returns CGAL::parameters::point_map (CGAL::First_of_pair_property_map ()). normal_map (CGAL::Second_of_pair_property_map ()). neighbor_radius (2. * spacing)); // use 2*spacing as neighborhood radius } else { - CGAL::pca_estimate_normals - (points, neighbors, + CGAL::pca_estimate_normals + (points_with_normals, num_neighbors, CGAL::parameters::point_map (CGAL::First_of_pair_property_map ()). normal_map (CGAL::Second_of_pair_property_map ())); } @@ -267,28 +200,28 @@ pointset_normal_estimation( // =========================================================================== // Orients normals, give at least 3 points to make a plane. // =========================================================================== - unsigned orientation_neighbors = (neighbors < 3) ? 3 : neighbors; + unsigned orientation_neighbors = (num_neighbors < 3) ? 3 : num_neighbors; std::list::iterator unoriented_points_begin = - CGAL::mst_orient_normals (points, neighbors, + CGAL::mst_orient_normals (points_with_normals, num_neighbors, CGAL::parameters::point_map (CGAL::First_of_pair_property_map ()). normal_map (CGAL::Second_of_pair_property_map ())); // =========================================================================== // Optional: delete points with an unoriented normal. // =========================================================================== - if(erase) - points.erase (unoriented_points_begin, points.end ()); + if(erase_unoriented) + points_with_normals.erase (unoriented_points_begin, points_with_normals.end ()); // =========================================================================== // Convert CGAL normals vector pair to Eigen matrix. // =========================================================================== - std::size_t s = points.size(); + std::size_t s = points_with_normals.size(); compas::RowMatrixXd eigen_points(s, 3); compas::RowMatrixXd eigen_vectors(s, 3); std::size_t i = 0; - for (const auto& e : points) { + for (const auto& e : points_with_normals) { eigen_points.row(i) << static_cast(e.first.x()), static_cast(e.first.y()), static_cast(e.first.z()); eigen_vectors.row(i) << static_cast(e.second.x()), static_cast(e.second.y()), static_cast(e.second.z()); i++; @@ -298,44 +231,49 @@ pointset_normal_estimation( } -// =========================================================================== -// PyBind11 -// =========================================================================== - -void init_reconstruction(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("reconstruction"); +void init_reconstruction(nb::module_& m) { + auto submodule = m.def_submodule("reconstruction"); submodule.def( "poisson_surface_reconstruction", &poisson_surface_reconstruction, - pybind11::arg("P").noconvert(), - pybind11::arg("N").noconvert()); + "Perform Poisson surface reconstruction on an oriented pointcloud with normals", + "points"_a, + "normals"_a + ); submodule.def( "pointset_outlier_removal", &pointset_outlier_removal, - pybind11::arg("P").noconvert(), - pybind11::arg("nnnbrs") = 10, - pybind11::arg("radius") = 1.0); + "Remove outliers from a pointcloud", + "points"_a, + "num_neighbors"_a, + "radius"_a + ); submodule.def( "pointset_reduction", &pointset_reduction, - pybind11::arg("P").noconvert(), - pybind11::arg("spacing") = 1.0); + "Reduce number of points using hierarchy simplification", + "points"_a, + "spacing"_a = 2.0 + ); submodule.def( "pointset_smoothing", &pointset_smoothing, - pybind11::arg("P").noconvert(), - pybind11::arg("neighbors") = 8, - pybind11::arg("iterations") = 1); - + "Smooth a pointcloud using jet smoothing", + "points"_a, + "num_neighbors"_a = 8, + "num_iterations"_a = 1 + ); + submodule.def( "pointset_normal_estimation", &pointset_normal_estimation, - pybind11::arg("P").noconvert(), - pybind11::arg("neighbors") = 8, - pybind11::arg("erase") = true); -}; + "Estimate pointcloud normals and orient them", + "points"_a, + "num_neighbors"_a = 8, + "erase_unoriented"_a = true + ); +} diff --git a/src/reconstruction.h b/src/reconstruction.h index d04da6f5..4bcad272 100644 --- a/src/reconstruction.h +++ b/src/reconstruction.h @@ -1,34 +1,79 @@ -#ifndef COMPAS_RECONSTRUCTION_H -#define COMPAS_RECONSTRUCTION_H +#pragma once -#include +#include "compas.h" +/** + * @brief Perform Poisson surface reconstruction on an oriented pointcloud with normals. + * + * @param points Matrix of point positions as Nx3 matrix in row-major order (float64) + * @param normals Matrix of point normals as Nx3 matrix in row-major order (float64) + * @return std::tuple containing: + * - vertices as Rx3 matrix (float64) + * - faces as Sx3 matrix (int32) + */ std::tuple poisson_surface_reconstruction( - Eigen::Ref &P, - Eigen::Ref &N); + Eigen::Ref points, + Eigen::Ref normals); +/** + * @brief Remove outliers from a pointcloud. + * + * @param points Matrix of point positions as Nx3 matrix in row-major order (float64) + * @param num_neighbors The number of nearest neighbors to consider + * @param radius The radius of the sphere to consider as a multiplication factor of the average spacing + * @return compas::RowMatrixXd + * - Filtered points as Mx3 matrix (float64), where M <= N + */ compas::RowMatrixXd pointset_outlier_removal( - Eigen::Ref &P, - int nnnbrs, + Eigen::Ref points, + int num_neighbors, double radius); +/** + * @brief Reduce number of points using hierarchy simplification. + * https://doc.cgal.org/latest/Point_set_processing_3/Point_set_processing_3_2hierarchy_simplification_example_8cpp-example.html + * + * @param points Matrix of point positions as Nx3 matrix in row-major order (float64) + * @param spacing The scale for the average spacing + * @return compas::RowMatrixXd + * - Reduced points as Mx3 matrix (float64), where M <= N + */ compas::RowMatrixXd pointset_reduction( - Eigen::Ref &P, + Eigen::Ref points, double spacing = 2.0); +/** + * @brief Smooth a pointcloud by number of neighbors and iterations using jet_smoothing algorithm. + * https://doc.cgal.org/4.3/Point_set_processing_3/Point_set_processing_3_2jet_smoothing_example_8cpp-example.html + * + * @param points Matrix of point positions as Nx3 matrix in row-major order (float64) + * @param num_neighbors The number of nearest neighbors to consider + * @param num_iterations The number of iterations + * @return compas::RowMatrixXd + * - Smoothed points as Nx3 matrix (float64) + */ compas::RowMatrixXd pointset_smoothing( - Eigen::Ref &P, - int neighbors = 8, - int iterations = 1); + Eigen::Ref points, + int num_neighbors = 8, + int num_iterations = 1); +/** + * @brief Estimate pointcloud normals and oriented them. + * https://doc.cgal.org/latest/Point_set_processing_3/Point_set_processing_3_2normals_example_8cpp-example.html + * + * @param points Matrix of point positions as Nx3 matrix in row-major order (float64) + * @param num_neighbors The number of nearest neighbors to consider + * @param erase_unoriented Erase points that are not oriented properly + * @return std::tuple containing: + * - points as Mx3 matrix (float64), where M <= N if erase_unoriented is true + * - normals as Mx3 matrix (float64) + */ std::tuple pointset_normal_estimation( - Eigen::Ref &P, - int neighbors=8, - bool erase=true); - -#endif /* COMPAS_RECONSTRUCTION_H */ + Eigen::Ref points, + int num_neighbors=8, + bool erase_unoriented=true); diff --git a/src/skeletonization.cpp b/src/skeletonization.cpp index 0b01ecd6..1b388b8f 100644 --- a/src/skeletonization.cpp +++ b/src/skeletonization.cpp @@ -1,96 +1,59 @@ #include "skeletonization.h" -#include -#include typedef CGAL::Mean_curvature_flow_skeletonization Skeletonization; typedef Skeletonization::Skeleton Skeleton; +typedef boost::graph_traits::vertex_descriptor SkeletonVertex; +typedef boost::graph_traits::edge_descriptor SkeletonEdge; -typedef boost::graph_traits::vertex_descriptor vertex_descriptor; - -typedef Skeleton::vertex_descriptor Skeleton_vertex; -typedef Skeleton::edge_descriptor Skeleton_edge; - -struct Split_polylines +std::tuple, std::vector> +pmp_mesh_skeleton( + Eigen::Ref vertices, + Eigen::Ref faces) { - const Skeleton &skeleton; - compas::Polylines &polylines; - compas::Polyline polyline; - int polyline_index = 0; - int polyline_size; - - Split_polylines(const Skeleton &skeleton, compas::Polylines &polylines) - : skeleton(skeleton), polylines(polylines) - { - } - - void start_new_polyline() - { - polyline = compas::Polyline(); - } - - void add_node(Skeleton_vertex v) - { - polyline.push_back(skeleton[v].point); - } - - void end_polyline() - { - polylines.push_back(polyline); - } -}; - -compas::Edges pmp_mesh_skeleton( - Eigen::Ref &V, - Eigen::Ref &F) -{ - - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); Skeleton skeleton; Skeletonization mcs(mesh); - // mcs.contract_geometry(); - // mcs.collapse_edges(); - // mcs.split_faces(); - // mcs.detect_degeneracies(); - - // mcs.contract(); - mcs.contract_until_convergence(); mcs.convert_to_skeleton(skeleton); - // compas::Polylines polylines; - // Split_polylines splitter(skeleton, polylines); - // CGAL::split_graph_into_polylines(skeleton, splitter); - - compas::Edges edgelist; - - for (Skeleton_edge e : CGAL::make_range(edges(skeleton))) - { - const compas::Kernel::Point_3 &s = skeleton[source(e, skeleton)].point; - const compas::Kernel::Point_3 &t = skeleton[target(e, skeleton)].point; - - std::vector s_vec = {s.x(), s.y(), s.z()}; - std::vector t_vec = {t.x(), t.y(), t.z()}; - compas::Edge edge = std::make_tuple(s_vec, t_vec); - - edgelist.push_back(edge); + // Initialize vectors to store start and end points + std::vector start_points; + std::vector end_points; + + // Reserve space for efficiency + size_t num_edges = boost::num_edges(skeleton); + start_points.reserve(num_edges * 3); // Each point has 3 coordinates + end_points.reserve(num_edges * 3); + + // Extract skeleton edges + for (SkeletonEdge edge : CGAL::make_range(edges(skeleton))) { + const compas::Kernel::Point_3& start = skeleton[source(edge, skeleton)].point; + const compas::Kernel::Point_3& end = skeleton[target(edge, skeleton)].point; + + // Add start point coordinates + start_points.push_back(start.x()); + start_points.push_back(start.y()); + start_points.push_back(start.z()); + + // Add end point coordinates + end_points.push_back(end.x()); + end_points.push_back(end.y()); + end_points.push_back(end.z()); } - return edgelist; + return std::make_tuple(start_points, end_points); }; -// =========================================================================== -// PyBind11 -// =========================================================================== - -void init_skeletonization(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("skeletonization"); +void init_skeletonization(nb::module_& m) { + auto submodule = m.def_submodule("skeletonization"); submodule.def( "mesh_skeleton", &pmp_mesh_skeleton, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert()); -}; + "Create a geometric skeleton from a mesh using mean curvature flow", + "vertices"_a, + "faces"_a + ); +} diff --git a/src/skeletonization.h b/src/skeletonization.h index 5479f55b..6a8322ef 100644 --- a/src/skeletonization.h +++ b/src/skeletonization.h @@ -1,10 +1,17 @@ -#ifndef COMPAS_SKELETONIZATION_H -#define COMPAS_SKELETONIZATION_H +#pragma once -#include +#include "compas.h" -compas::Edges pmp_mesh_skeleton( - Eigen::Ref &V, - Eigen::Ref &F); - -#endif /* COMPAS_SKELETONISATION_H */ +/** + * @brief Compute the geometric skeleton of a triangle mesh using mean curvature flow. + * + * @param vertices Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces Matrix of face indices as Mx3 matrix in row-major order (int32) + * @return std::tuple, std::vector> containing: + * - Start points of skeleton edges as vector of 3D coordinates (float64) + * - End points of skeleton edges as vector of 3D coordinates (float64) + */ +std::tuple, std::vector> +pmp_mesh_skeleton( + Eigen::Ref vertices, + Eigen::Ref faces); \ No newline at end of file diff --git a/src/slicer.cpp b/src/slicer.cpp index fe2483e4..a07d48ca 100644 --- a/src/slicer.cpp +++ b/src/slicer.cpp @@ -1,26 +1,25 @@ #include "slicer.h" -#include std::vector pmp_slice_mesh( - Eigen::Ref &V, - Eigen::Ref &F, - Eigen::Ref &P, - Eigen::Ref &N) + Eigen::Ref vertices, + Eigen::Ref faces, + Eigen::Ref points, + Eigen::Ref normals) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); CGAL::Polygon_mesh_slicer slicer(mesh); compas::Polylines polylines; std::back_insert_iterator slices(polylines); - int number_of_planes = P.rows(); + int num_planes = points.rows(); - for (int i = 0; i < number_of_planes; i++) + for (int i = 0; i < num_planes; i++) { compas::Kernel::Plane_3 plane = compas::Kernel::Plane_3( - compas::Kernel::Point_3(P(i, 0), P(i, 1), P(i, 2)), - compas::Kernel::Vector_3(N(i, 0), N(i, 1), N(i, 2))); + compas::Kernel::Point_3(points(i, 0), points(i, 1), points(i, 2)), + compas::Kernel::Vector_3(normals(i, 0), normals(i, 1), normals(i, 2))); slicer(plane, slices); } @@ -29,15 +28,30 @@ pmp_slice_mesh( return result; }; -void init_slicer(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("slicer"); +void init_slicer(nb::module_& m) { + auto submodule = m.def_submodule("slicer"); submodule.def( "slice_mesh", &pmp_slice_mesh, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert(), - pybind11::arg("P").noconvert(), - pybind11::arg("N").noconvert()); + "Slice a mesh with a set of planes defined by points and normals.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx3, float64)\n" + "faces : array-like\n" + " Matrix of face indices (Mx3, int32)\n" + "points : array-like\n" + " Matrix of plane points (Kx3, float64)\n" + "normals : array-like\n" + " Matrix of plane normals (Kx3, float64)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of polylines, each represented as a matrix of points (Px3, float64)", + "vertices"_a, + "faces"_a, + "points"_a, + "normals"_a); } diff --git a/src/slicer.h b/src/slicer.h index 30ca4e82..0e2a0ada 100644 --- a/src/slicer.h +++ b/src/slicer.h @@ -1,15 +1,19 @@ -#ifndef COMPAS_SLICER_H -#define COMPAS_SLICER_H - -#include +#pragma once +#include "compas.h" +/** + * @brief Slice a mesh with a set of planes defined by points and normals. + * + * @param vertices Matrix of vertex positions as Nx3 matrix in row-major order (float64) + * @param faces Matrix of face indices as Mx3 matrix in row-major order (int32) + * @param points Matrix of plane points as Kx3 matrix in row-major order (float64) + * @param normals Matrix of plane normals as Kx3 matrix in row-major order (float64) + * @return std::vector Vector of polylines, each represented as a matrix of points (Px3 float64) + */ std::vector pmp_slice_mesh( - Eigen::Ref & V, - Eigen::Ref & F, - Eigen::Ref & P, - Eigen::Ref & N); - - -#endif /* COMPAS_SLICER_H */ + Eigen::Ref vertices, + Eigen::Ref faces, + Eigen::Ref points, + Eigen::Ref normals); diff --git a/src/straight_skeleton_2.cpp b/src/straight_skeleton_2.cpp index c7885b20..ccde7fe5 100644 --- a/src/straight_skeleton_2.cpp +++ b/src/straight_skeleton_2.cpp @@ -1,162 +1,159 @@ #include "straight_skeleton_2.h" -#include -#include -#include -#include -#include -#include -#include typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef K::Point_2 Point; typedef CGAL::Polygon_2 Polygon_2; typedef CGAL::Polygon_with_holes_2 Polygon_with_holes; typedef CGAL::Straight_skeleton_2 Ss; -typedef boost::shared_ptr SsPtr; -typedef CGAL::Straight_skeleton_2::Halfedge_const_handle Halfedge_const_handle; -typedef CGAL::Straight_skeleton_2::Vertex_const_handle Vertex_const_handle; -typedef boost::shared_ptr PolygonPtr; +typedef Ss::Halfedge_const_handle Halfedge_const_handle; +typedef Ss::Vertex_const_handle Vertex_const_handle; + +// Change from boost to std shared_ptr +typedef std::shared_ptr SsPtr; +typedef std::shared_ptr PolygonPtr; typedef std::vector PolygonPtrVector; -typedef boost::shared_ptr PolygonWithHolesPtr; +typedef std::shared_ptr PolygonWithHolesPtr; typedef std::vector PolygonWithHolesPtrVector; std::tuple, compas::RowMatrixXi, std::vector> -mesh_data_from_skeleton(boost::shared_ptr &iss) +mesh_data_from_skeleton(std::shared_ptr& skeleton) { - std::size_t v = iss->size_of_vertices(); - std::size_t e = iss->size_of_halfedges() / 2; // halfedges are stored twice + std::size_t num_vertices = skeleton->size_of_vertices(); + std::size_t num_edges = skeleton->size_of_halfedges() / 2; // halfedges are stored twice - compas::RowMatrixXd Mv(v, 3); - std::vector Mvi; // to save the vertex ids - compas::RowMatrixXi Me(e, 2); - std::vector Mei; // to save the edge type: 0: inner bisector, 1: bisector, 2: boundary + compas::RowMatrixXd vertex_matrix(num_vertices, 3); + std::vector vertex_indices; // to save the vertex ids + compas::RowMatrixXi edge_matrix(num_edges, 2); + std::vector edge_types; // to save the edge type: 0: inner bisector, 1: bisector, 2: boundary std::size_t i = 0; - for (auto hit = iss->vertices_begin(); hit != iss->vertices_end(); ++hit) + for (auto vertex_iter = skeleton->vertices_begin(); vertex_iter != skeleton->vertices_end(); ++vertex_iter) { - const Vertex_const_handle vh = hit; - Mv(i, 0) = (double)vh->point().x(); - Mv(i, 1) = (double)vh->point().y(); - Mv(i, 2) = 0; - Mvi.push_back((int)vh->id()); + const Vertex_const_handle vertex = vertex_iter; + vertex_matrix(i, 0) = (double)vertex->point().x(); + vertex_matrix(i, 1) = (double)vertex->point().y(); + vertex_matrix(i, 2) = 0; + vertex_indices.push_back((int)vertex->id()); i++; } i = 0; - for (auto hit = iss->halfedges_begin(); hit != iss->halfedges_end(); ++hit) + for (auto edge_iter = skeleton->halfedges_begin(); edge_iter != skeleton->halfedges_end(); ++edge_iter) { - const Halfedge_const_handle h = hit; - const Vertex_const_handle &v1 = h->vertex(); - const Vertex_const_handle &v2 = h->opposite()->vertex(); + const Halfedge_const_handle halfedge = edge_iter; + const Vertex_const_handle& vertex1 = halfedge->vertex(); + const Vertex_const_handle& vertex2 = halfedge->opposite()->vertex(); - if (&*v1 < &*v2) + if (&*vertex1 < &*vertex2) { - Me(i, 0) = (int)v1->id(); - Me(i, 1) = (int)v2->id(); + edge_matrix(i, 0) = (int)vertex1->id(); + edge_matrix(i, 1) = (int)vertex2->id(); - if (h->is_inner_bisector()) + if (halfedge->is_inner_bisector()) { - Mei.push_back(0); + edge_types.push_back(0); } - else if (h->is_bisector()) + else if (halfedge->is_bisector()) { - Mei.push_back(1); + edge_types.push_back(1); } else { - Mei.push_back(2); + edge_types.push_back(2); } i++; } } - std::tuple, compas::RowMatrixXi, std::vector> result = std::make_tuple(Mv, Mvi, Me, Mei); - return result; + return std::make_tuple(vertex_matrix, vertex_indices, edge_matrix, edge_types); } compas::RowMatrixXd -polygon_to_data(Polygon_2 const& poly) +polygon_to_data(Polygon_2 const& polygon) { - std::size_t n = poly.size(); + std::size_t n = polygon.size(); compas::RowMatrixXd points(n, 3); - int j = 0; - for(typename Polygon_2::Vertex_const_iterator vi = poly.vertices_begin() ; vi != poly.vertices_end() ; ++ vi){ - points(j, 0) = (double)(*vi).x(); - points(j, 1) = (double)(*vi).y(); - points(j, 2) = 0; - j++; + int i = 0; + for(typename Polygon_2::Vertex_const_iterator vertex_iter = polygon.vertices_begin(); + vertex_iter != polygon.vertices_end(); ++vertex_iter) + { + points(i, 0) = (double)(*vertex_iter).x(); + points(i, 1) = (double)(*vertex_iter).y(); + points(i, 2) = 0; + i++; } return points; } std::tuple> -polygon_with_holes_to_data(Polygon_with_holes const& polywh) +polygon_with_holes_to_data(Polygon_with_holes const& polygon_with_holes) { std::vector holes; - compas::RowMatrixXd points = polygon_to_data(polywh.outer_boundary()); - for(typename Polygon_with_holes::Hole_const_iterator hi = polywh.holes_begin() ; hi != polywh.holes_end() ; ++ hi){ - compas::RowMatrixXd hole = polygon_to_data(*hi); - holes.push_back(hole); + compas::RowMatrixXd boundary_points = polygon_to_data(polygon_with_holes.outer_boundary()); + for(typename Polygon_with_holes::Hole_const_iterator hole_iter = polygon_with_holes.holes_begin(); + hole_iter != polygon_with_holes.holes_end(); ++hole_iter) + { + compas::RowMatrixXd hole_points = polygon_to_data(*hole_iter); + holes.push_back(hole_points); } - std::tuple> result = std::make_tuple(points, holes); - return result; + return std::make_tuple(boundary_points, holes); } Polygon_2 -data_to_polygon(Eigen::Ref &V) +data_to_polygon(const compas::RowMatrixXd& vertices) { - Polygon_2 poly; - for (int i = 0; i < V.rows(); i++){ - poly.push_back(Point(V(i, 0), V(i, 1))); + Polygon_2 polygon; + for (int i = 0; i < vertices.rows(); i++) { + polygon.push_back(Point(vertices(i, 0), vertices(i, 1))); } - return poly; + return polygon; } Polygon_with_holes data_to_polygon_with_holes( - Eigen::Ref &V, - std::vector> &holes) + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices) { - Polygon_2 outer = data_to_polygon(V); - Polygon_with_holes poly(outer); - for (auto hit : holes){ - compas::RowMatrixXd H = hit; - //Polygon_2 hole = data_to_polygon(*H); // why does this not work? + Polygon_2 outer_polygon = data_to_polygon(boundary_vertices); + Polygon_with_holes polygon_with_holes(outer_polygon); + for (const auto& hole_points : hole_vertices) { Polygon_2 hole; - for (int i = 0; i < H.rows(); i++){ - hole.push_back(Point(H(i, 0), H(i, 1))); + for (int i = 0; i < hole_points.rows(); i++) { + hole.push_back(Point(hole_points(i, 0), hole_points(i, 1))); } - poly.add_hole(hole); + polygon_with_holes.add_hole(hole); } - return poly; + return polygon_with_holes; } std::tuple, compas::RowMatrixXi, std::vector> pmp_create_interior_straight_skeleton( - Eigen::Ref &V) + const compas::RowMatrixXd& vertices) { - Polygon_2 poly = data_to_polygon(V); - SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end()); - return mesh_data_from_skeleton(iss); -}; + Polygon_2 polygon = data_to_polygon(vertices); + SsPtr skeleton = CGAL::create_interior_straight_skeleton_2(polygon.vertices_begin(), polygon.vertices_end()); + return mesh_data_from_skeleton(skeleton); +} std::tuple, compas::RowMatrixXi, std::vector> pmp_create_interior_straight_skeleton_with_holes( - Eigen::Ref &V, - std::vector> &holes) + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices) { - Polygon_with_holes poly = data_to_polygon_with_holes(V, holes); - SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly); - return mesh_data_from_skeleton(iss); + Polygon_with_holes polygon = data_to_polygon_with_holes(boundary_vertices, hole_vertices); + SsPtr skeleton = CGAL::create_interior_straight_skeleton_2(polygon); + return mesh_data_from_skeleton(skeleton); } std::vector -pmp_create_offset_polygons_2_inner(Eigen::Ref &V, double &offset) +pmp_create_offset_polygons_2_inner( + const compas::RowMatrixXd& vertices, + double& offset_distance) { - Polygon_2 poly = data_to_polygon(V); - PolygonPtrVector offset_polygons = CGAL::create_interior_skeleton_and_offset_polygons_2(offset, poly); + Polygon_2 polygon = data_to_polygon(vertices); + PolygonPtrVector offset_polygons = CGAL::create_interior_skeleton_and_offset_polygons_2(offset_distance, polygon); std::vector result; - for(typename PolygonPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - compas::RowMatrixXd points = polygon_to_data(**pi); + for(const auto& offset_polygon : offset_polygons) { + compas::RowMatrixXd points = polygon_to_data(*offset_polygon); result.push_back(points); } return result; @@ -164,31 +161,32 @@ pmp_create_offset_polygons_2_inner(Eigen::Ref &V, dou std::vector>> pmp_create_offset_polygons_2_inner_with_holes( - Eigen::Ref &V, - std::vector> &holes, - double &offset) + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices, + double& offset_distance) { - - Polygon_with_holes poly = data_to_polygon_with_holes(V, holes); - PolygonWithHolesPtrVector offset_polygons = CGAL::create_interior_skeleton_and_offset_polygons_with_holes_2(offset, poly); + Polygon_with_holes polygon = data_to_polygon_with_holes(boundary_vertices, hole_vertices); + PolygonWithHolesPtrVector offset_polygons = CGAL::create_interior_skeleton_and_offset_polygons_with_holes_2( + offset_distance, polygon); std::vector>> result; - for(typename PolygonWithHolesPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - std::tuple> polywh_data = polygon_with_holes_to_data(**pi); - result.push_back(polywh_data); + for(const auto& offset_polygon : offset_polygons) { + result.push_back(polygon_with_holes_to_data(*offset_polygon)); } return result; } std::vector -pmp_create_offset_polygons_2_outer(Eigen::Ref &V, double &offset) +pmp_create_offset_polygons_2_outer( + const compas::RowMatrixXd& vertices, + double& offset_distance) { - Polygon_2 poly = data_to_polygon(V); - PolygonPtrVector offset_polygons = CGAL::create_exterior_skeleton_and_offset_polygons_2(offset, poly); + Polygon_2 polygon = data_to_polygon(vertices); + PolygonPtrVector offset_polygons = CGAL::create_exterior_skeleton_and_offset_polygons_2(offset_distance, polygon); std::vector result; - for(typename PolygonPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - compas::RowMatrixXd points = polygon_to_data(**pi); + for(const auto& offset_polygon : offset_polygons) { + compas::RowMatrixXd points = polygon_to_data(*offset_polygon); result.push_back(points); } return result; @@ -196,123 +194,256 @@ pmp_create_offset_polygons_2_outer(Eigen::Ref &V, dou std::vector>> pmp_create_offset_polygons_2_outer_with_holes( - Eigen::Ref &V, - std::vector> &holes, - double &offset) + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices, + double& offset_distance) { - Polygon_with_holes poly = data_to_polygon_with_holes(V, holes); - PolygonWithHolesPtrVector offset_polygons = CGAL::create_exterior_skeleton_and_offset_polygons_with_holes_2(offset, poly); + Polygon_with_holes polygon = data_to_polygon_with_holes(boundary_vertices, hole_vertices); + PolygonWithHolesPtrVector offset_polygons = CGAL::create_exterior_skeleton_and_offset_polygons_with_holes_2( + offset_distance, polygon); std::vector>> result; - for(typename PolygonWithHolesPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - std::tuple> polywh_data = polygon_with_holes_to_data(**pi); - result.push_back(polywh_data); + for(const auto& offset_polygon : offset_polygons) { + result.push_back(polygon_with_holes_to_data(*offset_polygon)); } return result; } std::vector pmp_create_weighted_offset_polygons_2_inner( - Eigen::Ref &V, - double &offset, - Eigen::Ref &weights) + const compas::RowMatrixXd& vertices, + double offset_distance, + const compas::RowMatrixXd& edge_weights) { - Polygon_2 poly = data_to_polygon(V); - std::vector weights_vec; - for (int i = 0; i < weights.rows(); i++) - { - weights_vec.push_back(weights(i, 0)); + if (edge_weights.rows() != vertices.rows()) { + throw std::invalid_argument("Number of weights must match number of polygon vertices"); } - SsPtr iss = CGAL::create_interior_weighted_straight_skeleton_2(poly, weights_vec); - PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2(offset, *iss); - std::vector result; - for(typename PolygonPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - compas::RowMatrixXd points = polygon_to_data(**pi); - result.push_back(points); + Polygon_2 polygon = data_to_polygon(vertices); + std::vector weight_values; + weight_values.reserve(edge_weights.rows()); + for (int i = 0; i < edge_weights.rows(); i++) { + if (edge_weights(i, 0) <= 0) { + throw std::invalid_argument("Weights must be positive"); + } + weight_values.push_back(edge_weights(i, 0)); + } + + try { + SsPtr skeleton = CGAL::create_interior_weighted_straight_skeleton_2(polygon, weight_values); + if (!skeleton) { + throw std::runtime_error("Failed to create weighted straight skeleton"); + } + + PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2(offset_distance, *skeleton); + std::vector result; + result.reserve(offset_polygons.size()); + for(const auto& offset_polygon : offset_polygons) { + result.push_back(polygon_to_data(*offset_polygon)); + } + return result; + } catch (const CGAL::Precondition_exception& e) { + throw std::runtime_error("CGAL precondition failed: Invalid input for weighted offset"); } - return result; } std::vector pmp_create_weighted_offset_polygons_2_outer( - Eigen::Ref &V, - double &offset, - Eigen::Ref &weights) + const compas::RowMatrixXd& vertices, + double offset_distance, + const compas::RowMatrixXd& edge_weights) { - Polygon_2 poly = data_to_polygon(V); - std::vector weights_vec; - for (int i = 0; i < weights.rows(); i++) - { - weights_vec.push_back(weights(i, 0)); + if (edge_weights.rows() != vertices.rows()) { + throw std::invalid_argument("Number of weights must match number of polygon vertices"); } - SsPtr iss = CGAL::create_exterior_weighted_straight_skeleton_2(offset, weights_vec, poly); - PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2(offset, *iss); - std::vector result; - for(typename PolygonPtrVector::const_iterator pi = offset_polygons.begin() ; pi != offset_polygons.end() ; ++ pi ){ - compas::RowMatrixXd points = polygon_to_data(**pi); - result.push_back(points); + Polygon_2 polygon = data_to_polygon(vertices); + std::vector weight_values; + weight_values.reserve(edge_weights.rows()); + for (int i = 0; i < edge_weights.rows(); i++) { + if (edge_weights(i, 0) <= 0) { + throw std::invalid_argument("Weights must be positive"); + } + weight_values.push_back(edge_weights(i, 0)); } - return result; -} -// =========================================================================== -// PyBind11 -// =========================================================================== + try { + SsPtr skeleton = CGAL::create_exterior_weighted_straight_skeleton_2(offset_distance, weight_values, polygon); + if (!skeleton) { + throw std::runtime_error("Failed to create weighted straight skeleton"); + } -void init_straight_skeleton_2(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("straight_skeleton_2"); + PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2(offset_distance, *skeleton); + std::vector result; + result.reserve(offset_polygons.size()); + for(const auto& offset_polygon : offset_polygons) { + result.push_back(polygon_to_data(*offset_polygon)); + } + return result; + } catch (const CGAL::Precondition_exception& e) { + throw std::runtime_error("CGAL precondition failed: Invalid input for weighted offset"); + } +} + +void init_straight_skeleton_2(nb::module_& m) { + auto submodule = m.def_submodule("straight_skeleton_2"); submodule.def( "create_interior_straight_skeleton", &pmp_create_interior_straight_skeleton, - pybind11::arg("V").noconvert()); + "Create an interior straight skeleton from a polygon.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx2, float64)\n" + "\n" + "Returns\n" + "-------\n" + "tuple\n" + " - Matrix of skeleton vertices (Mx2, float64)\n" + " - Vector of source vertex indices\n" + " - Matrix of skeleton edges (Kx2, int32)\n" + " - Vector of source edge indices", + "vertices"_a); submodule.def( "create_interior_straight_skeleton_with_holes", &pmp_create_interior_straight_skeleton_with_holes, - pybind11::arg("V").noconvert(), - pybind11::arg("holes").noconvert()); + "Create an interior straight skeleton from a polygon with holes.\n\n" + "Parameters\n" + "----------\n" + "boundary_vertices : array-like\n" + " Matrix of boundary vertex positions (Nx2, float64)\n" + "hole_vertices : list\n" + " List of hole vertex matrices (each Mx2, float64)\n" + "\n" + "Returns\n" + "-------\n" + "tuple\n" + " - Matrix of skeleton vertices (Px2, float64)\n" + " - Vector of source vertex indices\n" + " - Matrix of skeleton edges (Qx2, int32)\n" + " - Vector of source edge indices", + "boundary_vertices"_a, + "hole_vertices"_a); submodule.def( "create_offset_polygons_2_inner", &pmp_create_offset_polygons_2_inner, - pybind11::arg("V").noconvert(), - pybind11::arg("offset").noconvert()); + "Create inward offset polygons from a simple polygon.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx2, float64)\n" + "offset_distance : float\n" + " Offset distance (positive for inward)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of offset polygon matrices (each Mx2, float64)", + "vertices"_a, + "offset_distance"_a); submodule.def( "create_offset_polygons_2_inner_with_holes", &pmp_create_offset_polygons_2_inner_with_holes, - pybind11::arg("V").noconvert(), - pybind11::arg("holes").noconvert(), - pybind11::arg("offset").noconvert()); + "Create inward offset polygons from a polygon with holes.\n\n" + "Parameters\n" + "----------\n" + "boundary_vertices : array-like\n" + " Matrix of boundary vertex positions (Nx2, float64)\n" + "hole_vertices : list\n" + " List of hole vertex matrices (each Mx2, float64)\n" + "offset_distance : float\n" + " Offset distance (positive for inward)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of tuples (outer polygon, list of hole polygons)", + "boundary_vertices"_a, + "hole_vertices"_a, + "offset_distance"_a); submodule.def( "create_offset_polygons_2_outer", &pmp_create_offset_polygons_2_outer, - pybind11::arg("V").noconvert(), - pybind11::arg("offset").noconvert()); + "Create outward offset polygons from a simple polygon.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx2, float64)\n" + "offset_distance : float\n" + " Offset distance (positive for outward)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of offset polygon matrices (each Mx2, float64)", + "vertices"_a, + "offset_distance"_a); submodule.def( "create_offset_polygons_2_outer_with_holes", &pmp_create_offset_polygons_2_outer_with_holes, - pybind11::arg("V").noconvert(), - pybind11::arg("holes").noconvert(), - pybind11::arg("offset").noconvert()); + "Create outward offset polygons from a polygon with holes.\n\n" + "Parameters\n" + "----------\n" + "boundary_vertices : array-like\n" + " Matrix of boundary vertex positions (Nx2, float64)\n" + "hole_vertices : list\n" + " List of hole vertex matrices (each Mx2, float64)\n" + "offset_distance : float\n" + " Offset distance (positive for outward)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of tuples (outer polygon, list of hole polygons)", + "boundary_vertices"_a, + "hole_vertices"_a, + "offset_distance"_a); submodule.def( "create_weighted_offset_polygons_2_inner", &pmp_create_weighted_offset_polygons_2_inner, - pybind11::arg("V").noconvert(), - pybind11::arg("offset").noconvert(), - pybind11::arg("weights").noconvert()); + "Create inward weighted offset polygons from a simple polygon.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx2, float64)\n" + "offset_distance : float\n" + " Offset distance (must be positive)\n" + "edge_weights : array-like\n" + " Matrix of edge weights (Nx1, float64, must be positive)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of offset polygon matrices (each Mx2, float64)", + "vertices"_a, + "offset_distance"_a, + "edge_weights"_a); submodule.def( "create_weighted_offset_polygons_2_outer", &pmp_create_weighted_offset_polygons_2_outer, - pybind11::arg("V").noconvert(), - pybind11::arg("offset").noconvert(), - pybind11::arg("weights").noconvert()); -}; + "Create outward weighted offset polygons from a simple polygon.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx2, float64)\n" + "offset_distance : float\n" + " Offset distance (must be positive)\n" + "edge_weights : array-like\n" + " Matrix of edge weights (Nx1, float64, must be positive)\n" + "\n" + "Returns\n" + "-------\n" + "list\n" + " List of offset polygon matrices (each Mx2, float64)", + "vertices"_a, + "offset_distance"_a, + "edge_weights"_a); +} \ No newline at end of file diff --git a/src/straight_skeleton_2.h b/src/straight_skeleton_2.h index bd09a68e..42f16546 100644 --- a/src/straight_skeleton_2.h +++ b/src/straight_skeleton_2.h @@ -1,42 +1,129 @@ -#ifndef COMPAS_STRAIGHT_SKELETON_2_H -#define COMPAS_STRAIGHT_SKELETON_2_H +#pragma once -#include +#include "compas.h" +/** + * @brief Creates a straight skeleton from a simple polygon without holes. + * + * @param vertices Matrix of polygon vertices as Nx2 matrix in row-major order (float64) + * @return std::tuple, compas::RowMatrixXi, std::vector> containing: + * - Matrix of skeleton vertices (Mx2, float64) + * - Vector of source vertex indices from input polygon for each skeleton vertex + * - Matrix of skeleton edges as vertex pairs (Kx2, int32) + * - Vector of source edge indices from input polygon for each skeleton edge + */ +std::tuple, compas::RowMatrixXi, std::vector> +pmp_create_interior_straight_skeleton( + const compas::RowMatrixXd& vertices +); -std::tuple, compas::RowMatrixXi, std::vector> pmp_create_interior_straight_skeleton( - Eigen::Ref &V); +/** + * @brief Creates a straight skeleton from a polygon with holes. + * + * @param boundary_vertices Matrix of boundary polygon vertices as Nx2 matrix in row-major order (float64) + * @param hole_vertices Vector of hole polygons, each as Mx2 matrix in row-major order (float64) + * @return std::tuple, compas::RowMatrixXi, std::vector> containing: + * - Matrix of skeleton vertices (Px2, float64) + * - Vector of source vertex indices from input polygon for each skeleton vertex + * - Matrix of skeleton edges as vertex pairs (Qx2, int32) + * - Vector of source edge indices from input polygon for each skeleton edge + */ +std::tuple, compas::RowMatrixXi, std::vector> +pmp_create_interior_straight_skeleton_with_holes( + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices +); -std::tuple, compas::RowMatrixXi, std::vector> pmp_create_interior_straight_skeleton_with_holes( - Eigen::Ref &V, - std::vector> &holes); +/** + * @brief Creates offset polygons from a simple polygon. + * + * @param vertices Matrix of polygon vertices as Nx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (positive for inward, negative for outward) + * @return std::vector Vector of offset polygons, each as Mx2 matrix of vertices (float64) + */ +std::vector +pmp_create_offset_polygons_2_inner( + const compas::RowMatrixXd& vertices, + double& offset_distance +); -std::vector pmp_create_offset_polygons_2_inner( - Eigen::Ref &V, - double &offset); +/** + * @brief Creates offset polygons from a polygon with holes. + * + * @param boundary_vertices Matrix of boundary polygon vertices as Nx2 matrix in row-major order (float64) + * @param hole_vertices Vector of hole polygons, each as Mx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (positive for inward, negative for outward) + * @return std::vector>> Vector containing: + * - For each offset: tuple of outer polygon (Px2, float64) and vector of inner polygons (each Qx2, float64) + */ +std::vector>> +pmp_create_offset_polygons_2_inner_with_holes( + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices, + double& offset_distance +); -std::vector>> pmp_create_offset_polygons_2_inner_with_holes( - Eigen::Ref &V, - std::vector> &holes, - double &offset); +/** + * @brief Creates outward offset polygons from a simple polygon. + * + * @param vertices Matrix of polygon vertices as Nx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (positive for inward, negative for outward) + * @return std::vector Vector of offset polygons, each as Mx2 matrix of vertices (float64) + */ +std::vector +pmp_create_offset_polygons_2_outer( + const compas::RowMatrixXd& vertices, + double& offset_distance +); -std::vector pmp_create_offset_polygons_2_outer( - Eigen::Ref &V, - double &offset); +/** + * @brief Creates outward offset polygons from a polygon with holes. + * + * @param boundary_vertices Matrix of boundary polygon vertices as Nx2 matrix in row-major order (float64) + * @param hole_vertices Vector of hole polygons, each as Mx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (positive for inward, negative for outward) + * @return std::vector>> Vector containing: + * - For each offset: tuple of outer polygon (Px2, float64) and vector of inner polygons (each Qx2, float64) + */ +std::vector>> +pmp_create_offset_polygons_2_outer_with_holes( + const compas::RowMatrixXd& boundary_vertices, + const std::vector& hole_vertices, + double& offset_distance +); -std::vector>> pmp_create_offset_polygons_2_outer_with_holes( - Eigen::Ref &V, - std::vector> &holes, - double &offset); +/** + * @brief Create weighted offset polygons for the interior of a polygon. + * + * @param vertices Matrix of vertex coordinates as Nx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (must be positive) + * @param edge_weights Matrix of weights for each edge as Nx1 matrix (float64, must be positive) + * @return std::vector Vector of offset polygons, each as Mx2 matrix of vertices (float64) + * @throws std::invalid_argument if edge_weights size doesn't match vertices or if weights are not positive + * @throws std::runtime_error if CGAL fails to create the skeleton or offset + */ +std::vector +pmp_create_weighted_offset_polygons_2_inner( + const compas::RowMatrixXd& vertices, + double offset_distance, + const compas::RowMatrixXd& edge_weights +); -std::vector pmp_create_weighted_offset_polygons_2_inner( - Eigen::Ref &V, - double &offset, - Eigen::Ref &weights); +/** + * @brief Create weighted offset polygons for the exterior of a polygon. + * + * @param vertices Matrix of vertex coordinates as Nx2 matrix in row-major order (float64) + * @param offset_distance Offset distance (must be positive) + * @param edge_weights Matrix of weights for each edge as Nx1 matrix (float64, must be positive) + * @return std::vector Vector of offset polygons, each as Mx2 matrix of vertices (float64) + * @throws std::invalid_argument if edge_weights size doesn't match vertices or if weights are not positive + * @throws std::runtime_error if CGAL fails to create the skeleton or offset + */ +std::vector +pmp_create_weighted_offset_polygons_2_outer( + const compas::RowMatrixXd& vertices, + double offset_distance, + const compas::RowMatrixXd& edge_weights +); -std::vector pmp_create_weighted_offset_polygons_2_outer( - Eigen::Ref &V, - double &offset, - Eigen::Ref &weights); - -#endif /* COMPAS_STRAIGHT_SKELETON_2_H */ +void init_straight_skeleton_2(nb::module_& m); \ No newline at end of file diff --git a/src/subdivision.cpp b/src/subdivision.cpp index 496e541e..a077c7af 100644 --- a/src/subdivision.cpp +++ b/src/subdivision.cpp @@ -1,76 +1,107 @@ #include "subdivision.h" -#include std::tuple subd_catmullclark( - compas::RowMatrixXd &V, - std::vector> &faces, - unsigned int k) + compas::RowMatrixXd vertices, + std::vector> faces, + unsigned int num_iterations) { - compas::Mesh mesh = compas::ngon_from_vertices_and_faces(V, faces); - - CGAL::Subdivision_method_3::CatmullClark_subdivision(mesh, CGAL::parameters::number_of_iterations(k)); - + compas::Mesh mesh = compas::ngon_from_vertices_and_faces(vertices, faces); + CGAL::Subdivision_method_3::CatmullClark_subdivision(mesh, CGAL::parameters::number_of_iterations(num_iterations)); mesh.collect_garbage(); - - std::tuple R = compas::quadmesh_to_vertices_and_faces(mesh); - return R; -}; + return compas::quadmesh_to_vertices_and_faces(mesh); +} std::tuple subd_loop( - compas::RowMatrixXd &V, - compas::RowMatrixXi &F, - unsigned int k) + compas::RowMatrixXd vertices, + compas::RowMatrixXi faces, + unsigned int num_iterations) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - CGAL::Subdivision_method_3::Loop_subdivision(mesh, CGAL::parameters::number_of_iterations(k)); - + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); + CGAL::Subdivision_method_3::Loop_subdivision(mesh, CGAL::parameters::number_of_iterations(num_iterations)); mesh.collect_garbage(); - - std::tuple R = compas::mesh_to_vertices_and_faces(mesh); - return R; -}; + return compas::mesh_to_vertices_and_faces(mesh); +} std::tuple subd_sqrt3( - compas::RowMatrixXd &V, - compas::RowMatrixXi &F, - unsigned int k) + compas::RowMatrixXd vertices, + compas::RowMatrixXi faces, + unsigned int num_iterations) { - compas::Mesh mesh = compas::mesh_from_vertices_and_faces(V, F); - - CGAL::Subdivision_method_3::Sqrt3_subdivision(mesh, CGAL::parameters::number_of_iterations(k)); - + compas::Mesh mesh = compas::mesh_from_vertices_and_faces(vertices, faces); + CGAL::Subdivision_method_3::Sqrt3_subdivision(mesh, CGAL::parameters::number_of_iterations(num_iterations)); mesh.collect_garbage(); + return compas::mesh_to_vertices_and_faces(mesh); +} - std::tuple R = compas::mesh_to_vertices_and_faces(mesh); - return R; -}; - -void init_subdivision(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("subdivision"); +void init_subdivision(nb::module_& m) { + auto submodule = m.def_submodule("subdivision"); submodule.def( "subd_catmullclark", &subd_catmullclark, - pybind11::arg("V").noconvert(), - pybind11::arg("faces").noconvert(), - pybind11::arg("k")); + "Catmull-Clark subdivision of a polygonal mesh.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx3, float64)\n" + "faces : list\n" + " List of face vertex indices (list of lists)\n" + "num_iterations : int\n" + " Number of subdivision steps\n" + "\n" + "Returns\n" + "-------\n" + "tuple\n" + " - Matrix of subdivided vertex positions (Mx3, float64)\n" + " - Matrix of subdivided face vertex indices (Px4, int32)", + "vertices"_a, + "faces"_a, + "num_iterations"_a); submodule.def( "subd_loop", &subd_loop, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert(), - pybind11::arg("k")); + "Loop subdivision of a triangular mesh.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx3, float64)\n" + "faces : array-like\n" + " Matrix of face vertex indices (Mx3, int32)\n" + "num_iterations : int\n" + " Number of subdivision steps\n" + "\n" + "Returns\n" + "-------\n" + "tuple\n" + " - Matrix of subdivided vertex positions (Px3, float64)\n" + " - Matrix of subdivided face vertex indices (Qx3, int32)", + "vertices"_a, + "faces"_a, + "num_iterations"_a); submodule.def( "subd_sqrt3", &subd_sqrt3, - pybind11::arg("V").noconvert(), - pybind11::arg("F").noconvert(), - pybind11::arg("k")); -}; + "Sqrt3 subdivision of a triangular mesh.\n\n" + "Parameters\n" + "----------\n" + "vertices : array-like\n" + " Matrix of vertex positions (Nx3, float64)\n" + "faces : array-like\n" + " Matrix of face vertex indices (Mx3, int32)\n" + "num_iterations : int\n" + " Number of subdivision steps\n" + "\n" + "Returns\n" + "-------\n" + "tuple\n" + " - Matrix of subdivided vertex positions (Px3, float64)\n" + " - Matrix of subdivided face vertex indices (Qx3, int32)", + "vertices"_a, + "faces"_a, + "num_iterations"_a); +} diff --git a/src/subdivision.h b/src/subdivision.h index 071770f6..a560b55f 100644 --- a/src/subdivision.h +++ b/src/subdivision.h @@ -1,24 +1,54 @@ -#ifndef COMPAS_SUBD_H -#define COMPAS_SUBD_H +#pragma once -#include +#include "compas.h" +/** + * @brief Subdivide a mesh with the Catmull-Clark scheme. + * + * @param vertices Matrix of vertex positions (Nx3, float64) + * @param faces List of face vertex indices (list of lists) + * @param num_iterations Number of subdivision steps + * @return std::tuple + * Tuple containing: + * - Matrix of subdivided vertex positions (Mx3, float64) + * - Matrix of subdivided face vertex indices (Px4, int32) + */ std::tuple subd_catmullclark( - compas::RowMatrixXd &V, - std::vector> &faces, - unsigned int k); + compas::RowMatrixXd vertices, + std::vector> faces, + unsigned int num_iterations); +/** + * @brief Subdivide a mesh with the Loop scheme. + * + * @param vertices Matrix of vertex positions (Nx3, float64) + * @param faces Matrix of face vertex indices (Mx3, int32) + * @param num_iterations Number of subdivision steps + * @return std::tuple + * Tuple containing: + * - Matrix of subdivided vertex positions (Px3, float64) + * - Matrix of subdivided face vertex indices (Qx3, int32) + */ std::tuple subd_loop( - compas::RowMatrixXd &V, - compas::RowMatrixXi &F, - unsigned int k); + compas::RowMatrixXd vertices, + compas::RowMatrixXi faces, + unsigned int num_iterations); +/** + * @brief Subdivide a mesh with the Sqrt3 scheme. + * + * @param vertices Matrix of vertex positions (Nx3, float64) + * @param faces Matrix of face vertex indices (Mx3, int32) + * @param num_iterations Number of subdivision steps + * @return std::tuple + * Tuple containing: + * - Matrix of subdivided vertex positions (Px3, float64) + * - Matrix of subdivided face vertex indices (Qx3, int32) + */ std::tuple subd_sqrt3( - compas::RowMatrixXd &V, - compas::RowMatrixXi &F, - unsigned int k); - -#endif /* COMPAS_SUBD_H */ + compas::RowMatrixXd vertices, + compas::RowMatrixXi faces, + unsigned int num_iterations); \ No newline at end of file diff --git a/src/triangulation.cpp b/src/triangulation.cpp new file mode 100644 index 00000000..fdb24bc8 --- /dev/null +++ b/src/triangulation.cpp @@ -0,0 +1,427 @@ +#include "triangulation.h" + +struct face_info +{ + face_info(){}; + int nesting_level; + bool in_domain() + { + return nesting_level % 2 == 1; + } +}; + +using delaunay_polygon = CGAL::Polygon_2; + +using vertex_base1 = CGAL::Triangulation_vertex_base_with_info_2; +using tds1 = CGAL::Triangulation_data_structure_2; +using dt = CGAL::Delaunay_triangulation_2; + +using vertex_base2 = CGAL::Triangulation_vertex_base_2; +using _face_base2 = CGAL::Triangulation_face_base_with_info_2; +using face_base2 = CGAL::Constrained_triangulation_face_base_2; +using tds2 = CGAL::Triangulation_data_structure_2; +using cdt2 = CGAL::Constrained_Delaunay_triangulation_2; + +using vertex_base3 = CGAL::Delaunay_mesh_vertex_base_2; +using face_base3 = CGAL::Delaunay_mesh_face_base_2; +using tds3 = CGAL::Triangulation_data_structure_2; +using itag = CGAL::Exact_predicates_tag; +using cdt3 = CGAL::Constrained_Delaunay_triangulation_2; +using criteria = CGAL::Delaunay_mesh_size_criteria_2; + +compas::RowMatrixXi +pmp_delaunay_triangulation(Eigen::Ref vertices) +{ + dt triangulation; + std::vector> points(vertices.rows()); + + for (int i = 0; i < vertices.rows(); i++) + { + dt::Point point = dt::Point(vertices(i, 0), vertices(i, 1)); + points[i] = std::make_pair(point, i); + } + + triangulation.insert(points.begin(), points.end()); + + compas::RowMatrixXi faces(triangulation.number_of_faces(), 3); + + int j = 0; + for (dt::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) + { + dt::Face_handle face = fit; + + faces(j, 0) = face->vertex(0)->info(); + faces(j, 1) = face->vertex(1)->info(); + faces(j, 2) = face->vertex(2)->info(); + + j++; + } + + return faces; +} + +void mark_domains(cdt2 &triangulation, + cdt2::Face_handle start, + int index, + std::list &border) +{ + if (start->info().nesting_level != -1) + { + return; + } + + std::list queue; + queue.push_back(start); + + while (!queue.empty()) + { + cdt2::Face_handle face = queue.front(); + queue.pop_front(); + + if (face->info().nesting_level == -1) + { + face->info().nesting_level = index; + + for (int i = 0; i < 3; i++) + { + cdt2::Edge edge(face, i); + cdt2::Face_handle nbr = face->neighbor(i); + + if (nbr->info().nesting_level == -1) + { + if (triangulation.is_constrained(edge)) + { + border.push_back(edge); + } + else + { + queue.push_back(nbr); + } + } + } + } + } +} + +void mark_domains(cdt2 &triangulation) +{ + for (cdt2::Face_handle face : triangulation.all_face_handles()) + { + face->info().nesting_level = -1; + } + + std::list border; + + mark_domains(triangulation, triangulation.infinite_face(), 0, border); + + while (!border.empty()) + { + cdt2::Edge edge = border.front(); + border.pop_front(); + + cdt2::Face_handle nbr = edge.first->neighbor(edge.second); + + if (nbr->info().nesting_level == -1) + { + mark_domains(triangulation, nbr, edge.first->info().nesting_level + 1, border); + } + } +} + +std::tuple +pmp_constrained_delaunay_triangulation( + Eigen::Ref boundary_vertices, + Eigen::Ref internal_vertices, + const std::vector & holes, + const std::vector & curves, + bool is_conforming + ) +{ + cdt2 triangulation; + + // Add specific (isolated) points + std::vector points(internal_vertices.rows()); + + for (int i = 0; i < internal_vertices.rows(); i++) + { + points[i] = cdt2::Point(internal_vertices(i, 0), internal_vertices(i, 1)); + } + + triangulation.insert(points.begin(), points.end()); + + // Add the boundary as polygon constraint. + delaunay_polygon boundary; + + for (int i = 0; i < boundary_vertices.rows(); i++) + { + boundary.push_back(cdt2::Point(boundary_vertices(i, 0), boundary_vertices(i, 1))); + } + + triangulation.insert_constraint(boundary.vertices_begin(), boundary.vertices_end(), true); + + // Add the holes as polygon constraints. + for (auto hit : holes) + { + compas::RowMatrixXd hole_vertices = hit; + delaunay_polygon hole; + + for (int i = 0; i < hole_vertices.rows(); i++) + { + hole.push_back(cdt2::Point(hole_vertices(i, 0), hole_vertices(i, 1))); + } + + triangulation.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true); + } + + // Add the curves as polyline constraints. + for (auto cit : curves) + { + compas::RowMatrixXd curve_vertices = cit; + std::vector curve(curve_vertices.rows()); + + for (int i = 0; i < curve_vertices.rows(); i++) + { + curve[i] = cdt2::Point(curve_vertices(i, 0), curve_vertices(i, 1)); + } + + triangulation.insert_constraint(curve.begin(), curve.end()); + } + + if (is_conforming) + { + CGAL::make_conforming_Delaunay_2(triangulation); + } + + // Mark the faces belonging to the boundary constraint + mark_domains(triangulation); + + // Count the number of faces belonging to the boundary domain + int face_count = 0; + for (cdt2::Face_handle face : triangulation.finite_face_handles()) + { + if (face->info().in_domain()) + { + face_count++; + } + } + + // Prepare result matrices + compas::RowMatrixXd vertices_out(triangulation.number_of_vertices(), 3); + compas::RowMatrixXi faces_out(face_count, 3); + + // Make vertex index map + std::unordered_map vertex_indices; + + // Load the vertices into the result + int i = 0; + for (cdt2::Vertex_handle vertex : triangulation.finite_vertex_handles()) + { + cdt2::Point point = vertex->point(); + vertex_indices.insert({vertex, i}); + + vertices_out(i, 0) = point.hx(); + vertices_out(i, 1) = point.hy(); + vertices_out(i, 2) = 0.0; + + i++; + } + + // Load the faces into the result + int j = 0; + for (cdt2::Face_handle face : triangulation.finite_face_handles()) + { + if (face->info().in_domain()) + { + cdt2::Vertex_handle v0 = face->vertex(0); + cdt2::Vertex_handle v1 = face->vertex(1); + cdt2::Vertex_handle v2 = face->vertex(2); + + faces_out(j, 0) = vertex_indices[v0]; + faces_out(j, 1) = vertex_indices[v1]; + faces_out(j, 2) = vertex_indices[v2]; + + j++; + } + } + + return std::make_tuple(vertices_out, faces_out); +} + +std::tuple +pmp_refined_delaunay_mesh( + Eigen::Ref boundary_vertices, + Eigen::Ref internal_vertices, + const std::vector & holes, + const std::vector & curves, + double min_angle, + double max_length, + bool is_optimized) +{ + cdt3 triangulation; + + // Add specific (isolated) points + std::vector points(internal_vertices.rows()); + + for (int i = 0; i < internal_vertices.rows(); i++) + { + points[i] = cdt3::Point(internal_vertices(i, 0), internal_vertices(i, 1)); + } + + triangulation.insert(points.begin(), points.end()); + + // Add the boundary as polygon constraint. + delaunay_polygon boundary; + + for (int i = 0; i < boundary_vertices.rows(); i++) + { + boundary.push_back(cdt3::Point(boundary_vertices(i, 0), boundary_vertices(i, 1))); + } + + triangulation.insert_constraint(boundary.vertices_begin(), boundary.vertices_end(), true); + + // Add the holes as polygon constraints. + for (auto hit : holes) + { + compas::RowMatrixXd hole_vertices = hit; + delaunay_polygon hole; + + for (int i = 0; i < hole_vertices.rows(); i++) + { + hole.push_back(cdt3::Point(hole_vertices(i, 0), hole_vertices(i, 1))); + } + + triangulation.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true); + } + + // Add the curves as polyline constraints. + for (auto cit : curves) + { + compas::RowMatrixXd curve_vertices = cit; + std::vector curve(curve_vertices.rows()); + + for (int i = 0; i < curve_vertices.rows(); i++) + { + curve[i] = cdt3::Point(curve_vertices(i, 0), curve_vertices(i, 1)); + } + + triangulation.insert_constraint(curve.begin(), curve.end()); + } + + // Calculate hole centers for seeding + std::list seeds; + for (auto hit : holes) + { + compas::RowMatrixXd hole_vertices = hit; + double n = (double)hole_vertices.rows(); + + double x = hole_vertices.block(0, 0, hole_vertices.rows(), 1).sum() / n; + double y = hole_vertices.block(0, 1, hole_vertices.rows(), 1).sum() / n; + + seeds.push_back(cdt3::Point(x, y)); + } + + // Apply mesh refinement criteria + criteria mesh_criteria; + if (max_length > 0.0) + { + mesh_criteria = criteria(0.0, max_length); + } + else + { + mesh_criteria = criteria(); + } + + CGAL::refine_Delaunay_mesh_2(triangulation, seeds.begin(), seeds.end(), mesh_criteria); + + if (is_optimized) + { + CGAL::lloyd_optimize_mesh_2(triangulation, + CGAL::parameters::max_iteration_number = 100, + CGAL::parameters::seeds_begin = seeds.begin(), + CGAL::parameters::seeds_end = seeds.end()); + } + + // Count faces in domain + int face_count = 0; + for (cdt3::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) + { + if (fit->is_in_domain()) + { + face_count++; + } + } + + // Prepare result matrices + compas::RowMatrixXd vertices_out(triangulation.number_of_vertices(), 3); + compas::RowMatrixXi faces_out(face_count, 3); + + // Make vertex index map + std::unordered_map vertex_indices; + + // Load vertices + int i = 0; + for (cdt3::Vertex_handle vertex : triangulation.finite_vertex_handles()) + { + cdt3::Point point = vertex->point(); + vertex_indices.insert({vertex, i}); + + vertices_out(i, 0) = point.hx(); + vertices_out(i, 1) = point.hy(); + vertices_out(i, 2) = 0.0; + + i++; + } + + // Load faces + int j = 0; + for (cdt3::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) + { + if (fit->is_in_domain()) + { + cdt3::Face_handle face = fit; + cdt3::Vertex_handle v0 = face->vertex(0); + cdt3::Vertex_handle v1 = face->vertex(1); + cdt3::Vertex_handle v2 = face->vertex(2); + + faces_out(j, 0) = vertex_indices[v0]; + faces_out(j, 1) = vertex_indices[v1]; + faces_out(j, 2) = vertex_indices[v2]; + + j++; + } + } + + return std::make_tuple(vertices_out, faces_out); +} + +void init_triangulation(nb::module_& m) { + nb::module_ submodule = m.def_submodule("triangulation"); + + submodule.def( + "delaunay_triangulation", + &pmp_delaunay_triangulation, + "Create a Delaunay triangulation from a set of points.", + "vertices"_a); + + submodule.def( + "constrained_delaunay_triangulation", + &pmp_constrained_delaunay_triangulation, + "Create a constrained Delaunay triangulation with boundary, holes, and constraints.", + "boundary_vertices"_a, + "internal_vertices"_a, + "holes"_a, + "curves"_a, + "is_conforming"_a = false); + + submodule.def( + "refined_delaunay_mesh", + &pmp_refined_delaunay_mesh, + "Create a refined Delaunay mesh with quality constraints.", + "boundary_vertices"_a, + "internal_vertices"_a, + "holes"_a, + "curves"_a, + "min_angle"_a = 0.0, + "max_length"_a = 0.0, + "is_optimized"_a = true); +} \ No newline at end of file diff --git a/src/triangulation.h b/src/triangulation.h new file mode 100644 index 00000000..cd559e42 --- /dev/null +++ b/src/triangulation.h @@ -0,0 +1,55 @@ +#pragma once + +#include "compas.h" + +/** + * @brief Delaunay Triangulation of a given set of points. + * + * @param vertices The given vertex locations. + * @return compas::RowMatrixXi + */ +compas::RowMatrixXi +pmp_delaunay_triangulation( + Eigen::Ref vertices); + + +/** + * @brief Conforming delaunay triangulation of a given boundary with internal holes and constraint curves. + * + * @param boundary_vertices The vertices of the boundary. + * @param internal_vertices Additional internal vertices. + * @param holes A list of holes in the triangulation. + * @param curves A list of internal polyline constraints. + * @param is_conforming Whether the mesh is conforming. + * @return std::tuple + */ +std::tuple +pmp_constrained_delaunay_triangulation( + Eigen::Ref boundary_vertices, + Eigen::Ref internal_vertices, + const std::vector & holes, + const std::vector & curves, + bool is_conforming = false + ); + +/** + * @brief Conforming delaunay triangulation of a given boundary with internal holes and constraint curves. + * + * @param boundary_vertices The vertices of the boundary. + * @param internal_vertices Additional internal vertices. + * @param holes A list of holes in the triangulation. + * @param curves A list of internal polyline constraints. + * @param min_angle The minimum angle of the mesh. + * @param max_length The maximum length of the mesh. + * @param is_optimized Apply additional optimization to the mesh geometry. + * @return std::tuple + */ +std::tuple +pmp_refined_delaunay_mesh( + Eigen::Ref boundary_vertices, + Eigen::Ref internal_vertices, + const std::vector & holes, + const std::vector & curves, + double min_angle = 0.0, + double max_length = 0.0, + bool is_optimized = true); \ No newline at end of file diff --git a/src/triangulations.cpp b/src/triangulations.cpp deleted file mode 100644 index 7467b016..00000000 --- a/src/triangulations.cpp +++ /dev/null @@ -1,547 +0,0 @@ -#include "triangulations.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct FaceInfo -{ - FaceInfo(){}; - - int nesting_level; - - bool in_domain() - { - return nesting_level % 2 == 1; - } -}; - -using Polygon = CGAL::Polygon_2; - -using VertexBase1 = CGAL::Triangulation_vertex_base_with_info_2; -using TDS1 = CGAL::Triangulation_data_structure_2; -using DT = CGAL::Delaunay_triangulation_2; - -using VertexBase2 = CGAL::Triangulation_vertex_base_2; -using _FaceBase2 = CGAL::Triangulation_face_base_with_info_2; -using FaceBase2 = CGAL::Constrained_triangulation_face_base_2; -using TDS2 = CGAL::Triangulation_data_structure_2; -using CDT2 = CGAL::Constrained_Delaunay_triangulation_2; - -using VertexBase3 = CGAL::Delaunay_mesh_vertex_base_2; -using FaceBase3 = CGAL::Delaunay_mesh_face_base_2; -using TDS3 = CGAL::Triangulation_data_structure_2; -using Itag = CGAL::Exact_predicates_tag; -using CDT3 = CGAL::Constrained_Delaunay_triangulation_2; -using Criteria = CGAL::Delaunay_mesh_size_criteria_2; - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// Delaunay -// =========================================================================== -// =========================================================================== -// =========================================================================== - -/** - * Delaunay Triangulation of a given set of points. - * - * @param V The given vertex locations. - */ -compas::RowMatrixXi -pmp_delaunay_triangulation(Eigen::Ref &V) -{ - DT triangulation; - - std::vector> points(V.rows()); - - for (int i = 0; i < V.rows(); i++) - { - DT::Point point = DT::Point(V(i, 0), V(i, 1)); - points[i] = std::make_pair(point, i); - } - - triangulation.insert(points.begin(), points.end()); - - compas::RowMatrixXi F(triangulation.number_of_faces(), 3); - - int j = 0; - for (DT::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) - { - DT::Face_handle face = fit; - - F(j, 0) = face->vertex(0)->info(); - F(j, 1) = face->vertex(1)->info(); - F(j, 2) = face->vertex(2)->info(); - - j++; - } - - return F; -} - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// Regular -// =========================================================================== -// =========================================================================== -// =========================================================================== - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// Domains -// =========================================================================== -// =========================================================================== -// =========================================================================== - -void mark_domains(CDT2 &triangulation, - CDT2::Face_handle start, - int index, - std::list &border) -{ - if (start->info().nesting_level != -1) - { - return; - } - - std::list queue; - queue.push_back(start); - - while (!queue.empty()) - { - CDT2::Face_handle face = queue.front(); - - queue.pop_front(); - - if (face->info().nesting_level == -1) - { - face->info().nesting_level = index; - - for (int i = 0; i < 3; i++) - { - CDT2::Edge edge(face, i); - CDT2::Face_handle nbr = face->neighbor(i); - - if (nbr->info().nesting_level == -1) - { - if (triangulation.is_constrained(edge)) - { - border.push_back(edge); - } - else - { - queue.push_back(nbr); - } - } - } - } - } -} - -void mark_domains(CDT2 &triangulation) -{ - for (CDT2::Face_handle face : triangulation.all_face_handles()) - { - face->info().nesting_level = -1; - } - - std::list border; - - mark_domains(triangulation, triangulation.infinite_face(), 0, border); - - while (!border.empty()) - { - CDT2::Edge edge = border.front(); - border.pop_front(); - - CDT2::Face_handle nbr = edge.first->neighbor(edge.second); - - if (nbr->info().nesting_level == -1) - { - mark_domains(triangulation, nbr, edge.first->info().nesting_level + 1, border); - } - } -} - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// Constrained / Conforming Delaunay -// =========================================================================== -// =========================================================================== -// =========================================================================== - -/** - * Conforming delaunay triangulation of a given boundary - * with internal holes and constraint curves. - * - * @param B The vertices of the boundary. - * @param P Additional internal vertices. - * @param holes A list of holes in the triangulation. - * @param curves A list of internal polyline constraints. - */ -std::tuple -pmp_constrained_delaunay_triangulation( - Eigen::Ref &B, - Eigen::Ref &P, - std::vector> &holes, - std::vector> &curves, - bool is_conforming) -{ - CDT2 triangulation; - - // Add specific (isolated) points - - std::vector points(P.rows()); - - for (int i = 0; i < P.rows(); i++) - { - points[i] = CDT2::Point(P(i, 0), P(i, 1)); - } - - triangulation.insert(points.begin(), points.end()); - - // Add the boundary as polygon constraint. - - Polygon boundary; - - for (int i = 0; i < B.rows(); i++) - { - boundary.push_back(CDT2::Point(B(i, 0), B(i, 1))); - } - - triangulation.insert_constraint(boundary.vertices_begin(), boundary.vertices_end(), true); - - // Add the holes as polygon constraints. - - for (auto hit : holes) - { - compas::RowMatrixXd H = hit; - - Polygon hole; - - for (int i = 0; i < H.rows(); i++) - { - hole.push_back(CDT2::Point(H(i, 0), H(i, 1))); - } - - triangulation.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true); - } - - // Add the curves as polyline constraints. - - for (auto cit : curves) - { - compas::RowMatrixXd C = cit; - - std::vector curve(C.rows()); - - for (int i = 0; i < C.rows(); i++) - { - curve[i] = CDT2::Point(C(i, 0), C(i, 1)); - } - - triangulation.insert_constraint(curve.begin(), curve.end()); - } - - if (is_conforming) - { - // CGAL::make_conforming_Delaunay_2(triangulation); - CGAL::make_conforming_Gabriel_2(triangulation); - } - - // Mark the faces belonging to the boundary constraint - - mark_domains(triangulation); - - // Count the number of faces belonging to the boundary domain - - int f = 0; - for (CDT2::Face_handle face : triangulation.finite_face_handles()) - { - if (face->info().in_domain()) - { - f++; - } - } - - // Prepare result matrices - - compas::RowMatrixXd TV(triangulation.number_of_vertices(), 3); - compas::RowMatrixXi TF(f, 3); - - // Make vertex index map - - std::unordered_map vertex_index; - - // Load the vertices into the result - - int i = 0; - for (CDT2::Vertex_handle vertex : triangulation.finite_vertex_handles()) - { - CDT2::Point point = vertex->point(); - - vertex_index.insert({vertex, i}); - - TV(i, 0) = point.hx(); - TV(i, 1) = point.hy(); - TV(i, 2) = 0.0; - - i++; - } - - // Load the faces into the result - - int j = 0; - for (CDT2::Face_handle face : triangulation.finite_face_handles()) - { - if (face->info().in_domain()) - { - CDT2::Vertex_handle a = face->vertex(0); - CDT2::Vertex_handle b = face->vertex(1); - CDT2::Vertex_handle c = face->vertex(2); - - TF(j, 0) = vertex_index[a]; - TF(j, 1) = vertex_index[b]; - TF(j, 2) = vertex_index[c]; - - j++; - } - } - - std::tuple result = std::make_tuple(TV, TF); - return result; -} - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// Constrained Delaunay compas::Mesh -// =========================================================================== -// =========================================================================== -// =========================================================================== - -/** - * Conforming delaunay triangulation of a given boundary - * with internal holes and constraint curves. - * - * @param B The vertices of the boundary. - * @param P Additional internal vertices. - * @param holes A list of holes in the triangulation. - * @param curves A list of internal polyline constraints. - * @param minangle The minimum internal triangle angle. - * @param maxlength The maximum length of the triangle edges. - * @param is_optimized Apply additional optimization to the mesh geometry. - */ -std::tuple -pmp_refined_delaunay_mesh( - Eigen::Ref &B, - Eigen::Ref &P, - std::vector> &holes, - std::vector> &curves, - double minangle, - double maxlength, - bool is_optimized) -{ - CDT3 triangulation; - - // Add specific (isolated) points - - std::vector points(P.rows()); - - for (int i = 0; i < P.rows(); i++) - { - points[i] = CDT3::Point(P(i, 0), P(i, 1)); - } - - triangulation.insert(points.begin(), points.end()); - - // Add the boundary as polygon constraint. - - Polygon boundary; - - for (int i = 0; i < B.rows(); i++) - { - boundary.push_back(CDT3::Point(B(i, 0), B(i, 1))); - } - - triangulation.insert_constraint(boundary.vertices_begin(), boundary.vertices_end(), true); - - // Add the holes as polygon constraints. - - for (auto hit : holes) - { - compas::RowMatrixXd H = hit; - - Polygon hole; - - for (int i = 0; i < H.rows(); i++) - { - hole.push_back(CDT3::Point(H(i, 0), H(i, 1))); - } - - triangulation.insert_constraint(hole.vertices_begin(), hole.vertices_end(), true); - } - - // Add the curves as polyline constraints. - - for (auto cit : curves) - { - compas::RowMatrixXd C = cit; - - std::vector curve(C.rows()); - - for (int i = 0; i < C.rows(); i++) - { - curve[i] = CDT3::Point(C(i, 0), C(i, 1)); - } - - triangulation.insert_constraint(curve.begin(), curve.end()); - } - - // Remesh - - std::list seeds; - - for (auto hit : holes) - { - compas::RowMatrixXd H = hit; - - double n = (double)H.rows(); - - double x = H.block(0, 0, H.rows(), 1).sum() / n; - double y = H.block(0, 1, H.rows(), 1).sum() / n; - - seeds.push_back(CDT3::Point(x, y)); - } - - Criteria criteria; - - if (maxlength > 0.0) - { - criteria = Criteria(0.0, maxlength); - } - else - { - criteria = Criteria(); - } - - CGAL::refine_Delaunay_mesh_2(triangulation, seeds.begin(), seeds.end(), criteria); - - if (is_optimized) - { - CGAL::lloyd_optimize_mesh_2(triangulation, - CGAL::parameters::max_iteration_number = 100, - CGAL::parameters::seeds_begin = seeds.begin(), - CGAL::parameters::seeds_end = seeds.end()); - } - - // Count the number of faces belonging to the boundary domain - - int f = 0; - for (CDT3::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) - { - if (fit->is_in_domain()) - { - f++; - } - } - - // Prepare result matrices - - compas::RowMatrixXd TV(triangulation.number_of_vertices(), 3); - compas::RowMatrixXi TF(f, 3); - - // Make vertex index map - - std::unordered_map vertex_index; - - // Load the vertices into the result - - int i = 0; - for (CDT3::Vertex_handle vertex : triangulation.finite_vertex_handles()) - { - CDT3::Point point = vertex->point(); - - vertex_index.insert({vertex, i}); - - TV(i, 0) = point.hx(); - TV(i, 1) = point.hy(); - TV(i, 2) = 0.0; - - i++; - } - - // Load the faces into the result - - int j = 0; - for (CDT3::Finite_faces_iterator fit = triangulation.finite_faces_begin(); fit != triangulation.finite_faces_end(); fit++) - { - CDT3::Face_handle face = fit; - - if (fit->is_in_domain()) - { - CDT3::Vertex_handle a = face->vertex(0); - CDT3::Vertex_handle b = face->vertex(1); - CDT3::Vertex_handle c = face->vertex(2); - - TF(j, 0) = vertex_index[a]; - TF(j, 1) = vertex_index[b]; - TF(j, 2) = vertex_index[c]; - - j++; - } - } - - std::tuple result = std::make_tuple(TV, TF); - return result; -} - -// =========================================================================== -// =========================================================================== -// =========================================================================== -// PyBind -// =========================================================================== -// =========================================================================== -// =========================================================================== - -void init_triangulations(pybind11::module &m) -{ - pybind11::module submodule = m.def_submodule("triangulations"); - - submodule.def( - "delaunay_triangulation", - &pmp_delaunay_triangulation, - pybind11::arg("V").noconvert()); - - submodule.def( - "constrained_delaunay_triangulation", - &pmp_constrained_delaunay_triangulation, - pybind11::arg("B").noconvert(), - pybind11::arg("P").noconvert(), - pybind11::arg("holes").noconvert(), - pybind11::arg("curves").noconvert(), - pybind11::arg("is_conforming") = false); - - submodule.def( - "refined_delaunay_mesh", - &pmp_refined_delaunay_mesh, - pybind11::arg("B").noconvert(), - pybind11::arg("P").noconvert(), - pybind11::arg("holes").noconvert(), - pybind11::arg("curves").noconvert(), - pybind11::arg("minangle") = 0.0, - pybind11::arg("maxlength") = 0.0, - pybind11::arg("is_optimized") = true); -}; diff --git a/src/triangulations.h b/src/triangulations.h deleted file mode 100644 index 6b5d3ded..00000000 --- a/src/triangulations.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef COMPAS_TRIANGULATIONS_H -#define COMPAS_TRIANGULATIONS_H - -#include - - -compas::RowMatrixXi -pmp_delaunay_triangulation( - Eigen::Ref & V); - - -std::tuple -pmp_constrained_delaunay_triangulation( - Eigen::Ref & B, - Eigen::Ref & P, - std::vector< Eigen::Ref > & holes, - std::vector< Eigen::Ref > & curves, - bool is_conforming = false); - - -std::tuple -pmp_refined_delaunay_mesh( - Eigen::Ref & B, - Eigen::Ref & P, - std::vector< Eigen::Ref > & holes, - std::vector< Eigen::Ref > & curves, - double minangle = 0.0, - double maxlength = 0.0, - bool is_optimized = true); - - -#endif /* COMPAS_TRIANGULATIONS_H */ diff --git a/tasks.py b/tasks.py index 96f59292..ff82845e 100644 --- a/tasks.py +++ b/tasks.py @@ -24,4 +24,4 @@ { "base_folder": os.path.dirname(__file__), } -) +) \ No newline at end of file diff --git a/scripts/PLACEHOLDER b/temp/PLACEHOLDER similarity index 100% rename from scripts/PLACEHOLDER rename to temp/PLACEHOLDER diff --git a/tests/test_booleans.py b/tests/test_booleans.py index f764e096..73ffc499 100644 --- a/tests/test_booleans.py +++ b/tests/test_booleans.py @@ -1,32 +1,100 @@ -from compas.geometry import Box -from compas.geometry import Sphere +import pytest +import numpy as np + +from compas.geometry import Box, Sphere from compas.datastructures import Mesh -from compas_cgal.booleans import boolean_union_mesh_mesh -from compas_cgal.meshing import mesh_remesh +from compas_cgal.booleans import ( + boolean_union_mesh_mesh, + boolean_difference_mesh_mesh, + boolean_intersection_mesh_mesh, + split_mesh_mesh, +) + + +@pytest.fixture +def box_sphere_meshes(): + """Create test box and sphere meshes.""" + box = Box.from_width_height_depth(2.0, 2.0, 2.0) + sphere = Sphere(1.0, point=[1, 1, 1]) + + box_mesh = box.to_vertices_and_faces(triangulated=True) + sphere_mesh = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) + + return box_mesh, sphere_mesh + + +def test_boolean_union(box_sphere_meshes): + """Test boolean union operation.""" + mesh_a, mesh_b = box_sphere_meshes + vertices, faces = boolean_union_mesh_mesh(mesh_a, mesh_b) + + assert isinstance(vertices, np.ndarray) + assert isinstance(faces, np.ndarray) + assert vertices.shape[1] == 3 # 3D points + assert faces.shape[1] == 3 # Triangle faces + assert len(vertices) > 0 + assert len(faces) > 0 + + # Create mesh to verify topology + mesh = Mesh.from_vertices_and_faces(vertices, faces) + assert mesh.is_valid() + assert mesh.euler() == 2 # Valid closed manifold + + +def test_boolean_difference(box_sphere_meshes): + """Test boolean difference operation.""" + mesh_a, mesh_b = box_sphere_meshes + vertices, faces = boolean_difference_mesh_mesh(mesh_a, mesh_b) + + assert isinstance(vertices, np.ndarray) + assert isinstance(faces, np.ndarray) + assert vertices.shape[1] == 3 + assert faces.shape[1] == 3 + assert len(vertices) > 0 + assert len(faces) > 0 + + # Create mesh to verify topology + mesh = Mesh.from_vertices_and_faces(vertices, faces) + assert mesh.is_valid() + assert mesh.euler() == 2 # Valid closed manifold -def test_booleans(): - # ============================================================================== - # Make a box and a sphere - # ============================================================================== +def test_boolean_intersection(box_sphere_meshes): + """Test boolean intersection operation.""" + mesh_a, mesh_b = box_sphere_meshes + vertices, faces = boolean_intersection_mesh_mesh(mesh_a, mesh_b) - box = Box(2) - A = box.to_vertices_and_faces(triangulated=True) + assert isinstance(vertices, np.ndarray) + assert isinstance(faces, np.ndarray) + assert vertices.shape[1] == 3 + assert faces.shape[1] == 3 + assert len(vertices) > 0 + assert len(faces) > 0 - sphere = Sphere(1, point=[1, 1, 1]) - B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) + # Create mesh to verify topology + mesh = Mesh.from_vertices_and_faces(vertices, faces) + assert mesh.is_valid() + assert mesh.euler() == 2 # Valid closed manifold - # ============================================================================== - # Remesh the sphere - # ============================================================================== - B = mesh_remesh(B, 0.3, 10) +def test_split(box_sphere_meshes): + """Test mesh split operation.""" + mesh_a, mesh_b = box_sphere_meshes + vertices, faces = split_mesh_mesh(mesh_a, mesh_b) - # ============================================================================== - # Compute the boolean mesh - # ============================================================================== + assert isinstance(vertices, np.ndarray) + assert isinstance(faces, np.ndarray) + assert vertices.shape[1] == 3 + assert faces.shape[1] == 3 + assert len(vertices) > 0 + assert len(faces) > 0 - V, F = boolean_union_mesh_mesh(A, B) + # Create mesh to verify components + mesh = Mesh.from_vertices_and_faces(vertices, faces) + assert mesh.is_valid() - Mesh.from_vertices_and_faces(V, F) + # Split should create at least 2 connected vertex groups + components = list(mesh.connected_vertices()) + print(components) + assert len(components) >= 2 diff --git a/tests/test_intersections.py b/tests/test_intersections.py index 2a6cbae3..49d31f8e 100644 --- a/tests/test_intersections.py +++ b/tests/test_intersections.py @@ -1,20 +1,37 @@ -from compas.geometry import Point -from compas.geometry import Sphere -from compas.geometry import Polyline - +import pytest +import numpy as np +from compas.geometry import Box, Sphere from compas_cgal.intersections import intersection_mesh_mesh +from compas_cgal.compas_cgal_ext import VectorRowMatrixXd -def test_intersections(): - A = Sphere(1.0).to_vertices_and_faces(u=50, v=50, triangulated=True) - B = Sphere(1.0, point=[1, 0, 0]).to_vertices_and_faces(u=50, v=50, triangulated=True) - polylines = [] - pointsets = intersection_mesh_mesh(A, B) - for points in pointsets: - points = [Point(*point) for point in points] # otherwise Polygon throws an error - polyline = Polyline(points) - polylines.append(polyline) +@pytest.fixture +def box_sphere_meshes(): + """Create test meshes: a box and a sphere that intersect.""" + box = Box(2) # 2x2x2 box centered at origin + box_mesh = box.to_vertices_and_faces(triangulated=True) + + sphere = Sphere(1, point=[1, 1, 1]) # Sphere radius 1, centered at [1,1,1] + sphere_mesh = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True) + + return box_mesh, sphere_mesh + - print(pointsets) +def test_intersection_mesh_mesh(box_sphere_meshes): + """Test mesh-mesh intersection computation.""" + mesh_a, mesh_b = box_sphere_meshes + + # Compute intersections + pointsets = intersection_mesh_mesh(mesh_a, mesh_b) + + # Basic validation + assert isinstance(pointsets, VectorRowMatrixXd), "Result should be a nanobind type" + assert len(pointsets) > 0, "Should find at least one intersection curve" + + # Validate each pointset + for points in pointsets: + assert isinstance(points, np.ndarray), "Each pointset should be a numpy array" + assert points.shape[1] == 3, "Points should be 3D" + assert len(points) > 1, "Each intersection curve should have multiple points" diff --git a/tests/test_measure.py b/tests/test_measure.py new file mode 100644 index 00000000..cdc20091 --- /dev/null +++ b/tests/test_measure.py @@ -0,0 +1,40 @@ +"""Tests for COMPAS CGAL measure functionality.""" + +import pytest +from compas.geometry import Box +from compas.datastructures import Mesh +from compas_cgal.measure import mesh_area, mesh_volume, mesh_centroid + + +def test_area(): + """Test area computation with a unit cube.""" + box = Box.from_width_height_depth(1, 1, 1) + mesh = Mesh.from_vertices_and_faces(*box.to_vertices_and_faces()) + mesh.quads_to_triangles() + V, F = mesh.to_vertices_and_faces() + + # Unit cube has 6 faces, each 1x1 = total area of 6 + assert pytest.approx(mesh_area((V, F))) == 6.0 + + +def test_volume(): + """Test volume computation with a unit cube.""" + box = Box.from_width_height_depth(1, 1, 1) + mesh = Mesh.from_vertices_and_faces(*box.to_vertices_and_faces()) + mesh.quads_to_triangles() + V, F = mesh.to_vertices_and_faces() + + # Unit cube has volume of 1 + assert pytest.approx(mesh_volume((V, F))) == 1.0 + + +def test_centroid(): + """Test centroid computation with a unit cube.""" + box = Box.from_width_height_depth(1, 1, 1) + mesh = Mesh.from_vertices_and_faces(*box.to_vertices_and_faces()) + mesh.quads_to_triangles() + V, F = mesh.to_vertices_and_faces() + + # Centroid should be at origin (0,0,0) + result = mesh_centroid((V, F)) + assert pytest.approx(result) == [0.0, 0.0, 0.0] diff --git a/tests/test_meshing.py b/tests/test_meshing.py new file mode 100644 index 00000000..8621ac4f --- /dev/null +++ b/tests/test_meshing.py @@ -0,0 +1,59 @@ +"""Tests for COMPAS CGAL meshing functionality.""" + +import pytest +import numpy as np +from compas.datastructures import Mesh +from compas_cgal.meshing import mesh_remesh + + +@pytest.fixture +def sample_mesh(): + """Create a simple cube mesh for testing.""" + vertices = [ + [0, 0, 0], + [1, 0, 0], + [1, 1, 0], + [0, 1, 0], + [0, 0, 1], + [1, 0, 1], + [1, 1, 1], + [0, 1, 1], + ] + faces = [ + [0, 1, 2, 3], + [4, 5, 6, 7], # top, bottom + [0, 1, 5, 4], + [2, 3, 7, 6], # front, back + [0, 3, 7, 4], + [1, 2, 6, 5], # left, right + ] + mesh = Mesh.from_vertices_and_faces(vertices, faces) + mesh.quads_to_triangles() + return mesh + + +def test_remesh(sample_mesh): + """Test the remeshing functionality.""" + # Get mesh data + V, F = sample_mesh.to_vertices_and_faces() + + # Test parameters + target_edge_length = 0.5 + num_iterations = 10 + + # Run remeshing + V_new, F_new = mesh_remesh((V, F), target_edge_length, num_iterations) + + # Verify output types and shapes + assert isinstance(V_new, np.ndarray) + assert isinstance(F_new, np.ndarray) + assert V_new.dtype == np.float64 + assert F_new.dtype == np.int32 + assert V_new.flags["C_CONTIGUOUS"] # Check row-major order + assert F_new.flags["C_CONTIGUOUS"] + assert V_new.shape[1] == 3 # 3D points + assert F_new.shape[1] == 3 # Triangular faces + + # Verify mesh is valid + remeshed_mesh = Mesh.from_vertices_and_faces(V_new, F_new) + assert remeshed_mesh.is_valid() diff --git a/tests/test_nanobind.py b/tests/test_nanobind.py new file mode 100644 index 00000000..7109e9b6 --- /dev/null +++ b/tests/test_nanobind.py @@ -0,0 +1,44 @@ +import numpy as np +from compas_cgal.compas_cgal_ext import scale_matrix, create_matrix, add + + +def test_add_binding(): + """Test adding two numbers using C++ binding.""" + result = add(1, 2) + assert result == 3 + + +def test_scale_matrix_binding(): + """Test scaling a matrix using C++ binding.""" + # Create test matrix with exact values + input_matrix = np.array( + [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]], + dtype=np.float64, + order="C", # C-style, row-major order to match Eigen::RowMajor + ) + + expected = np.array([[2.2, 4.4, 6.6], [8.8, 11.0, 13.2], [15.4, 17.6, 19.8]]) + + # Make copy for testing since scale_matrix modifies in-place + test_matrix = input_matrix.copy() + scale_matrix(test_matrix) # Function modifies array in-place + + # Test in-place modification with exact values + np.testing.assert_array_almost_equal(test_matrix, expected) + + +def test_create_matrix_binding(): + """Test creating a matrix using C++ binding.""" + result = create_matrix() + + # Test exact values + expected = np.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]]) + + # Test shape and type + assert isinstance(result, np.ndarray) + assert result.dtype == np.float64 + assert result.flags["C_CONTIGUOUS"] # Check row-major order + assert result.shape == (3, 3) + + # Test exact values + np.testing.assert_array_almost_equal(result, expected) diff --git a/tests/test_placeholder.py b/tests/test_placeholder.py deleted file mode 100644 index 3ada1ee4..00000000 --- a/tests/test_placeholder.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_placeholder(): - assert True diff --git a/tests/test_reconstruction_pointset_normal_estimation.py b/tests/test_reconstruction_pointset_normal_estimation.py new file mode 100644 index 00000000..01435956 --- /dev/null +++ b/tests/test_reconstruction_pointset_normal_estimation.py @@ -0,0 +1,30 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas.geometry import Line +from compas_cgal.reconstruction import pointset_normal_estimation +from compas_cgal.reconstruction import pointset_reduction + + +def reconstruction_pointset_normal_estimation(): + FILE = Path(__file__).parent.parent / "data" / "box.ply" + + cloud = Pointcloud.from_ply(FILE) + reduced_cloud = Pointcloud(pointset_reduction(cloud, 1000)) + points, vectors = pointset_normal_estimation(reduced_cloud, 16, True) + + lines = [] + line_scale = 1000 + + for p, v in zip(points, vectors): + line = Line( + [p[0], p[1], p[2]], + [ + p[0] + v[0] * line_scale, + p[1] + v[1] * line_scale, + p[2] + v[2] * line_scale, + ], + ) + lines.append(line) + + assert len(lines) == len(points) diff --git a/tests/test_reconstruction_pointset_outlier_removal.py b/tests/test_reconstruction_pointset_outlier_removal.py new file mode 100644 index 00000000..8540dd1e --- /dev/null +++ b/tests/test_reconstruction_pointset_outlier_removal.py @@ -0,0 +1,40 @@ +from pathlib import Path + +from compas.geometry import Pointcloud +from compas_cgal.reconstruction import pointset_outlier_removal +import numpy as np +from line_profiler import profile + + +@profile +def reconstruction_pointset_outlier_removal(): + FILE = Path(__file__).parent.parent / "data" / "forked_branch_1.ply" + c1 = Pointcloud.from_ply(FILE) + points = pointset_outlier_removal(c1, 30, 2.0) + c2 = Pointcloud(points) + c3 = c1.difference(c2) + c2.name = "pointset_outlier_removal" + c3.name = "pointset_outlier_removal" + return c2, c3 + + +def test_pointset_outlier_removal(): + # Create a point cloud with an obvious outlier + points = np.array( + [ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 1, 0], + [10, 10, 10], # Outlier + ], + dtype=np.float64, + ) + + # Test outlier removal + filtered_points = pointset_outlier_removal(points, nnnbrs=3, radius=1.0) + + # Basic checks + assert filtered_points.shape[0] == 4 # Should have removed one point + assert filtered_points.shape[1] == 3 # Each point should be 3D + assert not np.any(np.all(filtered_points == [10, 10, 10], axis=1)) # Outlier should be removed diff --git a/tests/test_reconstruction_pointset_reduction.py b/tests/test_reconstruction_pointset_reduction.py new file mode 100644 index 00000000..42642961 --- /dev/null +++ b/tests/test_reconstruction_pointset_reduction.py @@ -0,0 +1,18 @@ +import numpy as np +from compas_cgal.reconstruction import pointset_reduction + + +def test_pointset_reduction(): + # Create a dense point cloud + x = np.linspace(0, 1, 10) + y = np.linspace(0, 1, 10) + X, Y = np.meshgrid(x, y) + points = np.column_stack((X.ravel(), Y.ravel(), np.zeros_like(X.ravel()))) + + # Test point reduction + spacing = 0.3 # Should reduce points significantly + reduced_points = pointset_reduction(points, spacing=spacing) + + # Basic checks + assert reduced_points.shape[0] < points.shape[0] # Should have fewer points + assert reduced_points.shape[1] == 3 # Each point should be 3D diff --git a/tests/test_reconstruction_pointset_smoothing.py b/tests/test_reconstruction_pointset_smoothing.py new file mode 100644 index 00000000..c145c3d9 --- /dev/null +++ b/tests/test_reconstruction_pointset_smoothing.py @@ -0,0 +1,22 @@ +import numpy as np +from compas_cgal.reconstruction import pointset_smoothing + + +def test_pointset_smoothing(): + # Create a noisy point cloud + points = np.array( + [ + [0, 0, 0], + [1.1, 0.1, 0.1], # Noisy point + [0, 1, 0], + [0.9, 0.9, -0.1], # Noisy point + ], + dtype=np.float64, + ) + + # Test smoothing + smoothed_points = pointset_smoothing(points, neighbors=3, iterations=2) + + # Basic checks + assert smoothed_points.shape == points.shape # Should preserve point count + assert smoothed_points.shape[1] == 3 # Each point should be 3D diff --git a/tests/test_reconstruction_poisson_surface_reconstruction.py b/tests/test_reconstruction_poisson_surface_reconstruction.py new file mode 100644 index 00000000..fdd8b560 --- /dev/null +++ b/tests/test_reconstruction_poisson_surface_reconstruction.py @@ -0,0 +1,35 @@ +import math +from pathlib import Path + +from compas.datastructures import Mesh +from compas.geometry import Pointcloud +from compas.geometry import Rotation +from compas.geometry import Scale +from compas_cgal.reconstruction import poisson_surface_reconstruction + + +def test_reconstruction_poisson_surface_reconstruction(): + FILE = Path(__file__).parent.parent / "data" / "oni.xyz" + + points = [] + normals = [] + with open(FILE, "r") as f: + for line in f: + x, y, z, nx, ny, nz = line.strip().split() + points.append([float(x), float(y), float(z)]) + normals.append([float(nx), float(ny), float(nz)]) + + V, F = poisson_surface_reconstruction(points, normals) + mesh = Mesh.from_vertices_and_faces(V, F) + + R = Rotation.from_axis_and_angle([1, 0, 0], math.radians(90)) + S = Scale.from_factors([5, 5, 5]) + T = R * S + + c = Pointcloud(V) + c.transform(T) + mesh.transform(T) + + assert mesh.is_manifold() + assert mesh.number_of_vertices() > 0 + assert mesh.number_of_faces() > 0 diff --git a/tests/test_skeletonization.py b/tests/test_skeletonization.py new file mode 100644 index 00000000..d6c1be46 --- /dev/null +++ b/tests/test_skeletonization.py @@ -0,0 +1,19 @@ +from compas.geometry import Box +from compas_cgal.skeletonization import mesh_skeleton + + +def test_mesh_skeleton(): + """Test mesh skeletonization with a simple box.""" + # Create test box mesh + box = Box.from_width_height_depth(2.0, 2.0, 2.0) + mesh = box.to_vertices_and_faces(triangulated=True) + + # Get skeleton + edges = mesh_skeleton(mesh) + + # Basic validation + assert isinstance(edges, list) + assert len(edges) > 0 + assert isinstance(edges[0], tuple) + assert len(edges[0]) == 2 + assert len(edges[0][0]) == 3 # 3D points diff --git a/tests/test_slicer.py b/tests/test_slicer.py new file mode 100644 index 00000000..d41d0e4a --- /dev/null +++ b/tests/test_slicer.py @@ -0,0 +1,31 @@ +from compas.geometry import Box, Plane, Point, Vector, Polyline +from compas_cgal.slicer import slice_mesh + + +def test_mesh_slice(): + """Test mesh slicing with a simple box.""" + # Create test box mesh + box = Box.from_width_height_depth(2.0, 2.0, 2.0) + mesh = box.to_vertices_and_faces(triangulated=True) + + # Create one horizontal slicing plane + point = Point(0, 0, 0) + normal = Vector(0, 0, 1) + plane = Plane(point, normal) + + # Get intersection curves + slicer_polylines = slice_mesh(mesh, [plane]) + + # Convert edges to polylines + polylines = [] + for polyline in slicer_polylines: + points = [] + for point in polyline: + points.append(Point(*point)) + polylines.append(Polyline(points)) + + # Basic validation + assert isinstance(polylines, list) + assert len(polylines) > 0 # Box intersected by middle plane should give edges + assert len(polylines[0]) == 9 + assert len(polylines[0][0]) == 3 # 3D points diff --git a/tests/test_straight_skeleton_2.py b/tests/test_straight_skeleton_2.py deleted file mode 100644 index 340bae33..00000000 --- a/tests/test_straight_skeleton_2.py +++ /dev/null @@ -1,96 +0,0 @@ -from compas_cgal.straight_skeleton_2 import interior_straight_skeleton -from compas_cgal.straight_skeleton_2 import interior_straight_skeleton_with_holes -from compas_cgal.straight_skeleton_2 import offset_polygon -from compas_cgal.straight_skeleton_2 import offset_polygon_with_holes -from compas_cgal.straight_skeleton_2 import weighted_offset_polygon - - -def test_straight_polygon(): - points = [ - (-1, -1, 0), - (0, -12, 0), - (1, -1, 0), - (12, 0, 0), - (1, 1, 0), - (0, 12, 0), - (-1, 1, 0), - (-12, 0, 0), - ] - graph = interior_straight_skeleton(points) - assert graph.number_of_edges() == 16 - - -def test_straight_skeleton_with_holes(): - points = [ - (-1, -1, 0), - (0, -12, 0), - (1, -1, 0), - (12, 0, 0), - (1, 1, 0), - (0, 12, 0), - (-1, 1, 0), - (-12, 0, 0), - ] - hole = [(-1, 0, 0), (0, 1, 0), (1, 0, 0), (0, -1, 0)] - graph = interior_straight_skeleton_with_holes(points, [hole]) - assert graph.number_of_edges() == 32 - - -def test_offset(): - points = [ - (-1, -1, 0), - (0, -12, 0), - (1, -1, 0), - (12, 0, 0), - (1, 1, 0), - (0, 12, 0), - (-1, 1, 0), - (-12, 0, 0), - ] - offset = 0.5 - polygons = offset_polygon(points, offset) - assert len(polygons) == 1, len(polygons) - polygons = offset_polygon(points, -offset) - assert len(polygons) == 1, len(polygons) - weights = [0.1, 0.5, 0.3, 0.3, 0.9, 1.0, 0.2, 1.0] - polygons = weighted_offset_polygon(points, offset, weights) - assert len(polygons) == 1, len(polygons) - polygons = weighted_offset_polygon(points, -offset, weights) - assert len(polygons) == 1, len(polygons) - - -def test_offset_with_holes(): - points = [ - (65.322, -16.156, 0.0), - (65.322, -11.157, 0.0), - (63.022, -11.157, 0.0), - (63.022, -11.167, 0.0), - (60.042, -11.167, 0.0), - (60.042, -16.156, 0.0), - (61.702, -16.156, 0.0), - (61.702, -16.416, 0.0), - (61.712, -16.416, 0.0), - (61.712, -16.156, 0.0), - ] - holes = [ - [ - (63.912, -14.956, 0.0), - (63.652, -14.956, 0.0), - (63.652, -14.946, 0.0), - (61.442, -14.946, 0.0), - (61.442, -12.367, 0.0), - (61.702, -12.367, 0.0), - (61.702, -12.377, 0.0), - (63.652, -12.377, 0.0), - (63.652, -12.367, 0.0), - (63.912, -12.367, 0.0), - (63.912, -12.834, 0.0), - ], - [(61.452, -14.946, 0.0), (61.532, -14.949, 0.0), (61.702, -14.956, 0.0), (61.532, -14.956, 0.0), (61.452, -14.956, 0.0)], - ] - result = offset_polygon_with_holes(points, holes, -0.2) - assert len(result) == 1, len(result) - polygon, holes = result[0] - assert len(holes) == 1, len(holes) - area_net = polygon.area - sum(h.area for h in holes) - assert abs(area_net - 26.25329) < 1e-3, area_net diff --git a/tests/test_straight_skeleton_2_interior_straight_skeleton.py b/tests/test_straight_skeleton_2_interior_straight_skeleton.py new file mode 100644 index 00000000..da5d2c12 --- /dev/null +++ b/tests/test_straight_skeleton_2_interior_straight_skeleton.py @@ -0,0 +1,32 @@ +from compas_cgal.straight_skeleton_2 import interior_straight_skeleton + + +def test_interior_straight_skeleton(): + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + + graph = interior_straight_skeleton(points) + + # Basic validation + edges = list(graph.edges()) + vertices = list(graph.nodes()) + assert len(edges) > 0 + assert len(vertices) > 0 + + # Check that we have some inner bisectors + has_inner_bisector = False + for edge in edges: + if graph.edge_attribute(edge, "inner_bisector"): + has_inner_bisector = True + break + assert has_inner_bisector diff --git a/tests/test_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py b/tests/test_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py new file mode 100644 index 00000000..c10769a2 --- /dev/null +++ b/tests/test_straight_skeleton_2_interior_straight_skeleton_offset_polygon.py @@ -0,0 +1,33 @@ +from compas.geometry import Polygon +from compas_cgal.straight_skeleton_2 import offset_polygon + + +def test_offset_polygon(): + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + + offset = 1.5 + + # Test inner offset + offset_polygon_inner = offset_polygon(points, offset) + assert len(offset_polygon_inner) > 0 + for polygon in offset_polygon_inner: + assert isinstance(polygon, Polygon) + assert len(polygon.points) > 0 + + # Test outer offset + offset_polygon_outer = offset_polygon(points, -offset) + assert len(offset_polygon_outer) > 0 + for polygon in offset_polygon_outer: + assert isinstance(polygon, Polygon) + assert len(polygon.points) > 0 diff --git a/tests/test_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygons.py b/tests/test_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygons.py new file mode 100644 index 00000000..297319fe --- /dev/null +++ b/tests/test_straight_skeleton_2_interior_straight_skeleton_weighted_offset_polygons.py @@ -0,0 +1,23 @@ +from compas_cgal.straight_skeleton_2 import weighted_offset_polygon + + +def test_weighted_offset_polygon(): + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + + distances = [0.1, 0.3, 0.6, 0.1, 0.7, 0.5, 0.2, 0.4, 0.8, 0.2] + weights = [1.0 / d for d in distances] + offset = 1.0 + offset_polygons_outer = weighted_offset_polygon(points, -offset, weights) + + assert len(offset_polygons_outer) == 1 diff --git a/tests/test_straight_skeleton_2_interior_straight_skeleton_with_holes.py b/tests/test_straight_skeleton_2_interior_straight_skeleton_with_holes.py new file mode 100644 index 00000000..bec7e9fa --- /dev/null +++ b/tests/test_straight_skeleton_2_interior_straight_skeleton_with_holes.py @@ -0,0 +1,47 @@ +from compas.geometry import Polygon +from compas_cgal.straight_skeleton_2 import interior_straight_skeleton_with_holes + + +def test_interior_straight_skeleton_with_holes(): + points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), + ] + + holes = [ + [(0.42, 0.88, 0.0), (1.1, -1.0, 0.0), (-1.97, -0.93, 0.0), (-1.25, 1.82, 0.0)], + [(4.25, -0.64, 0.0), (2.9, -3.03, 0.0), (2.12, -2.16, 0.0), (2.89, -0.36, 0.0)], + [(10.6, 0.29, 0.0), (9.48, -1.54, 0.0), (5.48, -1.26, 0.0), (5.98, -0.04, 0.0)], + ] + + polygon = Polygon(points) + holes = [Polygon(hole) for hole in holes] + graph = interior_straight_skeleton_with_holes(polygon, holes) + + # Basic validation + edges = list(graph.edges()) + vertices = list(graph.nodes()) + assert len(edges) > 0 + assert len(vertices) > 0 + + # Check that we have both inner bisectors and boundary edges + has_inner_bisector = False + has_boundary = False + for edge in edges: + if graph.edge_attribute(edge, "inner_bisector"): + has_inner_bisector = True + elif not graph.edge_attribute(edge, "bisector"): + has_boundary = True + if has_inner_bisector and has_boundary: + break + + assert has_inner_bisector + assert has_boundary diff --git a/tests/test_subdivision.py b/tests/test_subdivision.py new file mode 100644 index 00000000..5242558b --- /dev/null +++ b/tests/test_subdivision.py @@ -0,0 +1,36 @@ +import numpy as np + +from compas.geometry import Box +from compas_cgal.subdivision import mesh_subdivide_catmull_clark +from compas_cgal.subdivision import mesh_subdivide_loop +from compas_cgal.subdivision import mesh_subdivide_sqrt3 + + +def test_subdivision_methods(): + """Test all three subdivision methods on a simple box mesh.""" + # Create test box + box = Box.from_diagonal(([0, 0, 0], [1, 1, 1])) + + # Test Catmull-Clark on quad mesh + V1, F1 = box.to_vertices_and_faces(triangulated=False) + V1_subd, F1_subd = mesh_subdivide_catmull_clark((V1, F1), k=1) + assert isinstance(V1_subd, np.ndarray) + assert isinstance(F1_subd, np.ndarray) + assert V1_subd.shape[1] == 3 # Check vertex dimension + assert len(F1_subd) > len(F1) # Should have more faces after subdivision + + # Test Loop on triangle mesh + V2, F2 = box.to_vertices_and_faces(triangulated=True) + V2_subd, F2_subd = mesh_subdivide_loop((V2, F2), k=1) + assert isinstance(V2_subd, np.ndarray) + assert isinstance(F2_subd, np.ndarray) + assert V2_subd.shape[1] == 3 # Check vertex dimension + assert len(F2_subd) > len(F2) # Should have more faces after subdivision + + # Test Sqrt3 on triangle mesh + V3, F3 = box.to_vertices_and_faces(triangulated=True) + V3_subd, F3_subd = mesh_subdivide_sqrt3((V3, F3), k=1) + assert isinstance(V3_subd, np.ndarray) + assert isinstance(F3_subd, np.ndarray) + assert V3_subd.shape[1] == 3 # Check vertex dimension + assert len(F3_subd) > len(F3) # Should have more faces after subdivision diff --git a/tests/test_triangulation.py b/tests/test_triangulation.py new file mode 100644 index 00000000..a34cea64 --- /dev/null +++ b/tests/test_triangulation.py @@ -0,0 +1,87 @@ +import pytest +import numpy as np +from compas.geometry import Polygon +from compas.geometry import Translation +from compas_cgal.triangulation import delaunay_triangulation +from compas_cgal.triangulation import conforming_delaunay_triangulation +from compas_cgal.triangulation import refined_delaunay_mesh + + +@pytest.fixture +def boundary_polygon(): + """Create a square boundary polygon.""" + return Polygon.from_sides_and_radius_xy(64, 4) + + +@pytest.fixture +def hole_polygons(): + """Create four circular holes in the boundary.""" + hole = Polygon.from_sides_and_radius_xy(128, 1) + hole1 = hole.transformed(Translation.from_vector([2, 0, 0])) + hole2 = hole.transformed(Translation.from_vector([-2, 0, 0])) + hole3 = hole.transformed(Translation.from_vector([0, 2, 0])) + hole4 = hole.transformed(Translation.from_vector([0, -2, 0])) + return [hole1, hole2, hole3, hole4] + + +def test_delaunay_triangulation(boundary_polygon): + """Test basic Delaunay triangulation of points.""" + points = np.array(boundary_polygon.points) + F = delaunay_triangulation(points) + + assert isinstance(F, np.ndarray) + assert F.shape[1] == 3 # triangles + assert len(F) > 0 + + +def test_conforming_delaunay_triangulation(boundary_polygon, hole_polygons): + """Test constrained Delaunay triangulation with holes.""" + V, F = conforming_delaunay_triangulation( + boundary_polygon, + holes=hole_polygons, + ) + + assert isinstance(V, np.ndarray) + assert isinstance(F, np.ndarray) + assert V.shape[1] == 3 # 3D points + assert F.shape[1] == 3 # triangles + assert len(V) > 0 + assert len(F) > 0 + + # Check that we have more vertices than the input due to conforming constraints + input_points = len(boundary_polygon.points) + sum(len(h.points) for h in hole_polygons) + assert len(V) >= input_points + + +def test_refined_delaunay_mesh(boundary_polygon, hole_polygons): + """Test refined Delaunay mesh with quality constraints.""" + maxlength = 0.5 + V, F = refined_delaunay_mesh( + boundary_polygon, + holes=hole_polygons, + maxlength=maxlength, + is_optimized=True, + ) + + assert isinstance(V, np.ndarray) + assert isinstance(F, np.ndarray) + assert V.shape[1] == 3 # 3D points + assert F.shape[1] == 3 # triangles + assert len(V) > 0 + assert len(F) > 0 + + # Check refinement created more vertices than CDT + V_cdt, _ = conforming_delaunay_triangulation(boundary_polygon, holes=hole_polygons) + assert len(V) > len(V_cdt) + + # Check edge lengths + edges = [] + for face in F: + for i in range(3): + edge = sorted([face[i], face[(i + 1) % 3]]) + if edge not in edges: + edges.append(edge) + + for v1, v2 in edges: + edge_length = np.linalg.norm(V[v1] - V[v2]) + assert edge_length <= maxlength * 1.1 # Allow 10% tolerance