├── .github └── workflows │ ├── build_conda.yml │ ├── build_osx.yml │ ├── build_rocky.yml │ └── build_win.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── CMakeLists.txt.in ├── LICENSE ├── README.md ├── VVERSION ├── collapse.h ├── conda ├── bld.bat ├── build.sh ├── conda_build_config.yaml ├── environment.yml └── meta.yaml ├── dim3.h ├── edge_detect.h ├── factory.cpp ├── factory.h ├── fill_gaps.h ├── img ├── duplex_outer_voxel_pointcloud.png ├── evacuation_distance.png ├── exterior_interior.png └── heights.png ├── json_logger.cpp ├── json_logger.h ├── lru_cache.h ├── memoized_traversal.h ├── module └── __init__.py ├── offset.h ├── polyfill.cpp ├── polyfill.h ├── processor.h ├── progress.h ├── progress_writer.cpp ├── progress_writer.h ├── pyproject.toml ├── python └── voxec │ ├── __init__.py │ └── __main__.py ├── resample.h ├── shift.h ├── storage.cpp ├── storage.h ├── surface_count.sh ├── sweep.h ├── tests ├── fixtures │ ├── covering.ifc │ ├── demo2.ifc │ ├── duplex.ifc │ ├── duplex_wall.ifc │ ├── schependom_foundation.ifc │ ├── voxelfile1.txt │ ├── voxelfile2.txt │ ├── voxelfile3.txt │ ├── voxelfile4.txt │ ├── voxelfile5.txt │ ├── voxelfile7.txt │ ├── voxelfile8.txt │ └── voxelfilec.txt ├── test_boolean.cpp ├── test_box_surface.cpp ├── test_collapse.cpp ├── test_covering.cpp ├── test_dimensionality_estimate.cpp ├── test_edges.cpp ├── test_fill_gaps.cpp ├── test_foundation.cpp ├── test_keep_neighbours.cpp ├── test_memmap.cpp ├── test_memoized_traversal.cpp ├── test_normal.cpp ├── test_offset.cpp ├── test_parser.cpp ├── test_parser_assert.cpp ├── test_parser_prop_filter.cpp ├── test_parser_resample.cpp ├── test_pca.cpp ├── test_polyfill.cpp ├── test_resample.cpp ├── test_space_ids.cpp ├── test_sweep_max.cpp ├── test_traversal.cpp ├── test_traversal_float.cpp ├── test_validate.cpp ├── test_vec_n.cpp ├── test_volume.cpp ├── test_voxelizer.cpp └── test_wall.cpp ├── traversal.h ├── tribox3.cpp ├── util.h ├── volume.h ├── voxec.cpp ├── voxec.h ├── voxec_main.cpp ├── voxelfile.h ├── voxelizer.h ├── wrap └── wrapper.i └── writer.h /.github/workflows/build_conda.yml: -------------------------------------------------------------------------------- 1 | # Approach taken from https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/.github/workflows/ci-ifcopenshell-conda-daily.yml 2 | # Original work by Krande 3 | 4 | name: ci 5 | 6 | on: [push] 7 | 8 | jobs: 9 | activate: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set env 13 | run: echo ok go 14 | 15 | test: 16 | name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }} 17 | needs: activate 18 | runs-on: ${{ matrix.platform.distver }} 19 | defaults: 20 | run: 21 | shell: bash -l {0} 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | pyver: [ 26 | { name: py312, distver: '3.12'} 27 | ] 28 | platform: [ 29 | { name: Windows, distver: windows-2019 }, 30 | { name: Linux, distver: ubuntu-20.04 }, 31 | { name: macOS, distver: macos-13 } 32 | ] 33 | steps: 34 | - uses: actions/checkout@v3 35 | with: 36 | submodules: recursive 37 | - name: Download MacOSX SDK 38 | if: ${{ matrix.platform.name == 'macOS' }} 39 | run: | 40 | curl -o MacOSX10.13.sdk.tar.xz -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz && \ 41 | tar xf MacOSX10.13.sdk.tar.xz && \ 42 | sudo mv -v MacOSX10.13.sdk /opt/ && \ 43 | ls /opt/ 44 | - uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba 45 | with: 46 | environment-name: build-env 47 | cache-environment: true 48 | condarc: | 49 | channels: 50 | - conda-forge 51 | channel_priority: strict 52 | create-args: >- 53 | python=3.11 54 | anaconda-client 55 | boa 56 | - name: build, test and upload ifcopenshell 57 | run: | 58 | conda mambabuild . --python ${{ matrix.pyver.distver }} -c conda-forge --token ${{ secrets.ANACONDA_TOKEN }} --user ifcopenshell 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/build_osx.yml: -------------------------------------------------------------------------------- 1 | name: Build Voxelization Toolkit OSX 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_voxec: 8 | runs-on: ${{ matrix.runner }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | include: 13 | - os: macos 14 | runner: macos-13 15 | arch: x64 16 | oldarch: 17 | cpuarch: x86_64 18 | macosx_ver: '10_15' 19 | - os: macos 20 | runner: macos-14 21 | arch: arm64 22 | oldarch: m1 23 | cpuarch: arm64 24 | macosx_ver: '11_0' 25 | 26 | steps: 27 | - name: Checkout Repository 28 | uses: actions/checkout@v3 29 | with: 30 | submodules: recursive 31 | 32 | - name: Checkout IfcOpenShell 33 | uses: actions/checkout@v3 34 | with: 35 | repository: IfcOpenShell/IfcOpenShell 36 | path: IfcOpenShell 37 | token: ${{ secrets.BUILD_REPO_TOKEN }} 38 | submodules: recursive 39 | 40 | - name: Checkout Build Repository 41 | uses: actions/checkout@v3 42 | with: 43 | repository: IfcOpenShell/build-outputs 44 | path: IfcOpenShell/build 45 | ref: ${{ matrix.os }}-${{ matrix.arch }} 46 | lfs: true 47 | token: ${{ secrets.BUILD_REPO_TOKEN }} 48 | 49 | - name: Set up Python 50 | uses: actions/setup-python@v4 51 | with: 52 | python-version: '3.x' 53 | 54 | - name: Install Dependencies 55 | run: | 56 | brew update 57 | brew install git bison autoconf automake libffi cmake findutils swig 58 | echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH 59 | 60 | - name: Unpack IfcOpenShell Dependencies 61 | run: | 62 | install_root=$(find ./IfcOpenShell/build -maxdepth 4 -name install) 63 | find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; 64 | 65 | - name: Relocate python libraries 66 | run: | 67 | base=$(find $PWD/IfcOpenShell/build -maxdepth 4 -name install) 68 | for pybin in $(find $base -name python3); do 69 | l="$(ls $(dirname $pybin)/../lib/libpython*.dylib 2>/dev/null | head -n 1)" 70 | o="$(otool -L "$pybin" | awk '/libpython/ {print $1; exit}')" 71 | n="@executable_path/../lib/$(basename "$l")" 72 | install_name_tool -change "$o" "$n" "$pybin" 73 | otool -L "$pybin" 74 | done 75 | 76 | - name: Debug 77 | run: | 78 | base=$(find $PWD/IfcOpenShell/build -maxdepth 4 -name install) 79 | $base/python-3.9.11/bin/python3 --version 80 | $base/python-3.9.11/bin/python3 -c 'import sysconfig; print(sysconfig.get_path("platlib"))' 81 | 82 | - name: Run IfcOpenShell Build Script 83 | run: | 84 | if [ "${{ matrix.os }}" == "macos" ]; then 85 | DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE 86 | fi 87 | CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./IfcOpenShell/nix/build-all.py --diskcleanup IfcConvert python 88 | 89 | - name: Compile voxec 90 | run: | 91 | export CFLAGS="-Wl,-flat_namespace,-undefined,suppress" 92 | export CXXFLAGS="-Wl,-flat_namespace,-undefined,suppress" 93 | export LDFLAGS="-Wl,-flat_namespace,-undefined,suppress" 94 | base=$(find $PWD/IfcOpenShell/build -maxdepth 4 -name install) 95 | mkdir build 96 | cd build 97 | for python_root in $base/python-*; do 98 | [ -f CMakeCache.txt ] && rm CMakeCache.txt 99 | cmake .. \ 100 | "-DCMAKE_BUILD_TYPE=Release" \ 101 | "-DENABLE_PYTHON_WRAPPER=On" \ 102 | "-DENABLE_TESTS=Off" \ 103 | "-DUSE_BUILD_SCRIPT_OUTPUT=Off" \ 104 | "-DBoost_USE_STATIC_LIBS=On" \ 105 | "-DBoost_NO_BOOST_CMAKE=On" \ 106 | "-DCMAKE_INSTALL_PREFIX=../install" \ 107 | "-DIFC_INCLUDE_DIR=$base/ifcopenshell/include" \ 108 | "-DIFC_LIBRARY_DIR=$base/ifcopenshell/lib" \ 109 | "-DOCC_INCLUDE_DIR=$base/occt-7.8.1/include/opencascade" \ 110 | "-DOCC_LIBRARY_DIR=$base/occt-7.8.1/lib" \ 111 | "-DPython_EXECUTABLE=$python_root/bin/python3" \ 112 | "-DBOOST_ROOT=$base/boost-1.86.0" \ 113 | "-DGMP_LIBRARY_DIR=$base/gmp-6.2.1/lib" \ 114 | "-DMPFR_LIBRARY_DIR=$base/mpfr-3.1.6/lib" 115 | make -j4 install 116 | done 117 | 118 | - name: Package .zip archives 119 | run: | 120 | VERSION=`cat VVERSION` 121 | base=$(find $PWD/IfcOpenShell/build -maxdepth 4 -name install) 122 | mkdir -p $GITHUB_WORKSPACE/package 123 | cd $GITHUB_WORKSPACE/package 124 | cp $GITHUB_WORKSPACE/pyproject.toml . 125 | sed -i~ s/.VERSION./$VERSION/g pyproject.toml 126 | 127 | for python_root in $base/python-*; do 128 | PATH=$python_root/bin:$PATH python3 -m pip install --upgrade pip setuptools wheel 129 | PATH=$python_root/bin:$PATH python3 -m pip install build delocate 130 | 131 | mm=`$python_root/bin/python3 --version | cut -c8- | cut -d. -f 1,2 | tr -d '.'` 132 | mdm=`$python_root/bin/python3 --version | cut -c8- | cut -d. -f 1,2` 133 | cp -R $python_root/lib/python$mdm/site-packages/voxec . 134 | echo '[bdist_wheel]' > setup.cfg 135 | echo 'python-tag = cp'$mm >> setup.cfg 136 | 137 | PATH=$python_root/bin:$PATH python3 -m build -w 138 | PATH=$python_root/bin:$PATH delocate-wheel dist/*.whl -w audited 139 | mv audited/voxec-$VERSION-cp$mm-none-any.whl audited/voxec-$VERSION-cp$mm-none-macosx_${{ matrix.macosx_ver }}_${{ matrix.cpuarch }}.whl 140 | 141 | rm -r voxec dist/*.whl 142 | done 143 | 144 | - name: Publish a Python distribution to PyPI 145 | uses: ortega2247/pypi-upload-action@master 146 | with: 147 | user: __token__ 148 | password: ${{ secrets.PYPI_API_TOKEN }} 149 | packages_dir: package/audited 150 | -------------------------------------------------------------------------------- /.github/workflows/build_rocky.yml: -------------------------------------------------------------------------------- 1 | name: Build Voxelization Toolkit Linux 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_voxec: 8 | runs-on: ubuntu-20.04 9 | container: rockylinux:8 10 | 11 | steps: 12 | - name: Install Dependencies 13 | run: | 14 | yum update -y 15 | yum install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 \ 16 | bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \ 17 | sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \ 18 | readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \ 19 | findutils swig 20 | python3 -m pip install --upgrade pip 21 | ln -s /usr/bin/python3 /usr/bin/python 22 | 23 | - name: Checkout Repository 24 | uses: actions/checkout@v3 25 | with: 26 | submodules: recursive 27 | 28 | - name: Checkout IfcOpenShell 29 | uses: actions/checkout@v3 30 | with: 31 | repository: IfcOpenShell/IfcOpenShell 32 | path: IfcOpenShell 33 | token: ${{ secrets.BUILD_REPO_TOKEN }} 34 | submodules: recursive 35 | 36 | - name: Checkout Build Repository 37 | uses: actions/checkout@v3 38 | with: 39 | repository: IfcOpenShell/build-outputs 40 | path: IfcOpenShell/build 41 | ref: rockylinux8-x64 42 | lfs: true 43 | token: ${{ secrets.BUILD_REPO_TOKEN }} 44 | 45 | - name: Unpack IfcOpenShell Dependencies 46 | run: | 47 | install_root=$(find ./IfcOpenShell/build -maxdepth 4 -name install) 48 | find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; 49 | 50 | # Not supported on docker 51 | # - name: ccache 52 | # uses: hendrikmuhs/ccache-action@v1 53 | # with: 54 | # key: ${GITHUB_WORKFLOW}-rockylinux8-x64 55 | 56 | - name: Run IfcOpenShell Build Script 57 | run: | 58 | CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./IfcOpenShell/nix/build-all.py --diskcleanup IfcConvert python 59 | 60 | - name: Compile voxec 61 | run: | 62 | base=$GITHUB_WORKSPACE/IfcOpenShell/build/Linux/x86_64/install 63 | mkdir build 64 | cd build 65 | for python_root in $base/python-*; do 66 | [ -f CMakeCache.txt ] && rm CMakeCache.txt 67 | cmake .. \ 68 | "-DCMAKE_BUILD_TYPE=Release" \ 69 | "-DENABLE_PYTHON_WRAPPER=On" \ 70 | "-DENABLE_TESTS=Off" \ 71 | "-DUSE_BUILD_SCRIPT_OUTPUT=Off" \ 72 | "-DBoost_USE_STATIC_LIBS=On" \ 73 | "-DBoost_NO_BOOST_CMAKE=On" \ 74 | "-DCMAKE_INSTALL_PREFIX=../install" \ 75 | "-DIFC_INCLUDE_DIR=$base/ifcopenshell/include" \ 76 | "-DIFC_LIBRARY_DIR=$base/ifcopenshell/lib64" \ 77 | "-DOCC_INCLUDE_DIR=$base/occt-7.8.1/include/opencascade" \ 78 | "-DOCC_LIBRARY_DIR=$base/occt-7.8.1/lib" \ 79 | "-DPython_EXECUTABLE=$python_root/bin/python3" \ 80 | "-DBOOST_ROOT=$base/boost-1.86.0" \ 81 | "-DGMP_LIBRARY_DIR=$base/gmp-6.2.1/lib" \ 82 | "-DMPFR_LIBRARY_DIR=$base/mpfr-3.1.6/lib" 83 | make -j4 install 84 | done 85 | 86 | - name: Package .zip archives 87 | run: | 88 | VERSION=`cat VVERSION` 89 | base=$GITHUB_WORKSPACE/IfcOpenShell/build/Linux/x86_64/install 90 | mkdir -p $GITHUB_WORKSPACE/package 91 | cd $GITHUB_WORKSPACE/package 92 | cp $GITHUB_WORKSPACE/pyproject.toml . 93 | sed -i s/.VERSION./$VERSION/g pyproject.toml 94 | 95 | for python_root in $base/python-*; do 96 | PATH=$python_root/bin:$PATH python3 -m pip install --upgrade pip setuptools wheel 97 | PATH=$python_root/bin:$PATH python3 -m pip install build auditwheel patchelf 98 | 99 | mm=`$python_root/bin/python3 --version | cut -c8- | cut -d. -f 1,2 | tr -d '.'` 100 | mdm=`$python_root/bin/python3 --version | cut -c8- | cut -d. -f 1,2` 101 | cp -R $python_root/lib/python$mdm/site-packages/voxec . 102 | echo '[bdist_wheel]' > setup.cfg 103 | echo 'python-tag = cp'$mm >> setup.cfg 104 | 105 | PATH=$python_root/bin:$PATH python3 -m build -w 106 | PATH=$python_root/bin:$PATH python3 -m auditwheel repair --plat manylinux_2_28_x86_64 dist/*.whl -w audited 107 | 108 | rm -r voxec dist/*.whl 109 | done 110 | 111 | - name: Publish a Python distribution to PyPI 112 | uses: ortega2247/pypi-upload-action@master 113 | with: 114 | user: __token__ 115 | password: ${{ secrets.PYPI_API_TOKEN }} 116 | packages_dir: package/audited 117 | -------------------------------------------------------------------------------- /.github/workflows/build_win.yml: -------------------------------------------------------------------------------- 1 | name: Build Voxelization Toolkit Windows 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_voxec: 8 | runs-on: windows-2019 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | python: ['3.9.11', '3.10.3', '3.11.8', '3.12.1', '3.13.0'] 13 | arch: ['x64'] 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Checkout IfcOpenShell 21 | uses: actions/checkout@v3 22 | with: 23 | repository: IfcOpenShell/IfcOpenShell 24 | path: IfcOpenShell 25 | token: ${{ secrets.BUILD_REPO_TOKEN }} 26 | submodules: recursive 27 | 28 | - name: Checkout Build Repository 29 | uses: actions/checkout@v3 30 | with: 31 | repository: IfcOpenShell/build-outputs 32 | path: IfcOpenShell\_deps-vs2019-x64-installed 33 | ref: windows-${{ matrix.arch }} 34 | lfs: true 35 | token: ${{ secrets.BUILD_REPO_TOKEN }} 36 | 37 | - name: Install Dependencies 38 | run: | 39 | choco install -y sed 7zip.install awscli 40 | 41 | - name: Install Python 42 | run: | 43 | $installer = "python-${{ matrix.python }}-amd64.exe" 44 | $url = "https://www.python.org/ftp/python/${{ matrix.python }}/$installer" 45 | Invoke-WebRequest -Uri $url -OutFile $installer 46 | Start-Process -Wait -FilePath .\$installer -ArgumentList '/quiet InstallAllUsers=0 PrependPath=0 Include_test=0 TargetDir=C:\Python\${{ matrix.python }}' 47 | Remove-Item .\$installer 48 | 49 | - name: Unpack IfcOpenShell Dependencies 50 | run: | 51 | cd IfcOpenShell\_deps-vs2019-x64-installed 52 | Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object { 53 | 7z x $_.FullName 54 | } 55 | 56 | - name: Run IfcOpenShell Build Script 57 | shell: cmd 58 | run: | 59 | setlocal EnableDelayedExpansion 60 | SET PYTHON_VERSION=${{ matrix.python }} 61 | for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do ( 62 | set PY_VER_MAJOR_MINOR=%%a%%b 63 | ) 64 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" 65 | SET IFCOS_INSTALL_PYTHON=FALSE 66 | cd IfcOpenShell\win 67 | echo y | call build-deps.cmd vs2019-x64 Release 68 | SET PYTHONHOME=C:\Python\${{ matrix.python }} 69 | call run-cmake.bat vs2019-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON 70 | call install-ifcopenshell.bat vs2019-x64 Release 71 | 72 | - name: Compile voxec 73 | run: | 74 | mkdir build 75 | cd build 76 | $base=(Resolve-Path ../ifcopenshell/).Path 77 | cmake .. ` 78 | "-DCMAKE_BUILD_TYPE=Release" ` 79 | "-DENABLE_PYTHON_WRAPPER=On" ` 80 | "-DENABLE_TESTS=Off" ` 81 | "-DUSE_BUILD_SCRIPT_OUTPUT=Off" ` 82 | "-DBoost_USE_STATIC_LIBS=On" ` 83 | "-DBoost_NO_BOOST_CMAKE=On" ` 84 | "-DCMAKE_INSTALL_PREFIX=../install" ` 85 | "-DIFC_INCLUDE_DIR=$base/_installed-vs2019-x64/include" ` 86 | "-DIFC_LIBRARY_DIR=$base/_installed-vs2019-x64/lib" ` 87 | "-DOCC_INCLUDE_DIR=$base/_deps-vs2019-x64-installed/opencascade-7.8.1/inc" ` 88 | "-DOCC_LIBRARY_DIR=$base/_deps-vs2019-x64-installed/opencascade-7.8.1/win64/lib" ` 89 | "-DPython_EXECUTABLE=C:\Python\${{ matrix.python }}\python.exe" ` 90 | "-DBOOST_ROOT=$base/_deps/boost_1_86_0" ` 91 | "-DBOOST_LIBRARYDIR=$base/_deps/boost_1_86_0/stage/vs2019-x64/lib" ` 92 | "-DGMP_LIBRARY_DIR=$base/_deps-vs2019-x64-installed/mpir" ` 93 | "-DMPFR_LIBRARY_DIR=$base/_deps-vs2019-x64-installed/mpfr" ` 94 | "-DSWIG_EXECUTABLE=$base/_deps-vs2019-x64-installed/swigwin/swig.exe" 95 | cmake --build . --target install --config Release 96 | 97 | - name: Package .zip Archives 98 | run: | 99 | $VERSION = Get-Content VVERSION -Raw 100 | $pyVersion = "${{ matrix.python }}" 101 | $pyVersionMajor = ($pyVersion -split '\.')[0..1] -join '' 102 | $OUTPUT_DIR = "$env:GITHUB_WORKSPACE\package" 103 | New-Item -ItemType Directory -Force -Path $OUTPUT_DIR 104 | 105 | cd $OUTPUT_DIR 106 | 107 | cp -r C:\Python\${{ matrix.python }}\Lib\site-packages\voxec . 108 | cp $env:GITHUB_WORKSPACE\pyproject.toml . 109 | (Get-Content pyproject.toml).Replace('[VERSION]', $VERSION) | Set-Content pyproject.toml 110 | 111 | C:\Python\${{ matrix.python }}\python.exe -m pip install build 112 | C:\Python\${{ matrix.python }}\python.exe -m build -w 113 | mv .\dist\voxec-${VERSION}-py3-none-any.whl .\dist\voxec-${VERSION}-py${pyVersionMajor}-none-win_amd64.whl 114 | 115 | - name: Publish a Python distribution to PyPI 116 | uses: ortega2247/pypi-upload-action@master 117 | with: 118 | user: __token__ 119 | password: ${{ secrets.PYPI_API_TOKEN }} 120 | packages_dir: package\dist 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build/ 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/eigen"] 2 | path = 3rdparty/eigen 3 | url = https://gitlab.com/libeigen/eigen 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | language: cpp 3 | sudo: required 4 | compiler: 5 | - gcc 6 | - clang 7 | 8 | before_install: 9 | - sudo apt-get update -qq 10 | 11 | install: 12 | - | 13 | sudo apt-get install -y \ 14 | libocct-* \ 15 | libboost-all-dev 16 | 17 | script: 18 | - mkdir build && cd build 19 | - | 20 | cmake .. \ 21 | -DIFC_SUPPORT=Off \ 22 | -DOCC_INCLUDE_DIR=/usr/include/opencascade \ 23 | -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \ 24 | - make -j2 25 | - make CTEST_OUTPUT_ON_FAILURE=1 test 26 | -------------------------------------------------------------------------------- /CMakeLists.txt.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.2) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(googletest 7 | GIT_REPOSITORY https://github.com/google/googletest.git 8 | GIT_TAG v1.13.0 9 | SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" 10 | BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "" 15 | ) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 The open source BIM collective 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /VVERSION: -------------------------------------------------------------------------------- 1 | 0.4.2 -------------------------------------------------------------------------------- /collapse.h: -------------------------------------------------------------------------------- 1 | #ifndef COLLAPSE_H 2 | #define COLLAPSE_H 3 | 4 | #include "storage.h" 5 | 6 | class collapse { 7 | public: 8 | abstract_voxel_storage* until = nullptr; 9 | boost::optional max_depth; 10 | 11 | regular_voxel_storage* operator()(abstract_voxel_storage* storage, int dx, int dy, int dz) { 12 | int d[3] = { dx, dy, dz }; 13 | int nonzero = 0; 14 | int D = -1; 15 | for (int i = 0; i < 3; ++i) { 16 | if (d[i] != 0) { 17 | nonzero++; 18 | D = i; 19 | } 20 | } 21 | 22 | if (nonzero != 1 && D != 2 && dz != -1) { 23 | throw std::runtime_error("Only collapse over negative Z is implemented"); 24 | } 25 | 26 | regular_voxel_storage* collapsed = (regular_voxel_storage*)storage->empty_copy(); 27 | auto bounds = storage->bounds(); 28 | 29 | size_t i0, j0, k0, i1, j1, k1; 30 | 31 | bounds[0].tie(i0, j0, k0); 32 | bounds[1].tie(i1, j1, k1); 33 | 34 | vec_n<3, size_t> ijk; 35 | ; for (ijk.get(0) = i0; ijk.get(0) <= i1; ++ijk.get(0)) { 36 | for (ijk.get(1) = j0; ijk.get(1) <= j1; ++ijk.get(1)) { 37 | for (ijk.get(2) = k0; ijk.get(2) <= k1;) { 38 | if (storage->Get(ijk)) { 39 | collapsed->Set(ijk); 40 | while (storage->Get(ijk)) { 41 | if (++ijk.get(2) > k1) { 42 | break; 43 | } 44 | } 45 | } else { 46 | ++ijk.get(2); 47 | } 48 | } 49 | } 50 | } 51 | 52 | return collapsed; 53 | } 54 | }; 55 | 56 | // @todo merge with above 57 | class collapse_count { 58 | public: 59 | abstract_voxel_storage* until = nullptr; 60 | boost::optional max_depth; 61 | 62 | regular_voxel_storage* operator()(abstract_voxel_storage* storage, int dx, int dy, int dz) { 63 | int d[3] = { dx, dy, dz }; 64 | int nonzero = 0; 65 | int D = -1; 66 | for (int i = 0; i < 3; ++i) { 67 | if (d[i] != 0) { 68 | nonzero++; 69 | D = i; 70 | } 71 | } 72 | 73 | if (nonzero != 1 && D != 2 && dz != -1) { 74 | throw std::runtime_error("Only collapse over negative Z is implemented"); 75 | } 76 | 77 | // @todo maybe create some static instances of these types, or in a map? 78 | voxel_uint32_t vut; 79 | regular_voxel_storage* collapsed = (regular_voxel_storage*)storage->empty_copy_as(&vut); 80 | auto bounds = storage->bounds(); 81 | 82 | size_t i0, j0, k0, i1, j1, k1; 83 | 84 | bounds[0].tie(i0, j0, k0); 85 | bounds[1].tie(i1, j1, k1); 86 | 87 | uint32_t count = 0; 88 | 89 | vec_n<3, size_t> ijk; 90 | ; for (ijk.get(0) = i0; ijk.get(0) <= i1; ++ijk.get(0)) { 91 | for (ijk.get(1) = j0; ijk.get(1) <= j1; ++ijk.get(1)) { 92 | for (ijk.get(2) = k0; ijk.get(2) <= k1;) { 93 | if (storage->Get(ijk)) { 94 | 95 | count = 0; 96 | 97 | auto ijk_0 = ijk; 98 | 99 | while (storage->Get(ijk)) { 100 | ++count; 101 | if (++ijk.get(2) > k1) { 102 | break; 103 | } 104 | } 105 | 106 | collapsed->Set(ijk_0, &count); 107 | } 108 | else { 109 | ++ijk.get(2); 110 | } 111 | } 112 | } 113 | } 114 | 115 | return collapsed; 116 | } 117 | }; 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /conda/bld.bat: -------------------------------------------------------------------------------- 1 | mkdir build && cd build 2 | 3 | set MY_PY_VER=%PY_VER:.=% 4 | 5 | cmake -G "Visual Studio 16 2019" -A x64 ^ 6 | -DCMAKE_BUILD_TYPE:STRING=Release ^ 7 | -DCMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^ 8 | -DCMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^ 9 | -DCMAKE_SYSTEM_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^ 10 | -DCMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE=x64 ^ 11 | -DENABLE_PYTHON_WRAPPER=On ^ 12 | ^ 13 | -DENABLE_TESTS=Off ^ 14 | -DUSE_BUILD_SCRIPT_OUTPUT=Off ^ 15 | -DUSE_STATIC_MSVC_RUNTIME=Off ^ 16 | -DBoost_USE_STATIC_LIBS=Off ^ 17 | -DIFC_INCLUDE_DIR=%LIBRARY_PREFIX%\include ^ 18 | -DIFC_LIBRARY_DIR=%LIBRARY_PREFIX%\lib ^ 19 | -DOCC_INCLUDE_DIR=%LIBRARY_PREFIX%\include\opencascade ^ 20 | -DOCC_LIBRARY_DIR=%LIBRARY_PREFIX%\lib ^ 21 | -DPYTHON_INCLUDE_DIR=%PREFIX%\include ^ 22 | -DPYTHON_EXECUTABLE:FILEPATH=%PREFIX%\python.exe ^ 23 | -DPYTHON_LIBRARY:FILEPATH="%PREFIX%"\libs/python%MY_PY_VER%.lib ^ 24 | %SRC_DIR% 25 | 26 | if errorlevel 1 exit 1 27 | 28 | :: Build and install 29 | cmake --build . --target INSTALL --config Release 30 | 31 | if errorlevel 1 exit 1 32 | -------------------------------------------------------------------------------- /conda/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a CMAKE_PLATFORM_FLAGS 4 | 5 | cmake -G Ninja \ 6 | -DCMAKE_BUILD_TYPE=Release \ 7 | -DCMAKE_INSTALL_PREFIX=$PREFIX \ 8 | ${CMAKE_PLATFORM_FLAGS[@]} \ 9 | -DCMAKE_PREFIX_PATH=$PREFIX \ 10 | -DCMAKE_SYSTEM_PREFIX_PATH=$PREFIX \ 11 | -DENABLE_PYTHON_WRAPPER=On \ 12 | \ 13 | -DUSE_BUILD_SCRIPT_OUTPUT=Off \ 14 | -DUSE_STATIC_MSVC_RUNTIME=Off \ 15 | -DBoost_USE_STATIC_LIBS=Off \ 16 | -DIFC_INCLUDE_DIR=$PREFIX/include \ 17 | -DIFC_LIBRARY_DIR=$PREFIX/lib \ 18 | -DOCC_INCLUDE_DIR=$PREFIX/include/opencascade \ 19 | -DOCC_LIBRARY_DIR=$PREFIX/lib \ 20 | -DPython_EXECUTABLE:FILEPATH=$PYTHON \ 21 | . 22 | 23 | ninja -j2 24 | 25 | ninja install 26 | -------------------------------------------------------------------------------- /conda/conda_build_config.yaml: -------------------------------------------------------------------------------- 1 | CONDA_BUILD_SYSROOT: 2 | - /opt/MacOSX10.13.sdk # [osx] -------------------------------------------------------------------------------- /conda/environment.yml: -------------------------------------------------------------------------------- 1 | name: conda-build 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - conda-build 6 | - conda-verify 7 | - anaconda-client 8 | - ninja 9 | - doxygen 10 | - ripgrep 11 | - pip 12 | - pip: 13 | - --extra-index-url https://lief.s3-website.fr-par.scw.cloud/latest 14 | - lief==0.13.0 15 | -------------------------------------------------------------------------------- /conda/meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "voxelization_toolkit" %} 2 | {% set version = "0.5.1" %} 3 | 4 | package: 5 | name: {{ name }} 6 | version: {{ version }} 7 | 8 | source: 9 | path: .. 10 | 11 | build: 12 | binary_relocation: false [osx] 13 | 14 | requirements: 15 | build: 16 | - {{ compiler('cxx') }} 17 | - ninja 18 | - cmake 19 | - ifcopenshell =v0.7.0 20 | 21 | host: 22 | - python 23 | - ifcopenshell =v0.7.0 24 | 25 | run: [] 26 | 27 | about: 28 | home: https://github.com/opensourceBIM/voxelization_toolkit 29 | license: MIT 30 | license_file: LICENSE 31 | summary: 'Voxelization Toolkit for (IFC) Building Models' 32 | description: | 33 | Voxelization Toolkit for (IFC) Building Models 34 | doc_url: https://github.com/opensourceBIM/voxelization_toolkit 35 | dev_url: https://github.com/opensourceBIM/voxelization_toolkit 36 | -------------------------------------------------------------------------------- /edge_detect.h: -------------------------------------------------------------------------------- 1 | #ifndef EDGE_DETECT_H 2 | #define EDGE_DETECT_H 3 | 4 | #include "storage.h" 5 | #include "progress_writer.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | // A post process (ie. subtype of post_process) is a functor that takes a voxel storage 12 | // applies a procedure (such as edge detection, filling gaps, extrusion, ...) and returns 13 | // a new voxel storage with the operation applied. 14 | class post_process { 15 | protected: 16 | boost::optional> progress_callback; 17 | boost::optional> application_progress_callback; 18 | 19 | void progress(float f) { 20 | if (application_progress_callback) { 21 | (*application_progress_callback)(f); 22 | } 23 | if (!silent) { 24 | if (progress_callback) { 25 | (*progress_callback)(static_cast(f * 100.)); 26 | } 27 | } 28 | } 29 | 30 | public: 31 | bool silent = false; 32 | 33 | void set_progress_callback(boost::optional> cb) { 34 | progress_callback = cb; 35 | } 36 | 37 | void set_progress_callback(boost::optional> cb) { 38 | application_progress_callback = cb; 39 | } 40 | 41 | virtual regular_voxel_storage* operator()(regular_voxel_storage*) = 0; 42 | virtual ~post_process() {} 43 | }; 44 | 45 | // A threaded post process is a special kind of post process that can be parallelized 46 | // by taking applying the operation in parallel to multiple subregions of the voxel 47 | // storage. These are almost always local operations as opposed to the queue based 48 | // operations in traversal.h 49 | template 50 | class threaded_post_process : public post_process { 51 | private: 52 | size_t n_; 53 | public: 54 | threaded_post_process(size_t n) 55 | : n_(n > 0 ? n : std::thread::hardware_concurrency()) 56 | {} 57 | 58 | regular_voxel_storage* operator()(regular_voxel_storage* storage) { 59 | progress_writer p(typeid(T).name(), silent); 60 | 61 | std::vector ts; 62 | std::vector results(n_); 63 | ts.reserve(n_); 64 | auto progress = p.thread(n_); 65 | progress.application_progress_callback = application_progress_callback; 66 | 67 | size_t x0 = 0; 68 | 69 | // std::cerr << std::endl << "full " << storage->bounds()[0].format() << " - " << storage->bounds()[1].format() << std::endl; 70 | // std::cerr << " count " << storage->count() << std::endl; 71 | 72 | if (n_ == 1) { 73 | T t; 74 | t.set_progress_callback(std::function([&progress](int p) { 75 | progress(0, p); 76 | })); 77 | results[0] = t(storage); 78 | } else { 79 | // Create regions 80 | std::vector regions; 81 | regions.reserve(n_); 82 | for (size_t i = 0; i < n_; ++i) { 83 | regions.push_back(voxel_region::make(storage, i, n_)); 84 | // std::cerr << "region " << i << " " << regions.back()->bounds()[0].format() << " - " << regions.back()->bounds()[1].format() << std::endl; 85 | // std::cerr << " count " << regions.back()->count() << std::endl; 86 | } 87 | 88 | auto is_chunked = dynamic_cast(storage); 89 | if (is_chunked) { 90 | is_chunked->lock_bounds(); 91 | } 92 | 93 | // Launch threads 94 | for (size_t i = 0; i < n_; ++i) { 95 | ts.emplace_back(std::thread([this, i, ®ions, &results, &progress]() { 96 | T t; 97 | t.set_progress_callback(std::function([&progress, i](int p) { 98 | progress(i, p); 99 | })); 100 | // @todo: on nix we sometimes get an empty result back. Is this due to the mutable bounds_ in abstract abstract_voxel_storage? 101 | results[i] = t(regions[i]); 102 | })); 103 | } 104 | 105 | // Wait for completion 106 | for (auto jt = ts.begin(); jt != ts.end(); ++jt) { 107 | jt->join(); 108 | } 109 | 110 | if (is_chunked) { 111 | is_chunked->unlock_bounds(); 112 | } 113 | } 114 | 115 | // std::cerr << std::endl; 116 | 117 | regular_voxel_storage* first = 0; 118 | for (auto jt = results.begin(); jt != results.end(); ++jt) { 119 | if (*jt) { 120 | if (first == 0) { 121 | first = *jt; 122 | } else { 123 | // std::cerr << " + " << (*jt)->bounds()[0].format() << " - " << (*jt)->bounds()[1].format() << std::endl; 124 | first->boolean_union_inplace(*jt); 125 | delete *jt; 126 | } 127 | if (n_ > 1) { 128 | // std::cerr << "boolean union " << std::distance(results.begin(), jt) << " " << first->bounds()[0].format() << " - " << first->bounds()[1].format() << std::endl; 129 | } 130 | } 131 | } 132 | 133 | if (T::UNION_INPUT) { 134 | first->boolean_union_inplace(storage); 135 | // std::cerr << "boolean union final " << first->bounds()[0].format() << " - " << first->bounds()[1].format() << std::endl; 136 | } 137 | 138 | return first; 139 | } 140 | }; 141 | 142 | class edge_detect : public post_process { 143 | private: 144 | regular_voxel_storage* storage_; 145 | regular_voxel_storage* output_; 146 | bool* empty_chunks_; 147 | size_t chunk_size_; 148 | vec_n<3, size_t> num_chunks_; 149 | std::array, 2> extents_; 150 | 151 | bool get_(const vec_n<3, size_t>& pos) { 152 | if ((pos < extents_[0]).any()) { 153 | return false; 154 | } 155 | 156 | if ((pos > extents_[1]).any()) { 157 | return false; 158 | } 159 | 160 | if (chunk_size_) { 161 | if (empty_chunk(pos / chunk_size_)) { 162 | return false; 163 | } 164 | } 165 | 166 | return storage_->Get(pos); 167 | } 168 | 169 | void init_(regular_voxel_storage* storage) { 170 | storage_ = storage; 171 | output_ = (regular_voxel_storage*)storage_->empty_copy(); 172 | extents_ = storage->bounds(); 173 | 174 | chunked_voxel_storage* cvs; 175 | 176 | if ((cvs = dynamic_cast*>(storage))) { 177 | chunk_size_ = cvs->chunk_size(); 178 | num_chunks_ = storage_->extents().ceil_div(chunk_size_); 179 | size_t n = num_chunks_.get<0>() * num_chunks_.get<1>() * num_chunks_.get<2>(); 180 | empty_chunks_ = new bool[n] {false}; 181 | 182 | auto cs = cvs->empty_chunks(); 183 | for (auto& c : cs) { 184 | empty_chunk(c) = true; 185 | } 186 | } 187 | } 188 | 189 | bool& empty_chunk(const vec_n<3, size_t>& c) { 190 | return empty_chunks_[c.get(0) + (num_chunks_.get(0) * c.get(1)) + (num_chunks_.get(0) * num_chunks_.get(1) * c.get(2))]; 191 | } 192 | 193 | public: 194 | static const bool UNION_INPUT = false; 195 | 196 | edge_detect() 197 | : empty_chunks_(nullptr) 198 | , chunk_size_(0) {} 199 | 200 | ~edge_detect() { 201 | delete[] empty_chunks_; 202 | } 203 | 204 | regular_voxel_storage* operator()(regular_voxel_storage* storage) { 205 | init_(storage); 206 | 207 | size_t i0, i1, j0, j1, k0, k1; 208 | extents_[0].tie(i0, j0, k0); 209 | extents_[1].tie(i1, j1, k1); 210 | 211 | vec_n<3, size_t> ijk; 212 | for (long i = i0; i <= (long) i1; ++i) { 213 | ijk.get(0) = i; 214 | for (long j = j0; j <= (long)j1; ++j) { 215 | ijk.get(1) = j; 216 | bool inside = false; 217 | for (long k = k0; k <= (long) k1; ++k) { 218 | ijk.get(2) = k; 219 | 220 | 221 | bool some_set = false, some_unset = false; 222 | 223 | const vec_n<3> ijk = vec_n<3, long>(i, j, k).as(); 224 | 225 | if (!get_(ijk)) { 226 | continue; 227 | } 228 | 229 | for (int di = -1; di <= 1; ++di) { 230 | for (int dj = -1; dj <= 1; ++dj) { 231 | for (int dk = -1; dk <= 1; ++dk) { 232 | const vec_n<3> ijk0 = vec_n<3, long>(i + di, j + dj, k + dk).as(); 233 | if ((ijk0 == ijk).all()) { 234 | continue; 235 | } 236 | if (get_(ijk0)) { 237 | some_set = true; 238 | if (some_unset) { 239 | goto break_all; 240 | } 241 | } else { 242 | some_unset = true; 243 | if (some_set) { 244 | goto break_all; 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | break_all: 252 | if (some_set && some_unset) { 253 | output_->Set(ijk); 254 | } 255 | } 256 | } 257 | } 258 | 259 | return output_; 260 | } 261 | }; 262 | 263 | #endif -------------------------------------------------------------------------------- /factory.cpp: -------------------------------------------------------------------------------- 1 | #include "factory.h" 2 | 3 | #include "storage.h" 4 | 5 | boost::optional factory::mmap_; 6 | size_t factory::post_fix_; 7 | 8 | abstract_voxel_storage* factory::create(double x1, double y1, double z1, double d, int nx, int ny, int nz) { 9 | if (chunk_size_ && *chunk_size_) { 10 | if (mmap_) { 11 | return new memory_mapped_chunked_voxel_storage(x1, y1, z1, d, nx, ny, nz, *chunk_size_, mmap_filename()); 12 | } else { 13 | return new chunked_voxel_storage(x1, y1, z1, d, nx, ny, nz, *chunk_size_); 14 | } 15 | } else { 16 | return new continuous_voxel_storage(x1, y1, z1, d, nx, ny, nz); 17 | } 18 | } -------------------------------------------------------------------------------- /factory.h: -------------------------------------------------------------------------------- 1 | #ifndef FACTORY_H 2 | #define FACTORY_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | class abstract_voxel_storage; 11 | 12 | class factory { 13 | private: 14 | boost::optional chunk_size_; 15 | static boost::optional mmap_; 16 | static size_t post_fix_; 17 | 18 | static std::string thread_id() { 19 | std::stringstream ss; 20 | ss << std::this_thread::get_id(); 21 | return ss.str(); 22 | } 23 | public: 24 | factory& chunk_size() { 25 | chunk_size_ = boost::none; 26 | return *this; 27 | } 28 | 29 | factory& chunk_size(size_t n) { 30 | chunk_size_ = n; 31 | return *this; 32 | } 33 | 34 | static void mmap(const std::string& filename) { 35 | mmap_ = filename; 36 | post_fix_ = 0; 37 | } 38 | 39 | static void post_fix(size_t pf) { 40 | post_fix_ = pf; 41 | } 42 | 43 | static std::string mmap_filename() { 44 | return *mmap_ + "." + thread_id() + "." + std::to_string(post_fix_++); 45 | } 46 | 47 | abstract_voxel_storage* create(double x1, double y1, double z1, double d, int nx, int ny, int nz); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /fill_gaps.h: -------------------------------------------------------------------------------- 1 | #ifndef FILL_GAPS_H 2 | #define FILL_GAPS_H 3 | 4 | #include "storage.h" 5 | #include "edge_detect.h" 6 | 7 | typedef bool(abstract_voxel_storage::*voxel_getter_t)(const vec_n<3, size_t>&) const; 8 | typedef void(abstract_voxel_storage::*voxel_setter_t)(const vec_n<3, size_t>&); 9 | 10 | namespace { 11 | 12 | void specialize_getter_(chunked_voxel_storage* t, voxel_getter_t& getter_, voxel_setter_t& setter_) { 13 | if (!t) { 14 | return; 15 | } 16 | getter_ = static_cast(&chunked_voxel_storage::Get); 17 | setter_ = static_cast(&chunked_voxel_storage::Set); 18 | } 19 | 20 | void specialize_getter_(abstract_voxel_storage* t, voxel_getter_t& getter_, voxel_setter_t& setter_) { 21 | if (!t) { 22 | return; 23 | } 24 | getter_ = &abstract_voxel_storage::Get; 25 | setter_ = &abstract_voxel_storage::Set; 26 | } 27 | 28 | } 29 | 30 | class fill_gaps : public post_process { 31 | private: 32 | regular_voxel_storage * storage_; 33 | regular_voxel_storage * output_; 34 | std::array, 2> extents_; 35 | 36 | bool(abstract_voxel_storage::*getter_)(const vec_n<3, size_t>&) const; 37 | void(abstract_voxel_storage::*setter_)(const vec_n<3, size_t>&); 38 | 39 | bool get_(const vec_n<3, size_t>& pos) { 40 | if ((pos < extents_[0]).any()) { 41 | return false; 42 | } 43 | 44 | if ((pos > extents_[1]).any()) { 45 | return false; 46 | } 47 | 48 | return (storage_->*getter_)(pos); 49 | } 50 | 51 | 52 | void init_(regular_voxel_storage* storage) { 53 | storage_ = storage; 54 | output_ = (regular_voxel_storage*)storage_->empty_copy(); 55 | extents_ = storage->original_bounds(); 56 | getter_ = nullptr; 57 | setter_ = nullptr; 58 | 59 | // nah this is likely not going to make a difference 60 | specialize_getter_(storage_, getter_, setter_); 61 | specialize_getter_(dynamic_cast*>(storage_), getter_, setter_); 62 | if (dynamic_cast(storage_)) { 63 | storage_ = dynamic_cast(storage_)->base(); 64 | specialize_getter_(dynamic_cast*>(storage_), getter_, setter_); 65 | } 66 | } 67 | 68 | public: 69 | static const bool UNION_INPUT = true; 70 | 71 | regular_voxel_storage* operator()(regular_voxel_storage* storage) { 72 | init_(storage); 73 | 74 | size_t i0, i1, j0, j1, k0, k1; 75 | storage->bounds()[0].tie(i0, j0, k0); 76 | storage->bounds()[1].tie(i1, j1, k1); 77 | 78 | std::vector> opposites{ 79 | make_vec(1,0,0), 80 | make_vec(0,1,0), 81 | make_vec(0,0,1) 82 | }; 83 | 84 | vec_n<3, size_t> ijk; 85 | for (long i = i0; i <= (long)i1; ++i) { 86 | progress(static_cast(i - i0) / (i1 - i0)); 87 | 88 | ijk.get(0) = i; 89 | for (long j = j0; j <= (long)j1; ++j) { 90 | ijk.get(1) = j; 91 | bool inside = false; 92 | for (long k = k0; k <= (long)k1; ++k) { 93 | ijk.get(2) = k; 94 | 95 | const vec_n<3, long> ijk = vec_n<3, long>(i, j, k); 96 | 97 | if (get_(ijk.as())) { 98 | continue; 99 | } 100 | 101 | bool opposing_set = false; 102 | 103 | for (const auto& opp : opposites) { 104 | if (get_((ijk + opp).as()) && 105 | get_((ijk - opp).as())) 106 | { 107 | opposing_set = true; 108 | break; 109 | } 110 | } 111 | 112 | if (opposing_set) { 113 | (output_->*setter_)(ijk.as()); 114 | } 115 | } 116 | } 117 | } 118 | 119 | progress(1.); 120 | 121 | return output_; 122 | } 123 | }; 124 | 125 | #endif -------------------------------------------------------------------------------- /img/duplex_outer_voxel_pointcloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfcOpenShell/voxelization_toolkit/acf021e3b5bff4e03814dee060fcc3a674db1c84/img/duplex_outer_voxel_pointcloud.png -------------------------------------------------------------------------------- /img/evacuation_distance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfcOpenShell/voxelization_toolkit/acf021e3b5bff4e03814dee060fcc3a674db1c84/img/evacuation_distance.png -------------------------------------------------------------------------------- /img/exterior_interior.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfcOpenShell/voxelization_toolkit/acf021e3b5bff4e03814dee060fcc3a674db1c84/img/exterior_interior.png -------------------------------------------------------------------------------- /img/heights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfcOpenShell/voxelization_toolkit/acf021e3b5bff4e03814dee060fcc3a674db1c84/img/heights.png -------------------------------------------------------------------------------- /json_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "json_logger.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace { 15 | struct ptree_writer { 16 | boost::property_tree::ptree& pt; 17 | const std::string& k; 18 | void operator()(long v) { 19 | pt.put(k, v); 20 | } 21 | void operator()(double v) { 22 | pt.put(k, v); 23 | } 24 | void operator()(const std::string& v) { 25 | pt.put(k, v); 26 | } 27 | }; 28 | 29 | void print_tree(std::ostream& os, const boost::property_tree::ptree::value_type& pt, int indent = 0) { 30 | os << std::string(indent, ' '); 31 | if (pt.first.size()) { 32 | os << pt.first << ": "; 33 | } 34 | os << pt.second.data() << std::endl; 35 | for (auto& c : pt.second) { 36 | print_tree(os, c, indent + 2); 37 | } 38 | } 39 | 40 | void print_tree(std::ostream& os, const boost::property_tree::ptree& pt, int indent = 0) { 41 | print_tree(os, { "", pt }); 42 | } 43 | } 44 | 45 | std::string json_logger::to_json_string(const meta_data& m) { 46 | boost::property_tree::ptree pt; 47 | for (auto& p : m) { 48 | ptree_writer vis{ pt, p.first }; 49 | boost::apply_visitor(vis, p.second); 50 | } 51 | std::ostringstream oss; 52 | boost::property_tree::write_json(oss, pt, false); 53 | return oss.str(); 54 | } 55 | 56 | void json_logger::message(severity s, const std::string & message, const std::map& meta) 57 | { 58 | static const std::array severity_strings = { "", "debug", "notice", "warning", "error", "fatal" }; 59 | auto& s_str = severity_strings[static_cast(s)]; 60 | 61 | boost::property_tree::ptree pt; 62 | pt.put("severity", s_str); 63 | pt.put("message", message); 64 | 65 | auto now = std::chrono::system_clock::now(); 66 | auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; 67 | auto t = std::chrono::system_clock::to_time_t(now); 68 | auto tm = *std::localtime(&t); 69 | std::ostringstream oss; 70 | oss << std::put_time(&tm, "%Y:%m:%d %H:%M:%S"); 71 | oss << '.' << std::setfill('0') << std::setw(3) << ms.count(); 72 | 73 | pt.put("time", oss.str()); 74 | 75 | for (auto& p : meta) { 76 | boost::property_tree::ptree pt2; 77 | for (auto& p2 : p.second) { 78 | ptree_writer vis{ pt2, p2.first }; 79 | boost::apply_visitor(vis, p2.second); 80 | } 81 | pt.put_child(p.first, pt2); 82 | } 83 | 84 | for (auto& p : log_output) { 85 | if (p.first == FMT_JSON) { 86 | boost::property_tree::write_json(*p.second, pt, false); 87 | } 88 | else if (p.first == FMT_TEXT) { 89 | print_tree(*p.second, pt); 90 | } 91 | } 92 | } 93 | 94 | json_logger::severity json_logger::severity_; 95 | json_logger::severity json_logger::max_severity_ = json_logger::LOG_NONE; 96 | std::vector> json_logger::log_output; -------------------------------------------------------------------------------- /json_logger.h: -------------------------------------------------------------------------------- 1 | #ifndef JSON_LOGGER_H 2 | #define JSON_LOGGER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class json_logger { 12 | public: 13 | typedef enum { LOG_NONE, LOG_DEBUG, LOG_NOTICE, LOG_WARNING, LOG_ERROR, LOG_FATAL } severity; 14 | typedef enum { FMT_TEXT, FMT_JSON } format; 15 | typedef boost::variant meta_value; 16 | typedef std::map meta_data; 17 | static std::string to_json_string(const meta_data&); 18 | private: 19 | static std::vector> log_output; 20 | static severity severity_; 21 | static severity max_severity_; 22 | public: 23 | static void register_output(format f, std::ostream* os) { log_output.push_back({ f, os }); } 24 | static void set_severity(severity s) { severity_ = s; } 25 | static severity get_severity() { return severity_; } 26 | static severity get_max_severity() { return max_severity_; } 27 | static void message(severity s, const std::string& m, const std::map& meta); 28 | static void message(severity s, const std::string& m) { message(s, m, {}); }; 29 | }; 30 | 31 | #endif -------------------------------------------------------------------------------- /lru_cache.h: -------------------------------------------------------------------------------- 1 | #ifndef LRU_CACHE_H 2 | #define LRU_CACHE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template , typename Eq=std::equal_to> 9 | class LRUCache { 10 | public: 11 | typedef std::list> StorageType; 12 | private: 13 | size_t capacity_; 14 | 15 | // Storage of key-value pairs, std::list to prevent iterator invalidation 16 | StorageType cache_items_; 17 | 18 | // Map into storage type entries for O(1) access 19 | std::unordered_map cache_map_; 20 | 21 | public: 22 | // Constructor to initialize cache with a specific capacity 23 | explicit LRUCache(size_t capacity) : capacity_(capacity) { 24 | if (capacity_ == 0) { 25 | throw std::invalid_argument("Capacity must be greater than zero."); 26 | } 27 | } 28 | 29 | // Get the value associated with the key 30 | // Returns true and sets the value if the key is found, otherwise returns false 31 | bool get(const KeyType& key, ValueType& value) { 32 | auto it = cache_map_.find(key); 33 | if (it == cache_map_.end()) { 34 | return false; // Key not found 35 | } 36 | // Move the accessed item to the front of the cache (most recently used) 37 | cache_items_.splice(cache_items_.begin(), cache_items_, it->second); 38 | value = it->second->second; 39 | return true; 40 | } 41 | 42 | // Insert or update the key-value pair in the cache 43 | void insert(const KeyType& key, const ValueType& value) { 44 | auto it = cache_map_.find(key); 45 | if (it != cache_map_.end()) { 46 | // Update existing item and move it to the front 47 | it->second->second = value; 48 | cache_items_.splice(cache_items_.begin(), cache_items_, it->second); 49 | } else { 50 | // Insert new item 51 | if (cache_items_.size() == capacity_) { 52 | // Remove the least recently used item 53 | auto last = cache_items_.end(); 54 | --last; 55 | cache_map_.erase(last->first); 56 | cache_items_.pop_back(); 57 | } 58 | cache_items_.emplace_front(key, value); 59 | cache_map_[key] = cache_items_.begin(); 60 | } 61 | } 62 | 63 | // Remove a key-value pair from the cache 64 | bool remove(const KeyType& key) { 65 | auto it = cache_map_.find(key); 66 | if (it == cache_map_.end()) { 67 | return false; // Key not found 68 | } 69 | cache_items_.erase(it->second); 70 | cache_map_.erase(it); 71 | return true; 72 | } 73 | 74 | // Get the current size of the cache 75 | size_t size() const { 76 | return cache_items_.size(); 77 | } 78 | }; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /memoized_traversal.h: -------------------------------------------------------------------------------- 1 | #ifndef MEMOIZED_TRAVERSAL_H 2 | #define MEMOIZED_TRAVERSAL_H 3 | 4 | #include "lru_cache.h" 5 | #include "storage.h" 6 | #include "traversal.h" 7 | 8 | #include 9 | 10 | namespace std { 11 | // Specialization of std::equal_to for vec_n, because operator== is overloaded and 12 | // returns vec_n 13 | template <> 14 | struct equal_to, size_t>> { 15 | bool operator()(const std::pair, size_t>& lhs, const std::pair, size_t>& rhs) const { 16 | return lhs.first.as_array() == rhs.first.as_array() && lhs.second == rhs.second; 17 | } 18 | }; 19 | 20 | // Specialization of std::equal_to for vec_n, because operator== is overloaded and 21 | // returns vec_n 22 | template <> 23 | struct hash, size_t>> { 24 | size_t operator()(const std::pair, size_t>& p) const { 25 | return std::hash{}(p.first) ^ std::hash{}(p.second); 26 | } 27 | }; 28 | }; 29 | 30 | /* 31 | class memoized_traversal { 32 | // use 6-connectedness for commutativity 33 | LRUCache, size_t>, std::set>> cache_; 34 | regular_voxel_storage* storage_; 35 | regular_voxel_storage* visited_; 36 | 37 | public: 38 | size_t cache_hits = 0; 39 | size_t cache_misses = 0; 40 | 41 | memoized_traversal(regular_voxel_storage* storage, size_t capacity) 42 | : cache_(capacity) 43 | , storage_(storage) 44 | , visited_(nullptr) 45 | { 46 | clear(); 47 | } 48 | 49 | void clear() { 50 | delete visited_; 51 | visited_ = (regular_voxel_storage*)storage_->empty_copy(); 52 | } 53 | 54 | void operator()(const vec_n<3, size_t>& cur, size_t d, size_t neighbourhood_size, std::set>& out) { 55 | if (visited_->Get(cur)) { 56 | return; 57 | } 58 | // d can only reduce, so keeping track of visited is valid 59 | visited_->Set(cur); 60 | std::set> v = { { cur.get<0>(), cur.get<1>(), cur.get<2>() } }; 61 | if (cache_.get({ cur, d }, v)) { 62 | cache_hits += 1; 63 | } else if (d == 0) { 64 | } else { 65 | cache_misses += 1; 66 | // // with depth=1 you obviously don't need a queue 67 | // visitor<6> visitor_; 68 | // visitor_.max_depth = 1; 69 | // visitor_([&v, neighbourhood_size, d, this](const tagged_index& pos) { 70 | // if (pos.which == tagged_index::VOXEL) { 71 | // if (!neighbourhood_size || (v.size() < neighbourhood_size)) { 72 | // // out.push_back({ pos.pos.get(0),pos.pos.get(1),pos.pos.get(2) }); 73 | // (*this)(pos.pos, d - 1, neighbourhood_size, v); 74 | // } 75 | // } else { 76 | // throw std::runtime_error("Unexpected"); 77 | // } 78 | // }, storage_, i); 79 | auto pos = cur; 80 | for (size_t i = 0; i < 3; ++i) { 81 | pos.get(i)--; 82 | for (size_t j = 0; j < 2; ++j) { 83 | if (storage_->Get(pos)) { 84 | (*this)(pos, d - 1, neighbourhood_size, v); 85 | } 86 | pos.get(i) += 2; 87 | } 88 | pos.get(i) -= 3; 89 | } 90 | cache_.insert({ cur, d }, v); 91 | } 92 | out.insert(v.begin(), v.end()); 93 | } 94 | }; 95 | */ 96 | 97 | class memoized_traversal { 98 | // use 6-connectedness for commutativity 99 | LRUCache, size_t>, std::vector>> cache_; 100 | regular_voxel_storage* storage_; 101 | regular_voxel_storage* visited_; 102 | 103 | public: 104 | size_t cache_hits = 0; 105 | size_t cache_misses = 0; 106 | 107 | memoized_traversal(regular_voxel_storage* storage, size_t capacity) 108 | : cache_(capacity) 109 | , storage_(storage) 110 | , visited_(nullptr) 111 | { 112 | clear(); 113 | } 114 | 115 | void clear() { 116 | } 117 | 118 | void operator()(const vec_n<3, size_t>& cur, size_t d, std::unordered_map, size_t>& out, size_t doff = 0) { 119 | visited_ = (regular_voxel_storage*)storage_->empty_copy(); 120 | 121 | std::deque, size_t>> queue = { { cur, 0} }; 122 | 123 | while (!queue.empty()) { 124 | auto& current = queue.front(); 125 | auto& pos = current.first; 126 | out.insert(current); 127 | 128 | for (size_t i = 0; i < 3; ++i) { 129 | pos.get(i)--; 130 | for (size_t j = 0; j < 2; ++j) { 131 | if (current.second < d && storage_->Get(pos) && !visited_->Get(pos)) { 132 | visited_->Set(pos); 133 | 134 | out.insert({ pos, current.second + 1 }); 135 | 136 | bool used_cache = false; 137 | std::vector> vs; 138 | auto D = d - current.second - 1; 139 | while (cache_.get({ pos, D }, vs)) { 140 | used_cache = true; 141 | for (auto& v : vs) { 142 | if (!visited_->Get(v)) { 143 | out.insert({ v, current.second + D }); 144 | visited_->Set(v); 145 | } 146 | } 147 | D--; 148 | } 149 | if (!used_cache) { 150 | cache_misses += 1; 151 | queue.push_back({ pos, current.second + 1 }); 152 | } else { 153 | cache_hits += 1; 154 | } 155 | } 156 | pos.get(i) += 2; 157 | } 158 | pos.get(i) -= 3; 159 | } 160 | queue.pop_front(); 161 | } 162 | 163 | std::vector>> vss(d + 1); 164 | 165 | for (auto& p : out) { 166 | vss[p.second].push_back(p.first); 167 | out.insert({ p.first, p.second}); 168 | } 169 | 170 | for (size_t i = 0; i < d + 1; ++i) { 171 | cache_.insert({ cur, i }, vss[i]); 172 | } 173 | } 174 | }; 175 | 176 | struct vec_with_hash { 177 | std::array v; 178 | 179 | vec_with_hash(const vec_n<3, size_t>& vec) { 180 | v = { vec.get<0>(), vec.get<1>(), vec.get<2>(), std::hash>{}(vec) }; 181 | } 182 | }; 183 | 184 | 185 | namespace std { 186 | template <> 187 | struct hash { 188 | size_t operator()(const vec_with_hash& vec) const { 189 | return vec.v.back(); 190 | } 191 | }; 192 | 193 | template <> 194 | struct equal_to { 195 | bool operator()(const vec_with_hash& lhs, const vec_with_hash& rhs) const { 196 | return lhs.v == rhs.v; 197 | } 198 | }; 199 | } 200 | 201 | class squaring_traversal { 202 | public: 203 | using set_type = std::unordered_set; 204 | private: 205 | std::unordered_map neighbour_map; 206 | regular_voxel_storage* storage_; 207 | 208 | public: 209 | squaring_traversal(regular_voxel_storage* storage) 210 | : storage_(storage) 211 | { 212 | for (auto ijk : *storage) { 213 | auto pos = ijk; 214 | for (size_t i = 0; i < 3; ++i) { 215 | pos.get(i)--; 216 | for (size_t j = 0; j < 2; ++j) { 217 | if (storage_->Get(pos)) { 218 | neighbour_map[ijk].insert(pos); 219 | } 220 | pos.get(i) += 2; 221 | } 222 | pos.get(i) -= 3; 223 | } 224 | } 225 | std::vector maps = { neighbour_map }; 226 | 227 | for (int N = 0; N < 5; ++N) { 228 | maps.push_back(maps.back()); 229 | auto& prev = *(++maps.rbegin()); 230 | for (auto& p : prev) { 231 | for (auto& q : p.second) { 232 | // @todo this doesn't work in case of concavities, where topological 233 | // distance is 234 | auto D = std::abs((int)(p.first.v[0] - q.v[0])) + std::abs((int)(p.first.v[1] - q.v[1])) + std::abs((int)(p.first.v[2] - q.v[2])); 235 | if ((1 << N) == D) { 236 | maps.back()[p.first].insert(prev[q].begin(), prev[q].end()); 237 | } 238 | } 239 | } 240 | } 241 | 242 | neighbour_map = maps.back(); 243 | } 244 | 245 | void operator()(const vec_n<3, size_t>& seed, set_type& out) { 246 | out = neighbour_map[seed]; 247 | } 248 | }; 249 | 250 | 251 | #endif -------------------------------------------------------------------------------- /module/__init__.py: -------------------------------------------------------------------------------- 1 | from .voxec import * 2 | 3 | import multiprocessing 4 | 5 | default_VOXELSIZE = 0.05 6 | default_CHUNKSIZE = 16 7 | default_THREADS = multiprocessing.cpu_count() 8 | SILENT = False 9 | 10 | def run(name, *args, **kwargs): 11 | ctx = context() 12 | ctx.set('VOXELSIZE', default_VOXELSIZE) 13 | ctx.set('CHUNKSIZE', default_CHUNKSIZE) 14 | ctx.set('THREADS', default_THREADS) 15 | return run_(name, args, kwargs, ctx, SILENT) 16 | -------------------------------------------------------------------------------- /offset.h: -------------------------------------------------------------------------------- 1 | #ifndef OFFSET_H 2 | #define OFFSET_H 3 | 4 | #include "storage.h" 5 | #include "edge_detect.h" 6 | 7 | template 8 | class offset : public post_process { 9 | private: 10 | regular_voxel_storage * storage_; 11 | regular_voxel_storage * output_; 12 | std::array, 2> extents_; 13 | bool same_dim_; 14 | 15 | bool get_(const vec_n<3, size_t>& pos) { 16 | if ((pos < extents_[0]).any()) { 17 | return false; 18 | } 19 | 20 | if ((pos > extents_[1]).any()) { 21 | return false; 22 | } 23 | 24 | return storage_->Get(pos); 25 | } 26 | 27 | void init_(regular_voxel_storage* storage) { 28 | storage_ = storage; 29 | 30 | bool enough_padding = true; 31 | 32 | regular_voxel_storage* base = storage; 33 | if (dynamic_cast(storage_)) { 34 | base = dynamic_cast(storage_)->base(); 35 | } 36 | 37 | if (false && ((base->bounds()[0] < N).any() || ((base->extents() - base->bounds()[1]) < (N + 1U)).any())) { 38 | 39 | // @todo output might be truncated. 40 | 41 | same_dim_ = false; 42 | 43 | double ox, oy, oz, d = storage->voxel_size(); 44 | size_t dimx, dimy, dimz, chunk_size = 128; 45 | 46 | storage_->origin().tie(ox, oy, oz); 47 | storage_->extents().tie(dimx, dimy, dimz); 48 | 49 | if (dynamic_cast(storage_)) { 50 | regular_voxel_storage* base = dynamic_cast(storage_)->base(); 51 | base->origin().tie(ox, oy, oz); 52 | base->extents().tie(dimx, dimy, dimz); 53 | } 54 | 55 | // Grow voxel volume 56 | ox -= d * N; 57 | oy -= d * N; 58 | oz -= d * N; 59 | dimx += 2 * N; 60 | dimy += 2 * N; 61 | dimz += 2 * N; 62 | 63 | if (dynamic_cast*>(storage_)) { 64 | chunk_size = dynamic_cast*>(storage_)->chunk_size(); 65 | } 66 | 67 | output_ = new chunked_voxel_storage( 68 | ox, oy, oz, d, dimx, dimy, dimz, chunk_size 69 | ); 70 | 71 | } else { 72 | 73 | same_dim_ = true; 74 | 75 | output_ = (regular_voxel_storage*) storage_->empty_copy(); 76 | 77 | } 78 | 79 | extents_ = storage->original_bounds(); 80 | } 81 | 82 | public: 83 | static const bool UNION_INPUT = false; 84 | 85 | regular_voxel_storage * operator()(regular_voxel_storage* storage) { 86 | init_(storage); 87 | 88 | size_t i0, i1, j0, j1, k0, k1; 89 | storage->bounds()[0].tie(i0, j0, k0); 90 | storage->bounds()[1].tie(i1, j1, k1); 91 | 92 | auto extents = output_->extents().as(); 93 | auto zero = make_vec(0, 0, 0); 94 | 95 | vec_n<3, size_t> ijk; 96 | for (long i = i0; i <= (long)i1; ++i) { 97 | progress(static_cast(i - i0) / (i1 - i0)); 98 | 99 | ijk.get(0) = i; 100 | for (long j = j0; j <= (long)j1; ++j) { 101 | ijk.get(1) = j; 102 | bool inside = false; 103 | for (long k = k0; k <= (long)k1; ++k) { 104 | ijk.get(2) = k; 105 | 106 | const vec_n<3, long> ijk = vec_n<3, long>(i, j, k); 107 | 108 | if (!get_(ijk.as())) { 109 | continue; 110 | } 111 | 112 | for (long di = -1; di <= 1; ++di) { 113 | for (long dj = -1; dj <= 1; ++dj) { 114 | for (long dk = (D == 3 ? -1 : 0); dk <= (D == 3 ? 1 : 0); ++dk) { 115 | const vec_n<3, long> ijk2 = ijk + vec_n<3, long>(di, dj, dk); 116 | if ((ijk2 >= zero).all() && (ijk2 < extents).all()) { 117 | if (!get_(ijk2.as())) { 118 | if (same_dim_) { 119 | output_->Set(ijk2.as()); 120 | } else { 121 | output_->Set(ijk2.as() + vec_n<3, size_t>(N, N, N)); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | } 131 | 132 | progress(1.); 133 | 134 | return output_; 135 | } 136 | }; 137 | 138 | #endif -------------------------------------------------------------------------------- /polyfill.cpp: -------------------------------------------------------------------------------- 1 | // https://developer.blender.org/diffusion/B/browse/master/source/blender/blenlib/intern/math_geom.c;3b4a8f1cfa7339f3db9ddd4a7974b8cc30d7ff0b$2411 2 | 3 | #include "polyfill.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | struct horizontal_segment { 10 | int y, x0, x1; 11 | }; 12 | 13 | struct coord { 14 | int x, y; 15 | }; 16 | 17 | struct intersection_node { 18 | int at_x; 19 | coord d; 20 | }; 21 | 22 | bool operator <(const horizontal_segment& a, const horizontal_segment& b) { 23 | // interpret as tuple for lexicographic comparison 24 | return std::tie(a.y, a.x0, a.x1) < std::tie(b.y, b.x0, b.x1); 25 | } 26 | 27 | bool operator <(const intersection_node& a, const intersection_node& b) { 28 | // interpret as tuple for lexicographic comparison 29 | return std::tie(a.at_x, a.d.x, a.d.y) < std::tie(b.at_x, b.d.x, b.d.y); 30 | } 31 | 32 | int dot(const coord& a, const coord& b) { 33 | return a.x * b.x + a.y * b.y; 34 | } 35 | 36 | void get_minmax(const std::vector& verts, int& ymin, int& ymax) { 37 | ymin = std::numeric_limits::max(); 38 | ymax = std::numeric_limits::min(); 39 | 40 | for (size_t i = 0; i < verts.size(); ++i) { 41 | if (verts[i].get<1>() < ymin) { 42 | ymin = verts[i].get<1>(); 43 | } 44 | if (verts[i].get<1>() > ymax) { 45 | ymax = verts[i].get<1>(); 46 | } 47 | } 48 | } 49 | 50 | void get_horizontal(const std::vector& verts, std::set& horizontal_segments) { 51 | size_t j = verts.size() - 1; 52 | for (size_t i = 0; i < verts.size(); i++) { 53 | const int& curx = verts[i].get<0>(); 54 | const int& prex = verts[j].get<0>(); 55 | 56 | const int& cury = verts[i].get<1>(); 57 | const int& prey = verts[j].get<1>(); 58 | 59 | if (cury == prey) { 60 | horizontal_segment span = { cury, curx, prex }; 61 | if (span.x0 > span.x1) { 62 | std::swap(span.x1, span.x0); 63 | } 64 | horizontal_segments.insert(span); 65 | } 66 | 67 | j = i; 68 | } 69 | } 70 | 71 | void process_scanline(int pixel_y, const std::vector& verts, const std::set& horizontal_segments, std::vector& node_x) { 72 | size_t j = verts.size() - 1; 73 | 74 | for (size_t i = 0; i < verts.size(); i++) { 75 | const int& curx = verts[i].get<0>(); 76 | const int& prex = verts[j].get<0>(); 77 | 78 | const int& cury = verts[i].get<1>(); 79 | const int& prey = verts[j].get<1>(); 80 | 81 | if ((cury <= pixel_y && prey >= pixel_y) || 82 | (prey <= pixel_y && cury >= pixel_y)) { 83 | if (cury == prey) { 84 | // node_x.push_back({ curx,{ prex, prey } }); 85 | // node_x.push_back({ prex,{ curx, cury } }); 86 | } else { 87 | const int X = (int)std::lround(curx + 88 | ((double)(pixel_y - cury) / (cury - prey)) * 89 | (curx - prex)); 90 | 91 | node_x.push_back({ X,{ pixel_y == cury ? prex : curx, pixel_y == cury ? prey : cury } }); 92 | } 93 | } 94 | 95 | j = i; 96 | } 97 | 98 | std::sort(node_x.begin(), node_x.end()); 99 | 100 | /* remove duplicates for double registration of corner points */ 101 | 102 | for (size_t i = 1; i < node_x.size(); ) { 103 | if (node_x[i].at_x == node_x[i - 1].at_x) { 104 | 105 | for (const auto& v : verts) { 106 | if (v.get<1>() == pixel_y && v.get<0>() == node_x[i].at_x) { 107 | // if both other ends of the segment are on the same side of the scanline, a ray would have passed twice 108 | if ((node_x[i].d.y > pixel_y) != (node_x[i - 1].d.y > pixel_y)) { 109 | node_x.erase(node_x.begin() + i); 110 | if (node_x.size() == i) { 111 | break; 112 | } 113 | continue; 114 | } 115 | } 116 | } 117 | 118 | /* if (dot(node_x[i].d, node_x[i - 1].d) > 0) { 119 | node_x.erase(node_x.begin() + i); 120 | continue; 121 | } */ 122 | } 123 | i += 1; 124 | } 125 | 126 | /* work-around for horizontal segments */ 127 | 128 | for (int i = 1; i < (int)node_x.size() - 2; ) { 129 | horizontal_segment span = { pixel_y, node_x[i].at_x, node_x[i + 1].at_x }; 130 | if (horizontal_segments.find(span) != horizontal_segments.end()) { 131 | node_x.erase(node_x.begin() + i, node_x.begin() + i + 2); 132 | } else { 133 | i += 2; 134 | } 135 | } 136 | } 137 | 138 | #include 139 | 140 | void fill_poly(const std::vector& verts, polyfill_callback callback, void *userData) { 141 | /* Adapted from Darel Rex Finley, 2007 */ 142 | 143 | /* 144 | std::cerr << "poly: "; 145 | for (auto& p : verts) { 146 | std::cerr << p.format() << " "; 147 | } 148 | std::cerr << std::endl; 149 | */ 150 | 151 | int ymin, ymax; 152 | get_minmax(verts, ymin, ymax); 153 | 154 | std::vector node_x; 155 | node_x.reserve(verts.size() * 2); 156 | 157 | /* Horizontal segments are treated differently */ 158 | 159 | std::set horizontal_segments; 160 | get_horizontal(verts, horizontal_segments); 161 | 162 | /* Loop through the rows of the image. */ 163 | 164 | for (int pixel_y = ymin; pixel_y <= ymax; pixel_y++) { 165 | 166 | /* Build a list of nodes (intersections with scanline). */ 167 | 168 | process_scanline(pixel_y, verts, horizontal_segments, node_x); 169 | 170 | /* Fill the pixels between node pairs. */ 171 | 172 | std::vector node_x_x(node_x.size()); 173 | std::transform(node_x.begin(), node_x.end(), node_x_x.begin(), [](auto& s) { 174 | return s.at_x; 175 | }); 176 | 177 | if (node_x.size() >= 2) { 178 | int nspans = node_x.size() / 2; 179 | callback(pixel_y, nspans, node_x_x.data(), userData); 180 | } 181 | 182 | node_x.clear(); 183 | } 184 | } 185 | 186 | 187 | bool is_inside_poly(const std::vector& verts, int x, int y) { 188 | int ymin, ymax; 189 | get_minmax(verts, ymin, ymax); 190 | 191 | std::vector node_x; 192 | node_x.reserve(verts.size() * 2); 193 | 194 | /* Horizontal segments are treated differently */ 195 | 196 | std::set horizontal_segments; 197 | get_horizontal(verts, horizontal_segments); 198 | 199 | /* Build a list of nodes (intersections with scanline). */ 200 | 201 | process_scanline(y, verts, horizontal_segments, node_x); 202 | 203 | bool inside = false; 204 | 205 | for (auto& n : node_x) { 206 | if (n.at_x == x) { 207 | // on 208 | return true; 209 | } 210 | if (n.at_x > x) { 211 | inside = !inside; 212 | } 213 | } 214 | 215 | return inside; 216 | } 217 | 218 | // Not entirely the same as Bryce Boe's 219 | // Do not have a lot of confidence in this yet. 220 | bool ccw(const point_t& A, const point_t& B, const point_t& C) { 221 | auto ac = C - A; 222 | auto ab = B - A; 223 | auto d = ac.cross(ab); 224 | return d > 0; 225 | } 226 | 227 | // https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/ 228 | bool intersects(const line_t& a, const line_t& b) { 229 | return (ccw(a[0], b[0], b[1]) != ccw(a[1], b[0], b[1])) && (ccw(a[0], a[1], b[0]) != ccw(a[0], a[1], b[1])); 230 | } 231 | 232 | // https://github.com/pgkelley4/line-segments-intersect/blob/master/js/line-segments-intersect.js 233 | bool intersects_float(const line_t& a, const line_t& b) { 234 | const double eps = 1.e-9; 235 | const double zero = -eps; 236 | const double one = 1. - eps; 237 | 238 | auto p = a[0].as(); 239 | auto p2 = a[1].as(); 240 | auto q = b[0].as(); 241 | auto q2 = b[1].as(); 242 | 243 | auto r = p2 - p; 244 | auto s = q2 - q; 245 | 246 | auto u = (q, p).cross(r); 247 | auto d = r.cross(s); 248 | 249 | if (u < eps && d < eps) { 250 | 251 | } 252 | 253 | if (d < eps) { 254 | return false; 255 | } 256 | 257 | auto t = u / d; 258 | auto v = (q - p).cross(s) / d; 259 | 260 | return (t >= zero) && (t <= one) && (u >= zero) && (u <= one); 261 | } 262 | -------------------------------------------------------------------------------- /polyfill.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYFILL_H 2 | #define POLYFILL_H 3 | 4 | #include 5 | #include 6 | 7 | #include "dim3.h" 8 | 9 | typedef void(*polyfill_callback)(int, int, int*, void *); 10 | typedef vec_n<2, int> point_t; 11 | typedef std::array line_t; 12 | 13 | void fill_poly(const std::vector& verts, polyfill_callback callback, void *userData); 14 | bool is_inside_poly(const std::vector& verts, int x, int y); 15 | bool intersects(const line_t& a, const line_t& b); 16 | int triBoxOverlap(double boxcenter[3], double boxhalfsize[3], double triverts[3][3]); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /processor.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESSOR_H 2 | #define PROCESSOR_H 3 | 4 | #include "traversal.h" 5 | #include "factory.h" 6 | #include "storage.h" 7 | #include "writer.h" 8 | #include "progress_writer.h" 9 | #include "volume.h" 10 | #include "voxelizer.h" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #ifdef WITH_IFC 18 | 19 | #ifdef IFCOPENSHELL_08 20 | #include 21 | 22 | typedef IfcGeom::BRepElement elem_t; 23 | #else 24 | #ifdef IFCOPENSHELL_07 25 | #include // @todo < commit in IfcopenShell 26 | #include 27 | 28 | typedef IfcGeom::BRepElement elem_t; 29 | #else 30 | #include // @todo < commit in IfcopenShell 31 | #include // @todo < commit in IfcopenShell 32 | #include 33 | 34 | typedef IfcGeom::BRepElement elem_t; 35 | #endif 36 | #endif 37 | #endif 38 | 39 | typedef TopoDS_Shape geometry_t; 40 | typedef std::vector, TopoDS_Compound > > geometry_collection_t; 41 | 42 | class voxelization_mode { 43 | public: 44 | int tag; 45 | voxelization_mode(int t) : tag(t) {} 46 | }; 47 | class MERGED : public voxelization_mode { 48 | public: 49 | MERGED() : voxelization_mode(1) {} 50 | }; 51 | class SEPARATE : public voxelization_mode { 52 | public: 53 | SEPARATE() : voxelization_mode(2) {} 54 | }; 55 | class SEPARATE_MINUS : public voxelization_mode { 56 | public: 57 | SEPARATE_MINUS() : voxelization_mode(3) {} 58 | }; 59 | enum VOXEL_VALUATION { 60 | CONSTANT_ONE, 61 | PRODUCT_ID 62 | }; 63 | class fill_volume_t { 64 | public: 65 | bool y; 66 | VOXEL_VALUATION value; 67 | fill_volume_t(bool b, VOXEL_VALUATION v = CONSTANT_ONE) : y(b), value(v) {} 68 | }; 69 | class VOLUME : public fill_volume_t { 70 | public: 71 | VOLUME() : fill_volume_t(true) {} 72 | }; 73 | class SURFACE : public fill_volume_t { 74 | public: 75 | SURFACE() : fill_volume_t(false) {} 76 | }; 77 | class VOLUME_PRODUCT_ID : public fill_volume_t { 78 | public: 79 | VOLUME_PRODUCT_ID() : fill_volume_t(true, PRODUCT_ID) {} 80 | }; 81 | 82 | class output { 83 | private: 84 | std::string path_; 85 | const abstract_voxel_storage* voxels_; 86 | std::vector> post_processors_; 87 | 88 | public: 89 | int mode; 90 | 91 | output(const MERGED& mode, const std::string& path = "") 92 | : mode(mode.tag), path_(path), voxels_(0) {} 93 | output(const SEPARATE& mode, const std::string& path) 94 | : mode(mode.tag), path_(path), voxels_(0) {} 95 | output(const SEPARATE_MINUS& mode, const abstract_voxel_storage* voxels, const std::string& path) 96 | : mode(mode.tag), path_(path), voxels_(voxels) {} 97 | 98 | output parallelize() const { 99 | if (mode == 1) { 100 | return output(MERGED()); 101 | } else if (mode == 2 || mode == 3) { 102 | return *this; 103 | } else { 104 | throw std::runtime_error("invalid mode"); 105 | } 106 | } 107 | 108 | static void write(const std::string& filename, abstract_voxel_storage* voxels) { 109 | voxel_writer writer; 110 | writer.SetVoxels(voxels); 111 | writer.Write(filename); 112 | } 113 | 114 | static void interpolate(char* buff, const std::string& path, int i) { 115 | int r = snprintf(buff, 255, path.c_str(), i); 116 | if (r < 0 || r >= 255) { 117 | std::cerr << std::endl << "error allocating buffer" << std::endl; 118 | abort(); 119 | } 120 | } 121 | 122 | void intermediate_result(int i, abstract_voxel_storage* a, abstract_voxel_storage* b) const { 123 | if (mode == 1) { 124 | a->boolean_union_inplace(b); 125 | } else if (mode == 2 || mode == 3) { 126 | char buff[255]; 127 | interpolate(buff, path_, i); 128 | if (mode == 3) { 129 | b->boolean_subtraction_inplace(voxels_); 130 | } 131 | write(buff, b); 132 | } 133 | } 134 | 135 | abstract_voxel_storage* final_result(abstract_voxel_storage* a, abstract_voxel_storage* b) const { 136 | if (mode == 1 && path_.size()) { 137 | regular_voxel_storage* tmp = (regular_voxel_storage*)a; 138 | int i = 0; 139 | for (auto& fn : post_processors_) { 140 | write(path_ + "-step-" + boost::lexical_cast(i++), tmp); 141 | // std::cerr << "Step " << i << " bounds:" << tmp->bounds()[0].format() << " - " << a->bounds()[1].format() << std::endl; 142 | regular_voxel_storage* b = fn(tmp); 143 | if (tmp != a) { 144 | delete tmp; 145 | } 146 | tmp = b; 147 | } 148 | // std::cerr << "Final bounds:" << tmp->bounds()[0].format() << " - " << a->bounds()[1].format() << std::endl; 149 | write(path_, tmp); 150 | return tmp; 151 | } 152 | return nullptr; 153 | } 154 | 155 | template 156 | output& post(Fn p) { 157 | post_processors_.emplace_back(p); 158 | return *this; 159 | } 160 | 161 | }; 162 | 163 | class abstract_processor { 164 | public: 165 | virtual void process(geometry_collection_t::const_iterator start, geometry_collection_t::const_iterator end, const fill_volume_t& volume, const output& output) = 0; 166 | virtual ~abstract_processor() {} 167 | virtual abstract_voxel_storage* voxels() = 0; 168 | }; 169 | 170 | class processor : public abstract_processor { 171 | private: 172 | bit_t use_bits; 173 | factory factory_; 174 | abstract_voxel_storage* voxels_; 175 | abstract_voxel_storage* voxels_temp_; 176 | std::function progress_; 177 | int nx_, ny_, nz_; 178 | double x1_, y1_, z1_, d_; 179 | bool use_copy_, use_scanline_; 180 | public: 181 | processor(double x1, double y1, double z1, double d, int nx, int ny, int nz, size_t chunk_size, const std::function& progress) 182 | : factory_(factory().chunk_size(chunk_size)) 183 | , voxels_(factory_.create(x1, y1, z1, d, nx, ny, nz)) 184 | , voxels_temp_(factory_.create(x1, y1, z1, d, nx, ny, nz)) 185 | , progress_(progress) 186 | , nx_(nx), ny_(ny), nz_(nz) 187 | , x1_(x1), y1_(y1), z1_(z1), d_(d) 188 | , use_copy_(false) 189 | , use_scanline_(true) {} 190 | 191 | processor(abstract_voxel_storage* storage, const std::function& progress) 192 | : voxels_(storage) 193 | , voxels_temp_(storage->empty_copy_as(&use_bits)) 194 | , progress_(progress) 195 | , use_copy_(true) 196 | , use_scanline_(true) {} 197 | 198 | bool& use_scanline() { return use_scanline_; } 199 | 200 | ~processor() { 201 | if (!use_copy_) { 202 | delete voxels_; 203 | } 204 | delete voxels_temp_; 205 | } 206 | 207 | void process(geometry_collection_t::const_iterator a, geometry_collection_t::const_iterator b, const fill_volume_t& volume, const output& output) { 208 | int N = std::distance(a, b); 209 | int n = 0; 210 | for (geometry_collection_t::const_iterator it = a; it < b; ++it) { 211 | abstract_voxel_storage* to_write = volume.y ? voxels_temp_ : voxels_; 212 | 213 | voxelizer voxeliser(it->second, (regular_voxel_storage*)to_write, !use_scanline_, !use_scanline_); 214 | voxeliser.epsilon() = 1e-9; 215 | voxeliser.Convert(); 216 | 217 | if (volume.y) { 218 | auto filled = traversal_voxel_filler_inverse_with_input()((regular_voxel_storage*)to_write); 219 | 220 | if (volume.value == PRODUCT_ID) { 221 | // @todo this still needs to be generalized 222 | if (voxels_->value_bits() != 32) { 223 | throw std::runtime_error("Unable to assign product ids to this voxel value type"); 224 | } 225 | 226 | uint32_t v = it->first.second; 227 | for (auto& ijk : *filled) { 228 | voxels_->Set(ijk, &v); 229 | } 230 | } else { 231 | output.intermediate_result(it->first.second, voxels_, filled); 232 | } 233 | 234 | delete filled; 235 | 236 | if (use_copy_) { 237 | auto tmp = voxels_temp_->empty_copy(); 238 | delete voxels_temp_; 239 | voxels_temp_ = tmp; 240 | } else { 241 | delete voxels_temp_; 242 | voxels_temp_ = factory_.create(x1_, y1_, z1_, d_, nx_, ny_, nz_); 243 | } 244 | } 245 | 246 | n++; 247 | progress_(n * 100 / N); 248 | } 249 | 250 | auto post_result = output.final_result(voxels_, voxels_temp_); 251 | 252 | if (post_result && post_result != voxels_) { 253 | delete voxels_; 254 | voxels_ = post_result; 255 | } 256 | } 257 | 258 | abstract_voxel_storage* voxels() { return voxels_; } 259 | }; 260 | 261 | 262 | class threaded_processor : public abstract_processor { 263 | double x1_, y1_, z1_, d_; 264 | int nx_, ny_, nz_; 265 | std::vector cs; 266 | size_t num_threads_; 267 | progress_writer& p_; 268 | size_t chunk_size_; 269 | abstract_voxel_storage* result_; 270 | abstract_voxel_storage* voxels_; 271 | public: 272 | threaded_processor(double x1, double y1, double z1, double d, int nx, int ny, int nz, size_t chunk_size, progress_writer& p) 273 | : x1_(x1), y1_(y1), z1_(z1), d_(d), nx_(nx), ny_(ny), nz_(nz), chunk_size_(chunk_size), num_threads_(std::thread::hardware_concurrency()), p_(p), result_(nullptr), voxels_(nullptr) {} 274 | 275 | threaded_processor(double x1, double y1, double z1, double d, int nx, int ny, int nz, size_t chunk_size, size_t num_threads, progress_writer& p) 276 | : x1_(x1), y1_(y1), z1_(z1), d_(d), nx_(nx), ny_(ny), nz_(nz), chunk_size_(chunk_size), num_threads_(num_threads > 0 ? num_threads : std::thread::hardware_concurrency()), p_(p), result_(nullptr), voxels_(nullptr) {} 277 | 278 | threaded_processor(abstract_voxel_storage* storage, size_t num_threads, progress_writer& progress) 279 | : voxels_(storage) 280 | , num_threads_(num_threads) 281 | , p_(progress) 282 | , result_(nullptr) 283 | {} 284 | 285 | ~threaded_processor() { 286 | for (auto it = cs.begin(); it != cs.end(); ++it) { 287 | // delete *it; 288 | } 289 | } 290 | 291 | void process(geometry_collection_t::const_iterator start, geometry_collection_t::const_iterator end, const fill_volume_t& volume, const output& output) { 292 | const size_t N = std::distance(start, end); 293 | const double d = (double)N / num_threads_; 294 | 295 | std::vector ts; 296 | cs.resize(num_threads_); 297 | ts.reserve(num_threads_); 298 | auto progress = p_.thread(num_threads_); 299 | size_t x0 = 0; 300 | bool first = true; 301 | for (size_t i = 0; i < num_threads_; ++i) { 302 | size_t x1 = (size_t)floor((i + 1) * d); 303 | if (x1 == x0) continue; 304 | first = false; 305 | if (i == num_threads_ - 1) { 306 | x1 = N; 307 | } 308 | if (voxels_) { 309 | auto local_storage = first ? voxels_ : voxels_->empty_copy(); 310 | ts.emplace_back(std::thread([this, local_storage, x0, x1, i, &start, &volume, &output, &progress]() { 311 | processor* vf = new processor(local_storage, [i, &progress](int p) { 312 | progress(i, p); 313 | }); 314 | cs[i] = vf; 315 | vf->process(start + x0, start + x1, volume, output.parallelize()); 316 | })); 317 | } else { 318 | // @todo refactor this. 319 | ts.emplace_back(std::thread([this, x0, x1, i, &start, &volume, &output, &progress]() { 320 | processor* vf = new processor(x1_, y1_, z1_, d_, nx_, ny_, nz_, chunk_size_, [i, &progress](int p) { 321 | progress(i, p); 322 | }); 323 | cs[i] = vf; 324 | vf->process(start + x0, start + x1, volume, output.parallelize()); 325 | })); 326 | } 327 | x0 = x1; 328 | } 329 | for (auto jt = ts.begin(); jt != ts.end(); ++jt) { 330 | jt->join(); 331 | } 332 | if (output.mode == 1) { 333 | abstract_voxel_storage* first = 0; 334 | for (auto jt = cs.begin(); jt != cs.end(); ++jt) { 335 | if (*jt) { 336 | if (first == 0) { 337 | first = (**jt).voxels(); 338 | } else { 339 | first->boolean_union_inplace((**jt).voxels()); 340 | delete *jt; 341 | } 342 | } 343 | } 344 | if (first) { 345 | progress.end(); 346 | 347 | result_ = first; 348 | auto post_process_result = output.final_result(first, first); 349 | if (post_process_result) { 350 | result_ = post_process_result; 351 | } 352 | } 353 | } 354 | progress.end(); 355 | } 356 | 357 | abstract_voxel_storage* voxels() { 358 | return result_; 359 | } 360 | }; 361 | 362 | 363 | #endif 364 | -------------------------------------------------------------------------------- /progress.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | /* * 3 | /* Copyright 2021 AECgeeks * 4 | /* * 5 | /* Permission is hereby granted, free of charge, to any person obtaining a * 6 | /* copy of this software and associated documentation files (the * 7 | /* "Software"), to deal in the Software without restriction, including * 8 | /* without limitation the rights to use, copy, modify, merge, publish, * 9 | /* distribute, sublicense, and/or sell copies of the Software, and to * 10 | /* permit persons to whom the Software is furnished to do so, subject to * 11 | /* the following conditions: * 12 | /* * 13 | /* The above copyright notice and this permission notice shall be included * 14 | /* in all copies or substantial portions of the Software. * 15 | /* * 16 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 | /* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * 20 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * 21 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * 22 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 | /* * 24 | /***************************************************************************/ 25 | 26 | // A trivial C++ progress bar 27 | 28 | #ifndef PROGRESS_H 29 | #define PROGRESS_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | class progress_bar { 39 | std::ostream& s_; 40 | float max_; 41 | size_t width_; 42 | size_t* last_emitted_p_ = nullptr; 43 | 44 | public: 45 | enum style { 46 | BAR, DOTS 47 | }; 48 | 49 | private: 50 | style style_; 51 | 52 | public: 53 | progress_bar(std::ostream& s = std::cerr, style st = BAR, float max = 1., size_t width = 50) 54 | : s_(s) 55 | , max_(max) 56 | , width_(st == BAR ? width : 100U) 57 | , style_(st) 58 | {} 59 | 60 | void operator()(size_t p) { 61 | if (last_emitted_p_ && p <= *last_emitted_p_) { 62 | return; 63 | } 64 | 65 | p = p > width_ ? width_ : p; 66 | if (style_ == BAR) { 67 | s_ << "\r[" + std::string(p, '#') + std::string(width_ - p, ' ') + "]" << std::flush; 68 | } 69 | else { 70 | s_ << std::string(p - (last_emitted_p_ ? *last_emitted_p_ : 0U), '.') << std::flush; 71 | } 72 | 73 | if (last_emitted_p_) { 74 | *last_emitted_p_ = p; 75 | } 76 | else { 77 | last_emitted_p_ = new size_t(p); 78 | } 79 | } 80 | 81 | void operator()(float p) { 82 | (*this)((size_t)(p / max_ * width_)); 83 | } 84 | 85 | ~progress_bar() { 86 | delete last_emitted_p_; 87 | } 88 | }; 89 | 90 | class application_progress { 91 | std::vector estimates_; 92 | size_t phase_ = 0; 93 | std::function callback_; 94 | float total_; 95 | 96 | public: 97 | void operator()(float p) { 98 | auto progress = std::accumulate(estimates_.begin(), estimates_.begin() + phase_, 0.f); 99 | progress += p * estimates_[phase_]; 100 | callback_(progress / total_); 101 | } 102 | 103 | application_progress(const std::vector& estimates, const std::function& callback) 104 | : estimates_(estimates) 105 | , callback_(callback) 106 | { 107 | total_ = std::accumulate(estimates_.begin(), estimates_.end(), 0.f); 108 | (*this)(0.); 109 | } 110 | 111 | void finished() { 112 | ++phase_; 113 | (*this)(0.); 114 | } 115 | 116 | ~application_progress() { 117 | phase_ = estimates_.size() - 2; 118 | finished(); 119 | } 120 | 121 | }; 122 | 123 | #endif -------------------------------------------------------------------------------- /progress_writer.cpp: -------------------------------------------------------------------------------- 1 | #include "progress_writer.h" 2 | 3 | #include 4 | #include 5 | 6 | static std::mutex progress_mtx; 7 | 8 | void threaded_progress_writer::operator()(int n, int i) { 9 | if (silent_) return; 10 | 11 | if (ps_[n] == i) { 12 | return; 13 | } 14 | ps_[n] = i; 15 | progress_mtx.lock(); 16 | std::cerr << "\r" << item_ << " "; 17 | for (auto it = ps_.begin(); it != ps_.end(); ++it) { 18 | std::cerr << *it << " "; 19 | } 20 | std::cerr.flush(); 21 | 22 | int completed = std::accumulate(ps_.begin(), ps_.end(), 0); 23 | int total = ps_.size() * 100; 24 | 25 | progress_mtx.unlock(); 26 | 27 | float progress = (float)completed / total; 28 | if (application_progress_callback) { 29 | (*application_progress_callback)(progress); 30 | } 31 | } -------------------------------------------------------------------------------- /progress_writer.h: -------------------------------------------------------------------------------- 1 | #ifndef PROGRESS_WRITER_H 2 | #define PROGRESS_WRITER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class threaded_progress_writer { 12 | private: 13 | std::vector ps_; 14 | std::string item_; 15 | mutable bool ended_; 16 | bool silent_; 17 | public: 18 | boost::optional> application_progress_callback; 19 | 20 | threaded_progress_writer(const std::string& item, int n, bool silent=false) 21 | : item_(item), ended_(false), silent_(silent) 22 | { 23 | ps_.resize(n); 24 | } 25 | 26 | ~threaded_progress_writer() { 27 | end(); 28 | } 29 | 30 | void operator()(int n, int i); 31 | 32 | void end() const { 33 | if (silent_) return; 34 | if (!ended_) { 35 | std::cerr << std::endl; 36 | ended_ = true; 37 | } 38 | } 39 | }; 40 | 41 | class progress_writer { 42 | private: 43 | std::string item_; 44 | mutable bool ended_; 45 | bool silent_; 46 | public: 47 | boost::optional> application_progress_callback; 48 | 49 | progress_writer() : silent_(true) {} 50 | 51 | progress_writer(const std::string& item, bool silent = false) 52 | : item_(item), ended_(false), silent_(silent) {} 53 | 54 | ~progress_writer() { 55 | end(); 56 | } 57 | 58 | void operator()(int i) const { 59 | if (!silent_) { 60 | std::cerr << "\r" << item_ << " " << i; 61 | std::cerr.flush(); 62 | if (application_progress_callback) { 63 | (*application_progress_callback)((float)i / 100.f); 64 | } 65 | } 66 | } 67 | 68 | void end() const { 69 | if (!silent_ && !ended_) { 70 | std::cerr << std::endl; 71 | ended_ = true; 72 | } 73 | } 74 | 75 | threaded_progress_writer thread(int n) { 76 | return threaded_progress_writer(item_, n, silent_); 77 | } 78 | }; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "voxec" 7 | version = "[VERSION]" 8 | authors = [ 9 | { name="Thomas Krijnen", email="thomas@aecgeeks.com" }, 10 | ] 11 | description = "Python bindings for the voxelization toolkit" 12 | requires-python = ">=3.9,<3.14" 13 | classifiers = [ 14 | "Programming Language :: Python :: 3", 15 | "License :: OSI Approved :: MIT License", 16 | ] 17 | dependencies = [] 18 | 19 | [project.urls] 20 | "Bug Tracker" = "https://github.com/opensourceBIM/voxelization_toolkit/issues" 21 | 22 | [tool.setuptools.packages.find] 23 | include = ["voxec*"] 24 | 25 | [tool.setuptools.package-data] 26 | "*" = ["*.*"] 27 | -------------------------------------------------------------------------------- /python/voxec/__init__.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import datetime 3 | import json 4 | import os 5 | import shutil 6 | import subprocess 7 | import tempfile 8 | import threading 9 | import time 10 | 11 | from dataclasses import dataclass 12 | 13 | import numpy as np 14 | 15 | VOXEC = shutil.which("voxec") or shutil.which("voxec.exe") 16 | assert VOXEC 17 | 18 | def draw_points(arr, fn, colors, factor=1., label=None, vec=None): 19 | import numpy as np 20 | import matplotlib 21 | import matplotlib.pyplot as plt 22 | xyz = np.hstack((arr[:, 0:3], np.ones((len(arr), 1)))) 23 | clr = arr.T[3] 24 | if vec is not None: 25 | vec /= np.linalg.norm(vec) 26 | Z = np.array((0,0,1.0)) 27 | up = np.cross(vec, np.cross(Z, vec)) 28 | up /= np.linalg.norm(up) 29 | M = np.eye(4) 30 | M[0:3,0:3] = np.cross(up, vec), up, vec 31 | xyzw = (np.asmatrix(M) * np.asmatrix(xyz).T).T.A 32 | xyzw[:, 0:3] /= xyzw[:,3:4] 33 | xyz = xyzw 34 | order = xyz[:, 2].argsort() 35 | xy = xyz[order][:,0:2] 36 | plt.figure(figsize=np.ptp(xy, axis=0)) 37 | norm = plt.Normalize(colors[0][0], colors[-1][0]) 38 | colors = [(norm(p[0]), *p[1:]) for p in colors] 39 | cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", colors) 40 | plt.scatter(xy.T[0], xy.T[1], c=clr[order] * factor, s=4, label=label, cmap=cmap, norm=norm) 41 | plt.axis('off') 42 | if label: 43 | plt.colorbar(shrink=0.5) 44 | plt.savefig(fn, bbox_inches='tight') 45 | 46 | class voxel_post_process: 47 | pass 48 | 49 | class get_json(voxel_post_process): 50 | def __init__(self, filename): 51 | self.filename = filename 52 | 53 | def __call__(self, cwd): 54 | with open(os.path.join(cwd, self.filename)) as f: 55 | return json.load(f) 56 | 57 | class get_csv(voxel_post_process): 58 | def __init__(self, filename): 59 | self.filename = filename 60 | 61 | def __call__(self, cwd): 62 | import pandas as pd 63 | return pd.read_csv(os.path.join(cwd, self.filename), delimiter=',').values 64 | 65 | class get_output(voxel_post_process): 66 | def __init__(self, filename): 67 | self.filename = filename 68 | 69 | def __call__(self, cwd): 70 | return os.path.join(cwd, self.filename) 71 | 72 | @dataclass 73 | class obj_data: 74 | name : str 75 | vertex_positions : np.ndarray 76 | vertex_normals : np.ndarray 77 | face_indices : np.ndarray 78 | 79 | def read_voxec_obj(fn : str) -> obj_data: 80 | """Note that there are many assumptions in this read function 81 | on how the OBJ file is structured that may be very specific 82 | to how voxec omits OBJ data. 83 | 84 | Args: 85 | fn (str): file path 86 | 87 | Yields: 88 | obj_data: Parsed data of individual OBJ `g` groups 89 | """ 90 | 91 | init = lambda: ([], [], []) 92 | concat = lambda vs: np.concatenate([v.reshape((1, -1)) for v in vs], axis=0) if vs else None 93 | 94 | def safe_float(v): 95 | try: return float(v) 96 | except: return 0. 97 | 98 | accums = init() 99 | name = None 100 | 101 | with open(fn) as f: 102 | for l in f: 103 | if l.startswith("g "): 104 | if any(accums): 105 | yield obj_data(name, *map(concat, accums)) 106 | accums = init() 107 | name = l[2:].strip() 108 | elif l.startswith("f "): 109 | idxs = l.split(" ")[1:] 110 | vidx, nidx = zip(*(s.strip().split("//") for s in idxs)) 111 | assert vidx == nidx 112 | accums[2].append(np.array(tuple(map(int, vidx)))) 113 | elif l[0] == 'v': 114 | array_idx = 1 if l[1] == 'n' else 0 115 | accums[array_idx].append(np.array(tuple(map(safe_float, l.split(" ")[1:])))) 116 | if any(accums): 117 | yield obj_data(name, *map(concat, accums)) 118 | accums = init() 119 | 120 | 121 | class get_obj(voxel_post_process): 122 | def __init__(self, filename): 123 | self.filename = filename 124 | 125 | def __call__(self, cwd): 126 | return list(read_voxec_obj(os.path.join(cwd, self.filename))) 127 | 128 | 129 | class get_filecontent(voxel_post_process): 130 | def __init__(self, filename): 131 | self.filename = filename 132 | 133 | def __call__(self, cwd): 134 | return open(os.path.join(cwd, self.filename), 'r').read() 135 | 136 | 137 | def run(command, ifc_files, *ops, keep_dir=False, **kwargs): 138 | def make_args(d): 139 | for kv in d.items(): 140 | if kv[1] is True: 141 | yield "--%s" % kv[0] 142 | else: 143 | yield "--%s=%s" % kv 144 | 145 | if keep_dir: 146 | cwd = tempfile.mkdtemp() 147 | context = contextlib.nullcontext 148 | else: 149 | cwd = None 150 | context = tempfile.TemporaryDirectory 151 | 152 | with context() as c: 153 | cwd = cwd or c 154 | 155 | with open(os.path.join(cwd, "voxelfile.txt"), "w") as f: 156 | f.write(command) 157 | 158 | if isinstance(ifc_files, str): 159 | ifc_files = [ifc_files] 160 | for i, fn in enumerate(ifc_files): 161 | if isinstance(fn, str): 162 | shutil.copyfile(fn, os.path.join(cwd, os.path.basename(fn))) 163 | else: 164 | fn.write(os.path.join(cwd, f'model{i:03d}.ifc')) 165 | 166 | rc = None 167 | log = [] 168 | completed = False 169 | 170 | def run_process(): 171 | nonlocal rc, completed 172 | rc = subprocess.call([ 173 | VOXEC, 174 | "-q", "--no-vox", "--log-file", "log.json", 175 | "voxelfile.txt", 176 | *make_args(kwargs) 177 | ], cwd=cwd, stdout=subprocess.PIPE) 178 | completed = True 179 | 180 | thread = threading.Thread(target=run_process) 181 | thread.start() 182 | 183 | def check_log(): 184 | def read_log(): 185 | try: 186 | with open(os.path.join(cwd, "log.json"), "r") as f: 187 | for l in [l.strip() for l in f if l.strip()]: 188 | yield json.loads(l) 189 | except: pass 190 | 191 | new_log = list(read_log()) 192 | for x in new_log[len(log):]: 193 | print(x) 194 | log[:] = new_log 195 | 196 | while not completed: 197 | check_log() 198 | time.sleep(1.) 199 | 200 | if rc != 0: 201 | with open(os.path.join(cwd, "log.json"), "a") as f: 202 | json.dump({"severity": "fatal", "message": "internal error", "time": str(datetime.datetime.now())}, f) 203 | 204 | check_log() 205 | 206 | return [op(cwd) for op in ops] 207 | 208 | def do_try(fn, default=None): 209 | try: 210 | return fn() 211 | except: 212 | return default 213 | 214 | def exterior_elements(*file, threads=None, size=None): 215 | elements = [x['guid'] for x in run("""file = parse("*.ifc") 216 | all_surfaces = create_geometry(file) 217 | voxels = voxelize(all_surfaces) 218 | external = exterior(voxels) 219 | shell = offset(external) 220 | export_json(file, shell, all_surfaces, "result.json", individual_faces=0, factor=4) 221 | """, file, get_json("result.json"), keep_dir=True, threads=threads or 4, size=size or 0.1, chunk=16)[0]] 222 | print("Exterior:") 223 | print("=========") 224 | for el in elements: 225 | print(el) 226 | 227 | def headroom(*file, threads=None, size=None): 228 | cmd = """function get_reachability(file) 229 | surfaces = create_geometry(file, exclude={"IfcOpeningElement", "IfcDoor", "IfcSpace"}) 230 | surface_voxels = voxelize(surfaces) 231 | slabs = create_geometry(file, include={"IfcSlab"}) 232 | slab_voxels = voxelize(slabs) 233 | doors = create_geometry(file, include={"IfcDoor"}) 234 | door_voxels = voxelize(doors) 235 | walkable = shift(slab_voxels, dx=0, dy=0, dz=1) 236 | walkable_minus = subtract(walkable, slab_voxels) 237 | walkable_seed = intersect(door_voxels, walkable_minus) 238 | surfaces_sweep = sweep(surface_voxels, dx=0, dy=0, dz=0.5) 239 | surfaces_padded = offset_xy(surface_voxels, 0.1) 240 | surfaces_obstacle = sweep(surfaces_padded, dx=0, dy=0, dz=-0.5) 241 | walkable_region = subtract(surfaces_sweep, surfaces_obstacle) 242 | walkable_seed_real = subtract(walkable_seed, surfaces_padded) 243 | reachable = traverse(walkable_region, walkable_seed_real) 244 | return reachable 245 | 246 | file = parse("*.ifc") 247 | all_surfaces = create_geometry(file, exclude={"IfcSpace", "IfcOpeningElement", "IfcDoor"}) 248 | voxels = voxelize(all_surfaces) 249 | reachable = get_reachability(file) 250 | space = sweep(reachable, dx=0, dy=0, dz=1, until=voxels, max=60) 251 | height_count = collapse_count(space, dx=0, dy=0, dz=-1) 252 | export_csv(height_count, "height_count.csv") 253 | """ 254 | flow = run(cmd, file, get_csv("height_count.csv"), threads=threads or 4, size=size or 0.1, chunk=16)[0] 255 | colors = [(0.0, "red"), (1.5, "yellow"), (3, "green"), (5, "green"), (5.5, (0.9, 0.9, 0.9))] 256 | draw_points(flow, "heights.png", colors, label="height", factor=0.1, vec=(3,-1,1)) 257 | 258 | def evacuation_distance(*file, threads=None, size=None): 259 | cmd = """function get_reachability(file) 260 | surfaces = create_geometry(file, exclude={"IfcOpeningElement", "IfcDoor", "IfcSpace"}) 261 | surface_voxels = voxelize(surfaces) 262 | slabs = create_geometry(file, include={"IfcSlab"}) 263 | slab_voxels = voxelize(slabs) 264 | doors = create_geometry(file, include={"IfcDoor"}) 265 | door_voxels = voxelize(doors) 266 | walkable = shift(slab_voxels, dx=0, dy=0, dz=1) 267 | walkable_minus = subtract(walkable, slab_voxels) 268 | walkable_seed = intersect(door_voxels, walkable_minus) 269 | surfaces_sweep = sweep(surface_voxels, dx=0, dy=0, dz=0.5) 270 | surfaces_padded = offset_xy(surface_voxels, 0.1) 271 | surfaces_obstacle = sweep(surfaces_padded, dx=0, dy=0, dz=-0.5) 272 | walkable_region = subtract(surfaces_sweep, surfaces_obstacle) 273 | walkable_seed_real = subtract(walkable_seed, surfaces_padded) 274 | reachable = traverse(walkable_region, walkable_seed_real) 275 | return reachable 276 | 277 | file = parse("*.ifc") 278 | all_surfaces = create_geometry(file, exclude={"IfcSpace", "IfcOpeningElement"}) 279 | voxels = voxelize(all_surfaces) 280 | reachable = get_reachability(file) 281 | reachable_shifted = shift(reachable, dx=0, dy=0, dz=1) 282 | reachable_bottom = subtract(reachable, reachable_shifted) 283 | reachable_bottom_offset = offset_xy(reachable_bottom, 1) 284 | reachable_bottom_incl = union(reachable_bottom_offset, reachable_bottom) 285 | external = exterior(voxels) 286 | seed = intersect(external, reachable_bottom_incl) 287 | reachable = union(reachable, seed) 288 | distance = traverse(reachable, seed, connectedness=26, type="uint") 289 | distance_bottom = intersect(distance, reachable_bottom) 290 | export_csv(distance_bottom, "distance.csv") 291 | """ 292 | flow = run(cmd, file, get_csv("distance.csv"), threads=threads or 4, size=size or 0.1, chunk=16)[0] 293 | max = flow.T[3].max() * 0.01 294 | colors = [(0., (0.9,0.9,0.9)), (0.1, "green"), (max / 3., "orange"), (max / 3. * 2., "red"), (max, "purple")] 295 | draw_points(flow, "evacuation_distance.png", colors, label="evacuation distance", factor=0.01, vec=(3,-1,1)) 296 | 297 | COMMANDS = [exterior_elements, headroom, evacuation_distance] 298 | -------------------------------------------------------------------------------- /python/voxec/__main__.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import sys 3 | 4 | from . import COMMANDS 5 | 6 | cmds = {c.__name__: c for c in COMMANDS} 7 | 8 | if __name__ == "__main__": 9 | if len(sys.argv) < 3: 10 | print(f"Usage: {sys.argv[0]} ...") 11 | exit(1) 12 | cmd = cmds.get(sys.argv[1]) 13 | if not cmd: 14 | print(f"Unknown command {sys.argv[1]}, available: {', '.join(cmds)}") 15 | exit(1) 16 | files = sum(map(glob.glob, sys.argv[2:]), []) 17 | cmd(*files) -------------------------------------------------------------------------------- /resample.h: -------------------------------------------------------------------------------- 1 | #ifndef RESAMPLE_H 2 | #define RESAMPLE_H 3 | 4 | #include "storage.h" 5 | 6 | // Function so that integer division is selected 7 | template 8 | T multiply_or_divide(const T& t, int factor, bool ceil) { 9 | if (factor < 0) { 10 | /*if (ceil && std::is_integral::value) { 11 | integer_ceil_div(t, (T)(-factor)); 12 | } else {*/ 13 | return t / (-factor); 14 | // } 15 | } else { 16 | return t * factor; 17 | } 18 | } 19 | 20 | 21 | template 22 | vec_n<3, U> multiply_or_divide(const vec_n<3, U>& t, int factor, bool ceil) { 23 | if (factor < 0) { 24 | if (ceil) { 25 | return t.ceil_div(-factor); 26 | } else { 27 | return t.floor_div(-factor); 28 | } 29 | } else { 30 | return t * ((U)factor); 31 | } 32 | } 33 | 34 | 35 | class resampler { 36 | private: 37 | // An integer factor. Negative means division by absolute 38 | int factor_; 39 | 40 | public: 41 | resampler(int factor) 42 | : factor_(factor) 43 | {} 44 | 45 | regular_voxel_storage* operator()(regular_voxel_storage* storage) { 46 | chunked_voxel_storage* input = (chunked_voxel_storage*) storage; 47 | 48 | // left is floored 49 | auto left = multiply_or_divide(input->grid_offset(), factor_, false); 50 | // right is ceiled 51 | auto right = multiply_or_divide((input->grid_offset() + input->num_chunks().as()), factor_, true); 52 | 53 | chunked_voxel_storage* result = new chunked_voxel_storage( 54 | left, 55 | multiply_or_divide(input->voxel_size(), -factor_, false), 56 | input->chunk_size(), 57 | (right - left).as() 58 | ); 59 | 60 | // upsampling retains all primitives. downsampling can only retain primitives 61 | // if there are similar adjacent primitives, but this is currently not checked 62 | 63 | const bool is_upsampling = factor_ > 0; 64 | 65 | if (is_upsampling) { 66 | auto block = make_vec(factor_, factor_, factor_).as(); 67 | 68 | BEGIN_LOOP_ZERO_2(input->num_chunks()) 69 | const auto& chunk_idx = ijk; 70 | auto c = input->get_chunk(ijk); 71 | auto ijk2 = ijk * ((size_t)factor_); 72 | if (c != nullptr) { 73 | if (c->is_constant()) { 74 | BEGIN_LOOP_ZERO_2(block) 75 | result->create_constant(ijk2 + ijk, 1); 76 | END_LOOP; 77 | } else if (!c->is_explicit()) { 78 | auto p = (planar_voxel_storage*) c; 79 | for (auto& i : p->offsets()) { 80 | BEGIN_LOOP_ZERO_2(block) 81 | if ((i * factor_) / result->chunk_size() == ijk.get(p->axis())) { 82 | result->create_plane_primitive(ijk2 + ijk, p->axis(), (i * factor_) % result->chunk_size()); 83 | } 84 | END_LOOP; 85 | } 86 | } else { 87 | auto cv = (continuous_voxel_storage*) c; 88 | for (auto& v : (*cv)) { 89 | BEGIN_LOOP_ZERO_2(block) 90 | result->Set((v + chunk_idx * input->chunk_size()) * ((size_t)factor_) + ijk); 91 | END_LOOP; 92 | } 93 | } 94 | } 95 | END_LOOP; 96 | } else { 97 | auto block = make_vec(-factor_, -factor_, -factor_).as(); 98 | auto grid_difference_in_voxels = input->grid_offset() * (long)input->chunk_size() / ((long)(-factor_)) - result->grid_offset() * (long)input->chunk_size(); 99 | // std::cout << "grid_difference_in_voxels " << grid_difference_in_voxels.format() << std::endl; 100 | 101 | // @todo constrain loop to input so that no further check is necessary? 102 | BEGIN_LOOP_ZERO_2(result->extents()) 103 | auto ijk2 = ijk * ((size_t)(-factor_)); 104 | if ((ijk2 < input->extents()).all()) { 105 | bool any = false; 106 | BEGIN_LOOP_ZERO_2(block) 107 | if (input->Get(ijk2 + ijk)) { 108 | any = true; 109 | goto outer; 110 | } 111 | END_LOOP; 112 | 113 | outer: 114 | if (any) { 115 | // @todo double check that this does not underflow 116 | result->Set((ijk.as() + grid_difference_in_voxels).as()); 117 | } 118 | } 119 | END_LOOP; 120 | } 121 | 122 | return result; 123 | } 124 | 125 | }; 126 | 127 | #endif -------------------------------------------------------------------------------- /shift.h: -------------------------------------------------------------------------------- 1 | #ifndef SHIFT_H 2 | #define SHIFT_H 3 | 4 | #include "storage.h" 5 | 6 | class shift { 7 | public: 8 | abstract_voxel_storage* until = nullptr; 9 | boost::optional max_depth; 10 | 11 | regular_voxel_storage* operator()(abstract_voxel_storage* storage, int dx, int dy, int dz) { 12 | regular_voxel_storage* shifted = (regular_voxel_storage*)storage->empty_copy(); 13 | auto bounds = storage->bounds(); 14 | 15 | auto extents = shifted->extents().as(); 16 | auto zero = make_vec(0, 0, 0); 17 | 18 | auto dxyz = make_vec(dx, dy, dz); 19 | 20 | bool use_value = storage->value_bits() == 32; 21 | 22 | if (use_value) { 23 | uint32_t v; 24 | BEGIN_LOOP_I2(bounds[0], bounds[1]) 25 | storage->Get(ijk, &v); 26 | if (v) { 27 | auto ijk2 = ijk.as() + dxyz; 28 | if ((ijk2 >= zero).all() && (ijk2 < extents).all()) { 29 | shifted->Set(ijk2.as(), &v); 30 | } 31 | } 32 | END_LOOP; 33 | 34 | } else { 35 | BEGIN_LOOP_I2(bounds[0], bounds[1]) 36 | if (storage->Get(ijk)) { 37 | auto ijk2 = ijk.as() + dxyz; 38 | if ((ijk2 >= zero).all() && (ijk2 < extents).all()) { 39 | shifted->Set(ijk2.as()); 40 | } 41 | } 42 | END_LOOP; 43 | } 44 | 45 | return shifted; 46 | } 47 | }; 48 | 49 | #endif -------------------------------------------------------------------------------- /storage.cpp: -------------------------------------------------------------------------------- 1 | #include "storage.h" 2 | #include "traversal.h" 3 | 4 | #include 5 | #include 6 | 7 | set_voxel_iterator::set_voxel_iterator(regular_voxel_storage* storage, vec_n<3, size_t> current) 8 | : storage_(storage) 9 | , current_(current) 10 | , bounds_(storage->bounds()) {} 11 | 12 | set_voxel_iterator& set_voxel_iterator::operator++() { 13 | size_t i, j, k; 14 | current_.tie(i, j, k); 15 | 16 | size_t i0, j0, k0, i1, j1, k1; 17 | bounds_[0].tie(i0, j0, k0); 18 | bounds_[1].tie(i1, j1, k1); 19 | 20 | goto break_into; 21 | 22 | for (i = i0; i <= i1; ++i) { 23 | for (j = j0; j <= j1; ++j) { 24 | for (k = k0; k <= k1; ++k) { 25 | if (storage_->Get(make_vec(i, j, k))) { 26 | current_ = make_vec(i, j, k); 27 | goto break_out; 28 | } 29 | break_into: 30 | 1 == 1; 31 | } 32 | } 33 | } 34 | 35 | current_ = storage_->end().current_; 36 | 37 | break_out: 38 | return *this; 39 | } 40 | 41 | bool set_voxel_iterator::neighbour(const vec_n<3, long>& d) const { 42 | vec_n<3, size_t> v = (current_.as() + d).as(); 43 | if ((v < bounds_[0]).any() || (v > bounds_[1]).any()) { 44 | return false; 45 | } 46 | return storage_->Get(v); 47 | } 48 | 49 | void* set_voxel_iterator::value(void* val) const { 50 | storage_->Get(current_, val); 51 | return val; 52 | } 53 | 54 | void* set_voxel_iterator::neighbour_value(const vec_n<3, long>& d, void* val) const { 55 | vec_n<3, size_t> v = (current_.as() + d).as(); 56 | if ((v < bounds_[0]).any() || (v > bounds_[1]).any()) { 57 | uint8_t* loc = (uint8_t*)val; 58 | // @todo is this correct? 59 | for (int i = 0; i < storage_->value_bits() / 8; ++i) { 60 | (*loc++) = 0; 61 | } 62 | } else { 63 | storage_->Get(v, val); 64 | } 65 | return val; 66 | } 67 | 68 | void regular_voxel_storage::obj_export(std::ostream& fs, bool with_components, bool use_value, bool with_vertex_normals) { 69 | obj_export_helper helper(fs); 70 | obj_export(helper, with_components, use_value, with_vertex_normals); 71 | } 72 | 73 | void regular_voxel_storage::obj_export(obj_export_helper& obj, bool with_components, bool use_value, bool with_vertex_normals) { 74 | std::ostream& fs = *obj.stream; 75 | 76 | // Take care to only emit normals once, even though it does not really affect file integrity 77 | // When computing (averaged) vertex normals we emit them on the fly. 78 | if (!obj.normals_emitted && !with_vertex_normals) { 79 | fs << "vn 1 0 0\n"; 80 | fs << "vn -1 0 0\n"; 81 | fs << "vn 0 1 0\n"; 82 | fs << "vn 0 -1 0\n"; 83 | fs << "vn 0 0 1\n"; 84 | fs << "vn 0 0 -1\n"; 85 | obj.normals_emitted = true; 86 | } 87 | 88 | 89 | if (with_components && !use_value) { 90 | size_t counter = 0; 91 | connected_components(this, [&obj, &fs, &counter, use_value, with_vertex_normals](regular_voxel_storage* component) { 92 | fs << "g component" << (counter++) << "\n"; 93 | component->obj_export(obj, false, use_value, with_vertex_normals); 94 | }); 95 | return; 96 | } 97 | 98 | // for progress print 99 | // auto N = count(); 100 | // size_t n = 0; 101 | 102 | size_t& nv = obj.vert_counter; 103 | 104 | // big enough? 105 | char V0[8]; 106 | char V1[8]; 107 | 108 | // A map of verte coords (size_t, size_t, size_t) to 0-based index in the OBJ stream 109 | std::map< std::array, size_t > vertex_map; 110 | // Only in use when use_value=true 111 | std::map< std::array, std::vector< std::pair, size_t> > > triangles; 112 | // Only in use when with_vertex_normals=true 113 | std::vector >> vertex_normals; 114 | 115 | const double d = voxel_size(); 116 | auto end_ = end(); 117 | for (auto it = begin(); it != end_; ++it) { 118 | it.value(V1); 119 | 120 | // Loop over 6 directions and store in `n`: 121 | // - (+1, 0, 0) 122 | // - (-1, 0, 0) 123 | // - (0, +1, 0) 124 | // - (0, -1, 0) 125 | // - (0, 0, +1) 126 | // - (0, 0, -1) 127 | for (size_t f = 0; f < 6; ++f) { 128 | vec_n<3, long> n; 129 | size_t normal = f / 2; 130 | size_t o0 = (normal + 1) % 3; 131 | size_t o1 = (normal + 2) % 3; 132 | size_t side = f % 2; 133 | for (size_t i = 0; i < 3; ++i) { 134 | if (i == normal) { 135 | n.get(i) = side ? 1 : -1; 136 | break; 137 | } 138 | } 139 | 140 | if (use_value 141 | ? ( 142 | !equal_pointed_to(value_bits() / 8, it.neighbour_value(n, V0), V1) && 143 | (!(!is_zero(value_bits() / 8, V0) && !is_zero(value_bits() / 8, V1)) || side) 144 | ) 145 | : !it.neighbour(n)) 146 | { 147 | std::array< std::array, 4 > vs; 148 | vs.fill((*it).as_array()); 149 | vs[1][o0] += 1; 150 | vs[2][o0] += 1; 151 | vs[2][o1] += 1; 152 | vs[3][o1] += 1; 153 | if (!side) { 154 | std::reverse(vs.begin(), vs.end()); 155 | } 156 | for (auto& v : vs) { 157 | if (side) { 158 | v[normal] += 1; 159 | } 160 | auto inserted = vertex_map.insert({ v, vertex_map.size() + nv }); 161 | if (inserted.second) { 162 | fs << "v"; 163 | for (int i = 0; i < 3; ++i) { 164 | fs << " " << (v[i] * d + origin().get(i)); 165 | } 166 | fs << "\n"; 167 | 168 | if (with_vertex_normals) { 169 | vertex_normals.emplace_back(); 170 | } 171 | } 172 | 173 | if (with_vertex_normals) { 174 | auto vi = inserted.first->second - nv; 175 | vertex_normals[vi].first++; 176 | vertex_normals[vi].second += n; 177 | } 178 | } 179 | static std::array< std::array, 2 > indices = {{ 180 | {{0,1,2}}, 181 | {{0,2,3}} 182 | }}; 183 | if (!use_value) { 184 | for (auto& i : indices) { 185 | fs << "f"; 186 | for (auto& j : i) { 187 | auto vi = vertex_map.find(vs[j])->second; 188 | fs << " " << vi << "//" << (with_vertex_normals ? vi : (f+1)); 189 | } 190 | fs << "\n"; 191 | } 192 | } else { 193 | auto va = to_number(value_bits() / 8, V0); 194 | auto vb = to_number(value_bits() / 8, V1); 195 | if (va > vb) { 196 | std::swap(va, vb); 197 | } 198 | for (auto& i : indices) { 199 | std::array arr; 200 | for (int j = 0; j < 3; ++j) { 201 | arr[j] = vertex_map.find(vs[i[j]])->second; 202 | } 203 | triangles[{ { va, vb }}].push_back({ arr, f + 1 }); 204 | } 205 | } 206 | } 207 | } 208 | /* 209 | if (n % 1000) { 210 | std::cout << n * 100 / N << std::endl; 211 | } 212 | n++; 213 | */ 214 | } 215 | 216 | for (auto& vn : vertex_normals) { 217 | fs << "vn"; 218 | // @todo dividing by number of associated facets is not really 219 | // nessary as we take norm2 anyway? 220 | auto vnf = vn.second.as() / vn.first; 221 | auto vnf_normalized = vnf / vnf.norm2(); 222 | for (int i = 0; i < 3; ++i) { 223 | fs << " " << vnf_normalized.get(i); 224 | } 225 | fs << "\n"; 226 | } 227 | 228 | for (const auto& p : triangles) { 229 | fs << "g " << p.first[0] << "-" << p.first[1] << "\n"; 230 | for (const auto& t : p.second) { 231 | fs << "f"; 232 | for (auto& i : t.first) { 233 | fs << " " << i << "//" << t.second; 234 | } 235 | fs << "\n"; 236 | } 237 | } 238 | 239 | nv += vertex_map.size(); 240 | } 241 | 242 | regular_voxel_storage* storage_for(std::array< vec_n<3, double>, 2 >& bounds, size_t max_extents, size_t padding, size_t chunk_size) { 243 | auto bounds_size = bounds[1] - bounds[0]; 244 | auto voxel_size = (bounds_size / (double)max_extents).max_element(); 245 | auto extents = (bounds_size / voxel_size).ceil().as(); 246 | 247 | double x1, y1, z1; 248 | decltype(extents)::element_type nx, ny, nz; 249 | 250 | (bounds[0] - (padding*voxel_size)).tie(x1, y1, z1); 251 | (extents + 2 * padding).tie(nx, ny, nz); 252 | 253 | return new chunked_voxel_storage(x1, y1, z1, voxel_size, nx, ny, nz, chunk_size); 254 | } 255 | -------------------------------------------------------------------------------- /surface_count.sh: -------------------------------------------------------------------------------- 1 | echo " 2 | import os 3 | import sys 4 | import time 5 | import tempfile 6 | import subprocess 7 | 8 | from collections import defaultdict 9 | 10 | S = float(sys.argv[1]) 11 | 12 | def run_voxelfile(fn, args=None): 13 | def make_args(d): 14 | for kv in d.items(): 15 | yield \"--%s=%s\" % kv 16 | 17 | di = defaultdict(dict) 18 | proc = subprocess.Popen([r\"voxec\", fn, \"--threads=1\"] + list(make_args(args or {})), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 19 | while True: 20 | ln = proc.stderr.readline().decode('utf-8') 21 | if not ln: 22 | if proc.poll() is None: 23 | time.sleep(0.5) 24 | continue 25 | else: 26 | break 27 | if ln.startswith('@'): 28 | attrs = list(map(str.strip, ln.split(';'))) 29 | id = int(attrs[0][1:]) 30 | for a in attrs[1:]: 31 | k, v = map(str.strip, a.split(':')) 32 | di[id][k] = v 33 | 34 | return di 35 | 36 | 37 | tmp, fn = tempfile.mkstemp(suffix='voxelfile.txt', text=True) 38 | os.write(tmp, '''file = parse(\"duplex.ifc\") 39 | surfaces = create_geometry(file) 40 | voxels = voxelize(surfaces) 41 | offset_voxels = offset(voxels) 42 | outmost_voxels = outmost(offset_voxels) 43 | '''.encode('ascii')) 44 | os.fsync(tmp) 45 | os.close(tmp) 46 | 47 | di = run_voxelfile(fn, {'size': S}) 48 | print(S, float(int(di.get(4).get('count'))) * S ** 2., sep=',') 49 | 50 | os.remove(fn) 51 | " > exec.py 52 | 53 | cat <(seq 0.001 0.001 0.009) <(seq 0.01 0.01 0.2) | tac | xargs -P 4 -n 1 python3 exec.py 54 | -------------------------------------------------------------------------------- /sweep.h: -------------------------------------------------------------------------------- 1 | #ifndef SWEEP_H 2 | #define SWEEP_H 3 | 4 | #include "storage.h" 5 | 6 | class sweep { 7 | public: 8 | abstract_voxel_storage* until = nullptr; 9 | boost::optional max_depth; 10 | 11 | regular_voxel_storage* operator()(abstract_voxel_storage* storage, int dx, int dy, int dz) { 12 | int d[3] = { dx, dy, dz }; 13 | int nonzero = 0; 14 | int D = -1; 15 | for (int i = 0; i < 3; ++i) { 16 | if (d[i] != 0) { 17 | nonzero++; 18 | D = i; 19 | } 20 | } 21 | 22 | if (nonzero != 1) { 23 | throw std::runtime_error("Only orthogonal sweeps supported"); 24 | } 25 | 26 | uint32_t v = 1; 27 | const bool use_count = storage->value_bits() == 32; 28 | 29 | regular_voxel_storage* swepts = (regular_voxel_storage*) storage->copy(); 30 | auto bounds = storage->bounds(); 31 | 32 | auto extents = swepts->extents().as(); 33 | auto zero = make_vec(0, 0, 0); 34 | 35 | bool pos = d[D] > 0; 36 | 37 | BEGIN_LOOP_I2(bounds[0], bounds[1]) 38 | if (storage->Get(ijk)) { 39 | if (use_count) { 40 | storage->Get(ijk, &v); 41 | } 42 | auto ijk2 = ijk.as(); 43 | int i = 1; 44 | while (until || (i < std::abs(d[D]) * v)) { 45 | 46 | if (pos) { 47 | ijk2.get(D)++; 48 | } 49 | else { 50 | ijk2.get(D)--; 51 | } 52 | 53 | if (!((ijk2 >= zero).all() && (ijk2 < extents).all())) { 54 | break; 55 | } 56 | 57 | if (until) { 58 | if (until->Get(ijk2.as())) { 59 | break; 60 | } 61 | } 62 | 63 | swepts->Set(ijk2.as()); 64 | 65 | if (max_depth && i >= *max_depth) { 66 | break; 67 | } 68 | 69 | ++i; 70 | } 71 | } 72 | END_LOOP; 73 | 74 | 75 | return swepts; 76 | } 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /tests/fixtures/covering.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('','2018-10-15T15:45:01',(),(),'IfcOpenShell 0.5.0-dev','IfcOpenShell 0.5.0-dev',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #146=IFCCOVERING('00fSj8UXfAn8lUGu3HP6DA',#8,'boeideel',$,$,#58,#145,'00A5CB48-7A1A-4AC4-8BDE-4380D164634A',$); 9 | #145=IFCPRODUCTDEFINITIONSHAPE($,$,(#140,#144)); 10 | #144=IFCSHAPEREPRESENTATION(#141,'Box','BoundingBox',(#143)); 11 | #143=IFCBOUNDINGBOX(#142,16856.,2437.5,10.); 12 | #142=IFCCARTESIANPOINT((-15614.6919554,10.,-10.)); 13 | #141=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Plan',*,*,*,*,#20,$,.PLAN_VIEW.,$); 14 | #140=IFCSHAPEREPRESENTATION(#59,'Body','MappedRepresentation',(#139)); 15 | #139=IFCMAPPEDITEM(#133,#138); 16 | #138=IFCCARTESIANTRANSFORMATIONOPERATOR3D(#134,#135,#136,$,#137); 17 | #137=IFCDIRECTION((0.,0.,-1.)); 18 | #136=IFCCARTESIANPOINT((0.,0.,0.)); 19 | #135=IFCDIRECTION((0.,-1.,0.)); 20 | #134=IFCDIRECTION((1.,0.,0.)); 21 | #133=IFCREPRESENTATIONMAP(#63,#132); 22 | #132=IFCSHAPEREPRESENTATION(#59,'Body','Brep',(#131)); 23 | #131=IFCFACETEDBREP(#130); 24 | #130=IFCCLOSEDSHELL((#70,#75,#87,#91,#102,#105,#108,#111,#114,#117,#120,#123,#126,#129)); 25 | #129=IFCFACE((#128)); 26 | #128=IFCFACEOUTERBOUND(#127,.T.); 27 | #127=IFCPOLYLOOP((#84,#83,#92,#88)); 28 | #126=IFCFACE((#125)); 29 | #125=IFCFACEOUTERBOUND(#124,.T.); 30 | #124=IFCPOLYLOOP((#82,#93,#92,#83)); 31 | #123=IFCFACE((#122)); 32 | #122=IFCFACEOUTERBOUND(#121,.T.); 33 | #121=IFCPOLYLOOP((#93,#82,#81,#94)); 34 | #120=IFCFACE((#119)); 35 | #119=IFCFACEOUTERBOUND(#118,.T.); 36 | #118=IFCPOLYLOOP((#81,#80,#95,#94)); 37 | #117=IFCFACE((#116)); 38 | #116=IFCFACEOUTERBOUND(#115,.T.); 39 | #115=IFCPOLYLOOP((#79,#96,#95,#80)); 40 | #114=IFCFACE((#113)); 41 | #113=IFCFACEOUTERBOUND(#112,.T.); 42 | #112=IFCPOLYLOOP((#96,#79,#78,#97)); 43 | #111=IFCFACE((#110)); 44 | #110=IFCFACEOUTERBOUND(#109,.T.); 45 | #109=IFCPOLYLOOP((#77,#98,#97,#78)); 46 | #108=IFCFACE((#107)); 47 | #107=IFCFACEOUTERBOUND(#106,.T.); 48 | #106=IFCPOLYLOOP((#76,#99,#98,#77)); 49 | #105=IFCFACE((#104)); 50 | #104=IFCFACEOUTERBOUND(#103,.T.); 51 | #103=IFCPOLYLOOP((#99,#76,#72,#71)); 52 | #102=IFCFACE((#101)); 53 | #101=IFCFACEOUTERBOUND(#100,.T.); 54 | #100=IFCPOLYLOOP((#64,#67,#88,#92,#93,#94,#95,#96,#97,#98,#99,#71)); 55 | #99=IFCCARTESIANPOINT((-15376.9802762,-2212.5,10.)); 56 | #98=IFCCARTESIANPOINT((-14349.5208859,-10.,10.)); 57 | #97=IFCCARTESIANPOINT((-28.1181012974,-10.,10.)); 58 | #96=IFCCARTESIANPOINT((1003.68926929,-2212.5,10.)); 59 | #95=IFCCARTESIANPOINT((1241.30804456,-2212.5,10.)); 60 | #94=IFCCARTESIANPOINT((1241.30804456,-2447.5,10.)); 61 | #93=IFCCARTESIANPOINT((765.206885571,-2447.5,10.)); 62 | #92=IFCCARTESIANPOINT((-186.527044724,-404.5,10.)); 63 | #91=IFCFACE((#90)); 64 | #90=IFCFACEOUTERBOUND(#89,.T.); 65 | #89=IFCPOLYLOOP((#67,#66,#84,#88)); 66 | #88=IFCCARTESIANPOINT((-14169.7724238,-404.5,10.)); 67 | #87=IFCFACE((#86)); 68 | #86=IFCFACEOUTERBOUND(#85,.T.); 69 | #85=IFCPOLYLOOP((#65,#72,#76,#77,#78,#79,#80,#81,#82,#83,#84,#66)); 70 | #84=IFCCARTESIANPOINT((-14169.7724238,-404.5,0.)); 71 | #83=IFCCARTESIANPOINT((-186.527044724,-404.5,0.)); 72 | #82=IFCCARTESIANPOINT((765.206885571,-2447.5,0.)); 73 | #81=IFCCARTESIANPOINT((1241.30804456,-2447.5,0.)); 74 | #80=IFCCARTESIANPOINT((1241.30804456,-2212.5,0.)); 75 | #79=IFCCARTESIANPOINT((1003.68926929,-2212.5,0.)); 76 | #78=IFCCARTESIANPOINT((-28.1181012974,-10.,0.)); 77 | #77=IFCCARTESIANPOINT((-14349.5208859,-10.,0.)); 78 | #76=IFCCARTESIANPOINT((-15376.9802762,-2212.5,0.)); 79 | #75=IFCFACE((#74)); 80 | #74=IFCFACEOUTERBOUND(#73,.T.); 81 | #73=IFCPOLYLOOP((#65,#64,#71,#72)); 82 | #72=IFCCARTESIANPOINT((-15614.6919554,-2212.5,0.)); 83 | #71=IFCCARTESIANPOINT((-15614.6919554,-2212.5,10.)); 84 | #70=IFCFACE((#69)); 85 | #69=IFCFACEOUTERBOUND(#68,.T.); 86 | #68=IFCPOLYLOOP((#64,#65,#66,#67)); 87 | #67=IFCCARTESIANPOINT((-15121.506354,-2447.5,10.)); 88 | #66=IFCCARTESIANPOINT((-15121.506354,-2447.5,0.)); 89 | #65=IFCCARTESIANPOINT((-15614.6919554,-2447.5,0.)); 90 | #64=IFCCARTESIANPOINT((-15614.6919554,-2447.5,10.)); 91 | #63=IFCAXIS2PLACEMENT3D(#60,#61,#62); 92 | #62=IFCDIRECTION((1.,0.,0.)); 93 | #61=IFCDIRECTION((0.,0.,1.)); 94 | #60=IFCCARTESIANPOINT((0.,0.,0.)); 95 | #59=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); 96 | #58=IFCLOCALPLACEMENT(#53,#57); 97 | #57=IFCAXIS2PLACEMENT3D(#54,#55,#56); 98 | #56=IFCDIRECTION((1.,0.,0.)); 99 | #55=IFCDIRECTION((0.,0.,1.)); 100 | #54=IFCCARTESIANPOINT((15086.6919554,21910.0000004,3012.5)); 101 | #53=IFCLOCALPLACEMENT(#48,#52); 102 | #52=IFCAXIS2PLACEMENT3D(#49,#50,#51); 103 | #51=IFCDIRECTION((1.,0.,0.)); 104 | #50=IFCDIRECTION((0.,0.,1.)); 105 | #49=IFCCARTESIANPOINT((0.,0.,3000.)); 106 | #48=IFCLOCALPLACEMENT(#43,#47); 107 | #47=IFCAXIS2PLACEMENT3D(#44,#45,#46); 108 | #46=IFCDIRECTION((1.,0.,0.)); 109 | #45=IFCDIRECTION((0.,0.,1.)); 110 | #44=IFCCARTESIANPOINT((0.,0.,0.)); 111 | #43=IFCLOCALPLACEMENT($,#42); 112 | #42=IFCAXIS2PLACEMENT3D(#39,#40,#41); 113 | #41=IFCDIRECTION((1.,0.,0.)); 114 | #40=IFCDIRECTION((0.,0.,1.)); 115 | #39=IFCCARTESIANPOINT((0.,0.,0.)); 116 | #38=IFCPROJECT('344O7vICcwH8qAEnwJDjSU',#8,'10 Appartementen Schependomlaan',$,$,$,$,(#14,#20),#37); 117 | #37=IFCUNITASSIGNMENT((#21,#22,#23,#27,#28,#29,#33,#34,#35,#36)); 118 | #36=IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.); 119 | #35=IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.); 120 | #34=IFCSIUNIT(*,.MASSUNIT.,$,.GRAM.); 121 | #33=IFCCONVERSIONBASEDUNIT(#30,.TIMEUNIT.,'Year',#32); 122 | #32=IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#31); 123 | #31=IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.); 124 | #30=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 125 | #29=IFCMONETARYUNIT(.EUR.); 126 | #28=IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.); 127 | #27=IFCCONVERSIONBASEDUNIT(#24,.PLANEANGLEUNIT.,'DEGREE',#26); 128 | #26=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#25); 129 | #25=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 130 | #24=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 131 | #23=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 132 | #22=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 133 | #21=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); 134 | #20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.E-05,#18,#19); 135 | #19=IFCDIRECTION((0.,1.)); 136 | #18=IFCAXIS2PLACEMENT3D(#15,#16,#17); 137 | #17=IFCDIRECTION((1.,0.,0.)); 138 | #16=IFCDIRECTION((0.,0.,1.)); 139 | #15=IFCCARTESIANPOINT((0.,0.,0.)); 140 | #14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#12,#13); 141 | #13=IFCDIRECTION((0.,1.)); 142 | #12=IFCAXIS2PLACEMENT3D(#9,#10,#11); 143 | #11=IFCDIRECTION((1.,0.,0.)); 144 | #10=IFCDIRECTION((0.,0.,1.)); 145 | #9=IFCCARTESIANPOINT((0.,0.,0.)); 146 | #8=IFCOWNERHISTORY(#5,#7,$,.ADDED.,$,$,$,1440679749); 147 | #7=IFCAPPLICATION(#6,'18.0.0','ArchiCAD-64','IFC2x3 add-on version: 6000 NED FULL'); 148 | #6=IFCORGANIZATION('GS','Graphisoft','Graphisoft',$,$); 149 | #5=IFCPERSONANDORGANIZATION(#2,#4,$); 150 | #4=IFCORGANIZATION($,'ROOT bv',$,$,(#3)); 151 | #3=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 152 | #2=IFCPERSON($,$,'architect',$,$,$,$,(#1)); 153 | #1=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 154 | ENDSEC; 155 | END-ISO-10303-21; 156 | -------------------------------------------------------------------------------- /tests/fixtures/duplex_wall.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('','2018-10-19T15:19:31',(),(),'IfcOpenShell 0.5.0-dev','IfcOpenShell 0.5.0-dev',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #40=IFCWALLSTANDARDCASE('2O2Fr$t4X7Zf8NOew3FNr2',#6,'Basic Wall:Exterior - Brick on Block:138237',$,'Basic Wall:Exterior - Brick on Block:130892',#28,#39,'138237'); 9 | #39=IFCPRODUCTDEFINITIONSHAPE($,$,(#32,#38)); 10 | #38=IFCSHAPEREPRESENTATION(#9,'Body','SweptSolid',(#37)); 11 | #37=IFCEXTRUDEDAREASOLID(#36,#8,#25,3.1000000000002); 12 | #36=IFCRECTANGLEPROFILEDEF(.AREA.,$,#35,8.38299999999997,0.417); 13 | #35=IFCAXIS2PLACEMENT2D(#33,#34); 14 | #34=IFCDIRECTION((-1.,0.)); 15 | #33=IFCCARTESIANPOINT((4.19149999999999,0.)); 16 | #32=IFCSHAPEREPRESENTATION(#9,'Axis','Curve2D',(#31)); 17 | #31=IFCPOLYLINE((#29,#30)); 18 | #30=IFCCARTESIANPOINT((8.38299999999997,0.)); 19 | #29=IFCCARTESIANPOINT((0.,0.)); 20 | #28=IFCLOCALPLACEMENT(#23,#27); 21 | #27=IFCAXIS2PLACEMENT3D(#24,#25,#26); 22 | #26=IFCDIRECTION((-1.,0.,0.)); 23 | #25=IFCDIRECTION((0.,0.,1.)); 24 | #24=IFCCARTESIANPOINT((8.38299999999997,-17.5915,0.)); 25 | #23=IFCLOCALPLACEMENT(#22,#8); 26 | #22=IFCLOCALPLACEMENT(#21,#8); 27 | #21=IFCLOCALPLACEMENT($,#8); 28 | #20=IFCPROJECT('1xS3BCk291UvhgP2a6eflL',#6,'0001',$,$,'Duplex Apartment','Project Status',(#9,#10),#19); 29 | #19=IFCUNITASSIGNMENT((#11,#12,#13,#17,#18)); 30 | #18=IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.); 31 | #17=IFCCONVERSIONBASEDUNIT(#14,.PLANEANGLEUNIT.,'DEGREE',#16); 32 | #16=IFCMEASUREWITHUNIT(IFCRATIOMEASURE(0.0174532925199433),#15); 33 | #15=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 34 | #14=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 35 | #13=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 36 | #12=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 37 | #11=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); 38 | #10=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.E-09,#8,$); 39 | #9=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-09,#8,$); 40 | #8=IFCAXIS2PLACEMENT3D(#7,$,$); 41 | #7=IFCCARTESIANPOINT((0.,0.,0.)); 42 | #6=IFCOWNERHISTORY(#3,#5,$,.NOCHANGE.,$,$,$,0); 43 | #5=IFCAPPLICATION(#4,'2011','Autodesk Revit Architecture 2011','Revit'); 44 | #4=IFCORGANIZATION($,'Autodesk Revit Architecture 2011',$,$,$); 45 | #3=IFCPERSONANDORGANIZATION(#1,#2,$); 46 | #2=IFCORGANIZATION($,'','',$,$); 47 | #1=IFCPERSON($,$,'cskender',$,$,$,$,$); 48 | ENDSEC; 49 | END-ISO-10303-21; 50 | -------------------------------------------------------------------------------- /tests/fixtures/schependom_foundation.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); 4 | FILE_NAME('','2018-10-19T15:18:39',(),(),'IfcOpenShell 0.5.0-dev','IfcOpenShell 0.5.0-dev',''); 5 | FILE_SCHEMA(('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #94=IFCBEAM('3aUxcoO_j3tfpBZz8KhVDA',#8,'fund_strook',$,'IFC_strookfundering_500x250 500 x 250',#58,#93,'E47BB9B2-63EB-43DE-9CCB-8FD214ADF34A'); 9 | #93=IFCPRODUCTDEFINITIONSHAPE($,$,(#88,#92)); 10 | #92=IFCSHAPEREPRESENTATION(#89,'Box','BoundingBox',(#91)); 11 | #91=IFCBOUNDINGBOX(#90,500.,1145.98932508,250.); 12 | #90=IFCCARTESIANPOINT((0.,0.,-250.)); 13 | #89=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Plan',*,*,*,*,#20,$,.PLAN_VIEW.,$); 14 | #88=IFCSHAPEREPRESENTATION(#59,'Body','Brep',(#87)); 15 | #87=IFCFACETEDBREP(#86); 16 | #86=IFCCLOSEDSHELL((#66,#71,#75,#79,#82,#85)); 17 | #85=IFCFACE((#84)); 18 | #84=IFCFACEOUTERBOUND(#83,.T.); 19 | #83=IFCPOLYLOOP((#68,#67,#76,#72)); 20 | #82=IFCFACE((#81)); 21 | #81=IFCFACEOUTERBOUND(#80,.T.); 22 | #80=IFCPOLYLOOP((#67,#60,#63,#76)); 23 | #79=IFCFACE((#78)); 24 | #78=IFCFACEOUTERBOUND(#77,.T.); 25 | #77=IFCPOLYLOOP((#63,#62,#72,#76)); 26 | #76=IFCCARTESIANPOINT((500.,192.254122783,0.)); 27 | #75=IFCFACE((#74)); 28 | #74=IFCFACEOUTERBOUND(#73,.T.); 29 | #73=IFCPOLYLOOP((#61,#68,#72,#62)); 30 | #72=IFCCARTESIANPOINT((500.,192.254122783,-250.)); 31 | #71=IFCFACE((#70)); 32 | #70=IFCFACEOUTERBOUND(#69,.T.); 33 | #69=IFCPOLYLOOP((#60,#67,#68,#61)); 34 | #68=IFCCARTESIANPOINT((500.,923.711214273,-250.)); 35 | #67=IFCCARTESIANPOINT((500.,923.711214273,0.)); 36 | #66=IFCFACE((#65)); 37 | #65=IFCFACEOUTERBOUND(#64,.T.); 38 | #64=IFCPOLYLOOP((#60,#61,#62,#63)); 39 | #63=IFCCARTESIANPOINT((0.,0.,0.)); 40 | #62=IFCCARTESIANPOINT((0.,0.,-250.)); 41 | #61=IFCCARTESIANPOINT((0.,1145.98932508,-250.)); 42 | #60=IFCCARTESIANPOINT((0.,1145.98932508,0.)); 43 | #59=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); 44 | #58=IFCLOCALPLACEMENT(#53,#57); 45 | #57=IFCAXIS2PLACEMENT3D(#54,#55,#56); 46 | #56=IFCDIRECTION((0.742393117549,-0.669964520715,0.)); 47 | #55=IFCDIRECTION((0.,0.,1.)); 48 | #54=IFCCARTESIANPOINT((3660.,5949.22541228,250.)); 49 | #53=IFCLOCALPLACEMENT(#48,#52); 50 | #52=IFCAXIS2PLACEMENT3D(#49,#50,#51); 51 | #51=IFCDIRECTION((1.,0.,0.)); 52 | #50=IFCDIRECTION((0.,0.,1.)); 53 | #49=IFCCARTESIANPOINT((0.,0.,-1000.)); 54 | #48=IFCLOCALPLACEMENT(#43,#47); 55 | #47=IFCAXIS2PLACEMENT3D(#44,#45,#46); 56 | #46=IFCDIRECTION((1.,0.,0.)); 57 | #45=IFCDIRECTION((0.,0.,1.)); 58 | #44=IFCCARTESIANPOINT((0.,0.,0.)); 59 | #43=IFCLOCALPLACEMENT($,#42); 60 | #42=IFCAXIS2PLACEMENT3D(#39,#40,#41); 61 | #41=IFCDIRECTION((1.,0.,0.)); 62 | #40=IFCDIRECTION((0.,0.,1.)); 63 | #39=IFCCARTESIANPOINT((0.,0.,0.)); 64 | #38=IFCPROJECT('344O7vICcwH8qAEnwJDjSU',#8,'10 Appartementen Schependomlaan',$,$,$,$,(#14,#20),#37); 65 | #37=IFCUNITASSIGNMENT((#21,#22,#23,#27,#28,#29,#33,#34,#35,#36)); 66 | #36=IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.); 67 | #35=IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.); 68 | #34=IFCSIUNIT(*,.MASSUNIT.,$,.GRAM.); 69 | #33=IFCCONVERSIONBASEDUNIT(#30,.TIMEUNIT.,'Year',#32); 70 | #32=IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#31); 71 | #31=IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.); 72 | #30=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 73 | #29=IFCMONETARYUNIT(.EUR.); 74 | #28=IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.); 75 | #27=IFCCONVERSIONBASEDUNIT(#24,.PLANEANGLEUNIT.,'DEGREE',#26); 76 | #26=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#25); 77 | #25=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 78 | #24=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 79 | #23=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 80 | #22=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 81 | #21=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); 82 | #20=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',3,1.E-05,#18,#19); 83 | #19=IFCDIRECTION((0.,1.)); 84 | #18=IFCAXIS2PLACEMENT3D(#15,#16,#17); 85 | #17=IFCDIRECTION((1.,0.,0.)); 86 | #16=IFCDIRECTION((0.,0.,1.)); 87 | #15=IFCCARTESIANPOINT((0.,0.,0.)); 88 | #14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#12,#13); 89 | #13=IFCDIRECTION((0.,1.)); 90 | #12=IFCAXIS2PLACEMENT3D(#9,#10,#11); 91 | #11=IFCDIRECTION((1.,0.,0.)); 92 | #10=IFCDIRECTION((0.,0.,1.)); 93 | #9=IFCCARTESIANPOINT((0.,0.,0.)); 94 | #8=IFCOWNERHISTORY(#5,#7,$,.ADDED.,$,$,$,1440679749); 95 | #7=IFCAPPLICATION(#6,'18.0.0','ArchiCAD-64','IFC2x3 add-on version: 6000 NED FULL'); 96 | #6=IFCORGANIZATION('GS','Graphisoft','Graphisoft',$,$); 97 | #5=IFCPERSONANDORGANIZATION(#2,#4,$); 98 | #4=IFCORGANIZATION($,'ROOT bv',$,$,(#3)); 99 | #3=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 100 | #2=IFCPERSON($,$,'architect',$,$,$,$,(#1)); 101 | #1=IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Amsterdamsestraatweg 43'),$,'Amersfoort',$,'3812 RP',$); 102 | ENDSEC; 103 | END-ISO-10303-21; 104 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile1.txt: -------------------------------------------------------------------------------- 1 | file = parse("duplex.ifc") 2 | surfaces = create_geometry(file) 3 | slabs = create_geometry(file, include={"IfcSlab"}) 4 | slab_voxels = voxelize(slabs) 5 | voxels = voxelize(surfaces) 6 | fixed_voxels = fill_gaps(voxels) 7 | offset_voxels = offset(fixed_voxels) 8 | outmost_voxels = outmost(offset_voxels) 9 | result = count(outmost_voxels) 10 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile2.txt: -------------------------------------------------------------------------------- 1 | file = parse("duplex.ifc") 2 | slabs = create_geometry(file, include={"IfcSlab"}) 3 | doors = create_geometry(file, include={"IfcDoor"}) 4 | surfaces = create_geometry(file, exclude={"IfcOpeningElement", "IfcDoor"}) 5 | slab_voxel_surface = voxelize(slabs) 6 | slab_voxels = fill_volume(slab_voxel_surface) 7 | door_voxels = voxelize(doors) 8 | surface_voxels = voxelize(surfaces) 9 | walkable = shift(slab_voxels, dx=0, dy=0, dz=1) 10 | walkable_minus = subtract(walkable, slab_voxels) 11 | walkable_seed = intersect(door_voxels, walkable_minus) 12 | reachable = traverse_over(surface_voxels, walkable_seed, step=0.3, max_rise=0.5, xneg=0.1, xpos=0.1, yneg=0.1, ypos=0.1, zneg=0.0, zpos=1.0) 13 | full = constant_like(surfaces, 1) 14 | surfaces_sweep = sweep(surfaces, dx=0, dy=0, dz=0.5) 15 | deadly = subtract(full, surfaces_sweep) 16 | result = intersect(reachable, deadly) 17 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile3.txt: -------------------------------------------------------------------------------- 1 | file = parse("demo2.ifc") 2 | surfaces = create_geometry(file, exclude={"IfcOpeningElement", "IfcDoor", "IfcSpace"}) 3 | slabs = create_geometry(file, include={"IfcSlab"}) 4 | doors = create_geometry(file, include={"IfcDoor"}) 5 | surface_voxels = voxelize(surfaces) 6 | slab_voxels = voxelize(slabs) 7 | door_voxels = voxelize(doors) 8 | walkable = shift(slab_voxels, dx=0, dy=0, dz=1) 9 | walkable_minus = subtract(walkable, slab_voxels) 10 | walkable_seed = intersect(door_voxels, walkable_minus) 11 | surfaces_sweep = sweep(surface_voxels, dx=0, dy=0, dz=0.5) 12 | surfaces_padded = offset_xy(surface_voxels, 0.1) 13 | surfaces_obstacle = sweep(surfaces_padded, dx=0, dy=0, dz=-0.5) 14 | walkable_region = subtract(surfaces_sweep, surfaces_obstacle) 15 | walkable_seed_real = subtract(walkable_seed, surfaces_padded) 16 | reachable = traverse(walkable_region, walkable_seed_real) 17 | reachable_shifted = shift(reachable, dx=0, dy=0, dz=1) 18 | reachable_bottom = subtract(reachable, reachable_shifted) 19 | reachable_padded = offset_xy(reachable_bottom, 0.2) 20 | full = constant_like(surface_voxels, 1) 21 | surfaces_sweep_1m = sweep(surface_voxels, dx=0, dy=0, dz=1.0) 22 | deadly = subtract(full, surfaces_sweep_1m) 23 | really_reachable = subtract(reachable_padded, surfaces_obstacle) 24 | result = intersect(really_reachable, deadly) 25 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile4.txt: -------------------------------------------------------------------------------- 1 | file = parse("schependom_foundation.ifc") 2 | surfaces = create_geometry(file, exclude={"IfcOpeningElement", "IfcSpace"}) 3 | voxels_lowres = voxelize(surfaces, VOXELSIZE=0.1) 4 | voxels_highres = voxelize(surfaces, VOXELSIZE=0.025) 5 | voxels_highres_resampled = resample(voxels_highres, -4) 6 | difference = subtract(voxels_highres_resampled, voxels_lowres) 7 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile5.txt: -------------------------------------------------------------------------------- 1 | file = parse("schependom.ifc") 2 | surfaces = create_geometry(file) 3 | voxels_a = voxelize(surfaces, VOXELSIZE=0.08) 4 | voxels_b = voxelize(surfaces, VOXELSIZE=0.01) 5 | voxels_a_ab = offset(voxels_a) 6 | voxels_a_b = outmost(voxels_a_ab) 7 | voxels_a_b_ab = offset(voxels_a_b) 8 | voxels_a_b_b = outmost(voxels_a_b_ab) 9 | voxels_a_b_a = subtract(voxels_a_b_ab, voxels_a_b_b) 10 | voxels_a_b_a_ab = offset(voxels_a_b_a) 11 | voxels_a_b_a_b = outmost(voxels_a_b_a_ab) 12 | voxels_a_b_a_a = subtract(voxels_a_b_a_ab, voxels_a_b_a_b) 13 | voxels_a_b_a_a_ab = offset(voxels_a_b_a_a) 14 | voxels_a_b_a_a_b = outmost(voxels_a_b_a_a_ab) 15 | voxels_a_b_a_a_a = subtract(voxels_a_b_a_a_ab, voxels_a_b_a_a_b) 16 | offset_b = offset(voxels_b) 17 | outmost_b = outmost(offset_b) 18 | offset_b0 = offset(outmost_b) 19 | offset_b1 = outmost(offset_b0) 20 | offset_b2 = subtract(offset_b0, offset_b1) 21 | offset_b2_r = resample(offset_b2, -8) 22 | diff_internal = intersect(voxels_a_b_a_a_a, offset_b2_r) 23 | diff_external = intersect(voxels_a_b_a, offset_b2_r) 24 | diff_internal_ab = offset(diff_internal) 25 | diff_internal_b = outmost(diff_internal_ab) 26 | diff_internal_b_ab = offset(diff_internal_b) 27 | diff_internal_b_b = outmost(diff_internal_b_ab) 28 | diff = intersect(diff_internal_b_b, diff_external) 29 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile7.txt: -------------------------------------------------------------------------------- 1 | file = parse("duplex.ifc") 2 | slabss = create_geometry(file, include={"IfcSlab"}) 3 | roofss = create_geometry(file, include={"IfcRoof"}) 4 | slabs = voxelize(slabss) 5 | roofs = voxelize(roofss) 6 | floors_surface = subtract(slabs, roofs) 7 | floor_volume = volume2(floors_surface) 8 | floors = union(floors_surface, floor_volume) 9 | floors_surface = collapse(floors, 0, 0, -1) 10 | num = count(floors_surface) 11 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfile8.txt: -------------------------------------------------------------------------------- 1 | file = parse("schependom.ifc") 2 | surfaces = create_geometry(file) 3 | voxels_a = voxelize(surfaces, VOXELSIZE=0.08) 4 | voxels_b = voxelize(surfaces, VOXELSIZE=0.01) 5 | offset_a = offset(voxels_a) 6 | outmost_a = outmost(offset_a) 7 | offset_a0 = offset(outmost_a) 8 | offset_a1 = outmost(offset_a0) 9 | outer_a = subtract(offset_a0, offset_a1) 10 | offset_b = offset(voxels_b) 11 | outmost_b = outmost(offset_b) 12 | offset_b0 = offset(outmost_b) 13 | offset_b1 = outmost(offset_b0) 14 | outer_b_hr = subtract(offset_b0, offset_b1) 15 | outer_b = resample(outer_b_hr, -8) 16 | internal_a = volume2(outer_a) 17 | internal_b = volume2(outer_b) 18 | volume_a = union(outer_a, internal_a) 19 | volume_b = union(outer_b, internal_b) 20 | a_minus_b = subtract(volume_a, volume_b) 21 | print_components(a_minus_b) 22 | -------------------------------------------------------------------------------- /tests/fixtures/voxelfilec.txt: -------------------------------------------------------------------------------- 1 | file = parse("schependom.ifc") 2 | surfaces = create_geometry(file) 3 | voxels_a_lr = voxelize(surfaces, VOXELSIZE=0.04) 4 | voxels_a = resample(voxels_a_lr, 2) 5 | voxels_b_o = voxelize(surfaces, VOXELSIZE=0.02) 6 | hack = constant_like(voxels_a, 0) 7 | voxels_b = union(hack, voxels_b_o) 8 | external_a = exterior(voxels_a) 9 | external_b = exterior(voxels_b) 10 | internal_a = invert(external_a) 11 | shell_a = offset(internal_a) 12 | exb_sha = union(external_b, shell_a) 13 | shell_a_reach = traverse(exb_sha, shell_a, 8) 14 | s2 = subtract(external_b, shell_a_reach) 15 | s22 = intersect(s2, internal_a) 16 | reach_back = traverse(exb_sha, s22, 11) 17 | result = intersect(reach_back, shell_a) 18 | -------------------------------------------------------------------------------- /tests/test_boolean.cpp: -------------------------------------------------------------------------------- 1 | #include "../storage.h" 2 | #include 3 | 4 | TEST(Boolean, UnionConstantChunks) { 5 | const double d = 1; 6 | auto a = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 7 | auto b = new chunked_voxel_storage(15, 15, 15, d, 20, 20, 20, 10); 8 | a->create_constant(make_vec( 0U, 0U, 0U ), 1); 9 | b->create_constant(make_vec( 0U, 0U, 0U ), 1); 10 | 11 | auto c = a->boolean_union(b); 12 | ASSERT_EQ(c->count(), 2000); 13 | 14 | ASSERT_TRUE((c->bounds()[1] == make_vec(19U, 19U, 19U)).all()); 15 | } 16 | 17 | TEST(Boolean, UnionConstantChunksInPlaceInvalid) { 18 | const double d = 1; 19 | auto a = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 20 | auto b = new chunked_voxel_storage(15, 15, 15, d, 20, 20, 20, 10); 21 | a->create_constant(make_vec( 0U, 0U, 0U ), 1); 22 | b->create_constant(make_vec( 0U, 0U, 0U ), 1); 23 | 24 | ASSERT_THROW(a->boolean_union_inplace(b), std::runtime_error); 25 | } 26 | 27 | TEST(Boolean, UnionConstantChunksInPlace) { 28 | const double d = 1; 29 | auto a = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 30 | auto b = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 31 | a->create_constant(make_vec( 0U, 0U, 0U ), 1); 32 | b->create_constant(make_vec( 1U, 1U, 1U ), 1); 33 | 34 | a->boolean_union_inplace(b); 35 | ASSERT_EQ(a->count(), 2000); 36 | 37 | ASSERT_TRUE((a->bounds()[1] == make_vec(19U, 19U, 19U)).all()); 38 | } 39 | 40 | TEST(Boolean, SubtractPlane) { 41 | const double d = 1; 42 | auto a = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 43 | auto b = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 44 | a->create_constant(make_vec( 0U, 0U, 0U ), 1); 45 | b->create_plane_primitive(make_vec( 0U, 0U, 0U ), 2, 0); 46 | 47 | auto c = a->boolean_subtraction(b); 48 | ASSERT_EQ(c->count(), 900); 49 | 50 | ASSERT_TRUE((c->bounds()[0] == make_vec(0U, 0U, 1U)).all()); 51 | } 52 | 53 | TEST(Boolean, IntersectPlane) { 54 | { 55 | const double d = 1; 56 | auto a = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 57 | auto b = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 58 | a->create_constant(make_vec( 0U, 0U, 0U ), 1); 59 | b->create_plane_primitive(make_vec( 0U, 0U, 0U ), 2, 0); 60 | 61 | // In rare cases primitives are preserved 62 | auto c = (chunked_voxel_storage*) a->boolean_intersection(b); 63 | ASSERT_FALSE(c->get_chunk(make_vec( 0U, 0U, 0U ))->is_explicit()); 64 | } 65 | 66 | { 67 | const double d = 1; 68 | auto a = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 69 | auto b = new chunked_voxel_storage(0, 0, 0, d, 10, 10, 10, 10); 70 | a->create_plane_primitive(make_vec( 0U, 0U, 0U ), 1, 0); 71 | b->create_plane_primitive(make_vec( 0U, 0U, 0U ), 2, 0); 72 | 73 | // In rare cases primitives are preserved 74 | auto c = (chunked_voxel_storage*) a->boolean_intersection(b); 75 | ASSERT_TRUE(c->get_chunk(make_vec( 0U, 0U, 0U ))->is_explicit()); 76 | ASSERT_EQ(c->count(), 10); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /tests/test_box_surface.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../writer.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | TEST(Voxelizer, Volume) { 10 | const double d = 0.1; 11 | auto storage = new continuous_voxel_storage(-d, -d, -d, d, 100, 100, 100); 12 | 13 | BRepPrimAPI_MakeBox mb(8., 8., 8.); 14 | auto vox = voxelizer(mb.Solid(), storage); 15 | vox.Convert(); 16 | 17 | voxel_writer writer; 18 | writer.SetVoxels(storage); 19 | writer.Write("test_box_surface.vox"); 20 | 21 | ASSERT_FALSE(storage->Get(make_vec(0U, 0U, 1U))); 22 | 23 | ASSERT_TRUE(storage->Get(make_vec(1U, 1U, 1U))); 24 | ASSERT_TRUE(storage->Get(make_vec(80U, 1U, 1U))); 25 | ASSERT_TRUE(storage->Get(make_vec(1U, 80U, 1U))); 26 | ASSERT_TRUE(storage->Get(make_vec(80U, 80U, 1U))); 27 | ASSERT_TRUE(storage->Get(make_vec(80U, 80U, 2U))); 28 | 29 | ASSERT_FALSE(storage->Get(make_vec(81U, 80U, 1U))); 30 | ASSERT_FALSE(storage->Get(make_vec(80U, 81U, 1U))); 31 | ASSERT_FALSE(storage->Get(make_vec(81U, 81U, 1U))); 32 | ASSERT_FALSE(storage->Get(make_vec(81U, 80U, 2U))); 33 | ASSERT_FALSE(storage->Get(make_vec(80U, 81U, 2U))); 34 | ASSERT_FALSE(storage->Get(make_vec(81U, 81U, 2U))); 35 | } 36 | -------------------------------------------------------------------------------- /tests/test_collapse.cpp: -------------------------------------------------------------------------------- 1 | #include "../collapse.h" 2 | #include "../sweep.h" 3 | 4 | #include 5 | 6 | TEST(SweepAndCollapse, Same) { 7 | // Collapse and Sweep should be each other's reverse 8 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 9 | auto loc = make_vec(1U, 1U, 1U); 10 | storage->Set(loc); 11 | 12 | sweep s; 13 | collapse c; 14 | collapse_count cc; 15 | 16 | auto swept = s(storage, 0, 0, 5); 17 | ASSERT_EQ(swept->count(), 5); 18 | auto result = c(swept, 0, 0, -1); 19 | ASSERT_EQ(result->count(), 1); 20 | ASSERT_TRUE(result->Get(loc)); 21 | 22 | auto result2 = cc(swept, 0, 0, -1); 23 | ASSERT_EQ(result2->value_bits(), 32); 24 | size_t value = 0; 25 | result2->Get(loc, &value); 26 | ASSERT_EQ(value, 5); 27 | // @nb dz=1 because the extrusion depth is now stored in the voxel value 28 | auto swept2 = s(result2, 0, 0, 1); 29 | ASSERT_EQ(swept2->count(), 5); 30 | } -------------------------------------------------------------------------------- /tests/test_covering.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../writer.h" 3 | #include "../processor.h" 4 | 5 | #if defined(WITH_IFC) && defined(IFCOPENSHELL_05) 6 | #include 7 | #include 8 | using namespace Ifc2x3; 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | 18 | #ifdef WIN32 19 | #define DIRSEP "\\" 20 | #else 21 | #define DIRSEP "/" 22 | #endif 23 | 24 | #if defined(WITH_IFC) && defined(IFCOPENSHELL_05) 25 | TEST(Voxelization, IfcCovering) { 26 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "covering.ifc"; 27 | 28 | IfcParse::IfcFile ifc_file; 29 | 30 | const double d = 0.05; 31 | 32 | ASSERT_TRUE(ifc_file.Init(input_filename)); 33 | 34 | IfcGeom::IteratorSettings settings_surface; 35 | settings_surface.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); 36 | 37 | IfcGeom::Kernel kernel; 38 | kernel.initializeUnits(*ifc_file.entitiesByType()->begin()); 39 | 40 | IfcBuildingElement* buildingelem = ifc_file.entityByGuid("00fSj8UXfAn8lUGu3HP6DA")->as(); 41 | IfcShapeRepresentation* representation = nullptr; 42 | auto reps = ifc_file.traverse(buildingelem)->as(); 43 | for (auto& rep : *reps) { 44 | if (rep->RepresentationIdentifier() == "Body") { 45 | representation = rep; 46 | } 47 | } 48 | 49 | ASSERT_NE(representation, nullptr); 50 | 51 | auto elem = kernel.create_brep_for_representation_and_product(settings_surface, representation, buildingelem); 52 | 53 | const gp_Trsf& product_trsf = elem->transformation().data(); 54 | Bnd_Box global_bounds; 55 | TopoDS_Compound compound; 56 | BRep_Builder builder; 57 | builder.MakeCompound(compound); 58 | 59 | for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = elem->geometry().begin(); it != elem->geometry().end(); ++it) { 60 | const TopoDS_Shape& s = it->Shape(); 61 | gp_GTrsf trsf = it->Placement(); 62 | const TopoDS_Shape moved_shape_ = IfcGeom::Kernel::apply_transformation(s, trsf); 63 | const TopoDS_Shape moved_shape = IfcGeom::Kernel::apply_transformation(moved_shape_, product_trsf); 64 | BRepBndLib::Add(moved_shape, global_bounds); 65 | builder.Add(compound, moved_shape); 66 | } 67 | 68 | BRepMesh_IncrementalMesh(compound, 0.001); 69 | 70 | geometry_collection_t geoms{ { buildingelem->id(), compound } }; 71 | 72 | double x1, y1, z1, x2, y2, z2; 73 | global_bounds.Get(x1, y1, z1, x2, y2, z2); 74 | int nx = (int)ceil((x2 - x1) / d) + 10; 75 | int ny = (int)ceil((y2 - y1) / d) + 10; 76 | int nz = (int)ceil((z2 - z1) / d) + 10; 77 | 78 | x1 -= d * 5; 79 | y1 -= d * 5; 80 | z1 -= d * 5; 81 | 82 | { 83 | progress_writer progress_1("test_surface"); 84 | processor pr(x1, y1, z1, d, nx, ny, nz, 64, progress_1); 85 | pr.process(geoms.begin(), geoms.end(), SURFACE(), output(MERGED(), "test_covering_surface.vox")); 86 | } 87 | } 88 | #else 89 | TEST(Voxelization, DISABLED_IfcCovering) {} 90 | #endif 91 | -------------------------------------------------------------------------------- /tests/test_dimensionality_estimate.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxec.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST(Dimensionality, Estimate) { 8 | using std::chrono::duration_cast; 9 | using std::chrono::duration; 10 | using std::chrono::high_resolution_clock; 11 | 12 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 13 | 14 | for (size_t i = 1; i < 8; ++i) { 15 | for (size_t j = 1; j < 8; ++j) { 16 | for (size_t k = 1; k < 8; ++k) { 17 | storage->Set(make_vec(i, j, k)); 18 | } 19 | } 20 | } 21 | 22 | std::array times_ms; 23 | 24 | for (int i = 0; i < 4; ++i) { 25 | scope_map args; 26 | 27 | args["input"] = storage; 28 | args["max_depth"] = 5; 29 | args["max_depth_2"] = 2; 30 | args["reposition"] = 1; 31 | 32 | if (i == 1) { 33 | args["inward_distance_approximate"] = 1; 34 | args["subsampling_factor"] = 2; 35 | } 36 | if (i == 2) { 37 | args["inward_distance_skip"] = 1; 38 | } 39 | if (i == 3) { 40 | args["THREADS"] = 4; 41 | } 42 | 43 | auto t1 = high_resolution_clock::now(); 44 | auto v = op_dimensionality_estimate{}.invoke(args); 45 | auto t2 = high_resolution_clock::now(); 46 | duration ms = t2 - t1; 47 | 48 | std::cerr << "Time (ms) " << (times_ms[i] = ms.count()) << std::endl; 49 | 50 | ASSERT_EQ(v.which(), 3); 51 | 52 | auto vp = boost::get(v); 53 | 54 | ASSERT_EQ(vp->value_bits(), 4 * 2 * 8); 55 | 56 | normal_and_curvature vi16; 57 | 58 | static auto ONE_DIV_SQRT_THREE_F = 1.f / std::sqrt(3.f); 59 | 60 | for (auto ijk : *storage) { 61 | vp->Get(ijk, &vi16); 62 | auto n = vi16.convert().normal(); 63 | ASSERT_TRUE(((n - ONE_DIV_SQRT_THREE_F) < 1.e-5).all()); 64 | } 65 | } 66 | 67 | ASSERT_LT(times_ms[1] * 1.5, times_ms[0]); 68 | ASSERT_LT(times_ms[2] * 1.1, times_ms[1]); 69 | ASSERT_LT(times_ms[3] * 3, times_ms[0]); 70 | } 71 | -------------------------------------------------------------------------------- /tests/test_edges.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../writer.h" 4 | #include "../edge_detect.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | TEST(Volume, DISABLED_Edges) { 11 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 1000, 1000, 100, 32); 12 | 13 | for (int i = 0; i < 10; ++i) { 14 | for (int j = 0; j < 10; ++j) { 15 | BRepPrimAPI_MakeBox mb(gp_Pnt(10 * i, 10 * j, 0.), 8., 8., 8.); 16 | 17 | auto vox = voxelizer(mb.Solid(), storage); 18 | vox.Convert(); 19 | } 20 | } 21 | 22 | /* 23 | volume_filler volume(storage); 24 | volume.fill(); 25 | */ 26 | 27 | auto storage2 = edge_detect()(storage); 28 | 29 | std::cerr << storage->count() << " " << storage2->count() << std::endl; 30 | 31 | voxel_writer writer; 32 | writer.SetVoxels(storage2); 33 | writer.Write("test_edges.vox"); 34 | } 35 | -------------------------------------------------------------------------------- /tests/test_fill_gaps.cpp: -------------------------------------------------------------------------------- 1 | #include "../writer.h" 2 | #include "../fill_gaps.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | TEST(Postprocesses, FillGaps) { 9 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 10 | 11 | for (size_t i = 2; i < 5; ++i) { 12 | for (size_t j = 2; j < 5; ++j) { 13 | for (size_t k = 2; k < 5; ++k) { 14 | auto ijk = make_vec(i, j, k); 15 | if ((ijk == make_vec(3U, 3U, 3U)).all()) { 16 | continue; 17 | } 18 | storage->Set(ijk); 19 | } 20 | } 21 | } 22 | 23 | ASSERT_TRUE(storage->Get(make_vec( 2U, 2U, 2U ))); 24 | ASSERT_FALSE(storage->Get(make_vec( 3U, 3U, 3U ))); 25 | 26 | auto storage2 = fill_gaps()(storage); 27 | 28 | ASSERT_TRUE(storage2->Get(make_vec( 3U, 3U, 3U ))); 29 | } 30 | -------------------------------------------------------------------------------- /tests/test_foundation.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../writer.h" 3 | #include "../processor.h" 4 | 5 | #if defined(WITH_IFC) && defined(IFCOPENSHELL_05) 6 | #include 7 | #include 8 | using namespace Ifc2x3; 9 | #endif 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #ifdef WIN32 18 | #define DIRSEP "\\" 19 | #else 20 | #define DIRSEP "/" 21 | #endif 22 | 23 | #if defined(WITH_IFC) && defined(IFCOPENSHELL_05) 24 | TEST(Voxelization, Foundation) { 25 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "schependom_foundation.ifc"; 26 | 27 | IfcParse::IfcFile ifc_file; 28 | 29 | const double d = 0.02; 30 | 31 | ASSERT_TRUE(ifc_file.Init(input_filename)); 32 | 33 | IfcGeom::IteratorSettings settings_surface; 34 | settings_surface.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); 35 | 36 | IfcGeom::Kernel kernel; 37 | kernel.initializeUnits(*ifc_file.entitiesByType()->begin()); 38 | 39 | IfcBeam* beam = ifc_file.entityByGuid("3aUxcoO_j3tfpBZz8KhVDA")->as(); 40 | IfcShapeRepresentation* representation = nullptr; 41 | auto reps = ifc_file.traverse(beam)->as(); 42 | for (auto& rep : *reps) { 43 | if (rep->RepresentationIdentifier() == "Body") { 44 | representation = rep; 45 | } 46 | } 47 | 48 | ASSERT_NE(representation, nullptr); 49 | 50 | auto elem = kernel.create_brep_for_representation_and_product(settings_surface, representation, beam); 51 | 52 | const gp_Trsf& product_trsf = elem->transformation().data(); 53 | Bnd_Box global_bounds; 54 | TopoDS_Compound compound; 55 | BRep_Builder builder; 56 | builder.MakeCompound(compound); 57 | 58 | for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = elem->geometry().begin(); it != elem->geometry().end(); ++it) { 59 | const TopoDS_Shape& s = it->Shape(); 60 | gp_GTrsf trsf = it->Placement(); 61 | const TopoDS_Shape moved_shape_ = IfcGeom::Kernel::apply_transformation(s, trsf); 62 | const TopoDS_Shape moved_shape = IfcGeom::Kernel::apply_transformation(moved_shape_, product_trsf); 63 | BRepBndLib::Add(moved_shape, global_bounds); 64 | builder.Add(compound, moved_shape); 65 | } 66 | 67 | BRepMesh_IncrementalMesh(compound, 0.001); 68 | 69 | geometry_collection_t geoms{ { beam->id(), compound } }; 70 | 71 | double x1, y1, z1, x2, y2, z2; 72 | global_bounds.Get(x1, y1, z1, x2, y2, z2); 73 | int nx = (int)ceil((x2 - x1) / d) + 10; 74 | int ny = (int)ceil((y2 - y1) / d) + 10; 75 | int nz = (int)ceil((z2 - z1) / d) + 10; 76 | 77 | x1 -= d * 5; 78 | y1 -= d * 5; 79 | z1 -= d * 5; 80 | 81 | { 82 | progress_writer progress_1("test_surface"); 83 | processor pr(x1, y1, z1, d, nx, ny, nz, 128, progress_1); 84 | pr.process(geoms.begin(), geoms.end(), SURFACE(), output(MERGED(), "test_beam_surface.vox")); 85 | } 86 | } 87 | #else 88 | TEST(Voxelization, DISABLED_Foundation) {} 89 | #endif -------------------------------------------------------------------------------- /tests/test_keep_neighbours.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxec.h" 2 | 3 | #include 4 | 5 | TEST(Keep, Neighbours) { 6 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 8, 8, 8, 8); 7 | 8 | for (int i = 1; i <= 3; ++i) { 9 | for (int j = 1; j <= 3; ++j) { 10 | for (int k = 1; k <= 3; ++k) { 11 | storage->Set(make_vec(i, j, k)); 12 | } 13 | } 14 | } 15 | 16 | { 17 | op_keep_neighbours keep; 18 | scope_map arguments; 19 | { 20 | symbol_value v = storage; 21 | arguments["input"] = v; 22 | } 23 | { 24 | symbol_value v = 6; 25 | arguments["num_neighbours"] = v; 26 | } 27 | { 28 | symbol_value v = 6; 29 | arguments["connectivity"] = v; 30 | } 31 | 32 | auto result = (regular_voxel_storage*)boost::get(keep.invoke(arguments)); 33 | ASSERT_EQ(result->count(), 1); 34 | } 35 | 36 | { 37 | op_keep_neighbours keep; 38 | scope_map arguments; 39 | { 40 | symbol_value v = storage; 41 | arguments["input"] = v; 42 | } 43 | { 44 | symbol_value v = 26; 45 | arguments["num_neighbours"] = v; 46 | } 47 | { 48 | symbol_value v = 26; 49 | arguments["connectivity"] = v; 50 | } 51 | 52 | auto result = (regular_voxel_storage*)boost::get(keep.invoke(arguments)); 53 | ASSERT_EQ(result->count(), 1); 54 | } 55 | 56 | { 57 | op_keep_neighbours keep; 58 | scope_map arguments; 59 | { 60 | symbol_value v = storage; 61 | arguments["input"] = v; 62 | } 63 | { 64 | symbol_value v = 5; 65 | arguments["num_neighbours"] = v; 66 | } 67 | { 68 | symbol_value v = 6; 69 | arguments["connectivity"] = v; 70 | } 71 | 72 | auto result = (regular_voxel_storage*)boost::get(keep.invoke(arguments)); 73 | ASSERT_EQ(result->count(), 7); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/test_memmap.cpp: -------------------------------------------------------------------------------- 1 | #include "../storage.h" 2 | 3 | #include 4 | 5 | // Disabled because files are always re-created 6 | TEST(DISABLED_Storage, MemoryMapped) { 7 | std::remove("test.vox"); 8 | { 9 | memory_mapped_chunked_voxel_storage storage(0., 0., 0., 0.1, 1024, 1024, 1024, 128, "test.vox"); 10 | storage.Set(make_vec(1U, 1U, 1U)); 11 | } 12 | { 13 | memory_mapped_chunked_voxel_storage storage(0., 0., 0., 0.1, 1024, 1024, 1024, 128, "test.vox"); 14 | ASSERT_FALSE(storage.Get(make_vec( 0U, 0U, 0U ))); 15 | ASSERT_TRUE(storage.Get(make_vec( 1U, 1U, 1U ))); 16 | storage.Set(make_vec(2U, 2U, 2U)); 17 | storage.Set(make_vec(1000U, 1000U, 1000U)); 18 | } 19 | { 20 | memory_mapped_chunked_voxel_storage storage(0., 0., 0., 0.1, 1024, 1024, 1024, 128, "test.vox"); 21 | ASSERT_FALSE(storage.Get(make_vec( 0U, 0U, 0U ))); 22 | ASSERT_TRUE(storage.Get(make_vec( 1U, 1U, 1U ))); 23 | ASSERT_TRUE(storage.Get(make_vec( 2U, 2U, 2U ))); 24 | ASSERT_TRUE(storage.Get(make_vec( 1000U, 1000U, 1000U ))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/test_memoized_traversal.cpp: -------------------------------------------------------------------------------- 1 | #include "../memoized_traversal.h" 2 | #include "dim3.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | TEST(Vec, Hashing) { 11 | auto vec = make_vec(1, 2, 3); 12 | std::hash> hash_fn; 13 | auto vec2 = make_vec(1, 2, 3); 14 | ASSERT_EQ(hash_fn(vec), hash_fn(vec)); 15 | } 16 | 17 | TEST(Traversal, Comparison) { 18 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 19 | 20 | for (size_t i = 2; i < 7; ++i) { 21 | for (size_t j = 2; j < 7; ++j) { 22 | for (size_t k = 2; k < 7; ++k) { 23 | if (((make_vec(4, 4, 4) - make_vec(i, j, k).as()).abs() < 2).all()) { 24 | continue; 25 | } 26 | storage->Set(make_vec(i, j, k)); 27 | } 28 | } 29 | } 30 | 31 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 32 | ASSERT_EQ(storage->count(), 5 * 5 * 5 - 3 * 3 * 3); 33 | 34 | /* 35 | for (size_t k = 2; k < 4; ++k) { 36 | for (size_t j = 2; j < 4; ++j) { 37 | for (size_t i = 2; i < 9; ++i) { 38 | storage->Set(make_vec(i, j, k)); 39 | } 40 | } 41 | } 42 | 43 | ASSERT_EQ(storage->count(), 7 * 2 * 2); 44 | */ 45 | 46 | double accum_1 = 0.; 47 | double accum_2 = 0.; 48 | 49 | using std::chrono::duration_cast; 50 | using std::chrono::duration; 51 | using std::chrono::high_resolution_clock; 52 | 53 | auto t1 = high_resolution_clock::now(); 54 | memoized_traversal trav(storage, 1000); 55 | auto t2 = high_resolution_clock::now(); 56 | duration ms = t2 - t1; 57 | accum_2 += ms.count(); 58 | 59 | for (int k = 2; k < 3; ++k) { 60 | for (int j = 2; j < 5; ++j) { 61 | auto seed = make_vec(j, k, 2); 62 | std::cout << "From: " << seed.format() << std::endl; 63 | 64 | visitor<> v; 65 | v.max_depth = 16; 66 | int count = 0; 67 | 68 | std::vector> vecs; 69 | 70 | t1 = high_resolution_clock::now(); 71 | v([&count, &vecs](const tagged_index& v) { 72 | vecs.push_back(v.pos); 73 | count++; 74 | }, storage, seed); 75 | t2 = high_resolution_clock::now(); 76 | ms = t2 - t1; 77 | accum_1 += ms.count(); 78 | 79 | std::sort(vecs.begin(), vecs.end(), [](auto& a, auto& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); }); 80 | for (auto& p : vecs) { 81 | std::cerr << p.format() << std::endl; 82 | } 83 | std::cerr << "vs" << std::endl; 84 | vecs.clear(); 85 | 86 | std::unordered_map, size_t> out; 87 | t1 = high_resolution_clock::now(); 88 | trav(seed, (size_t)*v.max_depth, out); 89 | t2 = high_resolution_clock::now(); 90 | ms = t2 - t1; 91 | accum_2 += ms.count(); 92 | for (auto& p : out) { 93 | vecs.push_back(p.first); 94 | } 95 | std::sort(vecs.begin(), vecs.end(), [](auto& a, auto& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); }); 96 | for (auto& p : vecs) { 97 | std::cerr << p.format() << std::endl; 98 | } 99 | 100 | ASSERT_EQ(out.size(), count); 101 | 102 | std::cerr << "-------------------" << std::endl; 103 | } 104 | } 105 | std::cerr << accum_1 << " vs " << accum_2 << std::endl; 106 | std::cerr << "h: " << trav.cache_hits << " m: " << trav.cache_misses << std::endl; 107 | } 108 | 109 | TEST(DISABLED_Traversal, Squaring) { 110 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 111 | 112 | for (size_t i = 2; i < 7; ++i) { 113 | for (size_t j = 2; j < 7; ++j) { 114 | for (size_t k = 2; k < 7; ++k) { 115 | if (((make_vec(4, 4, 4) - make_vec(i, j, k).as()).abs() < 2).all()) { 116 | continue; 117 | } 118 | storage->Set(make_vec(i, j, k)); 119 | } 120 | } 121 | } 122 | 123 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 124 | ASSERT_EQ(storage->count(), 5 * 5 * 5 - 3 * 3 * 3); 125 | 126 | double accum_1 = 0.; 127 | double accum_2 = 0.; 128 | 129 | using std::chrono::duration_cast; 130 | using std::chrono::duration; 131 | using std::chrono::high_resolution_clock; 132 | 133 | auto t1 = high_resolution_clock::now(); 134 | squaring_traversal trav(storage); 135 | auto t2 = high_resolution_clock::now(); 136 | duration ms = t2 - t1; 137 | accum_2 += ms.count(); 138 | 139 | for (int k = 2; k < 4; ++k) { 140 | for (int j = 2; j < 7; ++j) { 141 | auto seed = make_vec(j, k, 2); 142 | 143 | visitor<> v; 144 | v.max_depth = 32; 145 | int count = 0; 146 | 147 | std::vector> vecs; 148 | 149 | t1 = high_resolution_clock::now(); 150 | v([&count, &vecs](const tagged_index& v) { 151 | vecs.push_back(v.pos); 152 | count++; 153 | }, storage, seed); 154 | t2 = high_resolution_clock::now(); 155 | ms = t2 - t1; 156 | accum_1 += ms.count(); 157 | 158 | std::sort(vecs.begin(), vecs.end(), [](auto& a, auto& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); }); 159 | for (auto& p : vecs) { 160 | std::cerr << p.format() << std::endl; 161 | } 162 | std::cerr << std::endl << std::endl; 163 | vecs.clear(); 164 | 165 | squaring_traversal::set_type out; 166 | t1 = high_resolution_clock::now(); 167 | trav(seed, out); 168 | t2 = high_resolution_clock::now(); 169 | ms = t2 - t1; 170 | accum_2 += ms.count(); 171 | 172 | std::transform(out.begin(), out.end(), std::back_inserter(vecs), [](auto& v) { 173 | return make_vec(v.v[0], v.v[1], v.v[2]); 174 | }); 175 | std::sort(vecs.begin(), vecs.end(), [](auto& a, auto& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); }); 176 | for (auto& p : vecs) { 177 | std::cerr << p.format() << std::endl; 178 | } 179 | 180 | ASSERT_EQ(out.size(), count); 181 | 182 | std::cout << "-------------------" << std::endl; 183 | } 184 | } 185 | std::cerr << accum_1 << " vs " << accum_2 << std::endl; 186 | // std::cerr << "h: " << trav.cache_hits << " m: " << trav.cache_misses << std::endl; 187 | } 188 | -------------------------------------------------------------------------------- /tests/test_normal.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../writer.h" 4 | #include "../voxec.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | TEST(Voxelizer, Volume) { 13 | const double d = 0.1; 14 | auto storage = new chunked_voxel_storage(-d, -d, -d, d, 100, 100, 100, 16); 15 | 16 | BRepPrimAPI_MakeBox mb(8., 8., 8.); 17 | auto vox = voxelizer(mb.Solid(), storage); 18 | vox.Convert(); 19 | 20 | op_normal_estimate normal_estimate; 21 | 22 | std::cout << "Num voxels set " << storage->count() << std::endl; 23 | 24 | scope_map arguments; 25 | { 26 | symbol_value v = storage; 27 | arguments["input"] = v; 28 | } 29 | { 30 | symbol_value v = 5; 31 | arguments["max_depth"] = v; 32 | } 33 | /* 34 | { 35 | symbol_value v = 4; 36 | arguments["THREADS"] = v; 37 | } 38 | */ 39 | 40 | auto r = normal_estimate.invoke(arguments); 41 | auto result = (regular_voxel_storage*)boost::get(r); 42 | 43 | std::cout << "Num voxels with normals " << result->count() << std::endl; 44 | 45 | normal_and_curvature v; 46 | 47 | /* 48 | for (auto ijk : *result) { 49 | result->Get(ijk, &v); 50 | auto v_float = v.convert(); 51 | std::cout << ijk.format() << ": " << v.nxyz_curv[0] << " " << v.nxyz_curv[1] << " " << v.nxyz_curv[2] << " " << v.nxyz_curv[3] << std::endl; 52 | std::cout << ijk.format() << ": " << v_float.nxyz_curv[0] << " " << v_float.nxyz_curv[1] << " " << v_float.nxyz_curv[2] << " " << v_float.nxyz_curv[3] << std::endl; 53 | } 54 | */ 55 | 56 | static const double tol = 1.e-5; 57 | static const double l = 1. / std::sqrt(3.); 58 | static const Eigen::Vector3f ref0(l, l, l); 59 | static const Eigen::Vector3f ref1(1, 0, 0); 60 | 61 | float curv_corner; 62 | 63 | { 64 | // corner 65 | 66 | result->Get(make_vec(95, 95, 95), &v); 67 | auto v_float = v.convert(); 68 | 69 | Eigen::Map v0(v_float.nxyz_curv.data()); 70 | 71 | double angle = std::acos(std::abs(ref0.dot(v0))); 72 | 73 | ASSERT_TRUE((std::abs(angle) < tol) || ((std::abs(angle) - M_PI) < tol)); 74 | 75 | // store for later use 76 | curv_corner = v_float.curvature(); 77 | } 78 | 79 | { 80 | // corner 81 | 82 | result->Get(make_vec(16, 16, 16), &v); 83 | auto v_float = v.convert(); 84 | 85 | Eigen::Map v0(v_float.nxyz_curv.data()); 86 | 87 | double angle = std::acos(std::abs(ref0.dot(v0))); 88 | 89 | ASSERT_TRUE((std::abs(angle) < tol) || ((std::abs(angle) - M_PI) < tol)); 90 | } 91 | 92 | { 93 | // mid-face 94 | 95 | result->Get(make_vec(95, 55, 55), &v); 96 | auto v_float = v.convert(); 97 | 98 | Eigen::Map v0(v_float.nxyz_curv.data()); 99 | 100 | double angle = std::acos(std::abs(ref1.dot(v0))); 101 | 102 | ASSERT_TRUE((std::abs(angle) < tol) || ((std::abs(angle) - M_PI) < tol)); 103 | 104 | // curvature in corner should be (much) higher 105 | ASSERT_LT(v_float.curvature(), curv_corner); 106 | 107 | // curvature mid-face should be near zero 108 | ASSERT_TRUE(std::abs(v_float.curvature()) < tol); 109 | } 110 | 111 | op_segment segment; 112 | 113 | scope_map arguments2; 114 | { 115 | symbol_value v = result; 116 | arguments2["input"] = v; 117 | } 118 | 119 | r = segment.invoke(arguments2); 120 | auto result2 = (regular_voxel_storage*)boost::get(r); 121 | 122 | size_t v2; 123 | std::map element_counts; 124 | 125 | for (auto ijk : *result2) { 126 | result2->Get(ijk, &v2); 127 | element_counts[v2] ++; 128 | } 129 | 130 | for (auto& p : element_counts) { 131 | std::cout << p.first << ": " << p.second << std::endl; 132 | } 133 | 134 | ASSERT_EQ(element_counts.size(), 6); 135 | 136 | op_export_csv<> export_csv; 137 | 138 | scope_map arguments3; 139 | { 140 | symbol_value v = result; 141 | arguments3["input"] = v; 142 | } 143 | { 144 | symbol_value v = std::string("result.csv"); 145 | arguments3["filename"] = v; 146 | } 147 | 148 | export_csv.invoke(arguments3); 149 | 150 | 151 | op_mesh mesh; 152 | 153 | scope_map arguments4; 154 | { 155 | symbol_value v = result2; 156 | arguments4["input"] = v; 157 | } 158 | { 159 | symbol_value v = std::string("result.obj"); 160 | arguments4["filename"] = v; 161 | } 162 | { 163 | symbol_value v = 1; 164 | arguments4["use_value"] = v; 165 | } 166 | { 167 | symbol_value v = 0; 168 | arguments4["with_components"] = v; 169 | } 170 | 171 | mesh.invoke(arguments4); 172 | } 173 | -------------------------------------------------------------------------------- /tests/test_offset.cpp: -------------------------------------------------------------------------------- 1 | #include "../offset.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST(Postproc, Offset) { 8 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 9 | 10 | for (size_t i = 2; i < 5; ++i) { 11 | for (size_t j = 2; j < 5; ++j) { 12 | for (size_t k = 2; k < 5; ++k) { 13 | if ((make_vec(i, j, k) == make_vec(3U, 3U, 3U)).all()) { 14 | continue; 15 | } 16 | storage->Set(make_vec(i, j, k)); 17 | } 18 | } 19 | } 20 | 21 | ASSERT_FALSE(storage->Get(make_vec(1U, 1U, 1U))); 22 | ASSERT_FALSE(storage->Get(make_vec(3U, 3U, 3U))); 23 | 24 | auto storage2 = offset<>()(storage); 25 | 26 | ASSERT_TRUE(storage2->Get(make_vec(1U, 1U, 1U))); 27 | ASSERT_FALSE(storage2->Get(make_vec(2U, 2U, 2U))); 28 | ASSERT_TRUE(storage2->Get(make_vec(3U, 3U, 3U))); 29 | } 30 | -------------------------------------------------------------------------------- /tests/test_parser.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_SPIRIT_DEBUG 2 | 3 | #include "../voxec.h" 4 | #include "../factory.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #ifdef WIN32 11 | #define DIRSEP "\\" 12 | #else 13 | #define DIRSEP "/" 14 | #endif 15 | 16 | TEST(Voxelfile, Parser) { 17 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "voxelfile3.txt"; 18 | 19 | factory::mmap("vox"); 20 | 21 | std::ifstream ifs(input_filename.c_str(), std::ios::binary); 22 | ifs.seekg(0, ifs.end); 23 | size_t size = ifs.tellg(); 24 | ifs.seekg(0, ifs.beg); 25 | ifs >> std::noskipws; 26 | 27 | boost::spirit::istream_iterator first(ifs), last; 28 | auto f = first; 29 | 30 | voxelfile_parser parser; 31 | 32 | std::vector tree; 33 | phrase_parse(first, last, parser, blank, tree); 34 | 35 | ASSERT_EQ(std::distance(f, first), size); 36 | 37 | // run(tree, 0.05, 1); 38 | } 39 | -------------------------------------------------------------------------------- /tests/test_parser_assert.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_IFC 2 | #ifndef IFCOPENSHELL_05 3 | 4 | #include "../voxec.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef WIN32 10 | #define DIRSEP "\\" 11 | #else 12 | #define DIRSEP "/" 13 | #endif 14 | 15 | TEST(Assert, Integer) { 16 | std::stringstream sss; 17 | sss << "file = parse(\"duplex.ifc\")\n" 18 | << "geom = create_geometry(file)\n" 19 | << "voxels = voxelize(geom)\n" 20 | << "nc = count(voxels)\n" 21 | << "assert(nc)\n" 22 | ; 23 | 24 | sss.seekg(0, sss.end); 25 | size_t size = sss.tellg(); 26 | sss.seekg(0, sss.beg); 27 | sss >> std::noskipws; 28 | 29 | boost::spirit::istream_iterator first(sss), last; 30 | auto f = first; 31 | 32 | voxelfile_parser parser; 33 | 34 | std::vector tree; 35 | phrase_parse(first, last, parser, blank, tree); 36 | 37 | boost::filesystem::current_path(boost::filesystem::path("..") / "tests" / "fixtures"); 38 | run(tree, 0.5, 1); 39 | } 40 | 41 | TEST(Assert, Voxels) { 42 | std::stringstream sss; 43 | sss << "file = parse(\"duplex.ifc\")\n" 44 | << "geom = create_geometry(file)\n" 45 | << "voxels = voxelize(geom)\n" 46 | << "assert(voxels)\n" 47 | ; 48 | 49 | sss.seekg(0, sss.end); 50 | size_t size = sss.tellg(); 51 | sss.seekg(0, sss.beg); 52 | sss >> std::noskipws; 53 | 54 | boost::spirit::istream_iterator first(sss), last; 55 | auto f = first; 56 | 57 | voxelfile_parser parser; 58 | 59 | std::vector tree; 60 | phrase_parse(first, last, parser, blank, tree); 61 | 62 | run(tree, 0.5, 1); 63 | } 64 | 65 | TEST(Assert, EmptyVoxels) { 66 | std::stringstream sss; 67 | sss << "file = parse(\"duplex.ifc\")\n" 68 | << "geom = create_geometry(file)\n" 69 | << "voxels = voxelize(geom)\n" 70 | << "empty = constant_like(voxels, 0)\n" 71 | << "assert(empty)\n" 72 | ; 73 | 74 | sss.seekg(0, sss.end); 75 | size_t size = sss.tellg(); 76 | sss.seekg(0, sss.beg); 77 | sss >> std::noskipws; 78 | 79 | boost::spirit::istream_iterator first(sss), last; 80 | auto f = first; 81 | 82 | voxelfile_parser parser; 83 | 84 | std::vector tree; 85 | phrase_parse(first, last, parser, blank, tree); 86 | 87 | EXPECT_THROW(run(tree, 0.5, 1), assertion_error); 88 | } 89 | 90 | #endif 91 | #endif 92 | -------------------------------------------------------------------------------- /tests/test_parser_prop_filter.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_IFC 2 | #ifndef IFCOPENSHELL_05 3 | 4 | #include "../voxec.h" 5 | 6 | #include 7 | #include 8 | 9 | #ifdef WIN32 10 | #define DIRSEP "\\" 11 | #else 12 | #define DIRSEP "/" 13 | #endif 14 | 15 | TEST(Filters, PropertyFilter) { 16 | std::stringstream sss; 17 | sss << "file = parse(\"duplex.ifc\")\n" 18 | << "external = filter_properties(file, IsExternal=1)\n" 19 | << "external_doors = create_geometry(external, include={\"IfcDoor\"})\n" 20 | << "external_doors_voxels = voxelize(external_doors)\n" 21 | << "nc = count_components(external_doors_voxels)\n" 22 | ; 23 | 24 | sss.seekg(0, sss.end); 25 | size_t size = sss.tellg(); 26 | sss.seekg(0, sss.beg); 27 | sss >> std::noskipws; 28 | 29 | boost::spirit::istream_iterator first(sss), last; 30 | auto f = first; 31 | 32 | voxelfile_parser parser; 33 | 34 | std::vector tree; 35 | phrase_parse(first, last, parser, blank, tree); 36 | 37 | ASSERT_EQ(std::distance(f, first), size); 38 | 39 | boost::filesystem::current_path(boost::filesystem::path("..") / "tests" / "fixtures"); 40 | 41 | // 0.5 sufficiently large to merge items 42 | auto scp = run(tree, 0.5, 1); 43 | 44 | ASSERT_EQ(scp.get_value("nc"), 4); 45 | } 46 | 47 | #endif 48 | #endif 49 | -------------------------------------------------------------------------------- /tests/test_parser_resample.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_SPIRIT_DEBUG 2 | 3 | #include "../voxec.h" 4 | #include "../factory.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #ifdef WIN32 11 | #define DIRSEP "\\" 12 | #else 13 | #define DIRSEP "/" 14 | #endif 15 | 16 | TEST(Voxelfile, Parser) { 17 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "voxelfile5.txt"; 18 | 19 | std::ifstream ifs(input_filename.c_str(), std::ios::binary); 20 | ifs.seekg(0, ifs.end); 21 | size_t size = ifs.tellg(); 22 | ifs.seekg(0, ifs.beg); 23 | ifs >> std::noskipws; 24 | 25 | boost::spirit::istream_iterator first(ifs), last; 26 | auto f = first; 27 | 28 | voxelfile_parser parser; 29 | std::vector tree; 30 | phrase_parse(first, last, parser, blank, tree); 31 | 32 | ASSERT_EQ(std::distance(f, first), size); 33 | 34 | // run(tree, 0.05, 1); 35 | } 36 | -------------------------------------------------------------------------------- /tests/test_pca.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../traversal.h" 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | TEST(NormalEstimation, EigenPCA) { 17 | static Eigen::Vector3f Z(0, 0, 1); 18 | Eigen::MatrixXf points(10, 3); 19 | points.setRandom(); 20 | points.col(2).setZero(); 21 | 22 | Eigen::MatrixXf centered = points.rowwise() - points.colwise().mean(); 23 | Eigen::MatrixXf cov = centered.adjoint() * centered; 24 | Eigen::SelfAdjointEigenSolver eig(cov); 25 | 26 | ASSERT_FLOAT_EQ(std::abs(eig.eigenvectors().col(0).dot(Z)), 1.); 27 | } 28 | 29 | 30 | TEST(NormalEstimation, OCC) { 31 | gp_Pln p(1, 2, 3, 4); 32 | auto face = BRepBuilderAPI_MakeFace(p, -1, 1, -1, 1).Face(); 33 | BRepMesh_IncrementalMesh(face, 0.1); 34 | 35 | auto surf = BRep_Tool::Surface(face); 36 | auto dir = Handle_Geom_Plane::DownCast(surf)->Position().Direction(); 37 | 38 | Eigen::Vector3f norm(dir.X(), dir.Y(), dir.Z()); 39 | std::cout << "Face normal " << norm << std::endl; 40 | 41 | Bnd_Box B; 42 | BRepBndLib::AddClose(face, B); 43 | 44 | double d = 0.01; 45 | double x0, y0, z0, x1, y1, z1; 46 | B.Get(x0, y0, z0, x1, y1, z1); 47 | 48 | auto storage = new chunked_voxel_storage( 49 | x0 - d, 50 | y0 - d, 51 | z0 - d, d, 52 | (x1 - x0) / d + 2, 53 | (y1 - y0) / d + 2, 54 | (z1 - z0) / d + 2, 55 | 64); 56 | auto vox = voxelizer(face, storage); 57 | vox.Convert(); 58 | 59 | std::cout << "count " << storage->count() << std::endl; 60 | 61 | auto it = storage->begin(); 62 | std::advance(it, storage->count() / 2); 63 | 64 | std::cout << "center " << (*it).format() << std::endl; 65 | 66 | int i = 2; 67 | 68 | visitor<26> vis; 69 | vis.max_depth = 0.1 * i / d; 70 | 71 | std::cout << "md " << (0.1 * i) << std::endl; 72 | 73 | std::vector coords; 74 | 75 | auto selection = storage->empty_copy(); 76 | 77 | vis([&coords, &selection](const tagged_index& pos) { 78 | if (pos.which == tagged_index::VOXEL) { 79 | coords.push_back(pos.pos.get(0)); 80 | coords.push_back(pos.pos.get(1)); 81 | coords.push_back(pos.pos.get(2)); 82 | selection->Set(pos.pos); 83 | } 84 | else { 85 | throw std::runtime_error("Unexpected"); 86 | } 87 | }, storage, *it); 88 | 89 | /*{ 90 | std::ofstream ofs("m2.obj"); 91 | ((chunked_voxel_storage*)selection)->obj_export(ofs); 92 | } 93 | 94 | storage->boolean_subtraction_inplace(selection); 95 | 96 | { 97 | std::ofstream ofs("m1.obj"); 98 | storage->obj_export(ofs); 99 | }*/ 100 | 101 | std::cout << "neighbours " << (coords.size() / 3) << std::endl; 102 | 103 | Eigen::MatrixXf points = Eigen::Map(coords.data(), 3, coords.size() / 3).transpose(); 104 | 105 | Eigen::MatrixXf centered = points.rowwise() - points.colwise().mean(); 106 | Eigen::MatrixXf cov = centered.adjoint() * centered; 107 | Eigen::SelfAdjointEigenSolver eig(cov); 108 | 109 | auto estimated = eig.eigenvectors().col(0); 110 | 111 | std::cout << estimated << std::endl; 112 | 113 | auto angle = std::acos(estimated.dot(norm)); 114 | if (angle > M_PI / 2) { 115 | angle = M_PI - angle; 116 | } 117 | auto angle_degrees = angle / M_PI * 180.; 118 | 119 | std::cout << "angle " << angle_degrees << "d" << std::endl; 120 | 121 | ASSERT_LT(angle_degrees, 1.); 122 | } -------------------------------------------------------------------------------- /tests/test_polyfill.cpp: -------------------------------------------------------------------------------- 1 | #include "../polyfill.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class PolyfillTest : public ::testing::Test { 9 | protected: 10 | size_t W_, H_; 11 | char* data; 12 | public: 13 | PolyfillTest(size_t W = 50, size_t H = 30) 14 | : W_(W), H_(H) 15 | { 16 | data = new char[W * H]{}; 17 | clear(); 18 | } 19 | 20 | void clear() { 21 | for (size_t i = 0; i < W_ * H_; ++i) { 22 | data[i] = ' '; 23 | } 24 | } 25 | 26 | void fill(const std::vector& verts) { 27 | fill_poly(verts, [](int y, int npairs, int* xses, void * d) { 28 | PolyfillTest* self = (PolyfillTest*)d; 29 | for (int i = 0; i < npairs; ++i) { 30 | for (int x = xses[i * 2 + 0]; x <= xses[i * 2 + 1]; ++x) { 31 | self->get(x, y) = 'x'; 32 | } 33 | } 34 | }, this); 35 | } 36 | 37 | char& get(int x, int y) { 38 | return data[W_ * y + x]; 39 | } 40 | 41 | void dump() { 42 | std::cerr << " "; 43 | for (size_t i = 0; i < W_; ++i) { 44 | std::cerr << (i % 10); 45 | } 46 | std::cerr << std::endl; 47 | std::cerr << " +"; 48 | for (size_t i = 0; i < W_; ++i) { 49 | std::cerr << "-"; 50 | } 51 | std::cerr << "+"; 52 | std::cerr << std::endl; 53 | for (size_t i = 0; i < H_; ++i) { 54 | std::cerr << (i % 10) << "|"; 55 | for (size_t j = 0; j < W_; ++j) { 56 | std::cerr << get(j, i); 57 | } 58 | std::cerr << "|"; 59 | std::cerr << std::endl; 60 | } 61 | std::cerr << " +"; 62 | for (size_t i = 0; i < W_; ++i) { 63 | std::cerr << "-"; 64 | } 65 | std::cerr << "+"; 66 | std::cerr << std::endl; 67 | std::cerr << std::endl; 68 | } 69 | 70 | size_t W() const { return W_; } 71 | size_t H() const { return H_; } 72 | }; 73 | 74 | TEST_F(PolyfillTest, RectanglePolygon) { 75 | const std::vector verts = { make_vec( 5, 5 ),make_vec( 5,25 ),make_vec( 45,25 ),make_vec( 30,5 ) }; 76 | 77 | fill(verts); 78 | dump(); 79 | 80 | EXPECT_EQ(get(0, 0), ' '); 81 | EXPECT_EQ(get(5, 5), 'x'); 82 | EXPECT_EQ(get(45, 25), 'x'); 83 | EXPECT_EQ(get(46, 25), ' '); 84 | 85 | // Assert that scanline results in the same result as point containment tests 86 | for (size_t x = 0; x < W(); ++x) { 87 | for (size_t y = 0; y < H(); ++y) { 88 | // std::cerr << x << "," << y << std::endl; 89 | EXPECT_EQ(is_inside_poly(verts, x, y), get(x, y) == 'x'); 90 | } 91 | } 92 | 93 | clear(); 94 | } 95 | 96 | 97 | TEST_F(PolyfillTest, ConcaveOrthogonal) { 98 | const std::vector verts = { make_vec( 5, 5 ),make_vec( 45, 5 ),make_vec( 45, 25 ),make_vec( 25,25 ),make_vec( 25,15 ),make_vec( 15,15 ),make_vec( 15,25 ),make_vec( 5,25 ) }; 99 | 100 | fill(verts); 101 | dump(); 102 | 103 | EXPECT_EQ(get(20, 15), 'x'); 104 | EXPECT_TRUE(is_inside_poly(verts, 20, 15)); 105 | 106 | // Assert that scanline results in the same result as point containment tests 107 | for (size_t x = 0; x < W(); ++x) { 108 | for (size_t y = 0; y < H(); ++y) { 109 | EXPECT_EQ(is_inside_poly(verts, x, y), get(x, y) == 'x'); 110 | } 111 | } 112 | } 113 | 114 | 115 | TEST_F(PolyfillTest, ConcaveNonOrtho) { 116 | const std::vector verts = { make_vec( 5, 5 ),make_vec( 45, 5 ),make_vec( 45, 25 ),make_vec( 25,25 ),make_vec( 25,15 ),make_vec( 15,12 ),make_vec( 15,25 ),make_vec( 5,25 ) }; 117 | 118 | fill(verts); 119 | dump(); 120 | 121 | // EXPECT_EQ(get(20, 15), ' '); 122 | // EXPECT_EQ(get(20, 14), 'x'); 123 | 124 | // Assert that scanline results in the same result as point containment tests 125 | for (size_t x = 0; x < W(); ++x) { 126 | for (size_t y = 0; y < H(); ++y) { 127 | // std::cerr << x << " " << y << std::endl; 128 | EXPECT_EQ(is_inside_poly(verts, x, y), get(x, y) == 'x'); 129 | } 130 | } 131 | } 132 | 133 | TEST_F(PolyfillTest, ConcaveNonOrtho2) { 134 | const std::vector verts = { make_vec( 5, 5 ),make_vec( 45, 5 ),make_vec( 45, 25 ),make_vec( 25,25 ),make_vec( 25,12 ),make_vec( 15,15 ),make_vec( 15,25 ),make_vec( 5,25 ) }; 135 | 136 | fill(verts); 137 | dump(); 138 | 139 | // EXPECT_EQ(get(20, 15), ' '); 140 | // EXPECT_EQ(get(20, 14), 'x'); 141 | 142 | // Assert that scanline results in the same result as point containment tests 143 | for (size_t x = 0; x < W(); ++x) { 144 | for (size_t y = 0; y < H(); ++y) { 145 | EXPECT_EQ(is_inside_poly(verts, x, y), get(x, y) == 'x'); 146 | } 147 | } 148 | } 149 | 150 | TEST(LineSegment, Intersection) { 151 | line_t ab{ point_t(0,0),point_t(10,0) }; 152 | line_t cd{ point_t(5,-1),point_t(5,1) }; 153 | ASSERT_TRUE(intersects(ab, cd)); 154 | } 155 | 156 | TEST(LineSegment, Touching) { 157 | line_t ab{ point_t(0,0),point_t(10,0) }; 158 | line_t cd{ point_t(5,0),point_t(5,5) }; 159 | // Toughing segments need to report as non-intersecting 160 | ASSERT_FALSE(intersects(ab, cd)); 161 | } 162 | 163 | TEST(LineSegment, Overlapping) { 164 | line_t ab{ point_t(0,0),point_t(10,0) }; 165 | line_t cd{ point_t(3,0),point_t(6,0) }; 166 | // Overlapping segments need to report as non-intersecting 167 | ASSERT_FALSE(intersects(ab, cd)); 168 | } 169 | 170 | 171 | TEST(LineSegment, Colinear) { 172 | line_t ab{ point_t(0,0),point_t(10,0) }; 173 | line_t cd{ point_t(11,0),point_t(12,0) }; 174 | // Colinear disjoint segments need to report as non-intersecting 175 | ASSERT_FALSE(intersects(ab, cd)); 176 | } 177 | -------------------------------------------------------------------------------- /tests/test_resample.cpp: -------------------------------------------------------------------------------- 1 | #include "../resample.h" 2 | 3 | #include 4 | 5 | TEST(DownSampling, Constant) { 6 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 7 | storage->create_constant(make_vec(0U,0U,0U), 1); 8 | auto result = resampler(-4)(storage); 9 | ASSERT_EQ(storage->count(), result->count() * 4 * 4 * 4); 10 | } 11 | 12 | TEST(UpSampling, Constant) { 13 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 14 | storage->create_constant(make_vec(0U,0U,0U), 1); 15 | auto result = (chunked_voxel_storage*) resampler(+2)(storage); 16 | BEGIN_LOOP(size_t(0), 2U, 0U, 2U, 0U, 2U) 17 | ASSERT_TRUE(result->get_chunk(ijk)->is_constant()); 18 | END_LOOP; 19 | } 20 | 21 | TEST(UpSampling, Plane) { 22 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 23 | // Z=20 24 | storage->create_plane_primitive(make_vec(0U,0U,0U), 2, 20); 25 | auto result = (chunked_voxel_storage*) resampler(+2)(storage); 26 | ASSERT_TRUE(result->get_chunk(make_vec(0U, 0U, 0U)) == nullptr); 27 | ASSERT_FALSE(result->get_chunk(make_vec(0U, 0U, 1U))->is_explicit()); 28 | // Z = (20 * 2) % 32 = 8 29 | ASSERT_TRUE(result->get_chunk(make_vec(0U, 0U, 1U))->Get(make_vec(0U, 0U, 8U))); 30 | } 31 | 32 | TEST(UpSampling, Continuous) { 33 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 34 | storage->Set(make_vec(1U,1U,1U )); 35 | auto result = (chunked_voxel_storage*) resampler(+2)(storage); 36 | ASSERT_FALSE(result->Get(make_vec(1U, 1U, 1U ))); 37 | ASSERT_TRUE(result->Get(make_vec(2U, 2U, 2U ))); 38 | ASSERT_TRUE(result->Get(make_vec(3U, 3U, 3U ))); 39 | ASSERT_FALSE(result->Get(make_vec(4U, 4U, 4U ))); 40 | } 41 | -------------------------------------------------------------------------------- /tests/test_space_ids.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../writer.h" 3 | #include "../processor.h" 4 | 5 | #ifdef WITH_IFC 6 | #include 7 | #ifdef IFCOPENSHELL_05 8 | #include 9 | using namespace Ifc2x3; 10 | #else 11 | #include 12 | #endif 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #ifdef WIN32 22 | #define DIRSEP "\\" 23 | #else 24 | #define DIRSEP "/" 25 | #endif 26 | 27 | #if defined(WITH_IFC) && !defined(IFCOPENSHELL_05) 28 | TEST(Voxelization, IfcSpaceIds) { 29 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "duplex.ifc"; 30 | 31 | IfcParse::IfcFile ifc_file(input_filename); 32 | 33 | const double d = 0.5; 34 | 35 | ASSERT_TRUE(ifc_file.good()); 36 | 37 | IfcGeom::IteratorSettings settings_surface; 38 | settings_surface.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); 39 | settings_surface.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, true); 40 | 41 | IfcGeom::entity_filter ef; 42 | ef.include = true; 43 | ef.entity_names = { "IfcSpace" }; 44 | ef.traverse = false; 45 | 46 | auto filters = std::vector({ ef }); 47 | 48 | IfcGeom::Iterator it(settings_surface, &ifc_file, filters, 1); 49 | 50 | ASSERT_TRUE(it.initialize()); 51 | 52 | geometry_collection_t geoms; 53 | 54 | while (true) { 55 | TopoDS_Compound C = it.get_native()->geometry().as_compound(); 56 | BRepMesh_IncrementalMesh(C, 0.001); 57 | geoms.push_back({ {&ifc_file, it.get_native()->id()}, C }); 58 | if (!it.next()) { 59 | break; 60 | } 61 | } 62 | 63 | Bnd_Box global_bounds; 64 | for (auto& P : geoms) { 65 | BRepBndLib::Add(P.second, global_bounds); 66 | } 67 | 68 | double x1, y1, z1, x2, y2, z2; 69 | global_bounds.Get(x1, y1, z1, x2, y2, z2); 70 | int nx = (int)ceil((x2 - x1) / d) + 10; 71 | int ny = (int)ceil((y2 - y1) / d) + 10; 72 | int nz = (int)ceil((z2 - z1) / d) + 10; 73 | 74 | x1 -= d * 5; 75 | y1 -= d * 5; 76 | z1 -= d * 5; 77 | 78 | chunked_voxel_storage* storage = new chunked_voxel_storage(x1, y1, z1, d, nx, ny, nz, 64); 79 | 80 | { 81 | progress_writer progress_1("test_space_ids"); 82 | processor pr(storage, progress_1); 83 | pr.use_scanline() = false; 84 | pr.process(geoms.begin(), geoms.end(), VOLUME_PRODUCT_ID(), output(MERGED(), "test_space_ids.vox")); 85 | } 86 | 87 | // PRINT WORLD BOUNDS OF STORAGE...e b 88 | 89 | std::ofstream fs("boundaries.obj"); 90 | obj_export_helper oeh{ fs }; 91 | storage->obj_export(oeh, false, true); 92 | } 93 | #else 94 | TEST(Voxelization, DISABLED_IfcSpaceIds) {} 95 | #endif -------------------------------------------------------------------------------- /tests/test_sweep_max.cpp: -------------------------------------------------------------------------------- 1 | #include "../storage.h" 2 | #include "../sweep.h" 3 | #include 4 | 5 | TEST(Sweep, SweepWithMax) { 6 | const double d = 1; 7 | auto a = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 8 | a->Set(make_vec(1U, 1U, 1U)); 9 | auto b = new chunked_voxel_storage(0, 0, 0, d, 20, 20, 20, 10); 10 | b->Set(make_vec(1U, 1U, 10U)); 11 | 12 | { 13 | sweep s; 14 | s.until = b; 15 | ASSERT_EQ(s(a, 0, 0, 1)->count(), 10 - 1); 16 | } 17 | { 18 | sweep s; 19 | s.until = b; 20 | s.max_depth = 3; 21 | ASSERT_EQ(s(a, 0, 0, 1)->count(), 1 + 3); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/test_traversal.cpp: -------------------------------------------------------------------------------- 1 | #include "../traversal.h" 2 | #include "../storage.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | TEST(Traversal, Outmost) { 9 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 10 | 11 | for (size_t i = 2; i < 7; ++i) { 12 | for (size_t j = 2; j < 7; ++j) { 13 | for (size_t k = 2; k < 7; ++k) { 14 | if (((make_vec(4, 4, 4) - make_vec(i, j, k).as()).abs() < 2).all()) { 15 | continue; 16 | } 17 | storage->Set(make_vec(i, j, k )); 18 | } 19 | } 20 | } 21 | 22 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 23 | 24 | storage->Set(make_vec(4, 4, 4)); 25 | 26 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2 + 1); 27 | 28 | auto leftmost = query_leftmost()(storage); 29 | ASSERT_EQ(leftmost.get(0), 2); 30 | 31 | int count = 0; 32 | visitor<>()([&count](const tagged_index&) { 33 | count++; 34 | }, storage, leftmost); 35 | 36 | auto outmost = keep_outmost()(storage); 37 | ASSERT_EQ(outmost->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 38 | } 39 | 40 | TEST(Traversal, MaxDepth) { 41 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 42 | 43 | for (int i = 2; i < 7; ++i) { 44 | for (int j = 2; j < 7; ++j) { 45 | for (int k = 2; k < 7; ++k) { 46 | if (((make_vec(4, 4, 4) - make_vec(i, j, k)).abs() < 2).all()) { 47 | continue; 48 | } 49 | storage->Set(make_vec(i, j, k).as()); 50 | } 51 | } 52 | } 53 | 54 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 55 | 56 | storage->Set(make_vec(4, 4, 4)); 57 | 58 | ASSERT_EQ(storage->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2 + 1); 59 | 60 | auto leftmost = query_leftmost()(storage); 61 | ASSERT_EQ(leftmost.get(0), 2); 62 | 63 | int count = 0; 64 | visitor<>()([&count](const tagged_index&) { 65 | count++; 66 | }, storage, leftmost); 67 | 68 | auto outmost = keep_outmost()(storage); 69 | ASSERT_EQ(outmost->count(), count); 70 | ASSERT_EQ(outmost->count(), 5 * 5 * 2 + 5 * 3 * 2 + 3 * 3 * 2); 71 | 72 | // The expected depth includes the original seed. 73 | static const int expected_count_for_depths[] = { 1, 4, 10 }; 74 | 75 | for (int i = 0; i < 3; ++i) { 76 | visitor<> v; 77 | v.max_depth = i; 78 | count = 0; 79 | v([&count](const tagged_index& v) { 80 | count++; 81 | }, storage, make_vec(2, 2, 2)); 82 | 83 | ASSERT_EQ(count, expected_count_for_depths[i]); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_traversal_float.cpp: -------------------------------------------------------------------------------- 1 | #include "../traversal.h" 2 | #include "../storage.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | TEST(Traversal, Float) { 9 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 10 | 11 | for (int i = 2; i < 7; ++i) { 12 | for (int j = 2; j < 7; ++j) { 13 | for (int k = 2; k < 7; ++k) { 14 | storage->Set(make_vec(i, j, k).as()); 15 | } 16 | } 17 | } 18 | 19 | auto seed = (regular_voxel_storage*)storage->empty_copy(); 20 | 21 | for (int i = 4; i < 5; ++i) { 22 | for (int j = 4; j < 5; ++j) { 23 | for (int k = 3; k < 6; ++k) { 24 | seed->Set(make_vec(i, j, k).as()); 25 | } 26 | } 27 | } 28 | 29 | visitor<26> v; 30 | v.max_depth = sqrt(3.); 31 | int count = 0; 32 | v([&count](const tagged_index& v) { 33 | count++; 34 | }, storage, seed); 35 | 36 | ASSERT_EQ(count, 45); 37 | } 38 | 39 | TEST(Traversal, Float2) { 40 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 10, 10, 10, 10); 41 | 42 | for (int i = 2; i < 7; ++i) { 43 | for (int j = 2; j < 7; ++j) { 44 | for (int k = 4; k < 5; ++k) { 45 | storage->Set(make_vec(i, j, k).as()); 46 | } 47 | } 48 | } 49 | 50 | auto seed = (regular_voxel_storage*)storage->empty_copy(); 51 | 52 | for (int i = 3; i < 6; ++i) { 53 | for (int j = 4; j < 5; ++j) { 54 | for (int k = 4; k < 5; ++k) { 55 | seed->Set(make_vec(i, j, k).as()); 56 | } 57 | } 58 | } 59 | 60 | visitor<26> v; 61 | v.max_depth = 2.; 62 | int count = 0; 63 | v([&count](const tagged_index& v) { 64 | count++; 65 | }, storage, seed); 66 | 67 | ASSERT_EQ(count, 21); 68 | } 69 | -------------------------------------------------------------------------------- /tests/test_validate.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../writer.h" 4 | #include "../traversal.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace { 14 | void voxelize(double vsize, bool rotated) { 15 | auto storage = new chunked_voxel_storage(0., 0., 0., vsize, 10. / vsize, 10. / vsize, 10. / vsize, 32); 16 | 17 | gp_Ax2 ax; 18 | if (rotated) { 19 | ax = gp_Ax2(gp_Pnt(3, 4, 4), gp_Dir(1, 1, 1)); 20 | } else { 21 | ax = gp_Ax2(gp_Pnt(1, 1, 1), gp::DZ()); 22 | } 23 | 24 | BRepPrimAPI_MakeBox mb(ax, 3., 3., 3.); 25 | auto solid = mb.Solid(); 26 | BRepMesh_IncrementalMesh(solid, 0.001); 27 | 28 | /* 29 | TopExp_Explorer exp(solid, TopAbs_VERTEX); 30 | std::set< std::tuple > points; 31 | for (; exp.More(); exp.Next()) { 32 | gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); 33 | points.insert(std::make_tuple(p.X(), p.Y(), p.Z())); 34 | } 35 | 36 | for (auto& p : points) { 37 | std::cout << std::get<0>(p) << " " << std::get<1>(p) << " " << std::get<2>(p) << std::endl; 38 | } 39 | */ 40 | 41 | double t_vox, t_vol; 42 | 43 | { 44 | std::clock_t c_start = std::clock(); 45 | auto vox = voxelizer(solid, storage); 46 | vox.Convert(); 47 | std::clock_t c_end = std::clock(); 48 | 49 | t_vox = (double) (c_end - c_start) / CLOCKS_PER_SEC; 50 | } 51 | 52 | /* 53 | std::ofstream fs("rotated.obj"); 54 | storage->obj_export(fs); 55 | */ 56 | 57 | regular_voxel_storage* volume; 58 | { 59 | std::clock_t c_start = std::clock(); 60 | traversal_voxel_filler_inverse filler; 61 | volume = filler(storage); 62 | std::clock_t c_end = std::clock(); 63 | 64 | t_vol = (double) (c_end - c_start) / CLOCKS_PER_SEC; 65 | } 66 | 67 | 68 | auto surface_count = storage->count(); 69 | auto volume_count = volume->count(); 70 | 71 | double total_volume = (volume_count + surface_count / 2) * (vsize * vsize * vsize); 72 | 73 | std::cout << std::setprecision(15) << vsize << "," << rotated << "," << t_vol << "," << t_vox << "," << total_volume << std::endl; 74 | 75 | delete storage; 76 | delete volume; 77 | } 78 | } 79 | 80 | TEST(Validation, Volume) { 81 | std::cout.precision(15); 82 | 83 | for (int j = 1; j < 3; ++j) { 84 | double b = 1. / std::pow(10, j); 85 | for (int i = 1; i < 10; ++i) { 86 | voxelize(b * i, false); 87 | voxelize(b * i, true); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/test_vec_n.cpp: -------------------------------------------------------------------------------- 1 | #include "../dim3.h" 2 | 3 | #include 4 | 5 | TEST(VecN, Arithmetic) { 6 | vec_n<3, int> vec123(1, 2, 3); 7 | vec_n<3, int> vec124(1, 2, 4); 8 | vec_n<3, int> vec234(2, 3, 4); 9 | vec_n<3, int> vec001(0, 0, 1); 10 | vec_n<3, int> vec003(0, 0, 3); 11 | 12 | EXPECT_TRUE((vec123 == vec123).all()); 13 | EXPECT_TRUE((vec123 == vec123).any()); 14 | EXPECT_FALSE((vec123 == vec124).all()); 15 | 16 | EXPECT_TRUE(((vec123 + vec001) == vec124).all()); 17 | EXPECT_TRUE(((vec123 + 1) == vec234).all()); 18 | 19 | int a, b, c; 20 | a = b = c = 0; 21 | 22 | vec123.tie(a, b, c); 23 | 24 | EXPECT_EQ(a, 1); 25 | 26 | EXPECT_TRUE((vec123.maximum(vec003 + 1) == vec124).all()); 27 | 28 | EXPECT_EQ(vec123.sum(), 6); 29 | EXPECT_EQ(vec123.as().sum(), 6); 30 | EXPECT_EQ(vec123.as().sum(), 6); 31 | } -------------------------------------------------------------------------------- /tests/test_volume.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../volume.h" 3 | #include "../writer.h" 4 | #include "../traversal.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | TEST(DISABLED_Voxelizer, ApproxVolume) { 11 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 1000, 1000, 100, 32); 12 | 13 | for (int i = 0; i < 10; ++i) { 14 | for (int j = 0; j < 10; ++j) { 15 | BRepPrimAPI_MakeBox mb(gp_Pnt(10 * i, 10 * j, 0.), 8., 8., 8.); 16 | 17 | auto vox = voxelizer(mb.Solid(), storage); 18 | vox.Convert(); 19 | } 20 | } 21 | 22 | /* 23 | volume_filler volume(storage); 24 | volume.fill(); 25 | */ 26 | 27 | size_t ncx, ncy, ncz; 28 | storage->num_chunks().tie(ncx, ncy, ncz); 29 | 30 | for (size_t i = 0; i < ncx; ++i) { 31 | for (size_t j = 0; j < ncy; ++j) { 32 | for (size_t k = 0; k < ncz; ++k) { 33 | if (storage->get_chunk(make_vec(i, j, k)) != nullptr) { 34 | std::cerr << vec_n<3, size_t>(i, j, k).format() << ": " << storage->get_chunk(make_vec(i, j, k))->count() << std::endl; 35 | } 36 | } 37 | } 38 | } 39 | 40 | std::cerr << "count: " << storage->count() << std::endl; 41 | 42 | voxel_writer writer; 43 | writer.SetVoxels(storage); 44 | writer.Write("test_volume.vox"); 45 | } 46 | 47 | TEST(DISABLED_Voxelizer, TraversalVolumeSingle) { 48 | auto surface = new chunked_voxel_storage(0., 0., 0., 0.1, 100, 100, 100, 32); 49 | BRepPrimAPI_MakeBox mb(gp_Pnt(1., 1., 1.), 8., 8., 8.); 50 | auto vox = voxelizer(mb.Solid(), surface); 51 | vox.Convert(); 52 | 53 | traversal_voxel_filler filler; 54 | auto volume = filler(surface); 55 | auto A = surface->count(); 56 | auto B = volume->count(); 57 | 58 | /* { 59 | voxel_writer writer; 60 | writer.SetVoxels(surface); 61 | writer.Write("test_volume_surface.vox"); 62 | } 63 | 64 | { 65 | voxel_writer writer; 66 | writer.SetVoxels(volume); 67 | writer.Write("test_volume_volume.vox"); 68 | } */ 69 | 70 | ASSERT_EQ(A, 81 * 81 * 2 + 81 * 79 * 2 + 79 * 79 * 2); 71 | ASSERT_EQ(B, 79 * 79 * 79); 72 | ASSERT_EQ(A + B, 81 * 81 * 81); 73 | 74 | std::cerr << "count: " << A << " + " << B << " = " << (A+B) << std::endl; 75 | 76 | delete surface; 77 | delete volume; 78 | } 79 | 80 | TEST(DISABLED_Voxelizer, TraversalVolumeDouble) { 81 | auto surface = new chunked_voxel_storage(0., 0., 0., 0.1, 200, 100, 100, 32); 82 | 83 | for (int i = 0; i < 2; ++i) { 84 | BRepPrimAPI_MakeBox mb(gp_Pnt(i * 10. + 1., 1., 1.), 8., 8., 8.); 85 | auto vox = voxelizer(mb.Solid(), surface); 86 | vox.Convert(); 87 | } 88 | 89 | // Traversal finds a seed outside of the volumes and after inversion two volumes remain. 90 | traversal_voxel_filler filler; 91 | auto volume = filler(surface); 92 | auto A = surface->count(); 93 | auto B = volume->count(); 94 | 95 | ASSERT_EQ(A, 2 * (81 * 81 * 2 + 81 * 79 * 2 + 79 * 79 * 2)); 96 | ASSERT_EQ(B, 2 * (79 * 79 * 79)); 97 | ASSERT_EQ(A + B, 2 * (81 * 81 * 81)); 98 | 99 | std::cerr << "count: " << A << " + " << B << " = " << (A + B) << std::endl; 100 | 101 | delete surface; 102 | delete volume; 103 | } 104 | 105 | TEST(Voxelizer, TraversalVolumeTriple) { 106 | auto surface = new chunked_voxel_storage(0., 0., 0., 0.1, 300, 100, 100, 32); 107 | 108 | for (int i = 0; i < 3; ++i) { 109 | BRepPrimAPI_MakeBox mb(gp_Pnt(i * 10. + 1., 1., 1.), 8., 8., 8.); 110 | auto vox = voxelizer(mb.Solid(), surface); 111 | vox.Convert(); 112 | } 113 | 114 | // Traversal finds a seed inside one of the volumes and two remain empty 115 | traversal_voxel_filler filler; 116 | auto volume = filler(surface); 117 | auto A = surface->count(); 118 | auto B = volume->count(); 119 | 120 | // NB: 3 Surfaces 1 volume 121 | ASSERT_EQ(A, 3 * (81 * 81 * 2 + 81 * 79 * 2 + 79 * 79 * 2)); 122 | ASSERT_EQ(B, 1 * (79 * 79 * 79)); 123 | 124 | delete surface; 125 | delete volume; 126 | } 127 | 128 | TEST(Voxelizer, TraversalVolumeTripleSeparate) { 129 | auto surface = new chunked_voxel_storage(0., 0., 0., 0.1, 300, 100, 100, 32); 130 | 131 | for (int i = 0; i < 3; ++i) { 132 | BRepPrimAPI_MakeBox mb(gp_Pnt(i * 10. + 1., 1., 1.), 8., 8., 8.); 133 | auto vox = voxelizer(mb.Solid(), surface); 134 | vox.Convert(); 135 | } 136 | 137 | // Traversal is done on separate distinct connected components 138 | traversal_voxel_filler_separate_components filler; 139 | auto volume = filler(surface); 140 | auto A = surface->count(); 141 | auto B = volume->count(); 142 | 143 | ASSERT_EQ(A, 3 * (81 * 81 * 2 + 81 * 79 * 2 + 79 * 79 * 2)); 144 | ASSERT_EQ(B, 3 * (79 * 79 * 79)); 145 | ASSERT_EQ(A + B, 3 * (81 * 81 * 81)); 146 | 147 | delete surface; 148 | delete volume; 149 | } 150 | 151 | TEST(Voxelizer, TraversalVolumeTripleInverted) { 152 | auto surface = new chunked_voxel_storage(0., 0., 0., 0.1, 300, 100, 100, 32); 153 | 154 | for (int i = 0; i < 3; ++i) { 155 | BRepPrimAPI_MakeBox mb(gp_Pnt(i * 10. + 1., 1., 1.), 8., 8., 8.); 156 | auto vox = voxelizer(mb.Solid(), surface); 157 | vox.Convert(); 158 | } 159 | 160 | // Traversal is done on external and then inverted 161 | traversal_voxel_filler_inverse filler; 162 | auto volume = filler(surface); 163 | auto A = surface->count(); 164 | auto B = volume->count(); 165 | 166 | ASSERT_EQ(A, 3 * (81 * 81 * 2 + 81 * 79 * 2 + 79 * 79 * 2)); 167 | ASSERT_EQ(B, 3 * (79 * 79 * 79)); 168 | ASSERT_EQ(A + B, 3 * (81 * 81 * 81)); 169 | 170 | delete surface; 171 | delete volume; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /tests/test_voxelizer.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../writer.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | TEST(Voxelizer, ChunkPrimitives) { 10 | auto storage = new chunked_voxel_storage(0., 0., 0., 0.1, 200, 150, 10, 32); 11 | 12 | { 13 | BRepBuilderAPI_MakePolygon mp(gp_Pnt(1, 1, 0), gp_Pnt(16, 1, 0), gp_Pnt(16, 9.8, 0), gp_Pnt(1, 9.8, 0), true); 14 | BRepBuilderAPI_MakeFace mf(mp.Wire()); 15 | TopoDS_Face face = mf.Face(); 16 | 17 | auto vox = voxelizer(face, storage); 18 | vox.Convert(); 19 | } 20 | 21 | voxel_writer writer; 22 | writer.SetVoxels(storage); 23 | writer.Write("test_voxelizer.vox"); 24 | 25 | return; 26 | 27 | EXPECT_TRUE((storage->num_chunks() == vec_n<3>(4U, 4U, 1U)).all()); 28 | EXPECT_TRUE(storage->get_chunk(make_vec(0U, 0U, 0U))->is_explicit()); 29 | std::array< vec_n<3, size_t>, 2 > out; 30 | storage->chunk_extents(make_vec( 1U,1U,0U), out); 31 | std::cerr << out[0].format() << " - " << out[1].format() << std::endl; 32 | EXPECT_FALSE(storage->get_chunk(make_vec(1U, 1U, 0U))->is_explicit()); 33 | 34 | { 35 | BRepBuilderAPI_MakePolygon mp(gp_Pnt(1, 1, 0.5), gp_Pnt(9.8, 1, 0.5), gp_Pnt(9.8, 9.8, 0.5), gp_Pnt(1, 9.8, 0.5), true); 36 | BRepBuilderAPI_MakeFace mf(mp.Wire()); 37 | TopoDS_Face face = mf.Face(); 38 | 39 | auto vox = voxelizer(face, storage); 40 | vox.Convert(); 41 | } 42 | 43 | EXPECT_TRUE((storage->num_chunks() == vec_n<3>(4U, 4U, 1U)).all()); 44 | EXPECT_TRUE(storage->get_chunk(make_vec(0U, 0U, 0U))->is_explicit()); 45 | EXPECT_FALSE(storage->get_chunk(make_vec(1U, 1U, 0U))->is_explicit()); 46 | EXPECT_FALSE(storage->get_chunk(make_vec(2U, 1U, 0U))->is_explicit()); 47 | 48 | { 49 | BRepBuilderAPI_MakePolygon mp(gp_Pnt(3.2, 3.2, 1.0), gp_Pnt(6.39, 3.2, 1.0), gp_Pnt(6.39, 6.39, 1.0), gp_Pnt(3.2, 6.39, 1.0), true); 50 | BRepBuilderAPI_MakeFace mf(mp.Wire()); 51 | TopoDS_Face face = mf.Face(); 52 | 53 | auto vox = voxelizer(face, storage); 54 | vox.Convert(); 55 | } 56 | 57 | EXPECT_TRUE((storage->num_chunks() == vec_n<3>(4U, 4U, 1U)).all()); 58 | EXPECT_TRUE(storage->get_chunk(make_vec(0U, 0U, 0U))->is_explicit()); 59 | EXPECT_FALSE(storage->get_chunk(make_vec(1U, 1U, 0U))->is_explicit()); 60 | 61 | std::ostringstream ss; 62 | storage->get_chunk(make_vec(1U, 1U, 0U))->write(file_part_primitives, ss); 63 | EXPECT_EQ(ss.str(), "Z=0,Z=5,Z=10"); 64 | 65 | ss.str(""); 66 | EXPECT_FALSE(storage->get_chunk(make_vec(2U, 1U, 0U))->is_explicit()); 67 | storage->get_chunk(make_vec(2U, 1U, 0U))->write(file_part_primitives, ss); 68 | EXPECT_EQ(ss.str(), "Z=0,Z=5"); 69 | 70 | voxel_writer writer2; 71 | writer2.SetVoxels(storage); 72 | writer2.Write("test_voxelizer.vox"); 73 | } -------------------------------------------------------------------------------- /tests/test_wall.cpp: -------------------------------------------------------------------------------- 1 | #include "../voxelizer.h" 2 | #include "../writer.h" 3 | #include "../processor.h" 4 | 5 | #ifdef WITH_IFC 6 | #include 7 | #ifdef IFCOPENSHELL_05 8 | #include 9 | using namespace Ifc2x3; 10 | #else 11 | #include 12 | #endif 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #ifdef WIN32 22 | #define DIRSEP "\\" 23 | #else 24 | #define DIRSEP "/" 25 | #endif 26 | 27 | #if defined(WITH_IFC) && defined(IFCOPENSHELL_05) 28 | TEST(Voxelization, IfcWall) { 29 | const std::string input_filename = ".." DIRSEP "tests" DIRSEP "fixtures" DIRSEP "duplex_wall.ifc"; 30 | 31 | IfcParse::IfcFile ifc_file; 32 | 33 | const double d = 0.02; 34 | 35 | ASSERT_TRUE(ifc_file.Init(input_filename)); 36 | 37 | IfcGeom::IteratorSettings settings_surface; 38 | settings_surface.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true); 39 | 40 | IfcGeom::Kernel kernel; 41 | kernel.initializeUnits(*ifc_file.entitiesByType()->begin()); 42 | 43 | IfcWall* wall = ifc_file.entityByGuid("2O2Fr$t4X7Zf8NOew3FNr2")->as(); 44 | IfcShapeRepresentation* representation = nullptr; 45 | auto reps = ifc_file.traverse(wall)->as(); 46 | for (auto& rep : *reps) { 47 | if (rep->RepresentationIdentifier() == "Body") { 48 | representation = rep; 49 | } 50 | } 51 | 52 | ASSERT_NE(representation, nullptr); 53 | 54 | auto elem = kernel.create_brep_for_representation_and_product(settings_surface, representation, wall); 55 | 56 | const gp_Trsf& product_trsf = elem->transformation().data(); 57 | Bnd_Box global_bounds; 58 | TopoDS_Compound compound; 59 | BRep_Builder builder; 60 | builder.MakeCompound(compound); 61 | 62 | for (IfcGeom::IfcRepresentationShapeItems::const_iterator it = elem->geometry().begin(); it != elem->geometry().end(); ++it) { 63 | const TopoDS_Shape& s = it->Shape(); 64 | gp_GTrsf trsf = it->Placement(); 65 | const TopoDS_Shape moved_shape_ = IfcGeom::Kernel::apply_transformation(s, trsf); 66 | const TopoDS_Shape moved_shape = IfcGeom::Kernel::apply_transformation(moved_shape_, product_trsf); 67 | BRepBndLib::Add(moved_shape, global_bounds); 68 | builder.Add(compound, moved_shape); 69 | } 70 | 71 | BRepMesh_IncrementalMesh(compound, 0.001); 72 | 73 | geometry_collection_t geoms{ {wall->id(), compound} }; 74 | 75 | double x1, y1, z1, x2, y2, z2; 76 | global_bounds.Get(x1, y1, z1, x2, y2, z2); 77 | int nx = (int)ceil((x2 - x1) / d) + 10; 78 | int ny = (int)ceil((y2 - y1) / d) + 10; 79 | int nz = (int)ceil((z2 - z1) / d) + 10; 80 | 81 | x1 -= d * 5; 82 | y1 -= d * 5; 83 | z1 -= d * 5; 84 | 85 | { 86 | progress_writer progress_1("test_surface"); 87 | processor pr(x1, y1, z1, d, nx, ny, nz, 64, progress_1); 88 | pr.process(geoms.begin(), geoms.end(), SURFACE(), output(MERGED(), "test_wall_surface.vox")); 89 | } 90 | { 91 | progress_writer progress_1("test_volume"); 92 | processor pr(x1, y1, z1, d, nx, ny, nz, 64, progress_1); 93 | pr.process(geoms.begin(), geoms.end(), VOLUME(), output(MERGED(), "test_wall_volume.vox")); 94 | } 95 | } 96 | #else 97 | TEST(Voxelization, DISABLED_IfcWall) {} 98 | #endif -------------------------------------------------------------------------------- /tribox3.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IfcOpenShell/voxelization_toolkit/acf021e3b5bff4e03814dee060fcc3a674db1c84/tribox3.cpp -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | 6 | template 7 | T integer_ceil_div(T a, T b) { 8 | return a / b + (a % b != 0); 9 | } 10 | 11 | template 12 | T integer_floor_div(T a, T b) { 13 | // @todo probably can be optimized. 14 | return a > 0 ? (a / b) : (a / b - (a % b != 0)); 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /volume.h: -------------------------------------------------------------------------------- 1 | #ifndef VOLUME_H 2 | #define VOLUME_H 3 | 4 | #include "storage.h" 5 | 6 | #if 0 7 | class volume_filler { 8 | private: 9 | regular_voxel_storage * storage_; 10 | bool* filled_chunks_; 11 | vec_n<3, size_t> num_chunks_; 12 | const std::array, 2>& extents_; 13 | 14 | public: 15 | volume_filler(regular_voxel_storage* storage) 16 | : storage_(storage) 17 | , filled_chunks_(nullptr) 18 | , extents_(storage->bounds()) 19 | { 20 | chunked_voxel_storage* cvs; 21 | if ((cvs = dynamic_cast*>(storage))) { 22 | num_chunks_ = storage_->extents().ceil_div(cvs->chunk_size()); 23 | size_t n = num_chunks_.get<0>() * num_chunks_.get<1>() * num_chunks_.get<2>(); 24 | filled_chunks_ = new bool[n] {false}; 25 | } 26 | } 27 | 28 | ~volume_filler() { 29 | delete[] filled_chunks_; 30 | } 31 | 32 | bool& filled_chunk(const vec_n<3, size_t>& c) { 33 | return filled_chunks_[c.get(0) + (num_chunks_.get(0) * c.get(1)) + (num_chunks_.get(0) * num_chunks_.get(1) * c.get(2))]; 34 | } 35 | 36 | void fill() { 37 | std::vector> constant_blocks_to_create; 38 | 39 | chunked_voxel_storage* cvs; 40 | if ((cvs = dynamic_cast*>(storage_))) { 41 | auto cs = cvs->empty_chunks(); 42 | for (auto& c : cs) { 43 | std::array< vec_n<3, size_t>, 2 > cext; 44 | cvs->chunk_extents(c, cext); 45 | if (storage_->ray_intersect_n(cext[0], make_vec(0U,0U,1U)) % 2 == 1) { 46 | constant_blocks_to_create.push_back(c); 47 | filled_chunk(c) = true; 48 | } 49 | } 50 | } 51 | 52 | size_t i0, i1, j0, j1, k0, k1; 53 | extents_[0].tie(i0, j0, k0); 54 | extents_[1].tie(i1, j1, k1); 55 | 56 | vec_n<3, size_t> ijk; 57 | 58 | for (size_t i = i0; i <= i1; ++i) { 59 | ijk.get(0) = i; 60 | for (size_t j = j0; j <= j1; ++j) { 61 | ijk.get(1) = j; 62 | bool inside = false; 63 | bool set0 = false; 64 | bool set1 = false; 65 | for (size_t k = k0; k <= k1;) { 66 | ijk.get(2) = k; 67 | if (filled_chunk(ijk / cvs->chunk_size())) { 68 | k += cvs->chunk_size(); 69 | } else { 70 | bool set2 = storage_->Get(ijk); 71 | if (!set2 && set1 && !set0) { 72 | inside = !inside; 73 | } 74 | if (inside) { 75 | storage_->Set(ijk); 76 | } 77 | set0 = set1; 78 | set1 = set2; 79 | ++k; 80 | } 81 | } 82 | } 83 | } 84 | 85 | for (size_t k = k0; k <= k1; ++k) { 86 | ijk.get(2) = k; 87 | for (size_t j = j0; j <= j1; ++j) { 88 | ijk.get(1) = j; 89 | bool inside = false; 90 | bool set0 = false; 91 | bool set1 = false; 92 | for (size_t i = i0; i <= i1;) { 93 | ijk.get(0) = i; 94 | if (filled_chunk(ijk / cvs->chunk_size())) { 95 | i += cvs->chunk_size(); 96 | } else { 97 | bool set2 = storage_->Get(ijk); 98 | if (!set2 && set1 && !set0) { 99 | inside = !inside; 100 | } 101 | if (inside) { 102 | storage_->Set(ijk); 103 | } 104 | set0 = set1; 105 | set1 = set2; 106 | ++i; 107 | } 108 | } 109 | } 110 | } 111 | 112 | for (size_t k = k0; k <= k1; ++k) { 113 | ijk.get(2) = k; 114 | for (size_t i = i0; i <= i1; ++i) { 115 | ijk.get(0) = i; 116 | bool inside = false; 117 | bool set0 = false; 118 | bool set1 = false; 119 | for (size_t j = j0; j <= j1;) { 120 | ijk.get(1) = j; 121 | if (filled_chunk(ijk / cvs->chunk_size())) { 122 | j += cvs->chunk_size(); 123 | } else { 124 | bool set2 = storage_->Get(ijk); 125 | if (!set2 && set1 && !set0) { 126 | inside = !inside; 127 | } 128 | if (inside) { 129 | storage_->Set(ijk); 130 | } 131 | set0 = set1; 132 | set1 = set2; 133 | ++j; 134 | } 135 | } 136 | } 137 | } 138 | 139 | for (auto& c : constant_blocks_to_create) { 140 | cvs->create_constant(c, 1); 141 | } 142 | } 143 | }; 144 | #endif 145 | 146 | #endif -------------------------------------------------------------------------------- /voxec_main.cpp: -------------------------------------------------------------------------------- 1 | #include "voxec.h" 2 | #include "json_logger.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace po = boost::program_options; 12 | 13 | int main(int argc, char** argv) { 14 | std::unique_ptr log_file; 15 | 16 | po::options_description opts("Command line options"); 17 | opts.add_options() 18 | ("quiet,q", "limit output on stdout to progress indication") 19 | ("log-file", po::value(), "filename to log json message") 20 | ("threads,t", po::value(), "number of parallel processing threads") 21 | ("size,d", po::value(), "voxel size in meters") 22 | ("padding,p", po::value(), "padding around geometry bounding box (default=32)") 23 | ("chunk,c", po::value(), "chunk size in number of voxels") 24 | ("mmap,m", "use memory-mapped files instead of pure RAM") 25 | ("mesh", "emit obj mesh for the last instruction") 26 | ("with-pointcloud", "emit point cloud for every voxel grid variable") 27 | ("no-vox", "do not write binary dumps") 28 | ("help,h", "emit usage information and exit") 29 | ("input-file", po::value(), "input IFC file") 30 | ("output-file", po::value(), "output voxel file"); 31 | 32 | po::positional_options_description positional_options; 33 | 34 | positional_options.add("input-file", 1); 35 | positional_options.add("output-file", 1); 36 | 37 | po::variables_map vmap; 38 | try { 39 | po::store(po::command_line_parser(argc, argv). 40 | options(opts).positional(positional_options).run(), vmap); 41 | } catch (const std::exception& e) { 42 | json_logger::message(json_logger::LOG_FATAL, "invalid command line {error}", { 43 | {"error", {{"message", std::string(e.what())}}} 44 | }); 45 | return 1; 46 | } 47 | 48 | if (vmap.count("help")) { 49 | std::cout << "voxec: Voxelization Toolkit Execution Runtime" << std::endl; 50 | std::cout << opts << std::endl << std::endl; 51 | std::cout << "Available commands:" << std::endl << std::endl; 52 | auto& m = voxel_operation_map::map(); 53 | for (auto& p : m) { 54 | voxel_operation* op = voxel_operation_map::create(p.first); 55 | std::cout << "### " << p.first << "()" << std::endl << std::endl; 56 | std::cout << "name|required|type" << std::endl; 57 | std::cout << "---|---|---" << std::endl; 58 | for (auto& spec : op->arg_names()) { 59 | std::cout << spec.name << "|" << (spec.required ? "Y" : "n") << "|" << boost::replace_all_copy(spec.type, "|", ",") << std::endl; 60 | } 61 | std::cout << std::endl << std::endl; 62 | } 63 | return 0; 64 | } 65 | 66 | if (!vmap.count("input-file")) { 67 | json_logger::message(json_logger::LOG_FATAL, "input file not specified"); 68 | return 1; 69 | } 70 | 71 | double d = 0.05; 72 | 73 | if (!vmap.count("size")) { 74 | json_logger::message(json_logger::LOG_NOTICE, "using default size 0.05m"); 75 | } else { 76 | d = vmap["size"].as(); 77 | } 78 | 79 | if (vmap.count("padding")) { 80 | set_padding(vmap["padding"].as()); 81 | } 82 | 83 | boost::optional threads, chunk; 84 | 85 | if (vmap.count("threads")) { 86 | threads = vmap["threads"].as(); 87 | } 88 | 89 | if (vmap.count("chunk")) { 90 | chunk = vmap["chunk"].as(); 91 | } 92 | 93 | if (vmap.count("log-file")) { 94 | const std::string log_filename = vmap["log-file"].as(); 95 | log_file = std::make_unique(log_filename.c_str()); 96 | json_logger::register_output(json_logger::FMT_JSON, &*log_file); 97 | } 98 | 99 | const std::string input_filename = vmap["input-file"].as(); 100 | const bool with_mesh = vmap.count("mesh") != 0; 101 | const bool quiet = vmap.count("quiet") != 0; 102 | const bool no_vox = vmap.count("no-vox") != 0; 103 | const bool with_pointcloud = vmap.count("with-pointcloud") != 0; 104 | 105 | if (!quiet) { 106 | json_logger::register_output(json_logger::FMT_TEXT, &std::cerr); 107 | } 108 | 109 | std::ifstream ifs(input_filename.c_str(), std::ios::binary); 110 | if (!ifs.good()) { 111 | json_logger::message(json_logger::LOG_FATAL, "unable to open file {file}", { 112 | {"file", {{"text", input_filename}}} 113 | }); 114 | return 1; 115 | } 116 | ifs.seekg(0, ifs.end); 117 | size_t size = ifs.tellg(); 118 | ifs.seekg(0, ifs.beg); 119 | ifs >> std::noskipws; 120 | 121 | boost::spirit::istream_iterator first(ifs), last; 122 | auto f = first; 123 | 124 | voxelfile_parser parser; 125 | std::vector tree; 126 | phrase_parse(first, last, parser, blank, tree); 127 | 128 | if (std::distance(f, first) != size) { 129 | json_logger::message(json_logger::LOG_FATAL, "parse errors in voxelfile at {offset}", { 130 | {"offset", {{"value", (long) std::distance(f, first)}}} 131 | }); 132 | return 1; 133 | } 134 | 135 | boost::optional error; 136 | 137 | try { 138 | run(tree, d, threads.get_value_or(1), chunk.get_value_or(128), with_mesh, quiet, no_vox, with_pointcloud); 139 | } catch (const std::runtime_error& e) { 140 | error = std::string(e.what()); 141 | } catch (const Standard_Failure& e) { 142 | if (e.GetMessageString()) { 143 | error = std::string(e.GetMessageString()); 144 | } else { 145 | error.emplace(); 146 | } 147 | } catch (...) { 148 | error.emplace(); 149 | } 150 | 151 | if (error) { 152 | json_logger::message(json_logger::LOG_FATAL, "encountered {error} while running voxelfile", { 153 | {"error", {{"message", *error}}} 154 | }); 155 | } 156 | 157 | return error ? 1 : 0; 158 | } -------------------------------------------------------------------------------- /voxelfile.h: -------------------------------------------------------------------------------- 1 | #ifndef VOXELFILE_H 2 | #define VOXELFILE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace boost::spirit::qi; 10 | 11 | using boost::spirit::int_; 12 | using boost::spirit::qi::grammar; 13 | using boost::spirit::qi::rule; 14 | 15 | typedef boost::variant > function_arg_value_type; 16 | 17 | class function_arg_type : public boost::fusion::vector, function_arg_value_type> { 18 | public: 19 | bool has_keyword() const { 20 | return !!boost::fusion::at_c<0>(*this); 21 | } 22 | const boost::optional& keyword() const { 23 | return boost::fusion::at_c<0>(*this); 24 | } 25 | const function_arg_value_type& value() const { 26 | return boost::fusion::at_c<1>(*this); 27 | } 28 | }; 29 | 30 | class function_call_type : public boost::fusion::vector > { 31 | public: 32 | const std::string& name() const { 33 | return boost::fusion::at_c<0>(*this); 34 | } 35 | const std::vector& args() const { 36 | return boost::fusion::at_c<1>(*this); 37 | } 38 | }; 39 | 40 | class statement_type : public boost::fusion::vector { 41 | public: 42 | const std::string& assignee() const { 43 | return boost::fusion::at_c<0>(*this); 44 | } 45 | const function_call_type& call() const { 46 | return boost::fusion::at_c<1>(*this); 47 | } 48 | }; 49 | 50 | class function_def_type : public boost::fusion::vector, std::vector, std::string> { 51 | public: 52 | const std::string& name() const { 53 | return boost::fusion::at_c<0>(*this); 54 | } 55 | const std::vector& args() const { 56 | return boost::fusion::at_c<1>(*this); 57 | } 58 | const std::vector& statements() const { 59 | return boost::fusion::at_c<2>(*this); 60 | } 61 | const std::string& result_identifier() const { 62 | return boost::fusion::at_c<3>(*this); 63 | } 64 | }; 65 | 66 | typedef boost::variant statement_or_function_def; 67 | 68 | std::ostream& operator<<(std::ostream& a, const function_call_type& b); 69 | 70 | std::ostream& operator<<(std::ostream& a, const statement_type& b); 71 | 72 | template 73 | struct voxelfile_parser : grammar(), blank_type> { 74 | 75 | voxelfile_parser() : grammar(), blank_type>(start) { 76 | iden = lexeme[(alpha >> *(alnum | char_('_')))]; 77 | statement = -hold[iden >> '='] >> function_call >> eol >> *eol; 78 | quoted_string = char_('"') >> *(char_ - '"') >> char_('"'); 79 | // @todo only sequence of strings for now 80 | sequence = '{' >> quoted_string % ',' >> '}'; 81 | value = strict_double | int_ | quoted_string | iden | sequence; 82 | function_arg = -hold[iden >> '='] >> value; 83 | function_args = function_arg % ','; 84 | function_call = iden >> '(' >> function_args >> ')'; 85 | argument_list = iden % ','; 86 | function_def = "function" >> iden >> '(' >> argument_list >> ')' >> eol >> *eol >> *statement >> "return" >> -hold[iden] >> eol >> *eol; 87 | start = *(statement | function_def); 88 | } 89 | 90 | real_parser> strict_double; 91 | rule value; 92 | rule function_arg; 93 | rule(), blank_type> sequence; 94 | rule(), blank_type> function_args; 95 | rule function_call; 96 | rule statement; 97 | rule iden, quoted_string; 98 | rule(), blank_type> argument_list; 99 | rule function_def; 100 | 101 | typename voxelfile_parser::start_type start; 102 | }; 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /writer.h: -------------------------------------------------------------------------------- 1 | #ifndef WRITER_H 2 | #define WRITER_H 3 | 4 | #include "storage.h" 5 | 6 | #include 7 | #include 8 | 9 | class voxel_writer { 10 | private: 11 | abstract_voxel_storage* voxels_; 12 | 13 | std::ofstream& assert_good_(const std::string& fn, std::ofstream& fs) { 14 | if (!fs.good()) { 15 | throw std::runtime_error("Unable to open file for writing. " + fn); 16 | } 17 | return fs; 18 | } 19 | 20 | public: 21 | void SetVoxels(abstract_voxel_storage* voxels) { 22 | voxels_ = voxels; 23 | } 24 | 25 | void Write(const std::string& fnc) { 26 | { 27 | std::string fn = fnc + std::string(".index"); 28 | std::ofstream fs(fn.c_str()); 29 | voxels_->write(file_part_index, assert_good_(fn, fs)); 30 | } 31 | { 32 | std::string fn = fnc + std::string(".contents"); 33 | std::ofstream fs(fn.c_str(), std::ios::binary); 34 | voxels_->write(file_part_contents, assert_good_(fn, fs)); 35 | } 36 | { 37 | std::string fn = fnc + std::string(".meta"); 38 | std::ofstream fs(fn.c_str()); 39 | voxels_->write(file_part_meta, assert_good_(fn, fs)); 40 | } 41 | { 42 | std::string fn = fnc + std::string(".primitives"); 43 | std::ofstream fs(fn.c_str()); 44 | voxels_->write(file_part_primitives, assert_good_(fn, fs)); 45 | } 46 | } 47 | }; 48 | 49 | #endif 50 | --------------------------------------------------------------------------------