diff --git a/.github/workflows/build_osx.yml b/.github/workflows/build_osx.yml index 0f6bb702e9..4c6cb9312c 100644 --- a/.github/workflows/build_osx.yml +++ b/.github/workflows/build_osx.yml @@ -53,7 +53,7 @@ jobs: python ../nix/cache_dependencies.py unpack - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: mac-${{ matrix.arch }} diff --git a/.github/workflows/build_pyodide.yml b/.github/workflows/build_pyodide.yml index 7da3bb408c..a5e80a32df 100644 --- a/.github/workflows/build_pyodide.yml +++ b/.github/workflows/build_pyodide.yml @@ -29,7 +29,7 @@ jobs: python ../IfcOpenShell/nix/cache_dependencies.py unpack - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: ubuntu-22.04-${{ runner.arch }} diff --git a/.github/workflows/build_rocky.yml b/.github/workflows/build_rocky.yml index 710b918564..9ac489378b 100644 --- a/.github/workflows/build_rocky.yml +++ b/.github/workflows/build_rocky.yml @@ -48,7 +48,7 @@ jobs: python3 ../nix/cache_dependencies.py unpack - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: ubuntu-22.04-${{ runner.arch }}-rockylinux9 diff --git a/.github/workflows/build_rocky_arm.yml b/.github/workflows/build_rocky_arm.yml index b54e62ccef..208e1da9da 100644 --- a/.github/workflows/build_rocky_arm.yml +++ b/.github/workflows/build_rocky_arm.yml @@ -48,7 +48,7 @@ jobs: python3 ../nix/cache_dependencies.py unpack - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: ubuntu-22.04-${{ runner.arch }}-rockylinux9 diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index 783084c1ae..7880296a2a 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -5,11 +5,26 @@ on: jobs: build_ifcopenshell: - runs-on: windows-2022 strategy: fail-fast: false matrix: - arch: ['x64'] + include: + - arch: x64 + runs_on: windows-2022 + deps_dir: _deps-vs2022-x64-installed + vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"' + build_branch: windows-x64 + zip_suffix: win64 + + - arch: ARM64 + runs_on: windows-11-arm + deps_dir: _deps-vs2022-ARM64-installed + vcvars: '"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsarm64.bat"' + build_branch: windows-arm64 + zip_suffix: win-arm64 + + runs-on: ${{ matrix.runs_on }} + steps: - name: Checkout Repository uses: actions/checkout@v6 @@ -20,8 +35,8 @@ jobs: uses: actions/checkout@v6 with: repository: IfcOpenShell/build-outputs - path: _deps-vs2022-x64-installed - ref: windows-${{ matrix.arch }} + path: ${{ matrix.deps_dir }} + ref: ${{ matrix.build_branch }} lfs: true token: ${{ secrets.BUILD_REPO_TOKEN }} @@ -31,13 +46,13 @@ jobs: - name: Unpack Dependencies run: | - cd _deps-vs2022-x64-installed + cd ${{ matrix.deps_dir }} Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object { 7z x $_.FullName } - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: win-${{ matrix.arch }} # Windows ccache needs ~1GB @@ -46,14 +61,16 @@ jobs: - name: Run Build Script And Pack .zip Archives shell: cmd + env: + TARGET_ARCH: ${{ matrix.arch }} # lets the Python script know which arch to target (optional override) run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + call ${{ matrix.vcvars }} cd win python build-all-win.py - name: Pack Dependencies run: | - cd _deps-vs2022-x64-installed + cd ${{ matrix.deps_dir }} Get-ChildItem -Path . -Directory | ForEach-Object { $cacheFile = "cache-$($_.Name).zip" echo $cacheFile @@ -64,12 +81,13 @@ jobs: - name: Commit and Push Changes to Build Repository run: | - cd _deps-vs2022-x64-installed + cd ${{ matrix.deps_dir }} git config user.name "IfcOpenBot" git config user.email "ifcopenbot@ifcopenshell.org" + git checkout -B ${{ matrix.build_branch }} git add *.zip git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit" - git push || echo "Push failed" + git push --set-upstream origin ${{ matrix.build_branch }} || echo "Push failed" - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v6 diff --git a/.github/workflows/ci-black-formatting.yaml b/.github/workflows/ci-black-formatting.yaml index 90a0b95431..f081f02945 100644 --- a/.github/workflows/ci-black-formatting.yaml +++ b/.github/workflows/ci-black-formatting.yaml @@ -7,6 +7,9 @@ on: jobs: lint-formatting: runs-on: ubuntu-latest + env: + MIN_IOS_PY_VERSION: "3.10" + MIN_BLENDER_PY_VERSION: "3.11" steps: - name: Action - checkout repository uses: actions/checkout@v6 @@ -14,12 +17,12 @@ jobs: - name: Action - install python uses: actions/setup-python@v6 with: - python-version: "3.10" + python-version: ${{ env.MIN_IOS_PY_VERSION }} - name: Action - install python uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: ${{ env.MIN_BLENDER_PY_VERSION }} - name: Install dependencies run: | @@ -33,8 +36,10 @@ jobs: id: syntax-errors run: | ERROR=0 - python3.10 -W error -m compileall -q src/ifcopenshell-python || ERROR=1 - python3.11 -W error -m compileall -q src/bonsai || ERROR=1 + # Using 2 Python versions - one minimum required for IfcOpenShell + # and other that's used by Blender currently. + python${{ env.MIN_IOS_PY_VERSION }} -W error -m compileall -q src/ifcopenshell-python || ERROR=1 + python${{ env.MIN_BLENDER_PY_VERSION }} -W error -m compileall -q src/bonsai || ERROR=1 exit $ERROR continue-on-error: true diff --git a/.github/workflows/ci-bonsai-daily.yml b/.github/workflows/ci-bonsai-daily.yml index ddfa64a47a..68b6ac2dbb 100644 --- a/.github/workflows/ci-bonsai-daily.yml +++ b/.github/workflows/ci-bonsai-daily.yml @@ -109,7 +109,7 @@ jobs: # Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo. # Download Blender. - wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.0.1-linux-x64.tar.xz + wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.0/blender-5.1.0-linux-x64.tar.xz tar -xf blender.tar.xz # Setup Blender. diff --git a/.github/workflows/ci-ifcedit-pypi.yaml b/.github/workflows/ci-ifcedit-pypi.yaml new file mode 100644 index 0000000000..d961c72f56 --- /dev/null +++ b/.github/workflows/ci-ifcedit-pypi.yaml @@ -0,0 +1,35 @@ +name: ci-ifcedit-pypi + +on: + workflow_dispatch: + +jobs: + activate: + runs-on: ubuntu-latest + if: | + github.repository == 'IfcOpenShell/IfcOpenShell' + steps: + - name: Set env + run: echo ok go + + build: + needs: activate + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + - name: Compile + run: | + pip install build + cd src/ifcedit && + make dist IS_STABLE=TRUE + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: src/ifcedit/dist diff --git a/.github/workflows/ci-ifcmcp-pypi.yaml b/.github/workflows/ci-ifcmcp-pypi.yaml new file mode 100644 index 0000000000..388c10217a --- /dev/null +++ b/.github/workflows/ci-ifcmcp-pypi.yaml @@ -0,0 +1,36 @@ +name: ci-ifcmcp-pypi + +on: + workflow_dispatch: + +jobs: + activate: + runs-on: ubuntu-latest + if: | + github.repository == 'IfcOpenShell/IfcOpenShell' + steps: + - name: Set env + run: echo ok go + + build: + needs: activate + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + - name: Compile + run: | + pip install build + cd src/ifcmcp && + make dist IS_STABLE=TRUE + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: src/ifcmcp/dist + verbose: true diff --git a/.github/workflows/ci-ifcopenshell-conda-cleaner.yml b/.github/workflows/ci-ifcopenshell-conda-cleaner.yml index 5c1b80bcc6..c34cf50e0f 100644 --- a/.github/workflows/ci-ifcopenshell-conda-cleaner.yml +++ b/.github/workflows/ci-ifcopenshell-conda-cleaner.yml @@ -24,7 +24,7 @@ jobs: if: | github.repository == 'IfcOpenShell/IfcOpenShell' steps: - - uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba + - uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba with: environment-name: test-env create-args: >- diff --git a/.github/workflows/ci-ifcopenshell-conda-daily.yml b/.github/workflows/ci-ifcopenshell-conda-daily.yml index b648538ed7..3d18316963 100644 --- a/.github/workflows/ci-ifcopenshell-conda-daily.yml +++ b/.github/workflows/ci-ifcopenshell-conda-daily.yml @@ -84,7 +84,7 @@ jobs: run: | curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/ - - uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba + - uses: mamba-org/setup-micromamba@v3 # https://github.com/mamba-org/setup-micromamba with: environment-name: test-env create-args: >- diff --git a/.github/workflows/ci-ifcopenshell-docker.yml b/.github/workflows/ci-ifcopenshell-docker.yml index e10be5a18b..df672139b8 100644 --- a/.github/workflows/ci-ifcopenshell-docker.yml +++ b/.github/workflows/ci-ifcopenshell-docker.yml @@ -35,7 +35,7 @@ jobs: - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 - name: Build ifcopenshell @@ -91,26 +91,26 @@ jobs: lfs: true - name: Download - uses: actions/download-artifact@v8.0.0 + uses: actions/download-artifact@v8.0.1 with: # Artifact name name: ifcos-artifacts path: artifacts/ - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to Dockerhub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: aecgeeks password: ${{ secrets.DOCKER_HUB_TOKEN }} - name: Build container image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: artifacts repository: aecgeeks/ifcopenshell diff --git a/.github/workflows/ci-ifcopenshell-python-pypi.yml b/.github/workflows/ci-ifcopenshell-python-pypi.yml index 526aa09b64..7988a0bc6a 100644 --- a/.github/workflows/ci-ifcopenshell-python-pypi.yml +++ b/.github/workflows/ci-ifcopenshell-python-pypi.yml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - pyver: [py39, py310, py311, py312, py313, py314] + pyver: [py310, py311, py312, py313, py314] config: - { name: "Windows 64bit", diff --git a/.github/workflows/ci-ifcopenshell-python.yml b/.github/workflows/ci-ifcopenshell-python.yml index 5d34335ada..fe99f4857b 100644 --- a/.github/workflows/ci-ifcopenshell-python.yml +++ b/.github/workflows/ci-ifcopenshell-python.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - pyver: [py39, py310, py311, py312, py313, py314] + pyver: [py310, py311, py312, py313, py314] config: - { name: "Windows 64bit", diff --git a/.github/workflows/ci-ifcquery-pypi.yaml b/.github/workflows/ci-ifcquery-pypi.yaml new file mode 100644 index 0000000000..9dc39a9304 --- /dev/null +++ b/.github/workflows/ci-ifcquery-pypi.yaml @@ -0,0 +1,35 @@ +name: ci-ifcquery-pypi + +on: + workflow_dispatch: + +jobs: + activate: + runs-on: ubuntu-latest + if: | + github.repository == 'IfcOpenShell/IfcOpenShell' + steps: + - name: Set env + run: echo ok go + + build: + needs: activate + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.11' + - name: Compile + run: | + pip install build + cd src/ifcquery && + make dist IS_STABLE=TRUE + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + packages_dir: src/ifcquery/dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 896a179758..a49c92fcd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: libhdf5-dev libcgal-dev libeigen3-dev - name: ccache - uses: hendrikmuhs/ccache-action@v1.2.20 + uses: hendrikmuhs/ccache-action@v1.2.22 with: key: ubuntu-22.04-${{ runner.arch }} @@ -181,6 +181,7 @@ jobs: "-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \ -DGLTF_SUPPORT=On \ -DWITH_ROCKSDB=On \ + -DBUILD_EXAMPLES=ON \ ../cmake sudo make -j $(nproc) sudo make install @@ -253,6 +254,7 @@ jobs: cd ../ifcpatch && make test || ERROR=1 pip install -e ../ifctester --no-deps cd ../ifctester && make test || ERROR=1 + make build-ids-docs || ERROR=1 # Run mathutils related tests at the end to ensure no other code is relying on mathutils. cd ../ifcopenshell-python pip install mathutils diff --git a/.github/workflows/publish-aichat-app.yaml b/.github/workflows/publish-aichat-app.yaml new file mode 100644 index 0000000000..776781428e --- /dev/null +++ b/.github/workflows/publish-aichat-app.yaml @@ -0,0 +1,65 @@ +name: Deploy AI chat App to static page repo + +permissions: + id-token: write + pages: write + +on: + push: + paths: + - 'src/ifcchat/**' + - '.github/workflows/publish-aichat-app.yaml' + branches: + - v0.8.0 + workflow_dispatch: + +jobs: + activate: + runs-on: ubuntu-latest + if: | + github.repository == 'IfcOpenShell/IfcOpenShell' + steps: + - name: Set env + run: echo ok go + + build: + needs: activate + runs-on: ubuntu-latest + steps: + - name: Checkout (recursive) + uses: actions/checkout@v6 + with: + submodules: recursive + fetch-depth: 0 + - name: Checkout intermediate Pages repo + uses: actions/checkout@v4 + with: + repository: IfcOpenShell/aichat_ifcopenshell_org_static_html + ref: gh-pages + path: output + token: ${{ secrets.WEBSITE_PUBLISH }} + - name: Sync demo app into target subfolder + run: | + rsync -av --delete --exclude='.git/' src/ifcchat/ output/ + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Download wheels + working-directory: output/ + run: | + pip download ifcquery==0.8.5 ifcopenshell-mcp==0.8.5 ifcedit==0.8.5 lark==1.3.1 isodate==0.7.2 --no-deps -d ./dist + - name: Commit and push if changed + working-directory: output + run: | + git config --global user.name 'IfcOpenBot' + git config --global user.email 'IfcOpenBot@users.noreply.github.com' + + git add . + if git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + + git commit -m "$(git log --oneline -1)" + git push origin gh-pages diff --git a/.github/workflows/publish-pyodide-demo-app.yml b/.github/workflows/publish-pyodide-demo-app.yml index 4aebe4266e..9f4c3ddbf8 100644 --- a/.github/workflows/publish-pyodide-demo-app.yml +++ b/.github/workflows/publish-pyodide-demo-app.yml @@ -1,4 +1,4 @@ -name: Deploy Pyodide Demo App to GitHub Pages +name: Deploy Pyodide Demo App to static page repo permissions: id-token: write @@ -11,6 +11,7 @@ on: - '.github/workflows/publish-pyodide-demo-app.yml' branches: - v0.8.0 + workflow_dispatch: jobs: activate: @@ -30,21 +31,27 @@ jobs: with: submodules: recursive fetch-depth: 0 - - name: Setup Pages - uses: actions/configure-pages@v5 - - name: Upload static files as artifact - id: deployment - uses: actions/upload-pages-artifact@v4 + - name: Checkout intermediate Pages repo + uses: actions/checkout@v4 with: - path: src/pyodide/demo-app/ + repository: IfcOpenShell/wasm_ifcopenshell_org_static_html + ref: gh-pages + path: output + token: ${{ secrets.WEBSITE_PUBLISH }} + - name: Sync demo app into target subfolder + run: | + rsync -av --delete --exclude='.git/' src/pyodide/demo-app/ output/ + - name: Commit and push if changed + working-directory: output + run: | + git config --global user.name 'IfcOpenBot' + git config --global user.email 'IfcOpenBot@users.noreply.github.com' - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + git add . + if git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + + git commit -m "$(git log --oneline -1)" + git push origin gh-pages diff --git a/.gitignore b/.gitignore index 8a7482ab8e..80fbb01a00 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ /_installed-vs*-x*/ /build/ /src/examples/build/ +# ifctester docs output +/src/ifctester/test/build/ # output directories /cmake/out/ @@ -12,6 +14,7 @@ /src/ifcmax/out/ /src/ifcwrap/out/ /src/qtviewer/out/ +/src/ifctester/webapp/public/pyodide/ /win/BuildDepsCache*.txt @@ -80,10 +83,14 @@ src/ifcopenshell-python/test/build # bonsai i18n src/bonsai/bonsai/translations.py -# bonsai test temp files +# bonsai external dependencies (cloned for just ty checks) +src/bonsai/external_dependencies/ + +# bonsai test temp/cache files src/bonsai/test/files/temp -src/bonsai/test/files/basic.ifc.cache.blend -src/bonsai/test/files/basic.ifc.cache.sqlite +src/bonsai/test/files/*.cache.blend +src/bonsai/test/files/*.cache.json +src/bonsai/test/files/*.cache.sqlite # bonsai data src/bonsai/bonsai/bim/data/build/ @@ -115,3 +122,9 @@ dev_environment.bat src/ifcopenshell-python/ifcopenshell/express/*.exp src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat + + +# temp files from AI coding tools +*.claude +*.py.tmp* +*.json.tmp* diff --git a/README.md b/README.md index 6b77a04833..74c2ebe115 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,13 @@ Contents | [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcconvert/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcconvert-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true) | [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifccsv?label=PyPI&color=006dad)](https://pypi.org/project/ifccsv/) | | [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcdiff?label=PyPI&color=006dad)](https://pypi.org/project/ifcdiff/) | +| [ifcedit](https://docs.ifcopenshell.org/ifcedit.html) | CLI wrapper for ifcopenshell.api IFC model mutation functions | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcedit?label=PyPI&color=006dad)](https://pypi.org/project/ifcedit/) | | [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcfm?label=PyPI&color=006dad)](https://pypi.org/project/ifcfm/) | | [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcmax.html) +| [ifcmcp](https://docs.ifcopenshell.org/ifcmcp.html) | MCP server for querying and editing IFC building models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcmcp?label=PyPI&color=006dad)](https://pypi.org/project/ifcmcp/) | | [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [![Official](https://img.shields.io/badge/IfcOpenShell.org-Download-70ba35)](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [![GitHub](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcopenshell-python-*&label=GitHub&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [![PyPI](https://img.shields.io/pypi/v/ifcopenshell?label=PyPI&color=006dad)](https://pypi.org/project/ifcopenshell/) [![Anaconda](https://img.shields.io/conda/vn/conda-forge/ifcopenshell?label=Anaconda&color=43b02a)](https://anaconda.org/conda-forge/ifcopenshell) [![Anaconda](https://img.shields.io/conda/vn/ifcopenshell/ifcopenshell?label=Anaconda-Unstable&color=43b02a)](https://anaconda.org/ifcopenshell/ifcopenshell) [![Docker](https://img.shields.io/docker/pulls/aecgeeks/ifcopenshell?label=Docker&color=1D63ED)](https://hub.docker.com/r/aecgeeks/ifcopenshell) [![AUR](https://img.shields.io/aur/version/ifcopenshell?label=AUR&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell) [![AUR Unstable](https://img.shields.io/aur/version/ifcopenshell-git?label=AUR-Unstable&color=1793d1)](https://aur.archlinux.org/packages/ifcopenshell-git) [Pyodide WASM Wheels](https://github.com/IfcOpenShell/wasm-wheels#pyodide-test-wheels) | | [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcpatch?label=PyPI&color=006dad)](https://pypi.org/project/ifcpatch/) | +| [ifcquery](https://docs.ifcopenshell.org/ifcquery.html) | CLI tool for querying and inspecting IFC building models | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifcquery?label=PyPI&color=006dad)](https://pypi.org/project/ifcquery/) | | [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [![GitHub Unstable](https://img.shields.io/github/v/release/ifcopenshell/ifcopenshell?filter=ifcsverchok-*.*.*.*&label=GitHub-Unstable&color=f6f8fa)](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true) | [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [![PyPI](https://img.shields.io/pypi/v/ifctester?label=PyPI&color=006dad)](https://pypi.org/project/ifctester/) | diff --git a/choco/bonsai/choco_release.py b/choco/bonsai/choco_release.py index 681e6c58e5..ea53798c5d 100644 --- a/choco/bonsai/choco_release.py +++ b/choco/bonsai/choco_release.py @@ -13,6 +13,7 @@ import hashlib import os import pathlib import re +import subprocess from typing import NoReturn from urllib import request @@ -20,7 +21,7 @@ from github import Github def get_repo_tag_names() -> list[str]: - git_return = os.popen("git tag -l").read() + git_return = subprocess.check_output("git tag -l", text=True) tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name] print(f"{len(tag_names)} tag_names found in repo") return tag_names @@ -78,6 +79,10 @@ def get_release_zip(tag: str) -> tuple[str, str]: raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.") +def run(command: str) -> None: + subprocess.check_output(command) + + start = datetime.datetime.now() URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender" @@ -97,7 +102,7 @@ should_release = False target_release_tag = "" TARGET_OS = "windows-x64" -git_status = os.popen("git status").read() +git_status = subprocess.check_output("git status", text=True) print(git_status) for tag_name in get_repo_tag_names(): @@ -147,7 +152,7 @@ blenderbim_build_version = target_release_tag.replace("blenderbim-", "") # url_blenderbim_py3x_win_zip release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag) -os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read() +subprocess.check_call(f"wget {url_blenderbim_py3x_win_zip} --no-verbose") # sha256sum_blenderbim_py310_win_zip sha256sum_blenderbim_py3x_win_zip = get_file_sha256_hash(release_zip_file_name) @@ -201,13 +206,13 @@ print("[INFO] inserting dynamic chocolatey package parameters successful") print("\n_____ build choco.exe with mono") choco_version = "1.1.0" -os.popen(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet").read() -os.popen(f"tar -xzf {choco_version}.tar.gz").read() +run(f"wget https://github.com/chocolatey/choco/archive/refs/tags/{choco_version}.tar.gz --quiet") +run(f"tar -xzf {choco_version}.tar.gz") print("choco tar unpack successful") os.chdir("choco-1.1.0") -os.popen("./build.sh").read() +run("./build.sh") -os.popen("cp -r build_output/chocolatey /opt/chocolatey").read() +run("cp -r build_output/chocolatey /opt/chocolatey") os.chdir(BLENDERBIM_DIR) if pathlib.Path("/opt/chocolatey/choco.exe").exists(): @@ -215,11 +220,15 @@ if pathlib.Path("/opt/chocolatey/choco.exe").exists(): print("\n_____ build choco pack") -os.popen("mono /opt/chocolatey/choco.exe pack --allow-unofficial").read() -os.popen('mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial').read() +run("mono /opt/chocolatey/choco.exe pack --allow-unofficial") +run( + 'mono /opt/chocolatey/choco.exe setapikey --key="{choco_token}" --source="https://push.chocolatey.org/" --allow-unofficial' +) print("\n_____ build choco push") -os.popen('mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose').read() +run( + 'mono /opt/chocolatey/choco.exe push --source="https://push.chocolatey.org/" --key="$CHOCO_TOKEN" --allow-unofficial --verbose' +) print(f"choco push of version: {target_release_tag} successful!") print(f"it took: {datetime.datetime.now() - start}") diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index bf48427774..917e66d192 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -80,7 +80,7 @@ option(BUILD_IFCGEOM "Build IfcGeom." ON) option(BUILD_IFCPYTHON "Build IfcPython." ON) option(BUILD_CONVERT "Build IfcConvert executable." ON) option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF) -option(BUILD_EXAMPLES "Build example applications." ON) +option(BUILD_EXAMPLES "Build example applications." OFF) option(BUILD_GEOMSERVER "Build IfcGeomServer executable (Open CASCADE is required)." ON) option(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF) option(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF) # QtViewer requires Qt6 diff --git a/nix/cache_dependencies.py b/nix/cache_dependencies.py index 465d6002f1..3d115764b4 100644 --- a/nix/cache_dependencies.py +++ b/nix/cache_dependencies.py @@ -41,6 +41,9 @@ def pack_dependencies(install_dir: Path) -> None: if not dependency_path.is_dir(): continue dependency_name = dependency_path.name + # Skip ifcopenshell - it's a build output, not a dependency to reuse across builds. + if dependency_name == "ifcopenshell": + continue tar_path = install_dir / f"{CACHE_PREFIX}{dependency_name}.tar.gz" if tar_path.exists(): print(f"Skipping existing cache: '{tar_path}'") diff --git a/pyproject.toml b/pyproject.toml index bdc9bf1a64..330f6150d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,10 +2,10 @@ name = "IfcOpenShell" version = "0.0.0" dependencies = [ - "black==26.1.0", - "ruff==0.15.4", + "black==26.3.1", + "ruff==0.15.8", "poethepoet", - "gersemi==0.26.0", + "gersemi==0.26.1", ] [tool.black] @@ -28,6 +28,12 @@ extend-exclude = ''' reportInvalidTypeForm = false disableBytesTypePromotions = true reportUnnecessaryTypeIgnoreComment = true +# Pylance doesn't respect gitignore, so we have to exclude files manually here +# to avoid VS Code slowing down. +# https://github.com/microsoft/pylance-release/issues/5169 +exclude = [ + "_deps", +] # Define here general ruff settings, # then they will be inherited by projects' .toml files. @@ -72,6 +78,139 @@ ignore = [ "UP032", # Replace .format with f-string ] +[tool.ty.rules] +all = "ignore" + +# Structural rules (no deep type inference needed, easier to adapt). +abstract-method-in-final-class = "error" +ambiguous-protocol-member = "error" +byte-string-type-annotation = "error" +conflicting-declarations = "error" +conflicting-metaclass = "error" +cyclic-class-definition = "error" +cyclic-type-alias-definition = "error" +dataclass-field-order = "error" +duplicate-base = "error" +duplicate-kw-only = "error" +empty-body = "error" +escape-character-in-forward-annotation = "error" +final-on-non-method = "error" +final-without-value = "error" +fstring-type-annotation = "error" +ignore-comment-unknown-rule = "error" +implicit-concatenated-string-type-annotation = "error" +inconsistent-mro = "error" +ineffective-final = "error" +instance-layout-conflict = "error" +invalid-dataclass = "error" +invalid-dataclass-override = "error" +invalid-enum-member-annotation = "error" +invalid-explicit-override = "error" +invalid-frozen-dataclass-subclass = "error" +invalid-generic-class = "error" +invalid-generic-enum = "error" +invalid-ignore-comment = "error" +invalid-legacy-positional-parameter = "error" +invalid-legacy-type-variable = "error" +invalid-named-tuple = "error" +invalid-newtype = "error" +invalid-overload = "error" +invalid-paramspec = "error" +invalid-protocol = "error" +invalid-syntax-in-forward-annotation = "error" +invalid-total-ordering = "error" +invalid-type-alias-type = "error" +invalid-type-checking-constant = "error" +invalid-type-guard-definition = "error" +invalid-type-variable-bound = "error" +invalid-type-variable-constraints = "error" +invalid-typed-dict-header = "error" +invalid-typed-dict-statement = "error" +override-of-final-method = "error" +override-of-final-variable = "error" +possibly-missing-import = "error" +possibly-missing-submodule = "error" +# Has false positives due to ty walrus operator bug. +# possibly-unresolved-reference = "error" +raw-string-type-annotation = "error" +redundant-final-classvar = "error" +shadowed-type-variable = "error" +subclass-of-final-class = "error" +super-call-in-named-tuple-method = "error" +unavailable-implicit-super-arguments = "error" +unbound-type-variable = "error" +undefined-reveal = "error" +unresolved-global = "error" +unresolved-import = "error" +unresolved-reference = "error" +unused-ignore-comment = "error" +unused-type-ignore-comment = "error" +useless-overload-body = "error" + +# Non-structural rules: +deprecated = "error" +zero-stepsize-in-slice = "error" +possibly-missing-implicit-call = "error" +unused-awaitable = "error" + +# Function argument rules: +# Conflicts with `ifcopenshell.api.geometry.add_representation` type of callables we have, confusing them with a module. +# call-non-callable = "error" +conflicting-argument-forms = "error" +# Too many false positives. +# invalid-argument-type = "error" +missing-argument = "error" +parameter-already-assigned = "error" +positional-only-parameter-as-kwarg = "error" +too-many-positional-arguments = "error" +unknown-argument = "error" +# Has a lot of warnings due to current ty walrus operator issues. +# index-out-of-bounds = "error" +# unresolved-attribute = "error" + +[tool.ty.environment] +extra-paths = [ + "src/bonsai/external_dependencies", + "src/bcf", + "src/bsdd", + "src/bonsai", + "src/ifc4d", + "src/ifc5d", + "src/ifccityjson", + "src/ifcclash", + "src/ifccsv", + "src/ifcdiff", + "src/ifcfm", + "src/ifcopenshell-python", + "src/ifcpatch", + "src/ifctester", +] + +[tool.ty.src] +exclude = [ + # External dependencies cloned for type checking only. + "src/bonsai/external_dependencies", + # Submodules. + "src/ifcopenshell-python/ifcopenshell/express", + "src/ifcopenshell-python/ifcopenshell/mvd", + "src/ifcopenshell-python/ifcopenshell/simple_spf", + "src/svgfill/3rdparty", + # Has special dependencies. + "src/ifcopenshell-python/ifcopenshell/geom/app.py", + "src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py", + "src/ifcopenshell-python/ifcopenshell/util/doc.py", + "src/ifcopenshell-python/ifcopenshell/util/generate_pset_templates.py", + "src/ifcopenshell-python/ifcopenshell/util/ifc4x3dev_scrape_data_for_docs.py", + # Too esoteric. + "src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py", + "src/ifc2ca/templates", + # Too dev. + "src/bcf/setup.py", + "src/bsdd/yml_to_classes.py", + # Deprecated. + "src/ifc2ca/_deprecated", +] + [tool.poe.tasks] ruff-main = "ruff check --extend-exclude nix/build-all.py" @@ -81,6 +220,47 @@ ruff.sequence = ["ruff-main", "ruff-old"] black = "black ." +ty.sequence = ["ty-bonsai", "ty-ios"] +ty.help = "Run ty type checker. Requires ty-venv to be set up first." +ty-bonsai = "ty check src/bonsai --python=src/bonsai/.venv" + +ty-venv.sequence = ["ty-venv-bonsai", "ty-venv-ios"] + +ty-venv-bonsai.sequence = [ + {cmd = "uv venv src/bonsai/.venv --python=3.11 --allow-existing"}, + {cmd = "uv pip install -r src/bonsai/type-check-requirements.txt --python=src/bonsai/.venv"}, +] + +ty-venv-ios.sequence = [ + {cmd = "uv venv src/ifcopenshell-python/.venv --python=3.10 --allow-existing"}, + {cmd = "uv pip install -r src/ifcopenshell-python/type-check-requirements.txt --python=src/ifcopenshell-python/.venv"}, +] + format.sequence = ["black", "ruff-main", "ruff-old"] cmake-format = "gersemi . --in-place" + +[tool.poe.tasks.ty-ios] +# --ignore unresolved-reference: walrus operator false positives in ty. +cmd = """ + ty check + src/bcf + src/bsdd + src/ifc2ca + src/ifc4d + src/ifc5d + src/ifccityjson + src/ifcclash + src/ifccsv + src/ifcdiff + src/ifcfm + src/ifcopenshell-python + src/ifcpatch + src/ifctester + --python=src/ifcopenshell-python/.venv + --ignore unresolved-reference +""" + +[tool.poe.tasks.bonsai-deps] +help = "Clone or update Bonsai external dependencies." +cmd = "python src/bonsai/scripts/bonsai_deps.py" diff --git a/src/bcf/bcf/v3/bcfapi.py b/src/bcf/bcf/v3/bcfapi.py index 3ac85b9685..368c294cd0 100644 --- a/src/bcf/bcf/v3/bcfapi.py +++ b/src/bcf/bcf/v3/bcfapi.py @@ -34,8 +34,8 @@ client_id, client_secret = "", "" class OAuthReceiver(http.server.BaseHTTPRequestHandler): def do_GET(self) -> None: query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query) - self.server.auth_code = query.get("code", [""])[0] # type: ignore - self.server.auth_state = query.get("state", [""])[0] # type: ignore + self.server.auth_code = query.get("code", [""])[0] + self.server.auth_state = query.get("state", [""])[0] self.send_response(200) self.send_header("Content-type", "text/plain") self.end_headers() @@ -255,7 +255,7 @@ class BcfClient: project_id: str = "", topics: str = "", query_string: Optional[str] = None, - ) -> list[Any]: + ) -> None: # return self.get( # f"/projects/{project_id}/topics", # { diff --git a/src/bcf/tests/v3/test_example_files.py b/src/bcf/tests/v3/test_example_files.py index 3f141afda6..06eb5d22dd 100644 --- a/src/bcf/tests/v3/test_example_files.py +++ b/src/bcf/tests/v3/test_example_files.py @@ -173,16 +173,17 @@ def assert_viewpoints(viewpoints): assert viewpoint.snapshot is not None +# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None: expected_vp = mdl.VisualizationInfo( components=mdl.Components( - view_setup_hints=mdl.ViewSetupHints( - spaces_visible=False, - space_boundaries_visible=False, - openings_visible=False, - ), selection=expected_selection, visibility=mdl.ComponentVisibility( + view_setup_hints=mdl.ViewSetupHints( + spaces_visible=False, + space_boundaries_visible=False, + openings_visible=False, + ), exceptions=expected_exception, default_visibility=False, ), @@ -193,6 +194,7 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e camera_direction=mdl.Direction(x=0.6745243072509766, y=-0.6599355936050415, z=-0.33091068267822266), camera_up_vector=mdl.Direction(x=0.2271970510482788, y=-0.24091780185699463, z=0.9435783624649048), field_of_view=60, + aspect_ratio=1.0, ), guid="21dd4807-e9af-439e-a980-04d913a6b1ce", ) @@ -200,16 +202,17 @@ def assert_second_viewpoint(viewpoint, expected_selection, expected_exception, e assert viewpoint.snapshot is not None +# TODO: dead code - ported from v2 but buildingSMART/BCF-XML has no v3 MaximumInformation.bcf equivalent def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, expected_coloring) -> None: expected_vp = mdl.VisualizationInfo( components=mdl.Components( - view_setup_hints=mdl.ViewSetupHints( - spaces_visible=False, - space_boundaries_visible=False, - openings_visible=True, - ), selection=expected_selection, visibility=mdl.ComponentVisibility( + view_setup_hints=mdl.ViewSetupHints( + spaces_visible=False, + space_boundaries_visible=False, + openings_visible=True, + ), exceptions=expected_exception, default_visibility=True, ), @@ -220,6 +223,7 @@ def assert_third_viewpoint(viewpoint, expected_selection, expected_exception, ex camera_direction=mdl.Direction(x=0.7232745289802551, y=0.5967116951942444, z=-0.3475759029388428), camera_up_vector=mdl.Direction(x=0.27662187814712524, y=0.21082592010498047, z=0.937567412853241), field_of_view=60, + aspect_ratio=1.0, ), guid="81daa431-bf01-4a49-80a2-1ab07c177717", ) diff --git a/src/bonsai/Makefile b/src/bonsai/Makefile index 310d31a4b0..b517bc572b 100644 --- a/src/bonsai/Makefile +++ b/src/bonsai/Makefile @@ -48,6 +48,7 @@ VERSION_PATCH:=$(shell cat '../../VERSION' | cut -d '.' -f 3) VERSION_DATE:=$(shell date '+%y%m%d') LAST_COMMIT_HASH:=$(shell git rev-parse HEAD) LAST_COMMIT_DATE:=$(shell git show -s --format=%cI) +LAST_GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD) PYPI_IMP:=cp ifdef PYVERSION @@ -63,6 +64,7 @@ PYNUMBER:=3$(PYMINOR) PYPI_VERSION:=3.$(PYMINOR) endif # def PYVERSION +IFCMERGE_VERSION:=2026-04-02 ifdef PLATFORM SUPPORTED_PLATFORMS := linux macos macosm1 win @@ -223,19 +225,8 @@ endif cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css # Provides IFCJSON functionality - cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip - cd build && unzip ifc2json.zip && rm ifc2json.zip - # IFCJSON doesn't have pyproject.toml, so we use python command. - cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \ - $(PYTHON) -c "from setuptools import setup; \ - setup( \ - name='ifcjson', \ - version='0.0.1', \ - author='Jan Brouwer', \ - author_email='jan@brewsky.nl', \ - packages=['ifcjson'], \ - )" bdist_wheel - cp -r build/IFC2JSON_python-*/file_converters/dist/*.whl build/wheels/ + # TODO: Use official repo, once https://github.com/IFCJSON-Team/IFC2JSON_python/pull/8 is merged. + cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/Andrej730/IFC2JSON_python.git@pyproject_toml" --no-deps -w wheels/ # Brickschema requires pkg_resources which is provided by Blender. # Provides Brickschema functionality @@ -243,26 +234,16 @@ endif cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl # Required for hipped roof generation - cd build && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip - cd build && unzip master.zip && rm master.zip - cd build && . env/$(VENV_ACTIVATE) && cd bpypolyskel-master && \ - $(PYTHON) -c "from setuptools import setup; \ - setup( \ - name='bpypolyskel', \ - version='0.0.0', \ - packages=['bpypolyskel'], \ - )" bdist_wheel - cp -r build/bpypolyskel-master/dist/*.whl build/wheels/ + cd build && . env/$(VENV_ACTIVATE) && $(PYTHON) -m pip wheel "git+https://github.com/prochitecture/bpypolyskel" --no-deps -w wheels/ # folder for executable files mkdir -p build/bonsai/libs/bin # required for three-way git merging ifeq ($(PLATFORM), win) - cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2025-01-26/ifcmerge.zip - cd build/bonsai/libs/bin && unzip ifcmerge.zip && rm ifcmerge.zip + cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/$(IFCMERGE_VERSION)/ifcmerge.exe else - cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge + cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/$(IFCMERGE_VERSION)/ifcmerge && chmod +x ifcmerge endif # Generate translations module for Bonsai build @@ -281,6 +262,7 @@ else $(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml $(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py $(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py + $(SED) "s/7777777/$(LAST_GIT_BRANCH)/" build/bonsai/__init__.py $(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml endif diff --git a/src/bonsai/bonsai/__init__.py b/src/bonsai/bonsai/__init__.py index e30e2bf699..c6ec6405c0 100644 --- a/src/bonsai/bonsai/__init__.py +++ b/src/bonsai/bonsai/__init__.py @@ -35,15 +35,15 @@ IN_PACKAGE = __package__ == "bonsai" import platform import re import traceback -import uuid import webbrowser from collections import deque from collections.abc import Generator from pathlib import Path -from typing import Any, Union +from typing import TYPE_CHECKING, Any, Union last_commit_hash = "8888888" last_commit_date = "9999999" +last_git_branch = "7777777" def get_last_commit_hash() -> Union[str, None]: @@ -61,6 +61,15 @@ def get_last_commit_date() -> Union[str, None]: return last_commit_date +def get_git_branch() -> Union[str, None]: + # Using this weird way to write 7777777, + # so makefile won't accidentally replace it here + # we'll be able to distinguish branch from placeholder value. + if last_git_branch == str(7_777777): + return None + return last_git_branch + + # Accessed from bonsai extension: bbim_semver: dict[str, Any] = {} @@ -72,6 +81,21 @@ REINSTALLED_BBIM_VERSION: Union[str, None] = None REGISTERED_BBIM_PACKAGE: str +def is_registering() -> bool: + """ + During addon registration ``bpy.context`` and ``bpy.data`` are restricted + and you can't access their properties. + """ + import bpy + + if TYPE_CHECKING or bpy.app.version >= (5, 0, 0): + import _bpy_restrict_state as bpy_restrict_state + else: + import bpy_restrict_state + + return isinstance(bpy.context, bpy_restrict_state._RestrictContext) + + def initialize_bbim_semver(): """Initialize `bbim_semver` dictionary. @@ -93,9 +117,13 @@ def initialize_bbim_semver(): bbim_semver["version"] = version_str -def get_debug_info(): +def get_debug_info(*, bonsai_failed_to_load: bool = False) -> dict[str, Any]: + import bpy + bbim_version = bbim_semver["version"] + # All data here should be gettable even in case of `bpy.context` and `bpy.data` being inaccessible + # and Bonsai completely failed to load. debug_info = { "os": platform.system(), "os_version": platform.version(), @@ -107,10 +135,19 @@ def get_debug_info(): "bonsai_version": bbim_version, "bonsai_commit_hash": get_last_commit_hash(), "bonsai_commit_date": get_last_commit_date(), + "bonsai_git_branch": get_git_branch(), "last_actions": last_actions, "last_error": last_error, } + # Can't access blend data or context during registration. + # If Bonsai failed to load we cannot safely access any of its properties or its tools + # as they may not be registered yet and acessing them will break Bonsai Fatal Error UI. + if is_registering() or bonsai_failed_to_load: + return debug_info + + import bonsai.tool as tool + # Add .blend file save information if bpy.data.is_saved: debug_info["blend_file_path"] = bpy.data.filepath @@ -131,7 +168,7 @@ def get_debug_info(): return debug_info -def format_debug_info(info: dict): +def format_debug_info(info: dict[str, Any]) -> str: last_actions = "" for action in info["last_actions"]: last_actions += f"\n# {action['type']}: {action['name']}" @@ -149,33 +186,10 @@ def get_binaries(path: Path) -> Generator[Path, None, None]: yield from path.glob("**/*.so") -def safe_link_dlls() -> None: - # Blender 4.2+ has a problem on Windows for disabling/enabling/reinstalling extensions - # with loaded binary dependencies (on Windows you can't remove a binary if it's loaded by some program). - # To avoid this issue we temporary hard link dlls to our temp directory on unregister() - # (unregister is executed before Blender will try to uninstall dependencies and the issue will arise). - # Then, Blender won't have a problem unlinking unloaded dlls as they are still linked somewhere. - # On register() we clean up our temp directory with binaries. - # - # TODO: If user uninstalls Bonsai to never use it again, temporary directory won't be cleared. - # - # See: https://projects.blender.org/blender/blender/issues/125049 - import bpy - - ext_path = Path(bpy.utils.user_resource("EXTENSIONS")) - local_path = ext_path / ".local" - - # We use random hash subfolder as user may try to enable/disable addon multiple times. - random_hash = uuid.uuid4().hex[:8] - temp_local = ext_path / ".local_temp" / random_hash - temp_local.mkdir(parents=True) - - for filepath in get_binaries(local_path): - dest_path = temp_local / filepath.relative_to(local_path) - dest_path.parent.mkdir(exist_ok=True, parents=True) - os.link(filepath, dest_path) - - +# TODO: remove before 0.8.6 release. +# On Windows issues with removing extensions were resolved in Blender 4.3, +# but we removed our workaround that was producing some junk only in 0.8.5 release. +# So we're temporarily keeping the part that's cleaning up outputs from previous releases. def clean_up_dlls_safe_links() -> None: import bpy @@ -205,6 +219,8 @@ def clean_up_dlls_safe_links() -> None: if IN_BLENDER: + import bpy + initialize_bbim_semver() def get_binary_info() -> dict[str, Any]: @@ -246,10 +262,12 @@ if IN_BLENDER: global last_commit_hash global last_commit_date + global last_git_branch path = Path(__file__).resolve().parent repo = git.Repo(str(path), search_parent_directories=True) last_commit_hash = repo.head.object.hexsha last_commit_date = repo.head.object.committed_datetime.isoformat() + last_git_branch = repo.active_branch.name except: pass @@ -296,9 +314,6 @@ if IN_BLENDER: purge_cache() def unregister(): - if platform.system() == "Windows": - safe_link_dlls() - import bonsai.bim bonsai.bim.unregister() @@ -333,7 +348,7 @@ if IN_BLENDER: bl_context = "scene" def draw(self, context): - info = get_debug_info() + info = get_debug_info(bonsai_failed_to_load=True) layout = self.layout layout.alert = True @@ -409,7 +424,7 @@ if IN_BLENDER: bl_description = "Copies debugging information to your clipboard for use in bugreports" def execute(self, context): - info = get_debug_info() + info = get_debug_info(bonsai_failed_to_load=True) info.update(get_binary_info()) info = format_debug_info(info) context.window_manager.clipboard = info diff --git a/src/bonsai/bonsai/bim/export_ifc.py b/src/bonsai/bonsai/bim/export_ifc.py index 633d6292f9..f28c106e95 100644 --- a/src/bonsai/bonsai/bim/export_ifc.py +++ b/src/bonsai/bonsai/bim/export_ifc.py @@ -72,9 +72,7 @@ class IfcExporter: def set_header(self): self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file) - self.file.header.file_name.time_stamp = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat() - ) + self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) self.file.header.file_name.originating_system = "{} {}".format( self.get_application_name(), tool.Blender.get_bonsai_version() diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index 231b44c671..e11eb07ce8 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -45,16 +45,19 @@ from bonsai.bim.module.nest.decorator import NestDecorator cwd = os.path.dirname(os.path.realpath(__file__)) global_subscription_owner = object() +# Separate owner for per-object msgbus subscriptions (name, active_material_index). +# Using a dedicated owner allows clearing all per-object subscriptions at once +# during undo/redo without affecting other global subscriptions. +object_subscription_owner = object() def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None: try: obj.name except: - # The object is invalid but somehow still has a callback. Clear all - # msgbus subscriptions to prevent useless further triggers. - bpy.msgbus.clear_by_owner(obj) - return # In case the object RNA is gone during an undo / redo operation + # The object is invalid but somehow still has a callback. + # This can occur during undo/redo when the Python wrapper is stale. + return # Blender names are up to 63 UTF-8 bytes if len(bytes(obj.name, "utf-8")) >= 63: return @@ -189,7 +192,7 @@ def subscribe_to(obj: bpy.types.ID, data_path: str, callback: Callable[[bpy.type return bpy.msgbus.subscribe_rna( key=subscribe_to, - owner=obj, + owner=object_subscription_owner, args=( obj, data_path, diff --git a/src/bonsai/bonsai/bim/ifc.py b/src/bonsai/bonsai/bim/ifc.py index f7d23e1dbe..b07e584a71 100644 --- a/src/bonsai/bonsai/bim/ifc.py +++ b/src/bonsai/bonsai/bim/ifc.py @@ -316,11 +316,8 @@ class IfcStore: del IfcStore.id_map[data["id"]] if "guid" in data: del IfcStore.guid_map[data["guid"]] - obj = IfcStore.get_object_by_name(data["obj"]) - if obj is None: - # obj was just created during this step and didn't existed before. - return - bpy.msgbus.clear_by_owner(obj) + # Note: msgbus subscriptions are cleared globally during + # rebuild_element_maps which runs after every undo/redo. @staticmethod def commit_link_element(data: OperationData) -> None: @@ -367,10 +364,8 @@ class IfcStore: del IfcStore.id_map[data["id"]] if "guid" in data: del IfcStore.guid_map[data["guid"]] - obj = IfcStore.get_object_by_name(data["obj"]) - # obj might be removed after unlink. - if not obj: - bpy.msgbus.clear_by_owner(obj) + # Note: msgbus subscriptions are cleared globally during + # rebuild_element_maps which runs after every undo/redo. @staticmethod def unlink_element( diff --git a/src/bonsai/bonsai/bim/import_ifc.py b/src/bonsai/bonsai/bim/import_ifc.py index 7e8f40560e..267800469a 100644 --- a/src/bonsai/bonsai/bim/import_ifc.py +++ b/src/bonsai/bonsai/bim/import_ifc.py @@ -64,8 +64,8 @@ class MaterialCreator: mesh: Union[OBJECT_DATA_TYPE, None], shape_has_openings: bool, ) -> None: - if ((rep := getattr(element, "Representation", ...) is not ...) and not rep) or ( - (rep := getattr(element, "RepresentationMaps", ...) is not ...) and not rep + if ((rep := getattr(element, "Representation", ...)) is not ... and not rep) or ( + (rep := getattr(element, "RepresentationMaps", ...)) is not ... and not rep ): return @@ -747,6 +747,7 @@ class IfcImporter: self.update_progress((percent_average / 100 * progress_range) + start_progress) shape = iterator.get() if shape: + assert isinstance(shape, W.TriangulationElement) product = self.file.by_id(shape.id) self.create_product(product, shape) results.add(product) @@ -1020,7 +1021,8 @@ class IfcImporter: obj.hide_select = True obj.hide_viewport = True self.project["blender"].objects.link(obj) - self.project["blender"].BIMCollectionProperties.obj = obj + collection_props = tool.Blender.get_collection_props(self.project["blender"]) + collection_props.obj = obj props = tool.Blender.get_object_bim_props(obj) props.collection = self.collections[project.GlobalId] = self.project["blender"] diff --git a/src/bonsai/bonsai/bim/module/aggregate/decorator.py b/src/bonsai/bonsai/bim/module/aggregate/decorator.py index eb389a58bd..2cd1c1bca0 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/decorator.py +++ b/src/bonsai/bonsai/bim/module/aggregate/decorator.py @@ -101,7 +101,7 @@ class AggregateDecorator: cls.is_installed = False def dotted_line_shader(self): - vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments] vert_out.smooth("FLOAT", "v_ArcLength") shader_info = gpu.types.GPUShaderCreateInfo() diff --git a/src/bonsai/bonsai/bim/module/augin/prop.py b/src/bonsai/bonsai/bim/module/augin/prop.py index 9878909eb0..6358371613 100644 --- a/src/bonsai/bonsai/bim/module/augin/prop.py +++ b/src/bonsai/bonsai/bim/module/augin/prop.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -import bpy.props -import bpy.types +from typing import TYPE_CHECKING + +import bpy class AuginProperties(bpy.types.PropertyGroup): @@ -27,3 +28,11 @@ class AuginProperties(bpy.types.PropertyGroup): project_name: bpy.props.StringProperty(name="Project Name") project_filename: bpy.props.StringProperty(name="IFC Filename") is_success: bpy.props.BoolProperty(name="Is Successful Upload", default=False) + + if TYPE_CHECKING: + username: str + password: str + token: str + project_name: str + project_filename: str + is_success: bool diff --git a/src/bonsai/bonsai/bim/module/bcf/operator.py b/src/bonsai/bonsai/bim/module/bcf/operator.py index 6edf257f05..461321852d 100644 --- a/src/bonsai/bonsai/bim/module/bcf/operator.py +++ b/src/bonsai/bonsai/bim/module/bcf/operator.py @@ -1253,8 +1253,8 @@ class ActivateBcfViewpoint(bpy.types.Operator): else: obj.data.show_background_images = False - area = next(area for area in context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].region_3d.view_perspective = "CAMERA" + assert (space := tool.Blender.get_view3d_space()) + space.region_3d.view_perspective = "CAMERA" if self.file: self.set_viewpoint_components(viewpoint, context) diff --git a/src/bonsai/bonsai/bim/module/bcf/prop.py b/src/bonsai/bonsai/bim/module/bcf/prop.py index 3ac751387e..651c0c052f 100644 --- a/src/bonsai/bonsai/bim/module/bcf/prop.py +++ b/src/bonsai/bonsai/bim/module/bcf/prop.py @@ -230,7 +230,7 @@ class BcfTopic(PropertyGroup): def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: - global RELATED_TOPICS_ENUM_ITEMS + global RELATED_TOPICS_ENUM_ITEMS # ty: ignore[unresolved-global] props = self active_topic = props.active_topic active_related_topics = active_topic.related_topics.keys() diff --git a/src/bonsai/bonsai/bim/module/boundary/operator.py b/src/bonsai/bonsai/bim/module/boundary/operator.py index 5720d6aae5..45ee8c60e2 100644 --- a/src/bonsai/bonsai/bim/module/boundary/operator.py +++ b/src/bonsai/bonsai/bim/module/boundary/operator.py @@ -27,6 +27,7 @@ import ifcopenshell.api import ifcopenshell.api.boundary import ifcopenshell.api.root import ifcopenshell.geom +import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.util.element import ifcopenshell.util.placement import ifcopenshell.util.shape @@ -376,6 +377,8 @@ class EnableEditingBoundary(bpy.types.Operator): obj = tool.Ifc.get_object(entity) if entity and obj: setattr(bprops, blender_property, obj) + bprops.physical_or_virtual = boundary.PhysicalOrVirtualBoundary or "NOTDEFINED" + bprops.internal_or_external = boundary.InternalOrExternalBoundary or "NOTDEFINED" return {"FINISHED"} @@ -391,6 +394,8 @@ class DisableEditingBoundary(bpy.types.Operator): bprops.is_editing = False for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items(): setattr(bprops, blender_property, None) + bprops.physical_or_virtual = "NOTDEFINED" + bprops.internal_or_external = "NOTDEFINED" return {"FINISHED"} @@ -410,6 +415,8 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator): obj = getattr(bprops, blender_property, None) entity = tool.Ifc.get_entity(obj) attributes[blender_property] = entity + attributes["physical_or_virtual"] = bprops.physical_or_virtual + attributes["internal_or_external"] = bprops.internal_or_external ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes) bpy.ops.bim.disable_editing_boundary() return {"FINISHED"} @@ -701,6 +708,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): while True: tree.add_element(iterator.get_native()) shape = iterator.get() + assert isinstance(shape, W.TriangulationElement) shapes[shape.id] = { "verts": ifcopenshell.util.shape.get_vertices(shape.geometry), "faces": ifcopenshell.util.shape.get_faces(shape.geometry), diff --git a/src/bonsai/bonsai/bim/module/boundary/prop.py b/src/bonsai/bonsai/bim/module/boundary/prop.py index 2e9ab8c975..7b4f5069ad 100644 --- a/src/bonsai/bonsai/bim/module/boundary/prop.py +++ b/src/bonsai/bonsai/bim/module/boundary/prop.py @@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Union import bpy from bpy.props import ( BoolProperty, + EnumProperty, PointerProperty, ) from bpy.types import PropertyGroup @@ -50,12 +51,43 @@ def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object return False +def get_internal_or_external_items( + self: "BIMObjectBoundaryProperties", context: bpy.types.Context | None +) -> list[tuple[str, str, str]]: + items = [ + ("INTERNAL", "Internal", ""), + ("EXTERNAL", "External", ""), + ] + ifc = tool.Ifc.get() + if not ifc or ifc.schema != "IFC2X3": + items += [ + ("EXTERNAL_EARTH", "External Earth", ""), + ("EXTERNAL_WATER", "External Water", ""), + ("EXTERNAL_FIRE", "External Fire", ""), + ] + items.append(("NOTDEFINED", "Not Defined", "")) + return items + + class BIMObjectBoundaryProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter) related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter) parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter) corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter) + physical_or_virtual: EnumProperty( + name="PhysicalOrVirtualBoundary", + items=[ + ("PHYSICAL", "Physical", ""), + ("VIRTUAL", "Virtual", ""), + ("NOTDEFINED", "Not Defined", ""), + ], + default="NOTDEFINED", + ) + internal_or_external: EnumProperty( + name="InternalOrExternalBoundary", + items=get_internal_or_external_items, + ) if TYPE_CHECKING: is_editing: bool @@ -63,6 +95,8 @@ class BIMObjectBoundaryProperties(PropertyGroup): related_building_element: Union[bpy.types.Object, None] parent_boundary: Union[bpy.types.Object, None] corresponding_boundary: Union[bpy.types.Object, None] + physical_or_virtual: str + internal_or_external: str # values depend on schema: IFC2X3 omits EXTERNAL_EARTH/WATER/FIRE class BIMBoundaryProperties(PropertyGroup): diff --git a/src/bonsai/bonsai/bim/module/boundary/ui.py b/src/bonsai/bonsai/bim/module/boundary/ui.py index 0ca21b9ce5..91990a5eb0 100644 --- a/src/bonsai/bonsai/bim/module/boundary/ui.py +++ b/src/bonsai/bonsai/bim/module/boundary/ui.py @@ -77,6 +77,10 @@ class BIM_PT_Boundary(Panel): self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element") self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary") self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary") + row = self.layout.row() + row.prop(self.bprops, "physical_or_virtual") + row = self.layout.row() + row.prop(self.bprops, "internal_or_external") else: row = self.layout.row() row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit") @@ -84,6 +88,8 @@ class BIM_PT_Boundary(Panel): self.draw_relation_data(boundary, "RelatedBuildingElement") self.draw_relation_data(boundary, "ParentBoundary") self.draw_relation_data(boundary, "CorrespondingBoundary") + self.draw_enum_data(boundary, "PhysicalOrVirtualBoundary") + self.draw_enum_data(boundary, "InternalOrExternalBoundary") if hasattr(boundary, "InnerBoundaries"): for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())): row = self.layout.row(align=True) @@ -110,6 +116,11 @@ class BIM_PT_Boundary(Panel): else: row.label(text="") + def draw_enum_data(self, boundary, ifc_attribute: str): + row = self.layout.row(align=True) + row.label(text=ifc_attribute) + row.label(text=getattr(boundary, ifc_attribute, "") or "") + def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str): if hasattr(boundary, ifc_attribute): row = self.layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/brick/prop.py b/src/bonsai/bonsai/bim/module/brick/prop.py index 6201113a09..2d507411e4 100644 --- a/src/bonsai/bonsai/bim/module/brick/prop.py +++ b/src/bonsai/bonsai/bim/module/brick/prop.py @@ -46,26 +46,26 @@ def get_libraries(self, context): def get_namespaces(self, context): - global NAMESPACES_ENUM_ITEMS + global NAMESPACES_ENUM_ITEMS # ty: ignore[unresolved-global] NAMESPACES_ENUM_ITEMS = [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces] return NAMESPACES_ENUM_ITEMS def get_brick_entity_classes(self, context): - global ENTITY_CLASSES_ENUM_ITEMS + global ENTITY_CLASSES_ENUM_ITEMS # ty: ignore[unresolved-global] entity = self.brick_entity_create_type ENTITY_CLASSES_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]] return ENTITY_CLASSES_ENUM_ITEMS def get_brick_roots(self, context): - global BRICK_ROOTS_ENUM_ITEMS + global BRICK_ROOTS_ENUM_ITEMS # ty: ignore[unresolved-global] BRICK_ROOTS_ENUM_ITEMS = [(root, root, "") for root in BrickStore.root_classes] return BRICK_ROOTS_ENUM_ITEMS def get_brick_relations(self, context): - global BRICK_RELATIONS_ENUM_ITEMS + global BRICK_RELATIONS_ENUM_ITEMS # ty: ignore[unresolved-global] BRICK_RELATIONS_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships] for relation in BrickschemaData.data["active_relations"]: if relation["predicate_name"] == "label": diff --git a/src/bonsai/bonsai/bim/module/brick/ui.py b/src/bonsai/bonsai/bim/module/brick/ui.py index aa910c272f..2ec063a0dc 100644 --- a/src/bonsai/bonsai/bim/module/brick/ui.py +++ b/src/bonsai/bonsai/bim/module/brick/ui.py @@ -16,9 +16,18 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations + +from typing import TYPE_CHECKING + +import bpy from bpy.types import Panel, UIList import bonsai.tool as tool + +if TYPE_CHECKING: + from bonsai.bim.module.brick.prop import Brick + from bonsai.bim.helper import prop_with_search from bonsai.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData from bonsai.tool.brick import BrickStore @@ -274,7 +283,9 @@ class BIM_PT_brickschema_viewport(Panel): class BIM_UL_bricks(UIList): split_screen = False - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: Brick, icon, active_data, active_propname + ) -> None: if item: split = layout.split(factor=0.85, align=True) row = split.row() diff --git a/src/bonsai/bonsai/bim/module/cad/operator.py b/src/bonsai/bonsai/bim/module/cad/operator.py index 3b6ef8fc69..bef1d851af 100644 --- a/src/bonsai/bonsai/bim/module/cad/operator.py +++ b/src/bonsai/bonsai/bim/module/cad/operator.py @@ -37,6 +37,7 @@ messages = { class CadTrimExtend(bpy.types.Operator): bl_idname = "bim.cad_trim_extend" bl_label = "CAD Trim / Extend" + bl_description = "Extends/reduces element to 3D cursor" @classmethod def poll(cls, context): @@ -82,6 +83,7 @@ class CadTrimExtend(bpy.types.Operator): class CadMitre(bpy.types.Operator): bl_idname = "bim.cad_mitre" bl_label = "CAD Mitre" + bl_description = "Joins two non-parallel paths at their intersection" @classmethod def poll(cls, context): diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index 20faa4dcc7..24fb98ba3f 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -106,23 +106,37 @@ class CadTool(WorkSpaceTool): ) row = layout.row(align=True) - add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context) - row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) add_layout_hotkey_operator( - row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context + row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context) + add_layout_hotkey_operator( + row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__.split("\n", 1)[1].strip(), ui_context + ) + row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) + add_layout_hotkey_operator( + row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__.split("\n", 1)[1].strip(), ui_context + ) elif ( isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES) @@ -132,15 +146,21 @@ class CadTool(WorkSpaceTool): layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context ) row = layout.row(align=True) - add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context) - row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) add_layout_hotkey_operator( - row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context + row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__.split("\n", 1)[1].strip(), ui_context + ) + row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) + add_layout_hotkey_operator( + row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context + ) else: if ( @@ -168,19 +188,37 @@ class CadTool(WorkSpaceTool): add_layout_hotkey_operator(row, "Set Gable Roof Angle", "S_R", "Set Gable Roof Angle", ui_context) row = layout.row(align=True) - add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context) - row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) add_layout_hotkey_operator( - row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context + row, "Extend", "S_E", bpy.ops.bim.cad_trim_extend.__doc__.split("\n", 1)[1].strip(), ui_context ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Join", "S_T", bpy.ops.bim.cad_mitre.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "2-Point Arc", "S_C", bpy.ops.bim.cad_arc_from_2_points.__doc__, ui_context) + add_layout_hotkey_operator( + row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__.split("\n", 1)[1].strip(), ui_context + ) row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) - add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.cad_arc_from_3_points.__doc__, ui_context) + add_layout_hotkey_operator( + row, + "2-Point Arc", + "S_C", + bpy.ops.bim.cad_arc_from_2_points.__doc__.split("\n", 1)[1].strip(), + ui_context, + ) + row = row if ui_context == "TOOL_HEADER" else layout.row(align=True) + add_layout_hotkey_operator( + row, + "3-Point Arc", + "S_V", + bpy.ops.bim.cad_arc_from_3_points.__doc__.split("\n", 1)[1].strip(), + ui_context, + ) class CadHotkey(bpy.types.Operator): diff --git a/src/bonsai/bonsai/bim/module/clash/operator.py b/src/bonsai/bonsai/bim/module/clash/operator.py index 94866a4e97..3788130a12 100644 --- a/src/bonsai/bonsai/bim/module/clash/operator.py +++ b/src/bonsai/bonsai/bim/module/clash/operator.py @@ -457,9 +457,7 @@ class HideClash(bpy.types.Operator): def execute(self, context): ClashDecorator.uninstall() - for area in context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/cost/ui.py b/src/bonsai/bonsai/bim/module/cost/ui.py index 73a578171f..f8126d3c6c 100644 --- a/src/bonsai/bonsai/bim/module/cost/ui.py +++ b/src/bonsai/bonsai/bim/module/cost/ui.py @@ -26,10 +26,11 @@ from bpy.types import Panel, UIList import bonsai.bim.helper import bonsai.bim.module.cost.prop as CostProp import bonsai.tool as tool -from bonsai.bim.module.cost.data import CostSchedulesData +from bonsai.bim.module.cost.data import CostItem, CostSchedulesData if TYPE_CHECKING: from bonsai.bim.module.cost.prop import BIMCostProperties, CostItemQuantity + from bonsai.bim.prop import StrProperty class BIM_PT_cost_schedules(Panel): @@ -398,8 +399,8 @@ class BIM_PT_cost_item_types(Panel): op = row2.operator("bim.calculate_cost_item_resource_value", text="", icon="DISC") op.cost_item = cost_item.ifc_definition_id - rtprops = context.scene.BIMResourceTreeProperties rprops = tool.Resource.get_resource_props() + rtprops = rprops.tree if rtprops.resources and rprops.active_resource_index < len(rtprops.resources): if has_quantity_names: op = row2.operator("bim.assign_cost_item_quantity", text="", icon="PROPERTIES") @@ -661,9 +662,18 @@ class BIM_UL_cost_items_trait: split2.alignment = "LEFT" split2.label(text="Rate") - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMCostProperties, + item: CostProp.CostItem, + icon, + active_data, + active_propname, + ) -> None: if item: - self.props = tool.Cost.get_cost_props() + self.props = data cost_item = CostSchedulesData.data["cost_items"][item.ifc_definition_id] row = layout.row(align=True) @@ -694,7 +704,7 @@ class BIM_UL_cost_items_trait: # TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX - def draw_parent_operator(self, row, cost_item_id): + def draw_parent_operator(self, row: bpy.types.UILayout, cost_item_id: int) -> None: if self.props.active_cost_item_id: if self.props.active_cost_item_id != cost_item_id: op = row.operator("bim.change_parent_cost_item", text="", icon="LINKED", emboss=False).new_parent = ( @@ -703,7 +713,7 @@ class BIM_UL_cost_items_trait: else: row.label(text="", icon="BLANK1") - def draw_hierarchy(self, row, item): + def draw_hierarchy(self, row: bpy.types.UILayout, item: CostProp.CostItem) -> None: for i in range(0, item.level_index): row.label(text="", icon="BLANK1") if item.has_children: @@ -749,7 +759,7 @@ class BIM_UL_cost_items_trait: else: row.label(text="-") - def draw_value_column(self, layout, cost_item): + def draw_value_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None: if cost_item["TotalAppliedValue"]: text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ") if cost_item["UnitBasisValueComponent"] not in [None, 1]: @@ -760,13 +770,13 @@ class BIM_UL_cost_items_trait: else: layout.label(text="-") - def draw_total_cost_column(self, layout, cost_item): + def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None: format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ") currency = CostSchedulesData.data["currency"] text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers layout.label(text=text) - def draw_order_operator(self, row, ifc_definition_id, cost_item): + def draw_order_operator(self, row: bpy.types.UILayout, ifc_definition_id: int, cost_item: CostItem) -> None: if cost_item["NestingIndex"] is not None: if cost_item["NestingIndex"] == 0: op = row.operator("bim.reorder_cost_item_nesting", icon="TRIA_DOWN", text="") @@ -793,12 +803,14 @@ class BIM_UL_cost_item_rates(BIM_UL_cost_items_trait, UIList): def draw_quantity_column(self, layout, cost_item): self.draw_uom_column(layout, cost_item) - def draw_total_cost_column(self, layout, cost_item): + def draw_total_cost_column(self, layout: bpy.types.UILayout, cost_item: CostItem) -> None: pass # No such thing as a total cost in a schedule of rates class BIM_UL_cost_columns(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: StrProperty, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") @@ -806,9 +818,17 @@ class BIM_UL_cost_columns(UIList): class BIM_UL_cost_item_types(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - props = tool.Cost.get_cost_props() - cost_item = props.cost_items[props.active_cost_item_index] + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMCostProperties, + item: CostProp.CostItemType, + icon, + active_data, + active_propname, + ) -> None: + cost_item = data.cost_items[data.active_cost_item_index] if item: row = layout.row(align=True) @@ -844,7 +864,16 @@ class BIM_UL_cost_item_quantities(UIList): class BIM_UL_product_cost_items(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data, + item: CostItemQuantity, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE") diff --git a/src/bonsai/bonsai/bim/module/diff/operator.py b/src/bonsai/bonsai/bim/module/diff/operator.py index 330deed410..020c0c40f2 100644 --- a/src/bonsai/bonsai/bim/module/diff/operator.py +++ b/src/bonsai/bonsai/bim/module/diff/operator.py @@ -98,8 +98,8 @@ class VisualiseDiff(bpy.types.Operator): obj.color = (0.0, 1.0, 0.0, 1.0) elif global_id in diff["changed"]: obj.color = (0.0, 0.0, 1.0, 1.0) - area = next(area for area in context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.color_type = "OBJECT" + assert (space := tool.Blender.get_view3d_space()) + space.shading.color_type = "OBJECT" return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/document/operator.py b/src/bonsai/bonsai/bim/module/document/operator.py index 006ed27957..21abc42895 100644 --- a/src/bonsai/bonsai/bim/module/document/operator.py +++ b/src/bonsai/bonsai/bim/module/document/operator.py @@ -19,6 +19,7 @@ import json import bpy +import ifcopenshell.util.element import bonsai.core.document as core import bonsai.tool as tool diff --git a/src/bonsai/bonsai/bim/module/document/prop.py b/src/bonsai/bonsai/bim/module/document/prop.py index c51f7b636c..01ddf82f43 100644 --- a/src/bonsai/bonsai/bim/module/document/prop.py +++ b/src/bonsai/bonsai/bim/module/document/prop.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Literal, Union import bpy from bpy.props import ( @@ -66,7 +66,7 @@ class Document(PropertyGroup): tree_depth: int has_children: bool is_expanded: bool - document_type: str + document_type: Literal["PROJECT", "INFORMATION", "REFERENCE"] class DocumentObject(PropertyGroup): diff --git a/src/bonsai/bonsai/bim/module/document/ui.py b/src/bonsai/bonsai/bim/module/document/ui.py index d618d6d966..1b25c05c15 100644 --- a/src/bonsai/bonsai/bim/module/document/ui.py +++ b/src/bonsai/bonsai/bim/module/document/ui.py @@ -16,9 +16,22 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations + +from typing import TYPE_CHECKING + +import bpy from bpy.types import Panel, UIList import bonsai.tool as tool + +if TYPE_CHECKING: + from bonsai.bim.module.document.prop import ( + BIMDocumentProperties, + Document, + DocumentObject, + ) + from bonsai.bim.helper import draw_attributes from bonsai.bim.module.document.data import DocumentData, ObjectDocumentData @@ -207,7 +220,16 @@ class BIM_PT_object_documents(Panel): class BIM_UL_documents(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMDocumentProperties, + item: Document, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) indent_depth = 0 @@ -252,16 +274,22 @@ class BIM_UL_documents(UIList): class BIM_UL_document_objects(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, + context, + layout: bpy.types.UILayout, + data: BIMDocumentProperties, + item: DocumentObject, + icon, + active_data, + active_propname, + ) -> None: if item: row = layout.row(align=True) row.prop(item, "name", text="", emboss=False, icon="OBJECT_DATA") row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = item.name - props = tool.Document.get_document_props() - if props.active_document: - document = props.active_document - + if document := data.active_document: op = row.operator("bim.unassign_document", text="", icon="X") op.document = document.ifc_definition_id op.obj = item.name diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index 8373ed29bc..23ee7c1987 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -22,7 +22,9 @@ from pathlib import Path from typing import Any, Union import bpy +import ifcopenshell.util.classification import ifcopenshell.util.element +import ifcopenshell.util.placement import ifcopenshell.util.unit from natsort import natsorted diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index f950f7a1bb..b9b4dd40cb 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -70,9 +70,9 @@ class profile_consequential: cls.start_time = None lines = "\n".join(cls.lines) print(lines) - import pyperclip - pyperclip.copy(lines) + assert (wm := bpy.context.window_manager) + wm.clipboard = lines cls.lines = [] @@ -1787,7 +1787,7 @@ class CutDecorator: # Handle both old float64 and new float32 checksums for version compatibility rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum) - rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9) + rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9).reshape(3, 3) rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3()) rot_dot = np.dot(rot_check, rot_real.T) angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1)) diff --git a/src/bonsai/bonsai/bim/module/drawing/gizmos.py b/src/bonsai/bonsai/bim/module/drawing/gizmos.py index a83bd0a46a..d350cf80ee 100644 --- a/src/bonsai/bonsai/bim/module/drawing/gizmos.py +++ b/src/bonsai/bonsai/bim/module/drawing/gizmos.py @@ -151,7 +151,7 @@ from bpy_extras.view3d_utils import ( ) from gpu_extras.batch import batch_for_shader from ifcopenshell.util.unit import si_conversions -from mathutils import Matrix, Vector +from mathutils import Matrix, Vector, geometry from mathutils.geometry import intersect_line_line from mathutils.kdtree import KDTree @@ -1243,9 +1243,7 @@ class SnapManager: @staticmethod def _redraw_viewport() -> None: """Force 3D viewport redraw.""" - for area in bpy.context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports() def build_snap_cache( self, context: bpy.types.Context, active_obj: bpy.types.Object, include_active: bool = False @@ -1287,7 +1285,7 @@ class SnapManager: continue coords = np.empty(vertex_count * 3, dtype=np.float32) - mesh.vertices.foreach_get("co", coords) # type: ignore[arg-type] + mesh.vertices.foreach_get("co", coords) coords = coords.reshape(-1, 3) matrix = np.array(obj_eval.matrix_world, dtype=np.float32) diff --git a/src/bonsai/bonsai/bim/module/drawing/helper.py b/src/bonsai/bonsai/bim/module/drawing/helper.py index 7dce81359d..d4895410cb 100644 --- a/src/bonsai/bonsai/bim/module/drawing/helper.py +++ b/src/bonsai/bonsai/bim/module/drawing/helper.py @@ -456,7 +456,8 @@ def format_distance( tx_dist = fmt % d_cm else: - tx_dist = fmt % value + assert f"Unexpected unit_system - '{unit_system}'." + # tx_dist = fmt % value return tx_dist diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 582c7b062b..45f67b0769 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -42,6 +42,7 @@ import bmesh import bpy import ifcopenshell import ifcopenshell.api.document +import ifcopenshell.api.geometry import ifcopenshell.api.pset import ifcopenshell.api.style import ifcopenshell.geom @@ -1425,6 +1426,7 @@ class CreateDrawing(bpy.types.Operator): "/Pset_.*Common/.Status", "EPset_Status.Status", "EPset_Status.UserDefinedStatus", + "Material.Name", ] group = root.find("{http://www.w3.org/2000/svg}g") @@ -3304,9 +3306,8 @@ class AddTextLiteral(bpy.types.Operator): attr.data_type = "string" attr.string_value = literal_attr_values[attr_name] - box_alignment_mask = [False] * 9 - box_alignment_mask[6] = True # bottom_left box_alignment - literal_props.box_alignment = box_alignment_mask + literal_props.align_vertical = "bottom" + literal_props.align_horizontal = "left" return {"FINISHED"} @@ -3364,57 +3365,55 @@ class OrderTextLiteralDown(bpy.types.Operator): return {"FINISHED"} -# Ifc Operator is unnecessary, because suboperator is handling IFC changes. -class AssignSelectedObjectAsProduct(bpy.types.Operator): +class AssignSelectedObjectAsProduct(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_selected_as_product" bl_label = "Assign Selected Object As Product" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): - if len(context.selected_objects) != 2: - cls.poll_message_set("2 objects need to be selected") + if len(context.selected_objects) < 2: + cls.poll_message_set("At least 2 objects need to be selected") return False return True - def execute(self, context): - assert bpy.context.view_layer + def _execute(self, context): objs = context.selected_objects[:] - obj1, obj2 = objs - element1 = tool.Ifc.get_entity(obj1) - element2 = tool.Ifc.get_entity(obj2) - assert element1 and element2 + ifc_objs = [(o, tool.Ifc.get_entity(o)) for o in objs if tool.Ifc.get_entity(o)] - # Check if at least one object is an IfcAnnotation - is_annotation1 = element1.is_a("IfcAnnotation") - is_annotation2 = element2.is_a("IfcAnnotation") + annotations = [(o, e) for o, e in ifc_objs if e.is_a("IfcAnnotation")] + non_annotations = [(o, e) for o, e in ifc_objs if not e.is_a("IfcAnnotation")] - if not (is_annotation1 or is_annotation2): - self.report({"ERROR"}, "At least one of the selected objects must be IfcAnnotation.") + if not annotations: + self.report({"ERROR"}, "At least one selected object must be an IfcAnnotation.") return {"CANCELLED"} - # If both are annotations, use the currently active object as relating product - if is_annotation1 and is_annotation2: + if len(non_annotations) == 1: + # One product, one or more annotations — assign all annotations to the product. + product = non_annotations[0][1] + elif len(non_annotations) == 0 and len(annotations) == 2: + # Both objects are annotations — use the non-active one as the relating product. active_obj = context.active_object - if active_obj == obj1: - other_selected_object = obj1 - bpy.context.view_layer.objects.active = obj2 + if annotations[0][0] == active_obj: + annotation_obj, annotation = annotations[0] + product = annotations[1][1] else: - other_selected_object = obj2 - bpy.context.view_layer.objects.active = obj1 - # If only one is an annotation, make it the active object - elif is_annotation1: - other_selected_object = obj2 - bpy.context.view_layer.objects.active = obj1 + annotation_obj, annotation = annotations[1] + product = annotations[0][1] + core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product) + tool.Blender.update_viewport() + return else: - other_selected_object = obj1 - bpy.context.view_layer.objects.active = obj2 + self.report( + {"ERROR"}, + "Select exactly one product object and one or more IfcAnnotation objects.", + ) + return {"CANCELLED"} - assert (active_obj := context.active_object) - props = tool.Drawing.get_object_assigned_product_props(active_obj) - props.relating_product = other_selected_object - bpy.ops.bim.edit_assigned_product() - return {"FINISHED"} + for annotation_obj, _ in annotations: + core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=annotation_obj, product=product) + + tool.Blender.update_viewport() class EditAssignedProduct(bpy.types.Operator, tool.Ifc.Operator): @@ -3886,8 +3885,7 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): image_filepath = Path(tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)) ifc_file = tool.Ifc.get() - params = {"check_existing": False} - image = load_image(abs_path.name, str(abs_path.parent), **params) + image = load_image(abs_path.name, str(abs_path.parent), check_existing=False) mesh = bpy.data.meshes.new(image_filepath.stem) obj = bpy.data.objects.new(image_filepath.stem, mesh) @@ -4177,10 +4175,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator): should_select = True break elif self.attribute_type == "box_alignment": - box_alignment_attr = next( - (attr for attr in literal.attributes if attr.name == "BoxAlignment"), None - ) - if box_alignment_attr and box_alignment_attr.string_value == self.literal_value: + if literal.get_box_alignment() == self.literal_value: should_select = True break diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index cc597a4802..57c182c02e 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -27,7 +27,6 @@ import ifcopenshell.api.pset import ifcopenshell.util.element from bpy.props import ( BoolProperty, - BoolVectorProperty, CollectionProperty, EnumProperty, FloatProperty, @@ -673,20 +672,6 @@ class BIMCameraProperties(PropertyGroup): return ortho_scale, aspect_ratio -DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2 -BOX_ALIGNMENT_POSITIONS = [ - "top-left", - "top-middle", - "top-right", - "middle-left", - "center", - "middle-right", - "bottom-left", - "bottom-middle", - "bottom-right", -] - - class ElementValueRow(PropertyGroup): """Represents a single element value row with category, key, and formatted value""" @@ -789,40 +774,38 @@ def get_category_items_with_counts(self, context): class LiteralProps(PropertyGroup): - def set_box_alignment(self, new_value): - markers = new_value.count(True) - if not markers: - return - - if markers > 1: - prev_value = self.get("box_alignment", DEFAULT_BOX_ALIGNMENT) - # looking for the first value changed to positive - first_changed_value = next((i for i in range(9) if new_value[i] and new_value[i] != prev_value[i]), None) - - # if nothing have changed we just keep the previous value - if first_changed_value is None: - return - new_value = [False] * 9 - new_value[first_changed_value] = True - - self["box_alignment"] = new_value - position_string = BOX_ALIGNMENT_POSITIONS[next(i for i in range(9) if new_value[i])] - self.attributes["BoxAlignment"].set_value(position_string) - - def get_box_alignment(self): - return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT) - attributes: CollectionProperty(name="Attributes", type=Attribute) - box_alignment: BoolVectorProperty( - name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT - ) ifc_definition_id: IntProperty(name="IFC definition ID", default=0) + align_horizontal: EnumProperty( + items=[ + ("left", "Left", "", "ALIGN_LEFT", 0), + ("middle", "Middle", "", "ALIGN_CENTER", 1), + ("right", "Right", "", "ALIGN_RIGHT", 2), + ], + default="left", + name="Horizontal Alignment", + ) + align_vertical: EnumProperty( + items=[ + ("top", "Top", "", "ALIGN_TOP", 0), + ("middle", "Middle", "", "ALIGN_MIDDLE", 1), + ("bottom", "Bottom", "", "ALIGN_BOTTOM", 2), + ], + default="middle", + name="Vertical Alignment", + ) + + def get_box_alignment(self) -> str: + alignment = self.align_vertical + "-" + self.align_horizontal + if alignment == "middle-middle": + alignment = "center" + return alignment def get_literal_edited_data(self) -> dict[str, str]: text_data = { "CurrentValue": self.attributes["Literal"].string_value, "Literal": self.attributes["Literal"].string_value, - "BoxAlignment": self.attributes["BoxAlignment"].string_value, + "BoxAlignment": self.get_box_alignment(), } return text_data @@ -860,12 +843,19 @@ class LiteralProps(PropertyGroup): if TYPE_CHECKING: attributes: bpy.types.bpy_prop_collection_idprop[Attribute] value: str - box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool] ifc_definition_id: int + align_horizontal: str + align_vertical: str element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow] category_for_adding: str +def update_text_alignment(self, context): + for literal_props in self.literals: + literal_props.align_horizontal = self.align_horizontal + literal_props.align_vertical = self.align_vertical + + class BIMTextProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing", default=False) literals: CollectionProperty(name="Literals", type=LiteralProps) @@ -899,6 +889,7 @@ class BIMTextProperties(PropertyGroup): ], default="left", name="Horizontal Alignment", + update=update_text_alignment, ) align_vertical: EnumProperty( items=[ @@ -908,6 +899,7 @@ class BIMTextProperties(PropertyGroup): ], default="middle", name="Vertical Alignment", + update=update_text_alignment, ) if TYPE_CHECKING: diff --git a/src/bonsai/bonsai/bim/module/drawing/shaders.py b/src/bonsai/bonsai/bim/module/drawing/shaders.py index 774002fea3..c050cf0f41 100644 --- a/src/bonsai/bonsai/bim/module/drawing/shaders.py +++ b/src/bonsai/bonsai/bim/module/drawing/shaders.py @@ -366,9 +366,6 @@ class BaseLinesShader(BaseShader): } """ - def __init__(self, gap_size=16): - super().__init__(gap_size=gap_size) - def glenable(self): super().glenable() diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 1b6a11cd75..c1809995a0 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -781,33 +781,10 @@ class BIM_PT_text(Panel): if other_attributes: bonsai.bim.helper.draw_attributes(other_attributes, box) - row = box.row(align=True) - cols = [row.column(align=True) for j in range(3)] - for j in range(9): - cols[j % 3].prop( - literal_props, - "box_alignment", - text="", - index=j, - icon="RADIOBUT_ON" if literal_props.box_alignment[j] else "RADIOBUT_OFF", - ) - - col = row.column(align=True) - alignment_label_row = col.row(align=True) - alignment_label_row.label(text=" Text box alignment:") - - box_alignment_value = ( - literal_props.attributes[ - next( - (idx for idx, attr in enumerate(literal_props.attributes) if attr.name == "BoxAlignment"), - -1, - ) - ].string_value - if any(attr.name == "BoxAlignment" for attr in literal_props.attributes) - else "N/A" - ) - - col.label(text=f" {box_alignment_value}") + row = box.row() + row.label(text="Alignment") + row.prop(literal_props, "align_horizontal", text="", expand=True) + row.prop(literal_props, "align_vertical", text="", expand=True) def draw(self, context): obj = context.active_object @@ -839,7 +816,6 @@ class BIM_PT_text(Panel): for i, literal_data in enumerate(text_data["Literals"]): box = self.layout.box() - box.label(text=f"Literal[{i}]:") # Combine both approaches: clickable attributes from PR #7292 and display from PR #7106 for attribute in literal_data: diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index f8389cb3af..7382b093f4 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1066,7 +1066,7 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator): cls.poll_message_set("Only available from Outliner.") return False - def execute(self, context): + def execute(self, context): # ty:ignore[override-of-final-method] if len(getattr(context, "selected_ids", [])) == 0: return {"FINISHED"} @@ -2289,7 +2289,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): elif obj in pprops.clipping_planes_objs: self.report({"ERROR"}, "Clipping planes cannot be edited") elif element: - if not obj.data: + if not obj.data or obj.type not in ("MESH", "CURVE"): self.report({"INFO"}, "No geometry to edit") elif tool.Geometry.is_locked(element): self.report({"ERROR"}, lock_error_message(obj.name)) diff --git a/src/bonsai/bonsai/bim/module/geometry/ui.py b/src/bonsai/bonsai/bim/module/geometry/ui.py index 2af216c7a1..e7912c7a77 100644 --- a/src/bonsai/bonsai/bim/module/geometry/ui.py +++ b/src/bonsai/bonsai/bim/module/geometry/ui.py @@ -515,6 +515,8 @@ class BIM_PT_derived_coordinates(Panel): return context.active_object is not None def draw(self, context): + assert context.active_object + props = tool.Model.get_model_props() if not DerivedCoordinatesData.is_loaded: DerivedCoordinatesData.load() @@ -529,10 +531,8 @@ class BIM_PT_derived_coordinates(Panel): row = self.layout.row(align=True) row.enabled = False - area_3d = next((area for area in context.screen.areas if area.type == "VIEW_3D"), None) - space_3d = next((space for space in area_3d.spaces if space.type == "VIEW_3D"), None) - if bpy.context.scene.BIMModelProperties.show_bounding_box: + if props.show_bounding_box: for axis, icon, idx in [("X", "STRIP_COLOR_01", 0), ("Y", "STRIP_COLOR_04", 1), ("Z", "STRIP_COLOR_05", 2)]: row.label(text="", icon=icon) row.prop(context.active_object, "dimensions", text=axis, index=idx) diff --git a/src/bonsai/bonsai/bim/module/georeference/prop.py b/src/bonsai/bonsai/bim/module/georeference/prop.py index 41035398e9..9b1f8d5f4d 100644 --- a/src/bonsai/bonsai/bim/module/georeference/prop.py +++ b/src/bonsai/bonsai/bim/module/georeference/prop.py @@ -139,7 +139,9 @@ def update_local_coordinates(self: "BIMGeoreferenceProperties", context: bpy.typ tool.Georeference.set_coordinates( "blender", ifcopenshell.util.geolocation.enh2xyz( - *local_coordinates, + local_coordinates[0], + local_coordinates[1], + local_coordinates[2], float(props.blender_offset_x), float(props.blender_offset_y), float(props.blender_offset_z), @@ -162,7 +164,9 @@ def update_map_coordinates(self: "BIMGeoreferenceProperties", context: bpy.types tool.Georeference.set_coordinates( "blender", ifcopenshell.util.geolocation.enh2xyz( - *local_coordinates, + local_coordinates[0], + local_coordinates[1], + local_coordinates[2], float(props.blender_offset_x), float(props.blender_offset_y), float(props.blender_offset_z), @@ -267,6 +271,8 @@ class BIMGeoreferenceProperties(PropertyGroup): x_axis_ordinate: str x_axis_is_null: bool + model_is_georeferenced: bool + model_crs: str model_origin: str model_origin_si: str model_project_north: str diff --git a/src/bonsai/bonsai/bim/module/gis/prop.py b/src/bonsai/bonsai/bim/module/gis/prop.py index 1e6ae61614..5971fa80a9 100644 --- a/src/bonsai/bonsai/bim/module/gis/prop.py +++ b/src/bonsai/bonsai/bim/module/gis/prop.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from typing import TYPE_CHECKING + +import bpy from bpy.props import BoolProperty, CollectionProperty, EnumProperty, StringProperty from bpy.types import PropertyGroup @@ -24,7 +27,7 @@ from bonsai.bim.prop import StrProperty class BIMCityJsonProperties(PropertyGroup): def get_lods(self, context): - global LODS_ENUM_ITEMS + global LODS_ENUM_ITEMS # ty: ignore[unresolved-global] LODS_ENUM_ITEMS = [(item.name, "LOD" + item.name, "Level of Detail " + item.name) for item in self.lods] return LODS_ENUM_ITEMS @@ -37,3 +40,13 @@ class BIMCityJsonProperties(PropertyGroup): lod: EnumProperty(name="LOD", description="", items=get_lods, options={"ANIMATABLE"}, default=None) is_lod_found: BoolProperty(name="Is LOD Found", default=False) load_after_convert: BoolProperty(name="Load After Converting", default=True) + + if TYPE_CHECKING: + input: str + output: str + name: str + split_lod: bool + lods: bpy.types.bpy_prop_collection_idprop[StrProperty] + lod: str + is_lod_found: bool + load_after_convert: bool diff --git a/src/bonsai/bonsai/bim/module/ifcgit/data.py b/src/bonsai/bonsai/bim/module/ifcgit/data.py index 92da517a1c..7dbd26b5e0 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/data.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/data.py @@ -21,65 +21,71 @@ class IfcGitData: @classmethod def load(cls): + repo = None + if bool(tool.Ifc.get()): + path_ifc = tool.Ifc.get_path() + if os.path.isfile(path_ifc): + repo = tool.IfcGit.repo_from_path(path_ifc) + cls.data = { - "repo": cls.repo(), - "remotes": cls.remotes(), - "branch_names": cls.branch_names(), - "remote_names": cls.remote_names(), - "remote_urls": cls.remote_urls(), + "repo": repo, + "remotes": repo.remotes if repo else None, + "branch_names": cls.branch_names(repo), + "tag_names": cls.tag_names(repo), + "remote_names": cls.remote_names(repo), + "remote_urls": {r.name: r.url for r in repo.remotes} if repo else {}, "path_ifc": cls.path_ifc(), "branches_by_hexsha": cls.branches_by_hexsha(), "tags_by_hexsha": cls.tags_by_hexsha(), - "name_ifc": cls.name_ifc(), + "name_ifc": cls.name_ifc(repo), "dir_name": cls.dir_name(), "base_name": cls.base_name(), - "working_dir": cls.working_dir(), - "untracked_files": cls.untracked_files(), - "is_detached": cls.is_detached(), - "active_branch_name": cls.active_branch_name(), - "is_dirty": cls.is_dirty(), - "commit": cls.commit(), - "current_revision": cls.current_revision(), + "working_dir": repo.working_dir if repo else None, + "ifc_is_untracked": cls.ifc_is_untracked(repo), + "is_detached": repo.head.is_detached if repo else None, + "active_branch_name": repo.active_branch.name if repo and not repo.head.is_detached else None, + "is_dirty": cls.is_dirty(repo), + "current_revision": cls.current_revision(repo), "git_exe": cls.git_exe(), "ifcmerge_exe": cls.ifcmerge_exe(), } cls.is_loaded = True @classmethod - def repo(cls): - if bool(tool.Ifc.get()): - path_ifc = tool.Ifc.get_path() - if os.path.isfile(path_ifc): - return tool.IfcGit.repo_from_path(path_ifc) - return None + def branch_names(cls, repo): + if not repo or not repo.heads: + return [] + names = sorted([b.name for b in repo.branches]) + if "main" in names: + names.remove("main") + names = ["main"] + names + if repo.remotes: + for remote in repo.remotes: + for ref in remote.refs: + names.append(ref.name) + return names @classmethod - def remotes(cls): - if cls.repo(): - return cls.repo().remotes - return None + def tag_names(cls, repo): + if not repo: + return [] + return [t.name for t in repo.tags] @classmethod - def branch_names(cls): - return [] - - @classmethod - def remote_names(cls): - return [] - - @classmethod - def remote_urls(cls): - result = {} - if cls.repo(): - for remote in cls.repo().remotes: - result[remote.name] = remote.url - return result + def remote_names(cls, repo): + if not repo: + return [] + names = sorted([r.name for r in repo.remotes]) + if "origin" in names: + names.remove("origin") + names = ["origin"] + names + return names @classmethod def path_ifc(cls): path_ifc = tool.Ifc.get_path() if os.path.isfile(path_ifc): - return tool.Ifc.get_path() + return path_ifc return None @classmethod @@ -88,7 +94,8 @@ class IfcGitData: if tool.IfcGitRepo.repo.branches: return tool.IfcGit.branches_by_hexsha(tool.IfcGitRepo.repo) except AttributeError: - return {} + pass + return {} @classmethod def tags_by_hexsha(cls): @@ -97,12 +104,11 @@ class IfcGitData: return {} @classmethod - def name_ifc(cls): - if bool(tool.Ifc.get()): + def name_ifc(cls, repo): + if bool(tool.Ifc.get()) and repo: path_ifc = tool.Ifc.get_path() - if tool.IfcGitRepo.repo and os.path.isfile(path_ifc): - working_dir = tool.IfcGitRepo.repo.working_dir - return os.path.relpath(path_ifc, working_dir) + if os.path.isfile(path_ifc): + return os.path.relpath(path_ifc, repo.working_dir) return None @classmethod @@ -122,49 +128,28 @@ class IfcGitData: return None @classmethod - def working_dir(cls): - if cls.repo(): - return cls.repo().working_dir + def ifc_is_untracked(cls, repo): + """Return True if the IFC file exists in the repo but has not been added to git.""" + if not repo: + return False + path_ifc = tool.Ifc.get_path() + if not os.path.isfile(path_ifc): + return False + return not bool(repo.git.ls_files(path_ifc)) @classmethod - def untracked_files(cls): - if cls.repo(): - return cls.repo().untracked_files - return [] - - @classmethod - def is_detached(cls): - if cls.repo(): - return cls.repo().head.is_detached - - @classmethod - def active_branch_name(cls): - if cls.repo() and not cls.is_detached(): - return cls.repo().active_branch.name - - @classmethod - def is_dirty(cls): - if cls.repo() and cls.git_exe(): + def is_dirty(cls, repo): + if repo and cls.git_exe(): path_ifc = tool.Ifc.get_path() if os.path.isfile(path_ifc): - return cls.repo().is_dirty(path=path_ifc) + return repo.is_dirty(path=path_ifc) return False @classmethod - def commit(cls): + def current_revision(cls, repo): props = tool.IfcGit.get_ifcgit_props() - if cls.repo() and len(props.ifcgit_commits) > 0: - item = props.ifcgit_commits[props.commit_index] - try: - return cls.repo().commit(rev=item.hexsha) - except ValueError: - return - - @classmethod - def current_revision(cls): - props = tool.IfcGit.get_ifcgit_props() - if cls.repo() and cls.repo().head.is_valid() and len(props.ifcgit_commits) > 0: - return tool.IfcGitRepo.repo.commit() + if repo and repo.head.is_valid() and len(props.ifcgit_commits) > 0: + return repo.commit() @classmethod def git_exe(cls): diff --git a/src/bonsai/bonsai/bim/module/ifcgit/operator.py b/src/bonsai/bonsai/bim/module/ifcgit/operator.py index 829a0a62a2..5f01ef78ea 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/operator.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/operator.py @@ -120,11 +120,11 @@ class CommitChanges(bpy.types.Operator): if props.commit_message == "": return False if repo: - if props.new_branch_name in [branch.name for branch in repo.branches]: + if props.new_branch_name in IfcGitData.data["branch_names"]: cls.poll_message_set("Branch already exists!") return False elif not tool.IfcGit.is_valid_ref_format(props.new_branch_name): - if repo.head.is_detached: + if IfcGitData.data["is_detached"]: cls.poll_message_set("Branch name is invalid or empty!") return False elif props.new_branch_name != "": @@ -134,10 +134,17 @@ class CommitChanges(bpy.types.Operator): def execute(self, context): - repo = IfcGitData.data["repo"] - core.commit_changes(tool.IfcGit, tool.Ifc, repo) - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + props = tool.IfcGit.get_ifcgit_props() + commit_message = props.commit_message + new_branch_name = props.new_branch_name + core.commit_changes(tool.IfcGit, tool.Ifc, commit_message, new_branch_name) + props.new_branch_name = "" + props.commit_message = "" + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() + IfcGitData.load() + if new_branch_name: + props.display_branch = new_branch_name return {"FINISHED"} @@ -157,7 +164,7 @@ class AddTag(bpy.types.Operator): repo = IfcGitData.data["repo"] if repo and ( not tool.IfcGit.is_valid_ref_format(props.new_tag_name) - or props.new_tag_name in [tag.name for tag in repo.tags] + or props.new_tag_name in IfcGitData.data["tag_names"] ): return False return True @@ -165,8 +172,12 @@ class AddTag(bpy.types.Operator): def execute(self, context): repo = IfcGitData.data["repo"] - core.add_tag(tool.IfcGit, repo) - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + props = tool.IfcGit.get_ifcgit_props() + item = props.ifcgit_commits[props.commit_index] + core.add_tag(tool.IfcGit, repo, item.hexsha, props.new_tag_name, props.new_tag_message) + props.new_tag_name = "" + props.new_tag_message = "" + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() return {"FINISHED"} @@ -183,7 +194,7 @@ class DeleteTag(bpy.types.Operator): repo = IfcGitData.data["repo"] core.delete_tag(tool.IfcGit, repo, self.tag_name) - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() return {"FINISHED"} @@ -205,8 +216,7 @@ class RefreshGit(bpy.types.Operator): def execute(self, context): - repo = IfcGitData.data["repo"] - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() tool.IfcGit.decolourise() return {"FINISHED"} @@ -284,7 +294,7 @@ class Merge(bpy.types.Operator): def execute(self, context): - if core.merge_branch(tool.IfcGit, tool.Ifc, self): + if core.merge_branch(tool.IfcGit, tool.Ifc, self) is not False: refresh() return {"FINISHED"} else: @@ -314,9 +324,9 @@ class Fetch(bpy.types.Operator): def execute(self, context): props = tool.IfcGit.get_ifcgit_props() - repo = IfcGitData.data["repo"] - remote = repo.remotes[props.select_remote] - remote.fetch() + core.fetch(tool.IfcGit, props.select_remote) + core.refresh_revision_list(tool.IfcGit, tool.Ifc) + refresh() return {"FINISHED"} @@ -336,7 +346,7 @@ class AddRemote(bpy.types.Operator): not repo or not tool.IfcGit.is_valid_ref_format(props.remote_name) or not props.remote_url - or props.remote_name in [remote.name for remote in repo.remotes] + or props.remote_name in IfcGitData.data["remote_names"] ): return False return True @@ -344,8 +354,11 @@ class AddRemote(bpy.types.Operator): def execute(self, context): repo = IfcGitData.data["repo"] - core.add_remote(tool.IfcGit, repo) - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + props = tool.IfcGit.get_ifcgit_props() + core.add_remote(tool.IfcGit, repo, props.remote_name, props.remote_url) + props.remote_name = "" + props.remote_url = "" + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() return {"FINISHED"} @@ -360,8 +373,19 @@ class DeleteRemote(bpy.types.Operator): def execute(self, context): repo = IfcGitData.data["repo"] - core.delete_remote(tool.IfcGit, repo) - core.refresh_revision_list(tool.IfcGit, repo, tool.Ifc) + props = tool.IfcGit.get_ifcgit_props() + remote_name = props.select_remote + if props.display_branch.startswith(remote_name + "/"): + active = IfcGitData.data["active_branch_name"] + if active: + props.display_branch = active + else: + local_branches = [b for b in IfcGitData.data["branch_names"] if "/" not in b] + if local_branches: + props.display_branch = local_branches[0] + core.delete_remote(tool.IfcGit, repo, remote_name) + tool.IfcGit.select_first_remote() + core.refresh_revision_list(tool.IfcGit, tool.Ifc) refresh() return {"FINISHED"} @@ -375,8 +399,8 @@ class ObjectLog(bpy.types.Operator): @classmethod def poll(cls, context): - if not (obj := context.active_object): - cls.poll_message_set("No Active Object") + if not (obj := context.active_object) or not obj.select_get(): + cls.poll_message_set("No selected object") elif not tool.Blender.get_ifc_definition_id(obj): cls.poll_message_set("Active Object doesn't have an IFC definition") else: diff --git a/src/bonsai/bonsai/bim/module/ifcgit/prop.py b/src/bonsai/bonsai/bim/module/ifcgit/prop.py index cba3ca632c..865cb4eacf 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/prop.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/prop.py @@ -17,28 +17,14 @@ from bonsai.bim.module.ifcgit.data import IfcGitData def git_branches(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: # NOTE "Python must keep a reference to the strings returned by # the callback or Blender will misbehave or even crash" - IfcGitData.data["branch_names"] = sorted([branch.name for branch in IfcGitData.data["repo"].heads]) - - if "main" in IfcGitData.data["branch_names"]: - IfcGitData.data["branch_names"].remove("main") - IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"] - - if IfcGitData.data["remotes"]: - for remote in IfcGitData.data["remotes"]: - for remote_branch in remote.refs: - IfcGitData.data["branch_names"].append(remote_branch.name) - - return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]] + # Branch list (local + remote, main first) is computed once in IfcGitData.load() + IfcGitData.make_sure_is_loaded() + return [(name, name, name) for name in IfcGitData.data["branch_names"]] def git_remotes(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS: - IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]]) - - if "origin" in IfcGitData.data["remote_names"]: - IfcGitData.data["remote_names"].remove("origin") - IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"] - - return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]] + IfcGitData.make_sure_is_loaded() + return [(name, name, name) for name in IfcGitData.data["remote_names"]] def update_revlist(self: "IfcGitProperties", context: bpy.types.Context) -> None: @@ -90,6 +76,7 @@ class IfcGitListItem(PropertyGroup): name="Commit Message", default="", ) + committed_date: IntProperty(name="Committed Date", default=0) tags: CollectionProperty(type=IfcGitTag, name="List of revision tags") if TYPE_CHECKING: @@ -98,6 +85,7 @@ class IfcGitListItem(PropertyGroup): author_name: str author_email: str message: str + committed_date: int tags: bpy.types.bpy_prop_collection_idprop[IfcGitTag] diff --git a/src/bonsai/bonsai/bim/module/ifcgit/ui.py b/src/bonsai/bonsai/bim/module/ifcgit/ui.py index d8b7357e7b..6ac4aceb74 100644 --- a/src/bonsai/bonsai/bim/module/ifcgit/ui.py +++ b/src/bonsai/bonsai/bim/module/ifcgit/ui.py @@ -52,7 +52,7 @@ class IFCGIT_PT_panel(bpy.types.Panel): if IfcGitData.data["repo"] and os.path.exists(IfcGitData.data["repo"].git_dir): name_ifc = IfcGitData.data["name_ifc"] row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM") - if name_ifc in IfcGitData.data["untracked_files"]: + if IfcGitData.data["ifc_is_untracked"]: row.operator( "ifcgit.addfile", text="Add '" + name_ifc + "' to repository", @@ -216,13 +216,7 @@ class COMMIT_UL_List(bpy.types.UIList): ): current_revision = IfcGitData.data["current_revision"] - - # TODO Figure how this "item" can be acesse in "data.py" - # so it's possible to move the ".commit" - try: - commit = IfcGitData.data["repo"].commit(rev=item.hexsha) - except ValueError: - return + current_hexsha = current_revision.hexsha if current_revision else None lookup = IfcGitData.data["branches_by_hexsha"] refs = "" @@ -236,11 +230,11 @@ class COMMIT_UL_List(bpy.types.UIList): for tag in lookup[item.hexsha]: refs += "{" + tag.name + "} " - if commit == current_revision: - layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME") + if item.hexsha == current_hexsha: + layout.label(text="[HEAD] " + refs + item.message.split("\n")[0], icon="DECORATE_KEYFRAME") else: - layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE") - layout.label(text=time.strftime("%c", time.localtime(commit.committed_date))) + layout.label(text=refs + item.message.split("\n")[0], icon="DECORATE_ANIMATE") + layout.label(text=time.strftime("%c", time.localtime(item.committed_date))) def draw_filter(self, context, layout): diff --git a/src/bonsai/bonsai/bim/module/material/data.py b/src/bonsai/bonsai/bim/module/material/data.py index 543a44824b..6d9e568087 100644 --- a/src/bonsai/bonsai/bim/module/material/data.py +++ b/src/bonsai/bonsai/bim/module/material/data.py @@ -102,7 +102,6 @@ class MaterialsData: if (style_name := s.Name) is not None ] results = natsorted(results, key=lambda i: i[1]) - results.insert(0, ("-", "No Surface Style", "")) return results @classmethod diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index e0761bf416..7587a7e4ed 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -210,14 +210,15 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_material_to_selected" bl_label = "Assign Material To Selected" bl_description = ( - "Assign currently selected material in Materials UI to the selected objects.\n\n" - "ALT+CLICK to assign material as a usage." + "Assign currently selected material in Materials UI to the selected objects.\n" + "Occurrences automatically get usages for layer/profile sets.\n\n" + "ALT+CLICK to assign without a usage." ) bl_options = {"REGISTER", "UNDO"} material: bpy.props.IntProperty(name="Material IFC ID") - assign_as_usage: bpy.props.BoolProperty( - name="Assign Material As A Usage", - default=False, + should_auto_assign_usage: bpy.props.BoolProperty( + name="Auto Assign Usage", + default=True, options={"SKIP_SAVE"}, ) @@ -230,25 +231,19 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator): def invoke(self, context, event): if event.type == "LEFTMOUSE" and event.alt: - material_class = tool.Ifc.get().by_id(self.material).is_a() - if material_class not in ("IfcMaterialProfileSet", "IfcMaterialLayerSet"): - self.report({"ERROR"}, f"{material_class} cannot be assigned as a usage.") - return {"CANCELLED"} - self.assign_as_usage = True + self.should_auto_assign_usage = False return self.execute(context) def _execute(self, context): material = tool.Ifc.get().by_id(self.material) objects = tool.Blender.get_selected_objects() - material_type = material.is_a() - if self.assign_as_usage: - material_type += "Usage" core.assign_material( tool.Ifc, tool.Material, - material_type=material_type, + material_type=material.is_a(), objects=objects, material=material, + should_auto_assign_usage=self.should_auto_assign_usage, ) @@ -722,7 +717,11 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): self.props.material_set_item_material = str(material_set_item.Material.id()) self.props.material_set_item_attributes.clear() - bonsai.bim.helper.import_attributes(material_set_item, self.props.material_set_item_attributes) + bonsai.bim.helper.import_attributes( + material_set_item, + self.props.material_set_item_attributes, + callback=self.import_attributes_callback, + ) if material_set_item.is_a("IfcMaterialProfile"): if material_set_item.Profile and material_set_item.Profile.ProfileName: @@ -730,6 +729,29 @@ class EnableEditingMaterialSetItem(bpy.types.Operator): return {"FINISHED"} + def import_attributes_callback( + self, name: str, prop: Union["Attribute", None], data: dict[str, Any] + ) -> None | Literal[True]: + if data["type"] != "IfcMaterialLayer" or name != "IsVentilated" or not prop: + return None + + # Keep null semantics unchanged on export, but avoid an empty UI selection. + prop.data_type = "enum" + prop.special_type = "LOGICAL" + prop.enum_items = json.dumps(("TRUE", "FALSE", "UNKNOWN")) + + value = data[name] + if value == "UNKNOWN": + prop.enum_value = "UNKNOWN" + elif value is None: + # Keep visible default as FALSE, but preserve null semantics on save. + prop.enum_value = "FALSE" + prop.is_null = True + else: + prop.enum_value = "TRUE" if value else "FALSE" + + return True + class DisableEditingMaterialSetItem(bpy.types.Operator): bl_idname = "bim.disable_editing_material_set_item" diff --git a/src/bonsai/bonsai/bim/module/material/ui.py b/src/bonsai/bonsai/bim/module/material/ui.py index f743306767..db16755aad 100644 --- a/src/bonsai/bonsai/bim/module/material/ui.py +++ b/src/bonsai/bonsai/bim/module/material/ui.py @@ -118,12 +118,17 @@ class BIM_PT_materials(Panel): row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id row.operator("bim.disable_editing_material", text="", icon="CANCEL") elif self.props.editing_material_type == "STYLE": - row = self.layout.row(align=True) - row.prop(self.props, "contexts", text="") - prop_with_search(row, self.props, "styles", text="") - row = self.layout.row(align=True) - row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK") - row.operator("bim.disable_editing_material", text="", icon="CANCEL") + if MaterialsData.data["styles"]: + row = self.layout.row(align=True) + row.prop(self.props, "contexts", text="") + prop_with_search(row, self.props, "styles", text="") + row = self.layout.row(align=True) + row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK") + row.operator("bim.disable_editing_material", text="", icon="CANCEL") + else: + row = self.layout.row(align=True) + row.label(text="No Styles Found") + row.operator("bim.disable_editing_material", text="", icon="CANCEL") class BIM_PT_object_material(Panel): diff --git a/src/bonsai/bonsai/bim/module/misc/__init__.py b/src/bonsai/bonsai/bim/module/misc/__init__.py index a71e467b7f..c7e2e690d8 100644 --- a/src/bonsai/bonsai/bim/module/misc/__init__.py +++ b/src/bonsai/bonsai/bim/module/misc/__init__.py @@ -21,6 +21,11 @@ import bpy from . import operator, prop, ui classes = ( + operator.ImportQuickFavorites, + operator.RemoveQuickFavoritesItem, + operator.MoveQuickFavoritesItem, + operator.AddQuickFavoritesItem, + operator.ConfirmQuickFavoriteOperator, operator.DrawSystemArrows, operator.GetConnectedSystemElements, operator.IfcSverchokUseBonsaiFile, @@ -28,8 +33,12 @@ classes = ( operator.SetOverrideColour, operator.SnapSpacesTogether, operator.SplitAlongEdge, + prop.QuickFavoriteEnumItem, + prop.QuickFavoriteProperty, + prop.QuickFavoritesItem, prop.BIMMiscProperties, ui.BIM_PT_misc_utilities, + ui.BIM_PT_quick_favorites_manager, ) diff --git a/src/bonsai/bonsai/bim/module/misc/data.py b/src/bonsai/bonsai/bim/module/misc/data.py new file mode 100644 index 0000000000..eb697d0bff --- /dev/null +++ b/src/bonsai/bonsai/bim/module/misc/data.py @@ -0,0 +1,48 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + +from typing import Any + +import bpy + + +def refresh() -> None: + QuickFavoritesData.is_loaded = False + + +class QuickFavoritesData: + data: dict[str, Any] = {} + is_loaded = False + + @classmethod + def load(cls) -> None: + cls.data = { + "operators": cls.operators(), + } + cls.is_loaded = True + + @classmethod + def operators(cls) -> list[str]: + items: list[str] = [] + for module_name in dir(bpy.ops): + module = getattr(bpy.ops, module_name) + for op_name in dir(module): + op = getattr(module, op_name) + bl_label = op.get_rna_type().name + items.append(f"{module_name}.{op_name} - {bl_label}") + return items diff --git a/src/bonsai/bonsai/bim/module/misc/operator.py b/src/bonsai/bonsai/bim/module/misc/operator.py index 02cc56834b..33da9a05dc 100644 --- a/src/bonsai/bonsai/bim/module/misc/operator.py +++ b/src/bonsai/bonsai/bim/module/misc/operator.py @@ -30,6 +30,9 @@ import bonsai.core.misc as core import bonsai.core.root import bonsai.tool as tool +if TYPE_CHECKING: + from bpy.stub_internal import rna_enums + class SetOverrideColour(bpy.types.Operator): bl_idname = "bim.set_override_colour" @@ -41,10 +44,11 @@ class SetOverrideColour(bpy.types.Operator): return context.selected_objects def execute(self, context): + props = tool.Misc.get_misc_props() for obj in context.selected_objects: - obj.color = context.scene.BIMMiscProperties.override_colour - area = next(area for area in context.screen.areas if area.type == "VIEW_3D") - area.spaces[0].shading.color_type = "OBJECT" + obj.color = props.override_colour + assert (space := tool.Blender.get_view3d_space()) + space.shading.color_type = "OBJECT" return {"FINISHED"} @@ -351,6 +355,151 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator): return matrix +class ConfirmQuickFavoriteOperator(bpy.types.Operator): + bl_idname = "bim.confirm_quick_favorite_operator" + bl_label = "Confirm Operator" + bl_options = {"REGISTER", "UNDO"} + index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + index: int + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Misc.get_misc_props() + fav = props.quick_favorites[self.index] + rna = fav.get_searched_operator() + + if rna is None: + self.report({"INFO"}, "No operator entered for search.") + return {"CANCELLED"} + + fav.operator_id = tool.Blender.operator_idname_to_py(rna.identifier) + fav.label = rna.name + fav.properties.clear() + has_skipped = False + for p in rna.properties: + # skip silently, e.g. `rna_type` is a PointerProperty + if isinstance(p, bpy.types.PointerProperty): + continue + if isinstance(p, (bpy.types.FloatProperty, bpy.types.BoolProperty, bpy.types.IntProperty)) and p.is_array: + print(f"Array property '{p.identifier}' is not supported, skipping.") + has_skipped = True + continue + item = fav.properties.add() + item.name = p.identifier + item.display_name = p.name + if isinstance(p, bpy.types.FloatProperty): + item.value_prop = "float_value" + item.float_value = p.default + elif isinstance(p, bpy.types.BoolProperty): + item.value_prop = "bool_value" + item.bool_value = p.default + elif isinstance(p, bpy.types.IntProperty): + item.value_prop = "int_value" + item.int_value = p.default + elif isinstance(p, bpy.types.EnumProperty): + item.value_prop = "enum_value" + item.set_enum_items([(e.identifier, e.name, e.description) for e in p.enum_items]) + item.enum_value = p.default + elif isinstance(p, bpy.types.StringProperty): + item.value_prop = "string_value" + item.string_value = p.default + else: + print(f"Unhandled property type {type(p).__name__} for '{p.identifier}', skipping.") + has_skipped = True + if has_skipped: + self.report({"WARNING"}, "Some properties were skipped, see the system console for details.") + return {"FINISHED"} + + +class ImportQuickFavorites(bpy.types.Operator): + bl_idname = "bim.import_quick_favorites" + bl_label = "Import Quick Favorites" + bl_description = "Import operators from Blender's Quick Favorites menu, including their configured properties" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Misc.get_misc_props() + props.quick_favorites.clear() + + has_missing_props = False + for i, qf in enumerate(tool.Misc.QuickFavorites.get_quick_favorites()): + fav = props.quick_favorites.add() + fav.label = qf.ui_name + fav.search = qf.op_idname_py + bpy.ops.bim.confirm_quick_favorite_operator(index=i) + fav.label = qf.ui_name or fav.label + + for prop in fav.properties: + prop.is_active = prop.name in qf.props + + for key, value in qf.props.items(): + if key not in fav.properties: + print(f"Property '{key}' not found in operator '{qf.op_idname_py}'.") + has_missing_props = True + continue + item = fav.properties[key] + item.set_value(value) + + if has_missing_props: + self.report( + {"WARNING"}, "Some properties were not found during import, see the system console for details." + ) + return {"FINISHED"} + + +class MoveQuickFavoritesItem(bpy.types.Operator): + bl_idname = "bim.move_quick_favorites_item" + bl_label = "Move Quick Favorites Item" + bl_options = {"REGISTER", "UNDO"} + index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + direction: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=[("UP", "Up", ""), ("DOWN", "Down", "")] + ) + + if TYPE_CHECKING: + index: int + direction: Literal["UP", "DOWN"] + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Misc.get_misc_props() + total = len(props.quick_favorites) + new_index = self.index - 1 if self.direction == "UP" else self.index + 1 + if 0 <= new_index < total: + props.quick_favorites.move(self.index, new_index) + return {"FINISHED"} + + +class RemoveQuickFavoritesItem(bpy.types.Operator): + bl_idname = "bim.remove_quick_favorites_item" + bl_label = "Remove Quick Favorites Item" + bl_options = {"REGISTER", "UNDO"} + index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + index: int + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Misc.get_misc_props() + props.quick_favorites.remove(self.index) + return {"FINISHED"} + + +class AddQuickFavoritesItem(bpy.types.Operator): + bl_idname = "bim.add_quick_favorites_item" + bl_label = "Add Quick Favorites Item" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Misc.get_misc_props() + fav = props.quick_favorites.add() + fav.search = "bim.select_query_elements" + index = len(props.quick_favorites) - 1 + bpy.ops.bim.confirm_quick_favorite_operator(index=index) + fav.properties["query"].string_value = "IfcWall" + return {"FINISHED"} + + class IfcSverchokUseBonsaiFile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.ifcsverchok_use_bonsai_file" bl_label = "Use Bonsai IFC File" diff --git a/src/bonsai/bonsai/bim/module/misc/prop.py b/src/bonsai/bonsai/bim/module/misc/prop.py index f596f22fc6..ddeda73b88 100644 --- a/src/bonsai/bonsai/bim/module/misc/prop.py +++ b/src/bonsai/bonsai/bim/module/misc/prop.py @@ -16,19 +16,140 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from typing import TYPE_CHECKING, Any, Literal, cast, get_args + +import bpy from bpy.props import ( + BoolProperty, + CollectionProperty, + EnumProperty, + FloatProperty, FloatVectorProperty, IntProperty, + StringProperty, ) from bpy.types import PropertyGroup +from bonsai.bim.module.misc.data import QuickFavoritesData + +QuickFavoriteValueType = Literal["float_value", "bool_value", "int_value", "string_value", "enum_value"] + + +class QuickFavoriteEnumItem(PropertyGroup): + name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration] + display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration] + description: StringProperty(name="Description", default="") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + name: str + display_name: str + description: str + + +def get_enum_items(self: "QuickFavoriteProperty", context: bpy.types.Context | None) -> list[tuple[str, str, str]]: + return [(item.name, item.display_name, item.description) for item in self.enum_items] + + +class QuickFavoriteProperty(PropertyGroup): + name: StringProperty(name="Name", default="") # pyright: ignore[reportRedeclaration] + display_name: StringProperty(name="Display Name", default="") # pyright: ignore[reportRedeclaration] + value_prop: EnumProperty( # pyright: ignore[reportRedeclaration] + name="Value Prop", + items=tuple((v, v, "") for v in get_args(QuickFavoriteValueType)), + ) + string_value: StringProperty(name="String Value", default="") # pyright: ignore[reportRedeclaration] + float_value: FloatProperty(name="Float Value", default=0.0) # pyright: ignore[reportRedeclaration] + int_value: IntProperty(name="Int Value", default=0) # pyright: ignore[reportRedeclaration] + bool_value: BoolProperty(name="Bool Value", default=False) # pyright: ignore[reportRedeclaration] + enum_value: EnumProperty(name="Enum Value", items=get_enum_items) # pyright: ignore[reportRedeclaration] + enum_items: CollectionProperty(type=QuickFavoriteEnumItem) # pyright: ignore[reportRedeclaration] + is_active: BoolProperty( # pyright: ignore[reportRedeclaration] + name="Is Active", + description="Only active properties will be added to the operator when invoked from Quick Favorites", + default=False, + ) + + def set_value(self, value: Any) -> None: + setattr(self, self.value_prop, value) + + def set_enum_items(self, items: list[tuple[str, str, str]]) -> None: + self.enum_items.clear() + for identifier, name, description in items: + item = self.enum_items.add() + item.name = identifier + item.display_name = name + item.description = description + + if TYPE_CHECKING: + name: str + display_name: str + value_prop: QuickFavoriteValueType + string_value: str + float_value: float + int_value: int + bool_value: bool + enum_value: str + enum_items: bpy.types.bpy_prop_collection_idprop[QuickFavoriteEnumItem] + is_active: bool + + +def get_operator_suggestions(self: "QuickFavoritesItem", context: bpy.types.Context, edit_text: str) -> list[str]: + if not QuickFavoritesData.is_loaded: + QuickFavoritesData.load() + return QuickFavoritesData.data["operators"] + + +class QuickFavoritesItem(PropertyGroup): + is_expanded: BoolProperty(name="Is Expanded", default=False) # pyright: ignore[reportRedeclaration] + search: StringProperty( # pyright: ignore[reportRedeclaration] + name="Search", + default="", + search=get_operator_suggestions, + # Resetting `search_options`, allowing users only to use suggestions. + search_options=set(), + ) + properties: CollectionProperty(type=QuickFavoriteProperty) # pyright: ignore[reportRedeclaration] + operator_id: StringProperty( # pyright: ignore[reportRedeclaration] + name="Operator ID", + default="", + ) + label: StringProperty( # pyright: ignore[reportRedeclaration] + name="Label", + description="Label that will be used in Quick Favorites for this operator", + default="", + ) + + def get_searched_operator(self) -> bpy.types.Struct | None: + if not self.search: + return None + search_label = self.search + name = search_label.split(" - ", 1)[0] + module, func = name.split(".", 1) + op = getattr(getattr(bpy.ops, module), func) + rna = cast(bpy.types.Struct, op.get_rna_type()) + return rna + + if TYPE_CHECKING: + is_expanded: bool + search: str + """Internal property set when confirming results of the search field""" + properties: bpy.types.bpy_prop_collection_idprop[QuickFavoriteProperty] + operator_id: str + label: str + class BIMMiscProperties(PropertyGroup): - total_storeys: IntProperty( + total_storeys: IntProperty( # pyright: ignore[reportRedeclaration] name="Total Storeys", description="Number of storeys above object's storey to take into account for resizing", default=1, ) - override_colour: FloatVectorProperty( + override_colour: FloatVectorProperty( # pyright: ignore[reportRedeclaration] name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4 ) + quick_favorites: CollectionProperty(type=QuickFavoritesItem) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + total_storeys: int + override_colour: tuple[float, float, float, float] + quick_favorites: bpy.types.bpy_prop_collection_idprop[QuickFavoritesItem] diff --git a/src/bonsai/bonsai/bim/module/misc/ui.py b/src/bonsai/bonsai/bim/module/misc/ui.py index c009b720e4..3e4a952bc3 100644 --- a/src/bonsai/bonsai/bim/module/misc/ui.py +++ b/src/bonsai/bonsai/bim/module/misc/ui.py @@ -18,6 +18,8 @@ import bpy +import bonsai.tool as tool + class BIM_PT_misc_utilities(bpy.types.Panel): bl_idname = "BIM_PT_misc_utilities" @@ -30,7 +32,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel): def draw(self, context): layout = self.layout - props = context.scene.BIMMiscProperties + assert layout + props = tool.Misc.get_misc_props() row = layout.split(factor=0.2, align=True) row.prop(props, "override_colour", text="") row.operator("bim.set_override_colour") @@ -56,3 +59,72 @@ class BIM_PT_misc_utilities(bpy.types.Panel): row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL") row = layout.row() row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER") + + +class BIM_PT_quick_favorites_manager(bpy.types.Panel): + bl_idname = "BIM_PT_quick_favorites_manager" + bl_label = "Quick Favorites Manager" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "output" + bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_tab_sandbox" + + def draw(self, context): + layout = self.layout + assert layout + props = tool.Misc.get_misc_props() + + row = layout.row(align=True) + row.label(text="Quick Favorites:") + row.operator("bim.add_quick_favorites_item", text="", icon="ADD") + row.operator("bim.import_quick_favorites", text="", icon="BLENDER") + op = row.operator("bim.show_description", text="", icon="INFO") + op.attr_name = "Quick Favorites Manager" + op.description = ( + "Blender does not support editing Quick Favorites natively. " + "This manager allows you to load existing Quick Favorites operators, " + "configure their properties and labels, and re-add them to the menu with customized settings." + ) + + for fav in props.quick_favorites: + if fav.operator_id: + row = layout.row() + op = row.operator(fav.operator_id, text=fav.label) + for item in fav.properties: + if item.is_active: + setattr(op, item.name, getattr(item, item.value_prop)) + + layout.separator() + + for i, fav in enumerate(props.quick_favorites): + box = layout.box() + row = box.row(align=True) + row.prop(fav, "is_expanded", text="", icon="TRIA_DOWN" if fav.is_expanded else "TRIA_RIGHT", emboss=False) + row.prop(fav, "label", text="") + if i > 0: + up = row.operator("bim.move_quick_favorites_item", text="", icon="TRIA_UP") + up.index = i + up.direction = "UP" + if i < len(props.quick_favorites) - 1: + down = row.operator("bim.move_quick_favorites_item", text="", icon="TRIA_DOWN") + down.index = i + down.direction = "DOWN" + row.operator("bim.remove_quick_favorites_item", text="", icon="X").index = i + if not fav.is_expanded: + continue + row = box.row(align=True) + row.prop(fav, "search", text="") + row.operator("bim.confirm_quick_favorite_operator", text="", icon="VIEWZOOM").index = i + if not fav.operator_id: + continue + layout.separator() + if fav.properties: + box.label(text="Properties:") + prop_box = box.box() + for item in fav.properties: + row = prop_box.row(align=True) + row.prop(item, item.value_prop, text=item.display_name) + row.prop(item, "is_active", text="", icon="RADIOBUT_ON" if item.is_active else "RADIOBUT_OFF") + else: + box.label(text="No Properties.") diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index d4dc1a218d..49090e2c9f 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -19,7 +19,7 @@ from __future__ import annotations import math -from math import cos, radians, sin, tan +from math import cos, pi, radians, sin, tan from typing import Any, Literal import blf @@ -27,6 +27,10 @@ import bmesh import bpy import gpu import ifcopenshell +import ifcopenshell.geom +import ifcopenshell.util.element +import ifcopenshell.util.representation +import ifcopenshell.util.unit import mathutils from bpy.types import SpaceView3D from bpy_extras import view3d_utils @@ -35,6 +39,7 @@ from gpu_extras.batch import batch_for_shader from gpu_extras.presets import draw_circle_2d from mathutils import Matrix, Quaternion, Vector +import bonsai.core.geometry import bonsai.tool as tool from bonsai.bim.module.drawing.helper import format_distance @@ -1566,7 +1571,7 @@ class ProductDecorator: obj_type, representation, ) - context.view_layer.update() + bpy.context.view_layer.update() break translate_mouse = Matrix.Translation(mouse_point) diff --git a/src/bonsai/bonsai/bim/module/model/grid.py b/src/bonsai/bonsai/bim/module/model/grid.py index 8ceaa605dc..dd4e919e30 100644 --- a/src/bonsai/bonsai/bim/module/model/grid.py +++ b/src/bonsai/bonsai/bim/module/model/grid.py @@ -82,7 +82,7 @@ def add_object(self: "BIM_OT_add_object", context: bpy.types.Context) -> None: class BIM_OT_add_object(Operator, tool.Ifc.Operator): - bl_idname = "mesh.add_grid" + bl_idname = "bim.add_grid" bl_label = "Grid" bl_description = "Add IfcGrid." bl_options = {"REGISTER", "UNDO"} diff --git a/src/bonsai/bonsai/bim/module/model/mep.py b/src/bonsai/bonsai/bim/module/model/mep.py index 245e10a21b..df34166229 100644 --- a/src/bonsai/bonsai/bim/module/model/mep.py +++ b/src/bonsai/bonsai/bim/module/model/mep.py @@ -227,7 +227,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator): is_parallel21 = tool.Cad.is_x(angle21, (0, 180), tolerance=0.001) is_parallel23 = tool.Cad.is_x(angle23, (0, 180), tolerance=0.001) - if not all(is_parallel12, is_parallel13, is_parallel21, is_parallel23): + if not all([is_parallel12, is_parallel13, is_parallel21, is_parallel23]): fitting_type = "WYE" if not fitting_type: @@ -903,7 +903,7 @@ class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator): start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0) end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0) radius: bpy.props.FloatProperty( - "Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE", min=0 + name="Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE", min=0 ) def _execute(self, context): diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index c20325c2e7..1c157afe4e 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -151,29 +151,11 @@ class FilledOpeningGenerator: existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" ) assert representation - - # Check if mapped representation - PRESERVE the mapping structure - if ( - representation.RepresentationType == "MappedRepresentation" - and len(representation.Items) == 1 - and representation.Items[0].is_a("IfcMappedItem") - ): - # Store the existing RepresentationMap to reuse it - existing_mapping_source = representation.Items[0].MappingSource - reuse_mapped_representation = True - else: - representation = ifcopenshell.util.representation.resolve_representation(representation) - - if not reuse_mapped_representation: - # Check for library template before generating from filling - template_rep = self.get_opening_template_from_type(filling) - - if template_rep: - representation = template_rep - else: - representation = self.generate_opening_from_filling( - filling, filling_obj, opening_thickness_si=opening_thickness_si - ) + representation = ifcopenshell.util.representation.resolve_representation(representation) + else: + representation = self.generate_opening_from_filling( + filling, filling_obj, opening_thickness_si=opening_thickness_si + ) # Create mapped representation if reuse_mapped_representation: @@ -247,109 +229,38 @@ class FilledOpeningGenerator: voided_element = opening.VoidsElements[0].RelatingBuildingElement opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW") - - # ALWAYS preserve the existing opening representation (Tessellation, SweptSolid, etc.) - preserved_representation = None - if opening_rep: - if ( - opening_rep.RepresentationType == "MappedRepresentation" - and len(opening_rep.Items) == 1 - and opening_rep.Items[0].is_a("IfcMappedItem") - ): - # For mapped representations, copy the underlying representation - preserved_representation = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), - opening_rep.Items[0].MappingSource.MappedRepresentation, - exclude=["IfcGeometricRepresentationContext"], - ) - else: - # For direct representations (non-mapped), copy them too - preserved_representation = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), opening_rep, exclude=["IfcGeometricRepresentationContext"] - ) - ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep) ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), representation=opening_rep) existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) - # Priority order for choosing representation: - # 1. Existing occurrence with MappedRepresentation (preserve mapping!) - # 2. Library template with Tessellation - # 3. Preserved representation from old opening (maintain user's work) - # 4. Generate from filling (last resort) - - representation_to_use = None - reuse_mapped_representation = False - existing_mapping_source = None - if existing_opening_occurrence: representation = ifcopenshell.util.representation.get_representation( existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" ) - - if ( - representation - and representation.RepresentationType == "MappedRepresentation" - and len(representation.Items) == 1 - and representation.Items[0].is_a("IfcMappedItem") - ): - # PRESERVE the mapped structure - reuse the same RepresentationMap - existing_mapping_source = representation.Items[0].MappingSource - reuse_mapped_representation = True - else: - representation_to_use = ifcopenshell.util.representation.resolve_representation(representation) - - if not representation_to_use and not reuse_mapped_representation: - template_rep = self.get_opening_template_from_type(filling) - if template_rep and template_rep.RepresentationType == "Tessellation": - representation_to_use = template_rep - - if not representation_to_use and not reuse_mapped_representation and preserved_representation: - representation_to_use = preserved_representation - - if not representation_to_use and not reuse_mapped_representation: + representation = ifcopenshell.util.representation.resolve_representation(representation) + mapped_representation = ifcopenshell.api.geometry.map_representation( + tool.Ifc.get(), representation=representation + ) + ifcopenshell.api.geometry.assign_representation( + tool.Ifc.get(), product=opening, representation=mapped_representation + ) + else: opening_obj = tool.Ifc.get_object(opening) if opening_obj: tool.Ifc.unlink(element=opening) tool.Blender.remove_data_blocks([opening_obj], remove_unused_data=True) filling_obj = tool.Ifc.get_object(filling) - representation_to_use = self.generate_opening_from_filling(filling, filling_obj) - - # Create the mapped representation - if reuse_mapped_representation: - # Reuse existing RepresentationMap - don't create a new one! - context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") - new_mapped_item = tool.Ifc.get().create_entity( - "IfcMappedItem", - MappingSource=existing_mapping_source, - MappingTarget=tool.Ifc.get().create_entity( - "IfcCartesianTransformationOperator3D", - Axis1=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)), - Axis2=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0.0, 1.0, 0.0)), - LocalOrigin=tool.Ifc.get().create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)), - Scale=1.0, - Axis3=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)), - ), - ) - mapped_representation = tool.Ifc.get().create_entity( - "IfcShapeRepresentation", - ContextOfItems=context, - RepresentationIdentifier="Body", - RepresentationType="MappedRepresentation", - Items=[new_mapped_item], - ) - else: + representation = self.generate_opening_from_filling(filling, filling_obj) mapped_representation = ifcopenshell.api.geometry.map_representation( - tool.Ifc.get(), representation=representation_to_use + tool.Ifc.get(), representation=representation + ) + ifcopenshell.api.geometry.assign_representation( + tool.Ifc.get(), product=opening, representation=mapped_representation ) - ifcopenshell.api.geometry.assign_representation( - tool.Ifc.get(), product=opening, representation=mapped_representation - ) - - # update voided object representation... + # update voided object representation or all it's parts if it's an aggregate voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element] for voided_element in voided_elements: voided_obj = tool.Ifc.get_object(voided_element) @@ -363,36 +274,6 @@ class FilledOpeningGenerator: representation=representation, ) - def get_opening_template_from_type( - self, filling: ifcopenshell.entity_instance - ) -> Union[ifcopenshell.entity_instance, None]: - """ - Check if the filling's type has a stored opening template from library import. - """ - element_type = ifcopenshell.util.element.get_type(filling) - - if not element_type: - return None - - desc = element_type.Description - - if not desc or "||BonsaiOpeningTemplate:" not in desc: - return None - - # Extract template ID - marker = desc.split("||BonsaiOpeningTemplate:")[-1] - template_id = int(marker.split("||")[0]) - - try: - template_rep = tool.Ifc.get().by_id(template_id) - # Make a copy so we don't reuse the same representation instance - copied = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), template_rep, exclude=["IfcGeometricRepresentationContext"] - ) - return copied - except: - return None - def generate_opening_from_filling( self, filling: ifcopenshell.entity_instance, @@ -659,6 +540,16 @@ class AddBoolean(Operator, tool.Ifc.Operator): booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator) rep_obj = tool.Geometry.get_geometry_props().representation_obj + if booleans: + # Users typically select two top-level items and expect the + # operand to be absorbed into the boolean, not remain as a + # standalone item alongside it. + representation = tool.Geometry.get_active_representation(rep_obj) + representation = ifcopenshell.util.representation.resolve_representation(representation) + second_items_set = set(second_items) + new_items = [i for i in representation.Items if i not in second_items_set] + if new_items: + representation.Items = new_items rep_element = tool.Ifc.get_entity(rep_obj) tool.Model.mark_manual_booleans(rep_element, booleans) tool.Geometry.reload_representation(rep_obj) diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index e33fe2cf5d..be491e756b 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -421,7 +421,7 @@ class PolylineOperator: tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state) tool.Blender.update_viewport() - return {"RUNNING_MODAL"} + return {"RUNNING_MODAL"} def set_offset(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_instance) -> None: props = tool.Model.get_model_props() @@ -461,6 +461,7 @@ class PolylineOperator: self.tool_state.axis_method = None self.tool_state.plane_method = None self.tool_state.mode = "Mouse" + tool.Raycast.clear_snap_objs() self.visible_objs = tool.Raycast.get_visible_objects(context) for obj in self.visible_objs: if bbox_2d := tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj): diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index d7c96bce1d..4cf4e00172 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -694,10 +694,14 @@ def generate_box(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[ new_settings = settings.copy() new_settings["context"] = box_context - new_box = ifcopenshell.api.geometry.add_representation(ifc_file, should_run_listeners=False, **new_settings) + new_box = ifcopenshell.api.geometry.add_representation( + ifc_file, + should_run_listeners=False, # ty:ignore[unknown-argument] + **new_settings, + ) ifcopenshell.api.geometry.assign_representation( ifc_file, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] product=product, representation=new_box, ) diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py index f759d40a94..1369ad3cb6 100644 --- a/src/bonsai/bonsai/bim/module/model/profile.py +++ b/src/bonsai/bonsai/bim/module/model/profile.py @@ -18,7 +18,7 @@ import copy from math import atan2, degrees, pi, radians -from typing import Any, Literal, Optional, Union +from typing import TYPE_CHECKING, Any, Literal, Optional, Union import bpy import ifcopenshell @@ -49,7 +49,7 @@ ProfileFrom2PointsReturn = Union[dict[str, Any], None] class DumbProfileGenerator: - def __init__(self, relating_type): + def __init__(self, relating_type: ifcopenshell.entity_instance): self.relating_type = relating_type self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) @@ -201,7 +201,7 @@ class DumbProfileGenerator: class DumbProfileRegenerator: - def regenerate_from_profile_def(self, profile): + def regenerate_from_profile_def(self, profile: ifcopenshell.entity_instance) -> None: self.file = tool.Ifc.get() objs = [] if not profile: @@ -221,7 +221,7 @@ class DumbProfileRegenerator: for element in self.get_element_types_using_profile(profile): tool.Model.mark_thumbnail_for_update(element) - def regenerate_from_profile(self, usecase_path, ifc_file, settings): + def regenerate_from_profile(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None: self.file = ifc_file objs = [] profile = settings["profile"].Profile @@ -233,7 +233,7 @@ class DumbProfileRegenerator: objs.append(obj) DumbProfileRecalculator().recalculate(objs) - def get_elements_using_profile(self, profile): + def get_elements_using_profile(self, profile: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: results = [] profile_sets = [ mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") @@ -252,7 +252,9 @@ class DumbProfileRegenerator: results.extend(rel.RelatedObjects) return results - def get_element_types_using_profile(self, profile): + def get_element_types_using_profile( + self, profile: ifcopenshell.entity_instance + ) -> list[ifcopenshell.entity_instance]: results = [] profile_sets = [ mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") @@ -269,12 +271,18 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.extend_profile" bl_label = "Extend Profile" bl_options = {"REGISTER", "UNDO"} - join_type: bpy.props.StringProperty() + join_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration] + items=[("-", "Unjoin", ""), ("L", "L", ""), ("V", "V", ""), ("T", "T", "")], + default="-", + ) + + if TYPE_CHECKING: + join_type: Literal["-", "L", "V", "T"] def _execute(self, context): selected_objs = context.selected_objects joiner = DumbProfileJoiner() - if not self.join_type: + if self.join_type == "-": for obj in selected_objs: joiner.unjoin(obj) return {"FINISHED"} @@ -626,11 +634,15 @@ class DumbProfileJoiner: if connection1 == "ATEND": if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal: plane = self.get_profile_plane(profile2, furthest_plane) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) self.body[1] = intersect else: plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) max_dim = self.get_max_bound_box_dimension(profile1) self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim)) @@ -673,11 +685,15 @@ class DumbProfileJoiner: elif connection1 == "ATSTART": if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal: plane = self.get_profile_plane(profile2, furthest_plane) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) self.body[0] = intersect else: plane = self.get_profile_plane(profile2, furthest_plane, z_inwards=False) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) max_dim = self.get_max_bound_box_dimension(profile1) self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim)) @@ -721,7 +737,9 @@ class DumbProfileJoiner: if connection1 == "ATEND": if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal: plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) self.body[1] = intersect else: plane = self.get_profile_plane( @@ -729,7 +747,9 @@ class DumbProfileJoiner: furthest_plane if is_relating else closest_plane, z_inwards=False if is_relating else True, ) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) max_dim = self.get_max_bound_box_dimension(profile1) self.body[1] = intersect + profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim)) self.clippings.append( @@ -742,7 +762,9 @@ class DumbProfileJoiner: elif connection1 == "ATSTART": if tool.Cad.is_x(abs(xy_angle), (0, 90, 180), tolerance=0.001) and is_orthogonal: plane = self.get_profile_plane(profile2, furthest_plane if is_relating else closest_plane) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) self.body[0] = intersect else: plane = self.get_profile_plane( @@ -750,7 +772,9 @@ class DumbProfileJoiner: furthest_plane if is_relating else closest_plane, z_inwards=False if is_relating else True, ) - intersect = mathutils.geometry.intersect_line_plane(*axis1, plane.translation, plane.col[2].to_3d()) + intersect = mathutils.geometry.intersect_line_plane( + axis1[0], axis1[1], plane.translation, plane.col[2].to_3d() + ) max_dim = self.get_max_bound_box_dimension(profile1) self.body[0] = intersect - profile1.matrix_world.to_quaternion() @ Vector((0, 0, max_dim)) self.clippings.append( diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index cb246ac7c5..c4956056aa 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -733,6 +733,9 @@ class BIMStairProperties(PropertyGroup): class BIMSverchokProperties(PropertyGroup): node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree) + if TYPE_CHECKING: + node_group: bpy.types.NodeTree | None + def window_type_prop_update(self, context): number_of_panels, panels_data = self.window_types_panels[self.window_type] diff --git a/src/bonsai/bonsai/bim/module/model/sverchok_modifier.py b/src/bonsai/bonsai/bim/module/model/sverchok_modifier.py index 08f29890c4..448fb9725c 100644 --- a/src/bonsai/bonsai/bim/module/model/sverchok_modifier.py +++ b/src/bonsai/bonsai/bim/module/model/sverchok_modifier.py @@ -31,7 +31,7 @@ import bonsai.tool as tool def update_sverchok_modifier(context): obj = context.active_object - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) element = tool.Ifc.get_entity(obj) psets = ifcopenshell.util.element.get_psets(element) pset = psets.get("BBIM_Sverchok", None) @@ -69,10 +69,10 @@ class CreateNewSverchokGraph(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER"} def _execute(self, context): - import sverchok + import sverchok.ui.sv_temporal_viewers obj = context.active_object - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) node_group = bpy.data.node_groups.new("IfcNodeTree", type="SverchCustomTreeType") plane = node_group.nodes.new(type="SvPlaneNodeMk3") @@ -96,7 +96,7 @@ class DeleteSverchokGraph(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object element = tool.Ifc.get_entity(obj) - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) bpy.data.node_groups.remove(props.node_group) return {"FINISHED"} @@ -113,7 +113,8 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER"} def invoke(self, context, event): - if not context.active_object.BIMSverchokProperties.node_group: + props = tool.Model.get_sverchok_props(context.active_object) + if not props.node_group: return context.window_manager.invoke_props_dialog(self) return self._execute(context) @@ -124,7 +125,7 @@ class UpdateDataFromSverchok(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): obj = context.active_object element = tool.Ifc.get_entity(obj) - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) node_group = props.node_group if node_group: @@ -192,11 +193,11 @@ class ImportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ImportHelper): filename_ext = ".json" def _execute(self, context): - import sverchok + import sverchok.utils.sv_json_import importer = sverchok.utils.sv_json_import.JSONImporter.init_from_path(self.filepath) obj = context.active_object - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) node_group = context.scene.io_panel_properties.import_tree if not node_group: @@ -231,10 +232,10 @@ class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ExportHelper): compress: bpy.props.BoolProperty() def _execute(self, context): - import sverchok + import sverchok.utils.sv_json_export obj = context.active_object - props = obj.BIMSverchokProperties + props = tool.Model.get_sverchok_props(obj) ng = props.node_group destination_path = self.filepath if not destination_path.lower().endswith(".json"): @@ -273,7 +274,8 @@ class ExportSverchokGraph(bpy.types.Operator, tool.Ifc.Operator, ExportHelper): return {"FINISHED"} def draw(self, context): - graph_name = context.active_object.BIMSverchokProperties.node_group.name + props = tool.Model.get_sverchok_props(context.active_object) + graph_name = props.node_group.name self.layout.label(text=f'Save node tree "{graph_name}" into json:') col = self.layout.column(heading="Options") # new syntax in >= 2.90 diff --git a/src/bonsai/bonsai/bim/module/model/task.py b/src/bonsai/bonsai/bim/module/model/task.py index a6fe2607a2..72d2ee4556 100644 --- a/src/bonsai/bonsai/bim/module/model/task.py +++ b/src/bonsai/bonsai/bim/module/model/task.py @@ -31,11 +31,11 @@ def calculate_quantities(usecase_path, ifc_file: ifcopenshell.file, settings): return task = next(e for e in ifc_file.get_inverse(element) if e.is_a("IfcTask")) qto = ifcopenshell.api.pset.add_qto( - ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities" + ifc_file, should_run_listeners=False, product=task, name="Qto_TaskBaseQuantities" # ty:ignore[unknown-argument] ) ifcopenshell.api.pset.edit_qto( ifc_file, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] qto=qto, properties={ "StandardWork": ifcopenshell.util.date.ifc2datetime(element.ScheduleDuration).days, diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 512eaf3fdf..eef71ed433 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -360,7 +360,7 @@ class BIM_PT_sverchok(bpy.types.Panel): self.layout.label(text="Requires Sverchok Add-on", icon="ERROR") return - props = context.active_object.BIMSverchokProperties + props = tool.Model.get_sverchok_props(context.active_object) self.layout.prop_search(props, "node_group", bpy.data, "node_groups") self.layout.operator("bim.create_new_sverchok_graph", icon="ADD") diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 441566e3e6..4d0c9b3de7 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -1268,27 +1268,6 @@ class DumbWallJoiner: bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2) return wall2 - def join_Z(self, wall1, slab2): - element1 = tool.Ifc.get_entity(wall1) - element2 = tool.Ifc.get_entity(slab2) - - for rel in element1.ConnectedFrom: - if rel.is_a() == "IfcRelConnectsElements" and rel.Description == "TOP": - ifcopenshell.api.geometry.disconnect_element( - tool.Ifc.get(), - relating_element=rel.RelatingElement, - related_element=element1, - ) - - ifcopenshell.api.geometry.connect_element( - tool.Ifc.get(), - relating_element=element2, - related_element=element1, - description="TOP", - ) - - tool.Model.recreate_wall(element1, wall1) - def set_axis(self, wall, p1, p2): axis = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW") builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get()) @@ -1333,29 +1312,6 @@ class DumbWallJoiner: self.set_axis(element1, p1, p2) tool.Model.recreate_wall(element1, wall1) - def join_T(self, wall1: bpy.types.Object, wall2: bpy.types.Object) -> None: - element1 = tool.Ifc.get_entity(wall1) - element2 = tool.Ifc.get_entity(wall2) - axis1 = tool.Model.get_wall_axis(wall1) - axis2 = tool.Model.get_wall_axis(wall2) - intersect = tool.Cad.intersect_edges(axis1["reference"], axis2["reference"]) - if intersect: - intersect, _ = intersect - else: - return - connection = "ATEND" if tool.Cad.edge_percent(intersect, axis1["reference"]) > 0.5 else "ATSTART" - - ifcopenshell.api.geometry.connect_path( - tool.Ifc.get(), - related_element=element1, - relating_element=element2, - relating_connection="ATPATH", - related_connection=connection, - description="BUTT", - ) - - tool.Model.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"]) - def connect(self, obj1: bpy.types.Object, obj2: bpy.types.Object) -> None: wall1 = tool.Ifc.get_entity(obj1) wall2 = tool.Ifc.get_entity(obj2) diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 5f7cf7699d..828fc2c21f 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -943,7 +943,7 @@ class EditObjectUI: if "LAYER2" in AuthoringData.data["selected_material_usages"]: row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row add_layout_hotkey_operator( - cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_to_underside.__doc__, ui_context + cls.layout, "Extend To Underside", "S_E", bpy.ops.bim.extend_walls_to_underside.__doc__, ui_context ) if AuthoringData.data["is_flippable_element"]: diff --git a/src/bonsai/bonsai/bim/module/nest/decorator.py b/src/bonsai/bonsai/bim/module/nest/decorator.py index 66608b0171..4a3637caa6 100644 --- a/src/bonsai/bonsai/bim/module/nest/decorator.py +++ b/src/bonsai/bonsai/bim/module/nest/decorator.py @@ -101,7 +101,7 @@ class NestDecorator: cls.is_installed = False def dotted_line_shader(self): - vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments] vert_out.smooth("FLOAT", "v_ArcLength") shader_info = gpu.types.GPUShaderCreateInfo() @@ -215,8 +215,6 @@ class NestDecorator: self.draw_batch("LINES", line_z, color, [(0, 1)]) else: self.draw_batch("POINTS", [location], color) - # if context.scene.BIMNestProperties.in_aggregate_mode: - # return components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(nest)) components_objs = [tool.Ifc.get_object(p) for p in components] components_objs.append(nest) diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index db642c18c2..e83da49c5b 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -76,6 +76,7 @@ classes = ( operator.UnlinkIfc, operator.UnloadLink, workspace.ExploreHotkey, + operator.GenerateUVMap, prop.LibraryBreadcrumb, prop.LibraryElement, prop.FilterCategory, diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index a0768e0cf5..e24f6f41d8 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -178,9 +178,18 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper): bl_description = ( "Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file." ) - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) - append_all: bpy.props.BoolProperty(default=False) - use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) + filter_glob: bpy.props.StringProperty( + default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + append_all: bpy.props.BoolProperty(default=False) # pyright: ignore[reportRedeclaration] + use_relative_path: bpy.props.BoolProperty( + name="Use Relative Path", default=False + ) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + filter_glob: str + append_all: bool + use_relative_path: bool reload_previous_file = False @@ -558,7 +567,11 @@ class AppendEntireLibrary(bpy.types.Operator, tool.Ifc.Operator): class AppendLibraryElementByQuery(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.append_library_element_by_query" bl_label = "Append Library Element By Query" - query: bpy.props.StringProperty(name="Query") + + query: bpy.props.StringProperty(name="Query") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + query: str @classmethod def poll(cls, context): @@ -587,9 +600,16 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator): "Append element to the current project.\n\n" "ALT+CLICK to skip reusing materials, profiles, styles based on their name (may result in duplicates)" ) - definition: bpy.props.IntProperty() - prop_index: bpy.props.IntProperty() - assume_unique_by_name: bpy.props.BoolProperty(name="Assume Unique By Name", default=True, options={"SKIP_SAVE"}) + definition: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + prop_index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] + assume_unique_by_name: bpy.props.BoolProperty( + name="Assume Unique By Name", default=True, options={"SKIP_SAVE"} + ) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + definition: int + prop_index: int + assume_unique_by_name: bool file: ifcopenshell.file @@ -618,8 +638,6 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator): if not element: return {"FINISHED"} if element.is_a("IfcTypeProduct"): - # Store opening template from library if it exists - self.store_opening_template_from_library(element, library_file) self.import_type_from_ifc(element, context) elif element.is_a("IfcProduct"): # NOTE: Non-types are not exposed in UI directly @@ -720,53 +738,6 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator): if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()): ifc_importer.create_style(element) - def store_opening_template_from_library( - self, element: ifcopenshell.entity_instance, library_file: ifcopenshell.file - ) -> None: - """ - Find an opening representation in the library and copy it to the current file - as a template. Store the template ID on the type for later retrieval. - """ - try: - library_element = library_file.by_guid(element.GlobalId) - except: - return - - # Find occurrences with openings in the library - library_occurrences = ifcopenshell.util.element.get_types(library_element) - - for occurrence in library_occurrences: - if not getattr(occurrence, "FillsVoids", None): - continue - - library_opening = occurrence.FillsVoids[0].RelatingOpeningElement - library_opening_rep = ifcopenshell.util.representation.get_representation( - library_opening, "Model", "Body", "MODEL_VIEW" - ) - - if not library_opening_rep: - continue - - # Check if mapped representation - if ( - library_opening_rep.RepresentationType == "MappedRepresentation" - and len(library_opening_rep.Items) == 1 - and library_opening_rep.Items[0].is_a("IfcMappedItem") - ): - - mapped_rep = library_opening_rep.Items[0].MappingSource.MappedRepresentation - - # Store ALL representation types (Tessellation, SweptSolid, etc.) - template_rep = ifcopenshell.util.element.copy_deep( - self.file, mapped_rep, exclude=["IfcGeometricRepresentationContext"] - ) - - # Store reference in type's Description - current_desc = element.Description or "" - element.Description = f"{current_desc}||BonsaiOpeningTemplate:{template_rep.id()}" - return - break - class EditProjectLibrary(bpy.types.Operator): bl_idname = "bim.edit_project_library" @@ -988,24 +959,28 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): bl_label = "Load Project" bl_options = {"REGISTER", "UNDO"} bl_description = "Load an existing IFC project" - filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"}) - filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"}) - is_advanced: bpy.props.BoolProperty( + filepath: bpy.props.StringProperty( + subtype="FILE_PATH", options={"SKIP_SAVE"} + ) # pyright: ignore[reportRedeclaration] + filter_glob: bpy.props.StringProperty( + default="*.ifc;*.ifczip;*.ifcxml;*.ifcsqlite", options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + is_advanced: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] name="Enable Advanced Mode", description="Load IFC file with advanced settings. Checking this option will skip loading IFC file and will open advanced load settings", default=False, ) - use_relative_path: bpy.props.BoolProperty( + use_relative_path: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] name="Use Relative Path", description="Store the IFC project path relative to the .blend file. Requires .blend file to be saved", default=False, ) - should_start_fresh_session: bpy.props.BoolProperty( + should_start_fresh_session: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] name="Should Start Fresh Session", description="Clear current Blender session before loading IFC. Not supported with 'Use Relative Path' option", default=True, ) - import_without_ifc_data: bpy.props.BoolProperty( + import_without_ifc_data: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration] name="Import Without IFC Data", description=( "Import IFC objects as Blender objects without any IFC metadata and authoring capabilities." @@ -1013,9 +988,20 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): ), default=False, ) - use_detailed_tooltip: bpy.props.BoolProperty(default=False, options={"HIDDEN"}) + use_detailed_tooltip: bpy.props.BoolProperty( + default=False, options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] filename_ext = ".ifc" + if TYPE_CHECKING: + filepath: str + filter_glob: str + is_advanced: bool + use_relative_path: bool + should_start_fresh_session: bool + import_without_ifc_data: bool + use_detailed_tooltip: bool + @classmethod def description(cls, context, properties): tooltip = cls.bl_description @@ -1116,7 +1102,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper): else: return self.finish_loading_project(context) - def finish_loading_project(self, context): + def finish_loading_project(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]: try: filepath = self.get_filepath() if not self.is_existing_ifc_file(): @@ -1314,7 +1300,10 @@ class ToggleFilterCategories(bpy.types.Operator): bl_idname = "bim.toggle_filter_categories" bl_label = "Toggle Filter Categories" bl_options = {"REGISTER", "UNDO"} - should_select: bpy.props.BoolProperty(name="Should Select", default=True) + should_select: bpy.props.BoolProperty(name="Should Select", default=True) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + should_select: bool def execute(self, context): props = tool.Project.get_project_props() @@ -1338,6 +1327,14 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): default=False, ) use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) + query: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration] + name="Query", + description=( + "Custom selector query to use to load element from a linked model. E.g. 'IfcElement'.\n\n" + "Default query - IfcElement, but excluding IfcProxy, IfcSpatialStructureElement, IfcSpatialElement, IfcFeatureElement." + ), + ) + filename_ext = ".ifc" if TYPE_CHECKING: @@ -1347,20 +1344,25 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): filter_glob: str use_relative_path: bool use_cache: bool + query: str def draw(self, context): + assert self.layout pprops = tool.Project.get_project_props() row = self.layout.row() row.prop(self, "use_relative_path") row = self.layout.row() row.prop(self, "use_cache") row = self.layout.row() - row.prop(pprops, "false_origin_mode") + row.label(text="False Origin Mode:") + row = self.layout.row() + row.prop(pprops, "false_origin_mode", text="") if pprops.false_origin_mode == "MANUAL": row = self.layout.row() row.prop(pprops, "false_origin") row = self.layout.row() row.prop(pprops, "project_north") + self.layout.prop(self, "query", placeholder="IfcElement") def _execute(self, context): start = time.time() @@ -1393,7 +1395,7 @@ class LinkIfc(bpy.types.Operator, ImportHelper, tool.Ifc.Operator): new.ifc_definition_id = reference.id() new.name = filepath new.filepath = filepath - bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache) + bpy.ops.bim.load_link(link_index=-1, use_cache=self.use_cache, query=self.query) class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator): @@ -1401,7 +1403,11 @@ class UnlinkIfc(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Unlink IFC" bl_options = {"REGISTER", "UNDO"} bl_description = "Remove the selected file from the link list" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def _execute(self, context): props = tool.Project.get_project_props() @@ -1421,7 +1427,11 @@ class UnloadLink(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Unload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Unload the selected linked file" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def _execute(self, context): link = tool.Project.get_project_props().links[self.link_index] @@ -1446,10 +1456,12 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator): link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] use_cache: bpy.props.BoolProperty(name="Use Cache", default=True) # pyright: ignore[reportRedeclaration] + query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] if TYPE_CHECKING: link_index: int use_cache: bool + query: str def _execute(self, context): self.link = tool.Project.get_project_props().links[self.link_index] @@ -1491,8 +1503,20 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator): def link_ifc(self) -> Union[set[str], None]: blend_filepath = self.filepath_.with_suffix(".ifc.cache.blend") h5_filepath = self.filepath_.with_suffix(".ifc.cache.h5") + json_filepath = self.filepath_.with_suffix(".ifc.cache.json") - if not self.use_cache and blend_filepath.exists(): + def should_clear_cache() -> bool: + if not self.use_cache: + return True + if not blend_filepath.exists(): + return False + data = json.loads(json_filepath.read_text()) + # Empty 'query' - model loaded without custom query. + # Missing 'query' - model was loaded before custom queries were introduced in Bonsai. + query = data.get("query", "") + return query != self.query + + if should_clear_cache(): os.remove(blend_filepath) if not blend_filepath.exists(): @@ -1520,7 +1544,7 @@ def run(): pprops.project_north = "{pprops.project_north}" # Use absolute path to be safe from cwd changes. try: - bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}") + bpy.ops.bim.load_linked_project(filepath=r"{str(self.filepath_)}", query={repr(self.query)}) except RuntimeError as e: # Operator failed (returned CANCELLED with error report) print(f"Failed to load linked project: {{e}}") @@ -1606,7 +1630,11 @@ class ReloadLink(bpy.types.Operator): bl_label = "Reload Link" bl_options = {"REGISTER", "UNDO"} bl_description = "Reload the selected file" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): bpy.ops.bim.unload_link(link_index=self.link_index) @@ -1618,7 +1646,11 @@ class ToggleLinkSelectability(bpy.types.Operator): bl_label = "Toggle Link Selectability" bl_options = {"REGISTER", "UNDO"} bl_description = "Toggle selectability" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): props = tool.Project.get_project_props() @@ -1788,7 +1820,11 @@ class SelectLinkHandle(bpy.types.Operator): bl_label = "Select Link Handle" bl_options = {"REGISTER", "UNDO"} bl_description = "Select link empty object handle" - link_index: bpy.props.IntProperty(name="Link Index") + + link_index: bpy.props.IntProperty(name="Link Index") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + link_index: int def execute(self, context): props = tool.Project.get_project_props() @@ -1846,11 +1882,28 @@ class ExportIFC(bpy.types.Operator, ExportHelper): bl_options = {"REGISTER", "UNDO"} filename_ext = ".ifc" supported_filexts = (".ifc", ".ifczip", ".ifcjson") - filter_glob: bpy.props.StringProperty(default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"}) - json_version: bpy.props.EnumProperty(items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version") - json_compact: bpy.props.BoolProperty(name="Export Compact IFCJSON", default=False) - should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"}) - use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False) + filter_glob: bpy.props.StringProperty( + default=";".join(f"*{ext}" for ext in supported_filexts), options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + json_version: bpy.props.EnumProperty( + items=[("4", "4", ""), ("5a", "5a", "")], name="IFC JSON Version" + ) # pyright: ignore[reportRedeclaration] + json_compact: bpy.props.BoolProperty( + name="Export Compact IFCJSON", default=False + ) # pyright: ignore[reportRedeclaration] + should_save_as: bpy.props.BoolProperty( + name="Should Save As", default=False, options={"HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + use_relative_path: bpy.props.BoolProperty( + name="Use Relative Path", default=False + ) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + filter_glob: str + json_version: str + json_compact: bool + should_save_as: bool + use_relative_path: bool @classmethod def poll(cls, context): @@ -2000,6 +2053,12 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): bl_description = "Operator is used to load a project .cache.blend to then link it to the IFC file." bl_options = {"REGISTER", "UNDO"} + query: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] + """See ``bim.link_ifc``.""" + + if TYPE_CHECKING: + query: str + file: ifcopenshell.file meshes: dict[str, bpy.types.Mesh] # Material names is derived from diffuse as in 'r-g-b-a'. @@ -2049,14 +2108,17 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): tool.Loader.settings.context_settings = tool.Loader.create_settings() tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True) - self.elements = set(self.file.by_type("IfcElement")) - if self.file.schema in ("IFC2X3", "IFC4"): - self.elements |= set(self.file.by_type("IfcProxy")) - if self.file.schema == "IFC2X3": - self.elements |= set(self.file.by_type("IfcSpatialStructureElement")) + if self.query: + self.elements = ifcopenshell.util.selector.filter_elements(self.file, self.query) else: - self.elements |= set(self.file.by_type("IfcSpatialElement")) - self.elements -= set(self.file.by_type("IfcFeatureElement")) + self.elements = set(self.file.by_type("IfcElement")) + if self.file.schema in ("IFC2X3", "IFC4"): + self.elements |= set(self.file.by_type("IfcProxy")) + if self.file.schema == "IFC2X3": + self.elements |= set(self.file.by_type("IfcSpatialStructureElement")) + else: + self.elements |= set(self.file.by_type("IfcSpatialElement")) + self.elements -= set(self.file.by_type("IfcFeatureElement")) if tool.Loader.settings.false_origin_mode == "MANUAL" and tool.Loader.settings.false_origin: tool.Loader.set_manual_blender_offset(self.file) @@ -2081,6 +2143,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): "false_origin_mode": pprops.false_origin_mode, "false_origin": pprops.false_origin, "project_north": pprops.project_north, + "query": self.query, } with open(self.json_filepath, "w") as f: json.dump(data, f) @@ -2142,6 +2205,7 @@ class LoadLinkedProject(bpy.types.Operator, ImportHelper): if iterator.initialize(): while True: # Main loop. shape = iterator.get() + assert isinstance(shape, W.TriangulationElement) results.add(self.file.by_id(shape.id)) geometry = shape.geometry @@ -2372,18 +2436,23 @@ class HideQueriedLinkedElement(bpy.types.Operator): bl_label = "Hide Queried Linked Element" bl_description = ( "Hide geometry for currently queried linked element.\n\n" - "ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n" - "(Not Yet Implemented) SHIFT+Click to hide everything but currently queried element." + "SHIFT+Click (or SHIFT+H in Explore Tool) to hide everything " + "in the currently selected model, but the queried element.\n" + "ALT+Click (or ALT+H in Explore Tool) to unhide all geometry for currently selected linked model.\n\n" + "Known limitation: doesn't work with UNDO." ) bl_options = {"REGISTER", "UNDO"} unhide_all: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration] + hide_all_except: bpy.props.BoolProperty(options={"SKIP_SAVE"}) # pyright: ignore[reportRedeclaration] if TYPE_CHECKING: unhide_all: bool + hide_all_except: bool def invoke(self, context, event): self.unhide_all = event.alt + self.hide_all_except = event.shift return self.execute(context) def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: @@ -2392,6 +2461,9 @@ class HideQueriedLinkedElement(bpy.types.Operator): if self.unhide_all: return self.run_unhide_all() + if self.hide_all_except: + return self.run_hide_all_except() + obj = props.queried_obj if not obj: self.report({"INFO"}, "No object is queried to hide.") @@ -2413,6 +2485,21 @@ class HideQueriedLinkedElement(bpy.types.Operator): self.report({"INFO"}, "All linked model geometry is unhidden.") return {"FINISHED"} + def run_hide_all_except(self) -> set["rna_enums.OperatorReturnItems"]: + props = tool.Project.get_project_props() + obj = props.queried_obj + if not obj: + self.report({"INFO"}, "No object is queried.") + return {"FINISHED"} + link = props.active_link + if not link: + self.report({"INFO"}, "No linked model is currently selected.") + return {"FINISHED"} + guid = props.queried_guid + tool.Project.Link.hide_all_elements_except(link, obj, guid) + self.report({"INFO"}, "All other linked model geometry is now hidden.") + return {"FINISHED"} + class AppendInspectedLinkedElement(AppendLibraryElement): bl_idname = "bim.append_inspected_linked_element" @@ -2471,7 +2558,7 @@ class EnableCulling(bpy.types.Operator): self.total_mousemoves = 0 self.cullable_objects = [] - def modal(self, context, event): + def modal(self, context, event) -> set["rna_enums.OperatorReturnItems"]: if not LinksData.enable_culling: for obj in bpy.context.visible_objects: if obj.type == "MESH" and obj.name.startswith("Ifc"): @@ -2502,7 +2589,7 @@ class EnableCulling(bpy.types.Operator): return {"PASS_THROUGH"} - def is_view_changed(self, context): + def is_view_changed(self, context: bpy.types.Context) -> bool: view_matrix = context.region_data.view_matrix projection_matrix = context.region_data.window_matrix vp_matrix = projection_matrix @ view_matrix @@ -2517,7 +2604,7 @@ class EnableCulling(bpy.types.Operator): return True return False - def is_object_in_view(self, obj, context, camera_position): + def is_object_in_view(self, obj: bpy.types.Object, context: bpy.types.Context, camera_position: Vector) -> bool: # Get the view matrix and the projection matrix from the active viewport view_matrix = context.region_data.view_matrix projection_matrix = context.region_data.window_matrix @@ -2544,7 +2631,7 @@ class EnableCulling(bpy.types.Operator): return False return True - def invoke(self, context, event): + def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set["rna_enums.OperatorReturnItems"]: LinksData.enable_culling = True self.cullable_objects = [] for obj in bpy.context.visible_objects: @@ -2698,10 +2785,7 @@ class CreateClippingPlane(bpy.types.Operator): self.report({"INFO"}, "Maximum of six clipping planes allowed.") return {"FINISHED"} - assert context.screen - for area in context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) assert context.region and context.region_data region = context.region @@ -2834,8 +2918,16 @@ class IFCFileHandlerOperator(bpy.types.Operator): bl_label = "Import .ifc file" bl_options = {"REGISTER", "UNDO", "INTERNAL"} - directory: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"}) - files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"}) + directory: bpy.props.StringProperty( + subtype="FILE_PATH", options={"SKIP_SAVE", "HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + files: bpy.props.CollectionProperty( + type=bpy.types.OperatorFileListElement, options={"SKIP_SAVE", "HIDDEN"} + ) # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + directory: str + files: list[bpy.types.OperatorFileListElement] def invoke(self, context, event): # Keeping code in .invoke() as we'll probably add some @@ -2886,7 +2978,10 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): bl_label = "Measure Tool" bl_options = {"REGISTER", "UNDO"} - measure_type: bpy.props.StringProperty() + measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + measure_type: str @classmethod def poll(cls, context): @@ -2982,7 +3077,10 @@ class MeasureFaceAreaTool(bpy.types.Operator, PolylineOperator): bl_label = "Measure Face Area Tool" bl_options = {"REGISTER", "UNDO"} - measure_type: bpy.props.StringProperty() + measure_type: bpy.props.StringProperty() # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + measure_type: str @classmethod def poll(cls, context): @@ -3086,7 +3184,10 @@ class ClearMeasurement(bpy.types.Operator): @classmethod def poll(cls, context): polyline_props = tool.Model.get_polyline_props() - return len(polyline_props.measurement_polyline) > 0 + if len(polyline_props.measurement_polyline) > 0: + return True + cls.poll_message_set("No measurement to clear.") + return False def execute(self, context): polyline_props = tool.Model.get_polyline_props() @@ -3190,7 +3291,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): super().invoke(context, event) return {"RUNNING_MODAL"} - def cancel_tool(self, context): + def cancel_tool(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]: context.workspace.status_text_set(text=None) if hasattr(self, "tool_state"): self.tool_state.plane_method = None @@ -3198,7 +3299,7 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): tool.Blender.update_viewport() return {"CANCELLED"} - def handle_custom_instructions(self, context): + def handle_custom_instructions(self, context: bpy.types.Context) -> None: if len(self.selected_points) == 0: instruction_text = "Click First Point on Image" elif len(self.selected_points) == 1: @@ -3213,14 +3314,14 @@ class ImageScalingTool(bpy.types.Operator, PolylineOperator): context.workspace.status_text_set(text=instruction_text) - def calculate_distance(self): + def calculate_distance(self) -> None: if len(self.selected_points) == 2: point1 = self.selected_points[0] point2 = self.selected_points[1] distance_3d = (point2 - point1).length self.calculated_distance = distance_3d / self.unit_scale - def apply_scaling(self, context): + def apply_scaling(self, context: bpy.types.Context) -> set["rna_enums.OperatorReturnItems"]: if len(self.selected_points) != 2: self.report({"ERROR"}, "Two points must be selected") return {"CANCELLED"} @@ -3278,7 +3379,10 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator): bl_idname = "bim.load_blend_metadata_and_ifc" bl_label = "Load Blend Metadata and IFC" bl_options = {"REGISTER", "UNDO"} - filepath: bpy.props.StringProperty(name="IFC File Path", default="") + filepath: bpy.props.StringProperty(name="IFC File Path", default="") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + filepath: str def execute(self, context): ifc_file = self.filepath @@ -3312,3 +3416,19 @@ class LoadBlendMetadataAndIFC(bpy.types.Operator): bpy.app.handlers.load_post.append(load_handler) bpy.ops.wm.open_mainfile(filepath=metadata_path) return {"FINISHED"} + + +class GenerateUVMap(bpy.types.Operator): + bl_idname = "bim.generate_uv_map" + bl_label = "Generate UV Map" + bl_description = "Generate UV map for selected mesh." + bl_options = {"REGISTER", "UNDO", "INTERNAL"} + + def execute(self, context): + obj = context.active_object + if not obj or not isinstance(obj.data, bpy.types.Mesh): + self.report({"ERROR"}, "No valid mesh selected.") + return {"CANCELLED"} + tool.Loader.load_generated_uv_map(obj.data) + self.report({"INFO"}, "Generated UV map for selected mesh.") + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 5524f7efa7..5ba0cf6f28 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -230,11 +230,11 @@ class Link(PropertyGroup): ) georeferenced: EnumProperty( name="Georeferenced", - description="Georeferencing status: compatibility between host and linked model", + description="Georeferencing status, compatibility between host and linked model", items=[ - ("NONE", "No Georef", "Linked model has no georeferencing"), - ("NOT_COMPATIBLE", "Not Compatible", "Has geo data but CRS differ from host"), - ("FULL_COMPATIBLE", "Full Compatible", "Both CRS name and vertical datum match host"), + ("NONE", "No Georef", "Linked model has no georeferencing", "QUESTION", 0), + ("NOT_COMPATIBLE", "Not Compatible", "Has geo data but CRS differ from host", "ERROR", 1), + ("FULL_COMPATIBLE", "Full Compatible", "Both CRS name and vertical datum match host", "WORLD", 2), ], default="NONE", ) diff --git a/src/bonsai/bonsai/bim/module/project/ui.py b/src/bonsai/bonsai/bim/module/project/ui.py index d86dd3cf5b..dfe8e44987 100644 --- a/src/bonsai/bonsai/bim/module/project/ui.py +++ b/src/bonsai/bonsai/bim/module/project/ui.py @@ -255,7 +255,7 @@ class BIM_PT_project(Panel): row = self.layout.row(align=True) row.operator("bim.load_project_elements") - def draw_editing_buttons(self, context, row): + def draw_editing_buttons(self, context: object, row: bpy.types.UILayout) -> None: pprops = self.props if tool.Ifc.get(): if pprops.is_editing: @@ -496,7 +496,7 @@ class BIM_PT_links(Panel): row.operator("bim.reload_link", text="", icon="FILE_REFRESH").link_index = index else: row.operator("bim.load_link", text="", icon="LINKED").link_index = index - row.operator("bim.unlink_ifc", text="", icon="X").link_index = index + row.operator("bim.unlink_ifc", text="", icon="X").link_index = index self.layout.template_list("BIM_UL_links", "", self.props, "links", self.props, "active_link_index") if LinksData.enable_culling: @@ -619,12 +619,14 @@ class BIM_UL_links(UIList): ): row = layout.row(align=True) if item.is_loaded: - if item.georeferenced == "NONE": - row.label(text="", icon="QUESTION") - elif item.georeferenced == "NOT_COMPATIBLE": - row.label(text="", icon="ERROR") - elif item.georeferenced == "FULL_COMPATIBLE": - row.label(text="", icon="WORLD") + from bonsai.bim.module.project.prop import Link + + s = Link.bl_rna + geo_prop = s.properties["georeferenced"] + assert isinstance(geo_prop, bpy.types.EnumProperty) + enum_item = geo_prop.enum_items[item.georeferenced] + op = row.operator("bim.show_description", text="", icon=enum_item.icon, emboss=False) + op.description = f"{geo_prop.description}\n{enum_item.name}: {enum_item.description}" if item.has_transformation: row.label(text="", icon="OBJECT_ORIGIN") diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index 5437d1270b..bd60f4975a 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -41,6 +41,7 @@ class ExploreTool(bpy.types.WorkSpaceTool): ("bim.explore_hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}), ("bim.explore_hotkey", {"type": "S", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_S")]}), ("bim.explore_hotkey", {"type": "H", "value": "PRESS"}, {"properties": [("hotkey", "H")]}), + ("bim.explore_hotkey", {"type": "H", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_H")]}), ("bim.explore_hotkey", {"type": "H", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_H")]}), ) @@ -70,21 +71,26 @@ class ExploreTool(bpy.types.WorkSpaceTool): row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_M") - row = layout.row(align=True) op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT") op.hotkey = "S_M" row = layout.row(align=True) row.prop(prop, "measurement_type", text="Measure Type", expand=True, icon_only=True, emboss=True) - row = layout.row(align=True) op = row.operator("bim.clear_measurement", text="", icon="X") row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") row.label(text="", icon="EVENT_S") - row = layout.row(align=True) op = row.operator("bim.explore_hotkey", text="Image Scaling Tool", icon="IMAGE_PLANE") op.hotkey = "S_S" - op.description = "Scale Image Annotation. Allows to scale an IfcReferenceImage. Select image, select tool. Check lower left corner instructions to select two points and provide real distance between them" + op.description = ( + "Scale Image Annotation.\n\n" + "Allows to scale an IfcReferenceImage.\n\n" + "Select image, select tool. " + "Check lower left corner instructions to select two points and provide real distance between them" + ) + + row = layout.row(align=True) + row.operator("bim.generate_uv_map", icon="UV") class ExploreHotkey(bpy.types.Operator): @@ -147,5 +153,8 @@ class ExploreHotkey(bpy.types.Operator): def hotkey_H(self) -> None: bpy.ops.bim.hide_queried_linked_element() + def hotkey_S_H(self) -> None: + bpy.ops.bim.hide_queried_linked_element(hide_all_except=True) + def hotkey_A_H(self) -> None: bpy.ops.bim.hide_queried_linked_element(unhide_all=True) diff --git a/src/bonsai/bonsai/bim/module/pset/ui.py b/src/bonsai/bonsai/bim/module/pset/ui.py index 03d6f8bb36..addd03a504 100644 --- a/src/bonsai/bonsai/bim/module/pset/ui.py +++ b/src/bonsai/bonsai/bim/module/pset/ui.py @@ -262,7 +262,7 @@ class BIM_PT_object_psets(Panel): row = self.layout.row(align=True) prop_with_search(row, props, "pset_name", text="") - if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url): + if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url()): op = row.operator("bim.add_pset", icon="ADD", text="") op.obj = obj.name op.obj_type = "Object" diff --git a/src/bonsai/bonsai/bim/module/qto/calculator.py b/src/bonsai/bonsai/bim/module/qto/calculator.py index c0e5aa474f..638d603f89 100644 --- a/src/bonsai/bonsai/bim/module/qto/calculator.py +++ b/src/bonsai/bonsai/bim/module/qto/calculator.py @@ -321,7 +321,7 @@ def get_gross_perimeter(o: bpy.types.Object) -> float: return gross_perimeter -def get_space_net_perimeter(obj: bpy.types.Object) -> float: +def get_space_net_perimeter(obj: bpy.types.Object) -> None: pass diff --git a/src/bonsai/bonsai/bim/module/resource/prop.py b/src/bonsai/bonsai/bim/module/resource/prop.py index 9f2c5ba85c..f08bacbab5 100644 --- a/src/bonsai/bonsai/bim/module/resource/prop.py +++ b/src/bonsai/bonsai/bim/module/resource/prop.py @@ -188,7 +188,7 @@ class BIMResourceProperties(PropertyGroup): @property def productivity(self) -> "BIMResourceProductivity": assert bpy.context.scene - productivity = bpy.context.scene.BIMResourceProductivity + productivity = bpy.context.scene.BIMResourceProductivity # pyright: ignore[reportAttributeAccessIssue] assert isinstance(productivity, BIMResourceProductivity) return productivity diff --git a/src/bonsai/bonsai/bim/module/search/__init__.py b/src/bonsai/bonsai/bim/module/search/__init__.py index 5033fd3eb4..b6c76733a1 100644 --- a/src/bonsai/bonsai/bim/module/search/__init__.py +++ b/src/bonsai/bonsai/bim/module/search/__init__.py @@ -41,6 +41,7 @@ classes = ( operator.SelectByProperty, operator.SelectFilterElements, operator.SelectGlobalId, + operator.SelectQueryElements, operator.SelectIfcClass, operator.SelectSimilar, operator.ShowAllElements, diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py index b7ba62acdb..d5a6b9b1e6 100644 --- a/src/bonsai/bonsai/bim/module/search/operator.py +++ b/src/bonsai/bonsai/bim/module/search/operator.py @@ -42,6 +42,8 @@ from bonsai.bim.ifc import IfcStore from bonsai.bim.prop import StrProperty if TYPE_CHECKING: + from bpy.stub_internal import rna_enums + from bonsai.bim.prop import BIMFacet @@ -617,7 +619,7 @@ class SelectFilterElements(bpy.types.Operator): return {"FINISHED"} -class ApplyFilterFromText(Operator, tool.Ifc.Operator): +class ApplyFilterFromText(Operator): bl_idname = "bim.apply_filter_from_text" bl_label = "Apply Filter Configuration" bl_description = "Apply the JSON filter configuration from the current text block" @@ -791,6 +793,27 @@ class Search(Operator): return {"FINISHED"} +class SelectQueryElements(Operator): + bl_idname = "bim.select_query_elements" + bl_label = "Select Query Elements" + bl_description = "Select elements matching an provided selector query" + bl_options = {"REGISTER", "UNDO"} + + query: StringProperty(name="Query") # pyright: ignore[reportRedeclaration] + + if TYPE_CHECKING: + query: str + + def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: + results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), self.query) + objs = [obj for e in results if isinstance(obj := tool.Ifc.get_object(e), bpy.types.Object)] + active_object = context.active_object or next(iter(objs), None) + selection = tool.Blender.validate_object_selection(context, active_object, objs) + tool.Blender.set_objects_selection(*selection, clear_previous_selection=False) + self.report({"INFO"}, f"{len(results)} Results, {len(selection.selected_objects)} Objects Selected") + return {"FINISHED"} + + class SaveSearch(Operator, tool.Ifc.Operator): bl_idname = "bim.save_search" bl_label = "Save Search" @@ -1053,8 +1076,8 @@ class ColourByProperty(Operator): colourscheme[str(values[index])]["total"] += 1 obj.color = (*tool.Search.get_quantitative_palette(palette, value, min_value, max_value), 1) - if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]: - areas[0].spaces[0].shading.color_type = "OBJECT" + assert (space := tool.Blender.get_view3d_space()) + space.shading.color_type = "OBJECT" props.colourscheme.clear() @@ -1078,16 +1101,18 @@ class ColourByProperty(Operator): return (1, value) def store_state(self, context): - if areas := [a for a in context.screen.areas if a.type == "VIEW_3D"]: - self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type} + if space := tool.Blender.get_view3d_space(): + self.transaction_data = {"color_type": space.shading.color_type} def rollback(self, data): if data: - data["area"].spaces[0].shading.color_type = data["color_type"] + assert (space := tool.Blender.get_view3d_space()) + space.shading.color_type = data["color_type"] def commit(self, data): if data: - data["area"].spaces[0].shading.color_type = "OBJECT" + assert (space := tool.Blender.get_view3d_space()) + space.shading.color_type = "OBJECT" class SelectByProperty(Operator): @@ -1415,7 +1440,7 @@ class ShowAllElements(Operator): return {"FINISHED"} -class SelectSimilar(Operator, tool.Ifc.Operator): +class SelectSimilar(Operator): bl_idname = "bim.select_similar" bl_label = "Select Similar" bl_options = {"REGISTER", "UNDO"} diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index f758e89ef5..56847fa750 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations + from typing import TYPE_CHECKING, Any, Optional import bpy @@ -37,8 +39,11 @@ from bonsai.bim.module.sequence.data import ( if TYPE_CHECKING: from bonsai.bim.module.sequence.prop import ( BIMTaskTreeProperties, + BIMTaskTypeColor, BIMWorkScheduleProperties, Task, + TaskProduct, + TaskResource, ) from bonsai.bim.prop import Attribute @@ -799,23 +804,24 @@ class BIM_UL_task_columns(UIList): self, context, layout: bpy.types.UILayout, - data: "BIMWorkScheduleProperties", - item: "Attribute", + data: BIMWorkScheduleProperties, + item: Attribute, icon, active_data, active_propname, ): - props = tool.Sequence.get_work_schedule_props() if item: row = layout.row(align=True) row.prop(item, "name", emboss=False, text="") - if props.sort_column == item.name: + if data.sort_column == item.name: row.label(text="", icon="SORTALPHA") row.operator("bim.remove_task_column", text="", icon="X").name = item.name class BIM_UL_task_inputs(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF") @@ -825,7 +831,9 @@ class BIM_UL_task_inputs(UIList): class BIM_UL_task_resources(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: TaskResource, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) row.operator("bim.go_to_resource", text="", icon="STYLUS_PRESSURE").resource = item.ifc_definition_id @@ -834,7 +842,9 @@ class BIM_UL_task_resources(UIList): class BIM_UL_animation_colors(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: BIMTaskTypeColor, icon, active_data, active_propname + ) -> None: if item: row = layout.row() row.prop(item, "color", text="") @@ -842,7 +852,9 @@ class BIM_UL_animation_colors(UIList): class BIM_UL_task_outputs(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF") @@ -851,7 +863,9 @@ class BIM_UL_task_outputs(UIList): class BIM_UL_product_input_tasks(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE") @@ -861,7 +875,9 @@ class BIM_UL_product_input_tasks(UIList): class BIM_UL_product_output_tasks(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: TaskProduct, icon, active_data, active_propname + ) -> None: if item: row = layout.row(align=True) op = row.operator("bim.go_to_task", text="", icon="STYLUS_PRESSURE") @@ -886,8 +902,8 @@ class BIM_UL_tasks(UIList): self, context, layout: bpy.types.UILayout, - data: "BIMTaskTreeProperties", - item: "Task", + data: BIMTaskTreeProperties, + item: Task, icon, active_data, active_propname, diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py index 23c0c28335..f1eb4ec4ee 100644 --- a/src/bonsai/bonsai/bim/module/spatial/operator.py +++ b/src/bonsai/bonsai/bim/module/spatial/operator.py @@ -512,7 +512,7 @@ class SetContainerVisibility(bpy.types.Operator): containers -= set(tool.Ifc.get().by_type("IfcSpatialZone")) for container in containers: if obj := tool.Ifc.get_object(container): - if collection := obj.BIMObjectProperties.collection: + if collection := tool.Blender.get_object_bim_props(obj).collection: collection.hide_viewport = True should_hide = False else: @@ -523,7 +523,7 @@ class SetContainerVisibility(bpy.types.Operator): while queue: container = queue.pop() if obj := tool.Ifc.get_object(container): - if collection := obj.BIMObjectProperties.collection: + if collection := tool.Blender.get_object_bim_props(obj).collection: collection.hide_viewport = should_hide if self.should_include_children: queue.extend(ifcopenshell.util.element.get_parts(container)) diff --git a/src/bonsai/bonsai/bim/module/spatial/ui.py b/src/bonsai/bonsai/bim/module/spatial/ui.py index 69d9068f85..3a4a771e01 100644 --- a/src/bonsai/bonsai/bim/module/spatial/ui.py +++ b/src/bonsai/bonsai/bim/module/spatial/ui.py @@ -251,7 +251,7 @@ class BIM_PT_grids(Panel): bl_options = {"HEADER_LAYOUT_EXPAND"} def draw(self, context): - self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids") + self.layout.row().operator("bim.add_grid", icon="ADD", text="Add Grids") def draw_header(self, context): props = tool.Spatial.get_grid_props() @@ -357,7 +357,7 @@ class BIM_UL_elements(UIList): super().__init__(*args, **kwargs) self.use_filter_show = True - def draw_toggle(self, row: bpy.types.UILayout, is_expanded: bool, index: int): + def draw_toggle(self, row: bpy.types.UILayout, is_expanded: bool, index: int) -> None: icon_id = "DISCLOSURE_TRI_DOWN" if is_expanded else "DISCLOSURE_TRI_RIGHT" row.operator("bim.toggle_container_element", text="", emboss=False, icon=icon_id).element_index = index diff --git a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py index 132c305ad1..dc05fab89c 100644 --- a/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py +++ b/src/bonsai/bonsai/bim/module/structural/load_decoration_data.py @@ -28,6 +28,7 @@ import ifcopenshell.util.representation import ifcopenshell.util.unit import ifcopenshell.util.unit as ifcunit import numpy as np +import numpy.typing as npt from mathutils import Vector import bonsai.tool as tool @@ -478,7 +479,7 @@ class ShaderInfo: """get the args to the point shader""" location = np.array(location) indices = [] - direction_dict = { + direction_dict: dict[str, tuple[npt.NDArray, ...]] = { "fx": (np.array((1, 0, 0)), np.array((0, 1, 0)), np.array((0, 0, 1))), "fy": (np.array((0, 1, 0)), np.array((1, 0, 0)), np.array((0, 0, 1))), "fz": (np.array((0, 0, 1)), np.array((0, 1, 0)), np.array((1, 0, 0))), diff --git a/src/bonsai/bonsai/bim/module/structural/operator.py b/src/bonsai/bonsai/bim/module/structural/operator.py index 500dce4371..20b32c7a0c 100644 --- a/src/bonsai/bonsai/bim/module/structural/operator.py +++ b/src/bonsai/bonsai/bim/module/structural/operator.py @@ -42,14 +42,10 @@ class ShowLoads(bpy.types.Operator): assert context.screen if event.type == "F5": LoadsDecorator.update() - for area in context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) if event.type == "ESC": LoadsDecorator.uninstall() - for area in context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) return {"FINISHED"} return {"PASS_THROUGH"} @@ -69,9 +65,7 @@ class ShowLoads(bpy.types.Operator): raise exc context.window.cursor_modal_restore() context.window_manager.modal_handler_add(self) - for area in context.screen.areas: - if area.type == "VIEW_3D": - area.tag_redraw() + tool.Blender.update_all_viewports(context) return {"RUNNING_MODAL"} diff --git a/src/bonsai/bonsai/bim/module/structural/shader.py b/src/bonsai/bonsai/bim/module/structural/shader.py index b9b5a5c7bc..9688ce9f0e 100644 --- a/src/bonsai/bonsai/bim/module/structural/shader.py +++ b/src/bonsai/bonsai/bim/module/structural/shader.py @@ -83,7 +83,7 @@ class DecorationShader: PARALLEL DISTRIBUTED FORCE, DISTRIBUTED MOMENT, """ - vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty:ignore[too-many-positional-arguments] vert_out.smooth("VEC3", "forces") vert_out.smooth("VEC3", "co") @@ -203,7 +203,7 @@ class DecorationShader: """param: pattern: type of pattern SINGLE FORCE, SINGLE MOMENT""" - vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments] vert_out.smooth("VEC3", "co") shader_info = gpu.types.GPUShaderCreateInfo() @@ -253,7 +253,7 @@ class DecorationShader: def get_planar_shader(self) -> gpu.types.GPUShader: """shader for planar loads""" - vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") # ty: ignore[too-many-positional-arguments] vert_out.smooth("VEC3", "co") shader_info = gpu.types.GPUShaderCreateInfo() diff --git a/src/bonsai/bonsai/bim/module/style/prop.py b/src/bonsai/bonsai/bim/module/style/prop.py index 5d14bbd248..bd62021d65 100644 --- a/src/bonsai/bonsai/bim/module/style/prop.py +++ b/src/bonsai/bonsai/bim/module/style/prop.py @@ -102,7 +102,10 @@ def update_shading_styles(self: "BIMStylesProperties", context: bpy.types.Contex def update_shader_graph(self: Union["Texture", "BIMStylesProperties"], context: bpy.types.Context) -> None: - props = self.id_data.BIMStylesProperties if isinstance(self, Texture) else self + if isinstance(self, Texture): + props = tool.Style.get_style_props() + else: + props = self if not props.update_graph: return diff --git a/src/bonsai/bonsai/bim/module/unit/ui.py b/src/bonsai/bonsai/bim/module/unit/ui.py index 703c43c3d5..848425cd4e 100644 --- a/src/bonsai/bonsai/bim/module/unit/ui.py +++ b/src/bonsai/bonsai/bim/module/unit/ui.py @@ -123,7 +123,6 @@ class BIM_UL_units(UIList): active_data, active_propname, ) -> None: - props = tool.Unit.get_unit_props() if item: icon = tool.Unit.get_icon_for_unit_class(item.ifc_class) row = layout.row(align=True) @@ -137,10 +136,10 @@ class BIM_UL_units(UIList): op = row.operator("bim.assign_unit", text="", icon="KEYFRAME", emboss=False) op.unit = item.ifc_definition_id - if props.active_unit_id == item.ifc_definition_id: + if data.active_unit_id == item.ifc_definition_id: row.operator("bim.edit_unit", text="", icon="CHECKMARK").unit = item.ifc_definition_id row.operator("bim.disable_editing_unit", text="", icon="CANCEL") - elif props.active_unit_id: + elif data.active_unit_id: row.operator("bim.remove_unit", text="", icon="X").unit = item.ifc_definition_id else: op = row.operator("bim.enable_editing_unit", text="", icon="GREASEPENCIL") diff --git a/src/bonsai/bonsai/bim/module/void/ui.py b/src/bonsai/bonsai/bim/module/void/ui.py index 37415eb978..5fec870891 100644 --- a/src/bonsai/bonsai/bim/module/void/ui.py +++ b/src/bonsai/bonsai/bim/module/void/ui.py @@ -29,6 +29,8 @@ from bonsai.bim.module.void.data import BooleansData, VoidsData if TYPE_CHECKING: import bpy.stub_internal.rna_enums as rna_enums + from bonsai.bim.module.void.prop import Boolean + OPENING_ICON = "SELECT_SUBTRACT" FILLING_ICON = "SELECT_INTERSECT" @@ -175,7 +177,9 @@ class BIM_PT_booleans(Panel): class BIM_UL_booleans(UIList): - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + def draw_item( + self, context, layout: bpy.types.UILayout, data, item: Boolean, icon, active_data, active_propname + ) -> None: if item: if item.operator == "DIFFERENCE": icon = "SELECT_DIFFERENCE" diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py index f332c91401..3216478132 100644 --- a/src/bonsai/bonsai/bim/operator.py +++ b/src/bonsai/bonsai/bim/operator.py @@ -70,7 +70,7 @@ class SetTab(bpy.types.Operator): if context.area.spaces.active.search_filter: return {"FINISHED"} tool.Blender.setup_tabs() - aprops = tool.Blender.get_area_props(context) + aprops = tool.Blender.get_active_area_props(context) aprops.tab = self.tab return {"FINISHED"} @@ -85,7 +85,7 @@ class SwitchTab(bpy.types.Operator): if context.area.spaces.active.search_filter: return {"FINISHED"} tool.Blender.setup_tabs() - aprops = tool.Blender.get_area_props(context) + aprops = tool.Blender.get_active_area_props(context) aprops.tab = aprops.alt_tab return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/prop.py b/src/bonsai/bonsai/bim/prop.py index 61b916965e..e5d495c62a 100644 --- a/src/bonsai/bonsai/bim/prop.py +++ b/src/bonsai/bonsai/bim/prop.py @@ -59,7 +59,8 @@ def update_is_visible(self: "BIMTabVisibility", context: bpy.types.Context) -> N def update_global_tab(self: "BIMTabProperties", context: bpy.types.Context) -> None: tool.Blender.setup_tabs() screen = context.id_data - aprops = screen.BIMAreaProperties[screen.areas[:].index(context.area)] + assert isinstance(screen, bpy.types.Screen) + aprops = tool.Blender.get_area_props(screen)[screen.areas[:].index(context.area)] aprops.tab = self.tab diff --git a/src/bonsai/bonsai/bim/schema.py b/src/bonsai/bonsai/bim/schema.py index 4ef96e72a7..15dfa87a59 100644 --- a/src/bonsai/bonsai/bim/schema.py +++ b/src/bonsai/bonsai/bim/schema.py @@ -16,19 +16,12 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -from typing import TYPE_CHECKING - -import bpy import ifcopenshell import ifcopenshell.util.pset +import bonsai import bonsai.tool as tool -if TYPE_CHECKING or bpy.app.version >= (5, 0, 0): - import _bpy_restrict_state as bpy_restrict_state -else: - import bpy_restrict_state - class IfcSchema: data_dir: str @@ -62,7 +55,7 @@ class IfcSchema: self.psetqto.get_by_name.cache_clear() # During register we cannot access the context either way. - if isinstance(bpy.context, bpy_restrict_state._RestrictContext): + if bonsai.is_registering(): return for path in tool.Blender.get_data_dir_paths("pset", "*.ifc"): self.psetqto.templates.append(ifcopenshell.open(path)) diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index 1d0e8742dd..09eee51401 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -787,7 +787,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): doc: DocPreferences default_parameters: DefaultParameters container_hide_show_isolate: bool - mass_time_units_in_wizard: bool chain_filter_with_set_operations: bool save_metadata_blend_file: bool metadata_blend_file_suffix: str @@ -986,7 +985,6 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): def draw_extras_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: layout.prop(self, "container_hide_show_isolate") - layout.prop(self, "mass_time_units_in_wizard") row = layout.row(align=True) row.prop(self, "chain_filter_with_set_operations") row.operator("bim.open_uri", text="", icon="HELP").uri = "https://community.osarch.org/discussion/3270" @@ -1031,8 +1029,7 @@ class BIM_PT_tabs(Panel): def draw(self, context): if not UIData.is_loaded: UIData.load() - aprops = tool.Blender.get_area_props(context) - addon_prefs = tool.Blender.get_addon_preferences() + aprops = tool.Blender.get_active_area_props(context) row = self.layout.row() row.alignment = "CENTER" @@ -1119,7 +1116,9 @@ class BIM_PT_tabs(Panel): op = row.operator("bim.open_uri", text="", icon="QUESTION") op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#incompatible-blender-features" - def draw_tab_entry(self, row, icon, tab_name, enabled=True, highlight=True): + def draw_tab_entry( + self, row: bpy.types.UILayout, icon: int | str, tab_name: str, enabled: bool = True, highlight: bool = True + ) -> None: tab_entry = row.row(align=True) if isinstance(icon, int): tab_entry.operator("bim.set_tab", text="", emboss=highlight, icon_value=icon).tab = tab_name diff --git a/src/bonsai/bonsai/core/covering.py b/src/bonsai/bonsai/core/covering.py index a8c7a73f25..2ab0d39cc4 100644 --- a/src/bonsai/bonsai/core/covering.py +++ b/src/bonsai/bonsai/core/covering.py @@ -51,21 +51,11 @@ def add_instance_flooring_covering_from_cursor( if isinstance(space_polygon, str): return - bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True) - name = "Covering" - mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm) - - obj = spatial.get_named_obj_from_mesh(name, mesh) - + obj = spatial.create_object("Covering") spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj) spatial.translate_obj_to_z_location(obj, z) - points = spatial.get_2d_vertices_from_obj(obj) - points = spatial.get_scaled_2d_vertices(points) spatial.assign_type_to_obj(obj) - - spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points) - body = spatial.get_body_representation(obj) - spatial.regen_obj_representation(obj, body) + spatial.set_covering_representation_from_polygon(obj, space_polygon, polygon_is_si=True) def add_instance_ceiling_covering_from_cursor( @@ -95,21 +85,11 @@ def add_instance_ceiling_covering_from_cursor( if isinstance(space_polygon, str): return - bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True) - name = "Covering" - mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm) - - obj = spatial.get_named_obj_from_mesh(name, mesh) - + obj = spatial.create_object("Covering") spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj) spatial.translate_obj_to_z_location(obj, z + ceiling_height) - points = spatial.get_2d_vertices_from_obj(obj) - points = spatial.get_scaled_2d_vertices(points) spatial.assign_type_to_obj(obj) - - spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points) - body = spatial.get_body_representation(obj) - spatial.regen_obj_representation(obj, body) + spatial.set_covering_representation_from_polygon(obj, space_polygon, polygon_is_si=True) def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spatial]) -> None: @@ -127,19 +107,7 @@ def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spa if isinstance(space_polygon, str): return - bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True) - - name = "Aux" - mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm) - mesh = spatial.get_transformed_mesh_from_local_to_global(mesh) - obj = spatial.get_named_obj_from_mesh(name, mesh) - - points = spatial.get_2d_vertices_from_obj(obj) - points = spatial.get_scaled_2d_vertices(points) - - spatial.assign_swept_area_outer_curve_from_2d_vertices(active_obj, vertices=points) - body = spatial.get_body_representation(active_obj) - spatial.regen_obj_representation(active_obj, body) + spatial.set_covering_representation_from_polygon(active_obj, space_polygon, polygon_is_si=True) # TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS @@ -151,22 +119,13 @@ def add_instance_flooring_coverings_from_walls(root: type[tool.Root], spatial: t union = spatial.get_union_shape_from_selected_objects() for i, linear_ring in enumerate(union.interiors): poly = spatial.get_buffered_poly_from_linear_ring(linear_ring) - bm = spatial.get_bmesh_from_polygon(poly, h=0, polygon_is_si=False) name = "Covering" + str(i) - obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm) - - spatial.set_obj_origin_to_bboxcenter(obj) + obj = spatial.create_object(name) + spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False) spatial.translate_obj_to_z_location(obj, z) - - points = spatial.get_2d_vertices_from_obj(obj) - points = spatial.get_scaled_2d_vertices(points) - spatial.assign_type_to_obj(obj) - - spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points) - body = spatial.get_body_representation(obj) - spatial.regen_obj_representation(obj, body) + spatial.set_covering_representation_from_polygon(obj, poly, polygon_is_si=False) def add_instance_ceiling_coverings_from_walls( @@ -179,22 +138,13 @@ def add_instance_ceiling_coverings_from_walls( union = spatial.get_union_shape_from_selected_objects() for i, linear_ring in enumerate(union.interiors): poly = spatial.get_buffered_poly_from_linear_ring(linear_ring) - bm = spatial.get_bmesh_from_polygon(poly, h=0, polygon_is_si=False) name = "Covering" + str(i) - obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm) - - spatial.set_obj_origin_to_bboxcenter(obj) + obj = spatial.create_object(name) + spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False) spatial.translate_obj_to_z_location(obj, z) - - points = spatial.get_2d_vertices_from_obj(obj) - points = spatial.get_scaled_2d_vertices(points) - spatial.assign_type_to_obj(obj) - - spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points) - body = spatial.get_body_representation(obj) - spatial.regen_obj_representation(obj, body) + spatial.set_covering_representation_from_polygon(obj, poly, polygon_is_si=False) class NoDefaultContainer(Exception): diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 42b0f77fbb..5db2ced03e 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -432,10 +432,7 @@ def update_drawing_name( if drawing_tool.get_name(drawing) != name: ifc.run("attribute.edit_attributes", product=drawing, attributes={"Name": name}) - # Update the camera object name - camera = ifc.get_object(drawing) - if camera and camera.name != name: - camera.name = name + drawing_tool.set_camera_name(drawing, name) group = drawing_tool.get_drawing_group(drawing) if drawing_tool.get_name(group) != name: diff --git a/src/bonsai/bonsai/core/ifcgit.py b/src/bonsai/bonsai/core/ifcgit.py index fb23652ebc..f14dda6cbb 100644 --- a/src/bonsai/bonsai/core/ifcgit.py +++ b/src/bonsai/bonsai/core/ifcgit.py @@ -56,42 +56,45 @@ def discard_uncommitted(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None: ifcgit.load_project(path_ifc) -def commit_changes(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], repo: git.Repo) -> None: +def commit_changes( + ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], commit_message: str, new_branch_name: str = "" +) -> None: """Commit and create new branches as required""" path_ifc = ifc.get_path() - if repo.head.is_detached: - ifcgit.git_commit(path_ifc) - ifcgit.create_new_branch() + if ifcgit.is_head_detached(): + ifcgit.git_commit(path_ifc, commit_message) + ifcgit.create_new_branch(new_branch_name) else: - ifcgit.checkout_new_branch(path_ifc) - ifcgit.git_commit(path_ifc) + if new_branch_name: + ifcgit.checkout_new_branch(path_ifc, new_branch_name) + ifcgit.git_commit(path_ifc, commit_message) -def add_tag(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None: - ifcgit.add_tag(repo) +def add_tag(ifcgit: type[tool.IfcGit], repo: git.Repo, hexsha: str, tag_name: str, tag_message: str = "") -> None: + ifcgit.add_tag(repo, hexsha, tag_name, tag_message) def delete_tag(ifcgit: type[tool.IfcGit], repo: git.Repo, tag_name: git.TagReference) -> None: ifcgit.delete_tag(repo, tag_name) -def add_remote(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None: - ifcgit.add_remote(repo) +def add_remote(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str, remote_url: str) -> None: + ifcgit.add_remote(repo, remote_name, remote_url) -def delete_remote(ifcgit: type[tool.IfcGit], repo: git.Repo) -> None: - ifcgit.delete_remote(repo) +def delete_remote(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str) -> None: + ifcgit.delete_remote(repo, remote_name) def push(ifcgit: type[tool.IfcGit], repo: git.Repo, remote_name: str, operator: bpy.types.Operator) -> None: - error_message = ifcgit.push(repo, remote_name, repo.active_branch.name) + error_message = ifcgit.push(repo, remote_name, ifcgit.get_active_branch_name()) if error_message: operator.report({"ERROR"}, error_message) -def refresh_revision_list(ifcgit: type[tool.IfcGit], repo: git.Repo, ifc: type[tool.Ifc]) -> None: - if repo.heads: +def refresh_revision_list(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None: + if ifcgit.repo_has_commits(): ifcgit.refresh_revision_list(ifc.get_path()) @@ -125,10 +128,32 @@ def switch_revision(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc]) -> None: ifcgit.decolourise() -def merge_branch(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], operator: bpy.types.Operator) -> None: +def merge_branch(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], operator: bpy.types.Operator) -> bool | None: path_ifc = ifc.get_path() ifcgit.config_ifcmerge() - ifcgit.execute_merge(path_ifc, operator) + + branch_name = ifcgit.get_selected_branch() + if branch_name is None: + return + + mergetool = ifcgit.get_merge_tool(branch_name) + merge_result = ifcgit.git_merge(branch_name) + + if merge_result == "error": + operator.report({"ERROR"}, "Unknown IFC Merge failure") + return False + elif merge_result == "conflict": + error = ifcgit.git_mergetool(mergetool) + if error: + ifcgit.git_merge_abort() + operator.report({"ERROR"}, "IFC Merge failed:" + error) + return False + ifcgit.commit_merge(path_ifc) + + ifcgit.set_display_branch() + ifcgit.load_project(path_ifc) + ifcgit.refresh_revision_list(path_ifc) + ifcgit.decolourise() def entity_log(ifcgit: type[tool.IfcGit], ifc: type[tool.Ifc], step_id: int, operator: bpy.types.Operator) -> None: @@ -145,5 +170,9 @@ def install_git(ifcgit: type[tool.IfcGit], operator: bpy.types.Operator) -> None print("install_git() not implemented") +def fetch(ifcgit: type[tool.IfcGit], remote_name: str) -> None: + ifcgit.fetch(remote_name) + + def run_git_diff(ifcgit: type[tool.IfcGit], operator: bpy.types.Operator, save_to_temp: bool) -> None: ifcgit.run_git_diff(operator, save_to_temp) diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index 65c5955603..0a08f5e0bf 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -113,6 +113,7 @@ def assign_material( material_type: Union[str, None], objects: list[bpy.types.Object], material: Optional[ifcopenshell.entity_instance] = None, + should_auto_assign_usage: bool = True, ) -> None: """Assign material to the provided objects. @@ -121,12 +122,18 @@ def assign_material( """ material_type = material_type or material_tool.get_object_ui_material_type() material = material or material_tool.get_object_ui_active_material() + can_be_usage = should_auto_assign_usage and material_type in ("IfcMaterialLayerSet", "IfcMaterialProfileSet") for obj in objects: element = ifc.get_entity(obj) if not element: continue - ifc.run("material.assign_material", products=[element], type=material_type, material=material) + if can_be_usage and not material_tool.is_type_product(element): + element_material_type = material_type + "Usage" + else: + element_material_type = material_type + + ifc.run("material.assign_material", products=[element], type=element_material_type, material=material) assigned_material = material_tool.get_material(element) assert assigned_material # Type checker. @@ -136,7 +143,9 @@ def assign_material( material_tool.add_material_to_set(material_set=material, material=default_material) elif material_tool.is_a_material_set(assigned_material): material_tool.add_material_to_set(material_set=assigned_material, material=material) - material_tool.ensure_material_assigned(elements=[element], material_type=material_type, material=material) + material_tool.ensure_material_assigned( + elements=[element], material_type=element_material_type, material=material + ) def unassign_material(ifc: type[tool.Ifc], material_tool: type[tool.Material], objects: list[bpy.types.Object]) -> None: diff --git a/src/bonsai/bonsai/core/model.py b/src/bonsai/bonsai/core/model.py index 7b17d5de5d..e975505381 100644 --- a/src/bonsai/bonsai/core/model.py +++ b/src/bonsai/bonsai/core/model.py @@ -109,7 +109,7 @@ def align_walls( align_type: AlignType, ): reference_obj = blender.get_active_object(is_selected=True) - if not (e := ifc.get_entity(reference_obj) or not model.get_usage_type(e) == "LAYER2"): + if not reference_obj or not (e := ifc.get_entity(reference_obj)) or not model.get_usage_type(e) == "LAYER2": reference_obj = None objs = [ o @@ -159,48 +159,6 @@ def extend_wall_to_slab( model.reload_body_representation(wall_objs) -def join_walls_TZ( - ifc: type[tool.Ifc], - blender: type[tool.Blender], - geometry: type[tool.Geometry], - joiner: DumbWallJoiner, - model: type[tool.Model], -) -> None: - selected_objs = [ - o - for o in blender.get_selected_objects() - if (e := ifc.get_entity(o)) and model.get_usage_type(e) in ("LAYER2", "LAYER3") - ] - if len(selected_objs) < 2: - raise RequireAtLeastTwoLayeredElements( - "Two or more vertically or horizontally layered elements must be selected to connect their paths together" - ) - - for obj in selected_objs: - geometry.clear_scale(obj) - - elements = [ifc.get_entity(o) for o in blender.get_selected_objects()] - layer2_elements = [] - layer3_elements = [] - for element in elements: - usage = model.get_usage_type(element) - if usage == "LAYER2": - layer2_elements.append(element) - elif usage == "LAYER3": - layer3_elements.append(element) - if layer3_elements: - target = ifc.get_object(layer3_elements[0]) - for element in layer2_elements: - joiner.join_Z(ifc.get_object(element), target) - else: - if not (active_obj := blender.get_active_object()): - active_obj = selected_objs[0] - for obj in selected_objs: - if obj == active_obj: - continue - joiner.join_T(obj, active_obj) - - class RequireTwoWallsError(Exception): pass diff --git a/src/bonsai/bonsai/core/spatial.py b/src/bonsai/bonsai/core/spatial.py index 5086a8a872..5ce6d7c253 100644 --- a/src/bonsai/bonsai/core/spatial.py +++ b/src/bonsai/bonsai/core/spatial.py @@ -219,13 +219,8 @@ def generate_space( else: assert space_polygon - bm = spatial.get_bmesh_from_polygon(space_polygon, h=h, polygon_is_si=True) - - mesh = spatial.get_named_mesh_from_bmesh(name="Space", bmesh=bm) - if element and element.is_a("IfcSpace"): - mesh = spatial.get_transformed_mesh_from_local_to_global(mesh) - spatial.edit_active_space_obj_from_mesh(mesh) + spatial.set_space_representation_from_polygon(active_obj, element, space_polygon, h, polygon_is_si=True) spatial.translate_obj_to_z_location(active_obj, z) else: if relating_type: @@ -233,12 +228,13 @@ def generate_space( else: name = "Space" - obj = spatial.get_named_obj_from_mesh(name, mesh) + obj = spatial.create_object(name) spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj) spatial.translate_obj_to_z_location(obj, z) spatial.assign_ifcspace_class_to_obj(obj) element = ifc.get_entity(obj) + spatial.set_space_representation_from_polygon(obj, element, space_polygon, h, polygon_is_si=True) if relating_type: spatial.assign_relating_type_to_element(ifc, type, element, relating_type) @@ -257,16 +253,16 @@ def generate_spaces_from_walls( for i, linear_ring in enumerate(union.interiors): poly = spatial.get_buffered_poly_from_linear_ring(linear_ring) - bm = spatial.get_bmesh_from_polygon(poly, h, polygon_is_si=False) - name = "Space" + str(i) - obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm) - - spatial.set_obj_origin_to_bboxcenter_and_zero_elevation(obj) + obj = spatial.create_object(name) + spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False) spatial.translate_obj_to_z_location(obj, z) spatial.assign_ifcspace_class_to_obj(obj) + element = ifc.get_entity(obj) + spatial.set_space_representation_from_polygon(obj, element, poly, h, polygon_is_si=False) + def toggle_space_visibility(ifc: type[tool.Ifc], spatial: type[tool.Spatial]) -> None: model = ifc.get() diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index a0b6290b12..910ed79627 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -99,6 +99,7 @@ class Blender: def get_object_bounding_box(cls, obj): pass def get_selected_objects(cls, include_active=False): pass def get_viewport_context(cls): pass + def operator_idname_to_py(cls, idname): pass def is_ifc_class_active(cls, ifc_class): pass def is_ifc_object(cls, obj): pass def remove_object(cls, obj): pass @@ -348,7 +349,10 @@ class Drawing: def enable_editing_text(cls, obj): pass def ensure_unique_drawing_name(cls, name): pass def ensure_unique_identification(cls, identification): pass + def export_font_size(cls, obj): pass + def export_symbol(cls, obj): pass def export_text_literal_attributes(cls, obj): pass + def export_wrap_length(cls, obj): pass def generate_drawing_matrix(cls, target_view, location_hint): pass def generate_drawing_name(cls, target_view, location_hint): pass def generate_reference_attributes(cls, reference, **attributes): pass @@ -401,6 +405,7 @@ class Drawing: def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass def run_type_assign_type(cls, element=None, relating_type=None): pass def select_assigned_product(cls, drawing): pass + def set_camera_name(cls, drawing, name): pass def set_drawing_collection_name(cls, drawing, collection): pass def set_name(cls, element, name): pass def setup_annotation_object(cls, obj, object_type): pass @@ -530,6 +535,56 @@ class Ifc: def get_all_element_occurrences(cls, element): pass +@interface +class IfcGit: + def add_file_to_repo(cls, repo, path_file): pass + def add_remote(cls, repo, remote_name, remote_url): pass + def add_tag(cls, repo, hexsha, tag_name, tag_message): pass + def branches_by_hexsha(cls, repo): pass + def checkout_new_branch(cls, path_file, branch_name): pass + def clear_commits_list(cls): pass + def clone_repo(cls, remote_url, local_folder): pass + def colourise(cls, step_ids): pass + def config_ifcmerge(cls): pass + def create_new_branch(cls, branch_name): pass + def decolourise(cls): pass + def delete_remote(cls, repo, remote_name): pass + def delete_tag(cls, repo, tag_name): pass + def dos2unix(cls, path_file): pass + def commit_merge(cls, path_ifc): pass + def entity_log(cls, path_ifc, step_id): pass + def fetch(cls, remote_name): pass + def get_commits_list(cls, path_ifc, lookup): pass + def get_merge_tool(cls, branch_name): pass + def get_selected_branch(cls): pass + def git_merge(cls, branch_name): pass + def git_merge_abort(cls): pass + def git_mergetool(cls, mergetool): pass + def set_display_branch(cls): pass + def get_active_branch_name(cls): pass + def get_ifcgit_props(cls): pass + def get_modified_step_ids(cls, step_ids): pass + def get_path_dir(cls, path_ifc): pass + def get_revisions_step_ids(cls): pass + def is_head_detached(cls): pass + def repo_has_commits(cls): pass + def git_checkout(cls, path_file): pass + def git_commit(cls, path_file, commit_message): pass + def ifc_diff_ids(cls, repo, hash_a, hash_b, path_ifc): pass + def init_repo(cls, path_dir): pass + def install_git_windows(cls, operator): pass + def is_valid_ref_format(cls, string): pass + def load_anyifc(cls, repo): pass + def load_project(cls, path_ifc): pass + def push(cls, repo, remote_name, branch_name): pass + def refresh_revision_list(cls, path_ifc): pass + def repo_from_path(cls, path): pass + def run_git_diff(cls, operator, save_to_temp): pass + def switch_to_revision_item(cls): pass + def tags_by_hexsha(cls, repo): pass + def update_step_ids(cls, step_ids, modified_step_ids): pass + + @interface class Layer: pass @@ -579,6 +634,7 @@ class Material: def import_material_definitions(cls, material_type: str): pass def is_a_flow_segment(cls, element): pass def is_a_material_set(cls, material): pass + def is_type_product(cls, element): pass def is_editing_materials(cls): pass def is_material_used_in_sets(cls, material): pass def load_material_attributes(cls, material): pass @@ -618,7 +674,10 @@ class Model: def import_rectangle(cls, obj, position, profile): pass def load_openings(cls, openings): pass def purge_scene_openings(cls): pass + def recalculate_walls(cls, objs): pass def regenerate_array(cls, parent, data): pass + def regenerate_profile(cls, obj): pass + def regenerate_slab(cls, obj): pass def reload_body_representation(cls, obj_or_objects): pass def replace_object_ifc_representation(cls, ifc_file, ifc_context, obj, new_representation): pass @@ -990,14 +1049,12 @@ class Spatial: def get_purged_inner_holes_poly(cls, union_geom, min_area): pass def get_poly_valid_interior_list(cls, poly, min_area, interiors_list): pass def get_buffered_poly_from_linear_ring(cls, linear_ring): pass - def get_bmesh_from_polygon(cls, poly, h, polygon_is_si=False): pass - def get_named_obj_from_bmesh(cls, name, bmesh): pass - def get_named_obj_from_mesh(cls, name, mesh): pass - def get_named_mesh_from_bmesh(cls, name, bmesh): pass - def get_transformed_mesh_from_local_to_global(cls, mesh): pass - def edit_active_space_obj_from_mesh(cls, mesh): pass - def set_obj_origin_to_bboxcenter(cls, obj): pass - def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj): pass + def get_2d_vertices_from_polygon(cls, poly, obj, polygon_is_si=True): pass + def set_extrusion_representation_from_polygon(cls, obj, element, poly, depth_ifc, polygon_is_si=True): pass + def set_space_representation_from_polygon(cls, obj, element, poly, h, polygon_is_si=True): pass + def set_covering_representation_from_polygon(cls, obj, poly, polygon_is_si=True): pass + def create_object(cls, name): pass + def set_obj_origin_to_polygon_center(cls, obj, poly, polygon_is_si=True): pass def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj): pass def get_selected_objects(cls): pass def get_active_obj(cls): pass @@ -1005,14 +1062,9 @@ class Spatial: def get_active_obj_height(cls): pass def get_relating_type_id(cls): pass def translate_obj_to_z_location(cls, obj, z): pass - def get_2d_vertices_from_obj(cls, obj): pass - def get_scaled_2d_vertices(cls, points): pass - def assign_swept_area_outer_curve_from_2d_vertices(cls, obj, vertices): pass - def get_body_representation(cls, obj): pass def assign_ifcspace_class_to_obj(cls, obj): pass def assign_type_to_obj(cls, obj): pass def assign_relating_type_to_element(cls, ifc, type, element, relating_type): pass - def regen_obj_representation(cls, obj, body): pass def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass def toggle_hide_spaces(cls, spaces): pass def set_default_container(cls, container): pass @@ -1117,6 +1169,8 @@ class Type: def get_representation_context(cls, representation): pass def get_type_occurrences(cls, element_type): pass def has_material_usage(cls, element): pass + def record_material_usage_attributes(cls, element): pass + def restore_material_usage_attributes(cls, element, usage_attributes): pass def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass def run_geometry_switch_representation(cls, obj=None, representation=None): pass diff --git a/src/bonsai/bonsai/tool/aggregate.py b/src/bonsai/bonsai/tool/aggregate.py index 43b155a5aa..1f7f601862 100644 --- a/src/bonsai/bonsai/tool/aggregate.py +++ b/src/bonsai/bonsai/tool/aggregate.py @@ -49,21 +49,40 @@ class Aggregate(bonsai.core.tool.Aggregate): related_object = tool.Ifc.get_entity(related_obj) if not relating_object or not related_object: return False + if relating_object == related_object: + return False + + is_compatible_class = False if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a( "IfcElement" ): - return True - if tool.Ifc.get_schema() == "IFC2X3": + is_compatible_class = True + elif tool.Ifc.get_schema() == "IFC2X3": if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"): - return True - if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialStructureElement"): - return True + is_compatible_class = True + elif relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialStructureElement"): + is_compatible_class = True else: if relating_object.is_a("IfcSpatialElement") and related_object.is_a("IfcSpatialElement"): - return True - if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialElement"): - return True - return False + is_compatible_class = True + elif relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialElement"): + is_compatible_class = True + + if not is_compatible_class: + return False + + # Prevent cyclic references: walk up the full hierarchy from the + # proposed parent and reject if we encounter the proposed child. + ancestor = ifcopenshell.util.element.get_parent(relating_object) + seen = {relating_object} + while ancestor: + if ancestor == related_object: + return False + if ancestor in seen: + break + seen.add(ancestor) + ancestor = ifcopenshell.util.element.get_parent(ancestor) + return True @classmethod def has_physical_body_representation(cls, element: ifcopenshell.entity_instance) -> bool: diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 338c9da70c..6c759027f2 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -74,7 +74,13 @@ if TYPE_CHECKING: BIMSolarProperties, RadianceExporterProperties, ) - from bonsai.bim.prop import BIMObjectProperties, BIMProperties + from bonsai.bim.prop import ( + BIMAreaProperties, + BIMCollectionProperties, + BIMObjectProperties, + BIMProperties, + BIMTabProperties, + ) T = TypeVar("T") @@ -138,17 +144,20 @@ class Blender(bonsai.core.tool.Blender): space.region_3d.view_perspective = "CAMERA" @classmethod - def get_area_props(cls, context: bpy.types.Context) -> bpy.types.PropertyGroup: + def get_active_area_props(cls, context: bpy.types.Context) -> BIMAreaProperties | BIMTabProperties: + FULLSCREEN_SUFFIX = "-nonnormal" # Ctrl-space temporary fullscreen + assert (screen := context.screen) try: - if context.screen.name.endswith("-nonnormal"): # Ctrl-space temporary fullscreen - screen = bpy.data.screens[context.screen.name.removesuffix("-nonnormal")] + if screen.name.endswith(FULLSCREEN_SUFFIX): + screen = bpy.data.screens[screen.name.removesuffix(FULLSCREEN_SUFFIX)] # The original area object has its type changed to "EMPTY" apparently index = [a.type for a in screen.areas].index("EMPTY") - return screen.BIMAreaProperties[index] - return context.screen.BIMAreaProperties[context.screen.areas[:].index(context.area)] + return cls.get_area_props(screen)[index] + assert (area := context.area) + return cls.get_area_props(screen)[screen.areas[:].index(area)] except IndexError: # Fallback in case areas aren't setup yet. - return context.screen.BIMTabProperties + return cls.get_tab_props(screen) @classmethod def set_active_object(cls, obj: bpy.types.Object) -> None: @@ -165,15 +174,16 @@ class Blender(bonsai.core.tool.Blender): def setup_tabs(cls) -> None: # https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area for screen in bpy.data.screens: - if len(screen.BIMAreaProperties) == 20: + area_props = cls.get_area_props(screen) + if len(area_props) == 20: continue - screen.BIMAreaProperties.clear() + area_props.clear() for i in range(20): # 20 is an arbitrary value of split areas - screen.BIMAreaProperties.add() + area_props.add() @classmethod def should_show_panel(cls, context: bpy.types.Context, tab: str, panel: str) -> bool: - aprops = cls.get_area_props(context) + aprops = cls.get_active_area_props(context) if aprops.path_from_id() == "BIMAreaProperties" and context.area.spaces.active.search_filter: return True if (is_bookmark_tab := aprops.tab == "BOOKMARK") or aprops.tab == tab: @@ -185,6 +195,7 @@ class Blender(bonsai.core.tool.Blender): return True elif panel_visibility.is_visible: return True + return False @classmethod def is_default_scene(cls) -> bool: @@ -330,15 +341,27 @@ class Blender(bonsai.core.tool.Blender): @classmethod def get_view3d_area(cls) -> Union[bpy.types.Area, None]: - for window in bpy.context.window_manager.windows: + assert (wm := bpy.context.window_manager) + for window in wm.windows: for area in window.screen.areas: if area.type == "VIEW_3D": return area + @classmethod + def operator_idname_to_py(cls, idname: str) -> str: + """Convert a Blender internal operator idname to its Python equivalent. + + Example: ``MESH_OT_primitive_cube_add`` -> ``mesh.primitive_cube_add`` + """ + module, func = idname.split("_OT_", 1) + return f"{module.lower()}.{func}" + @classmethod def get_view3d_space(cls) -> Union[bpy.types.SpaceView3D, None]: if area := cls.get_view3d_area(): - return area.spaces.active + space = area.spaces.active + assert isinstance(space, bpy.types.SpaceView3D) + return space @classmethod def get_blender_prop_default_value(cls, props: bpy.types.bpy_struct, prop_name: str) -> Any: @@ -459,6 +482,14 @@ class Blender(bonsai.core.tool.Blender): def update_viewport(cls) -> None: cls.get_viewport_context()["area"].tag_redraw() + @classmethod + def update_all_viewports(cls, context: bpy.types.Context | None = None) -> None: + context = context or bpy.context + assert context.screen + for area in context.screen.areas: + if area.type == "VIEW_3D": + area.tag_redraw() + @classmethod def force_depsgraph_update(cls) -> None: """useful if you need to trigger callbacks like `depsgraph_update_pre`""" @@ -1489,7 +1520,7 @@ class Blender(bonsai.core.tool.Blender): def override_scene_panel(cls, original_panel: bpy.types.Panel) -> None: @classmethod def poll_check_blender_tab(cls, context): - aprops = tool.Blender.get_area_props(context) + aprops = tool.Blender.get_active_area_props(context) if aprops.path_from_id() == "BIMAreaProperties" and context.area.spaces.active.search_filter: return True return aprops.tab == "BLENDER" @@ -1829,6 +1860,18 @@ class Blender(bonsai.core.tool.Blender): assert (scene := bpy.context.scene) return scene.BIMProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod + def get_area_props(cls, screen: bpy.types.Screen) -> bpy.types.bpy_prop_collection_idprop[BIMAreaProperties]: + return screen.BIMAreaProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def get_tab_props(cls, screen: bpy.types.Screen) -> BIMTabProperties: + return screen.BIMTabProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def get_collection_props(cls, collection: bpy.types.Collection) -> BIMCollectionProperties: + return collection.BIMCollectionProperties # pyright: ignore[reportAttributeAccessIssue] + @classmethod def get_object_bim_props(cls, obj: bpy.types.Object) -> BIMObjectProperties: return obj.BIMObjectProperties # pyright: ignore[reportAttributeAccessIssue] @@ -1882,19 +1925,21 @@ class Blender(bonsai.core.tool.Blender): @classmethod def clear_undo_history(cls) -> None: """Clears the Blender history, Bonsai history, and IfcOpenShell history""" - old_undo_steps = bpy.context.preferences.edit.undo_steps - bpy.context.preferences.edit.undo_steps = 2 + assert (preferences := bpy.context.preferences) + old_undo_steps = preferences.edit.undo_steps + preferences.edit.undo_steps = 2 for i in range(3): bpy.ops.ed.undo_push(message="Undo history cleared") - bpy.context.preferences.edit.undo_steps = old_undo_steps + preferences.edit.undo_steps = old_undo_steps tool.Ifc.clear_history() old_history_size = tool.Ifc.get().history_size tool.Ifc.get().set_history_size(0) tool.Ifc.get().set_history_size(old_history_size) @classmethod - def get_unit_scale(cls): - unit_length = bpy.context.scene.unit_settings.length_unit + def get_unit_scale(cls) -> float: + assert (scene := bpy.context.scene) + unit_length = scene.unit_settings.length_unit unit_scale = 1.0 if unit_length == "CENTIMETERS": unit_scale = 0.01 diff --git a/src/bonsai/bonsai/tool/bsdd.py b/src/bonsai/bonsai/tool/bsdd.py index 47c948abcc..387474b81f 100644 --- a/src/bonsai/bonsai/tool/bsdd.py +++ b/src/bonsai/bonsai/tool/bsdd.py @@ -32,14 +32,28 @@ import bonsai.core.tool import bonsai.tool as tool if TYPE_CHECKING: + from bsdd.bsdd import ClassContractV1, ClassPropertyContractV1, PropertyContractV5 + from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDDictionary class Bsdd(bonsai.core.tool.Bsdd): - identifier_url = "https://identifier.buildingsmart.org" + default_identifier_url = "https://identifier.buildingsmart.org" + default_api_url = "https://api.bsdd.buildingsmart.org/api/" client = bsdd.Client() - bsdd_classes: dict[str, dict] = {} - bsdd_properties: dict[str, dict] = {} + bsdd_classes: dict[str, ClassContractV1] = {} + bsdd_properties: dict[str, ClassPropertyContractV1 | PropertyContractV5] = {} + + @classmethod + def identifier_url(cls) -> str: + """Derives the identifier base URL from the current client baseurl. + Falls back to the standard bSDD identifier URL when using the default API.""" + if cls.client.baseurl == cls.default_api_url: + return cls.default_identifier_url + from urllib.parse import urlparse + + parsed = urlparse(cls.client.baseurl) + return f"{parsed.scheme}://{parsed.netloc}" @classmethod def get_bsdd_props(cls) -> BIMBSDDProperties: @@ -255,7 +269,8 @@ class Bsdd(bonsai.core.tool.Bsdd): @classmethod def get_bsdd_property(cls, uri: str) -> dict: if not (bsdd_property := cls.bsdd_properties.get(uri, {})): - bsdd_property = cls.client.get_property(uri, include_classes=True) + # Cache miss occurs for keyword search mode, for classes cache is prepopulated. + bsdd_property = cls.client.get_property(uri) cls.bsdd_properties[uri] = bsdd_property return bsdd_property @@ -269,7 +284,7 @@ class Bsdd(bonsai.core.tool.Bsdd): for obj in tool.Blender.get_selected_objects(include_active=True): if element := tool.Ifc.get_entity(obj): for reference in ifcopenshell.util.classification.get_references(element): - if (uri := reference.Location) and uri.startswith(cls.identifier_url): + if (uri := reference.Location) and uri.startswith(cls.identifier_url()): classes.add((reference[1] or reference[2] or "Unnamed", uri)) dictionary_uris = ( @@ -383,7 +398,7 @@ class Bsdd(bonsai.core.tool.Bsdd): def get_applicable_psets(cls, element: ifcopenshell.entity_instance): uris = set() for reference in ifcopenshell.util.classification.get_references(element): - if (uri := reference.Location) and uri.startswith(cls.identifier_url): + if (uri := reference.Location) and uri.startswith(cls.identifier_url()): uris.add(uri) psets = set() for uri in uris: @@ -399,7 +414,7 @@ class Bsdd(bonsai.core.tool.Bsdd): def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool: uris = set() for reference in ifcopenshell.util.classification.get_references(element): - if (uri := reference.Location) and uri.startswith(cls.identifier_url): + if (uri := reference.Location) and uri.startswith(cls.identifier_url()): uris.add(uri) class_uri, pset_name = pset_uri.rsplit("#", 1) return class_uri in uris diff --git a/src/bonsai/bonsai/tool/cad.py b/src/bonsai/bonsai/tool/cad.py index 13678df15a..c91b5df0d8 100644 --- a/src/bonsai/bonsai/tool/cad.py +++ b/src/bonsai/bonsai/tool/cad.py @@ -841,7 +841,7 @@ class Cad: return new_verts @classmethod - def region_2d_to_vector_3d_np(cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector) -> Vector: + def region_2d_to_vector_3d_np(cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3D, coord: Vector) -> Vector: """ Numpy version of view3d_utils.region_2d_to_vector_3d Return a direction vector from the viewport at the specific 2d region @@ -880,7 +880,7 @@ class Cad: @classmethod def region_2d_to_location_3d_np( - cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, depth_location: Vector + cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3D, coord: Vector, depth_location: Vector ) -> Vector: """ Numpy version of view3d_utils.region_2d_to_location_3d @@ -913,7 +913,7 @@ class Cad: @classmethod def region_2d_to_origin_3d_np( - cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, clamp: float = None + cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3D, coord: Vector, *, clamp: float = None ) -> Vector: """ Numpy version of view3d_utils.region_2d_to_origin_3d @@ -971,7 +971,7 @@ class Cad: @classmethod def location_3d_to_region_2d_np( - cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, default=None + cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3D, coord: Vector, *, default=None ) -> Vector: """ Numpy version of view3d_utils.location_3d_to_region_2d diff --git a/src/bonsai/bonsai/tool/collector.py b/src/bonsai/bonsai/tool/collector.py index b0aa358629..1e6653acd1 100644 --- a/src/bonsai/bonsai/tool/collector.py +++ b/src/bonsai/bonsai/tool/collector.py @@ -157,7 +157,8 @@ class Collector(bonsai.core.tool.Collector): return collection = bpy.data.collections.new(obj.name) props.collection = collection - collection.BIMCollectionProperties.obj = obj + collection_props = tool.Blender.get_collection_props(collection) + collection_props.obj = obj return collection @classmethod diff --git a/src/bonsai/bonsai/tool/cost.py b/src/bonsai/bonsai/tool/cost.py index 663b16040e..bbec525ee9 100644 --- a/src/bonsai/bonsai/tool/cost.py +++ b/src/bonsai/bonsai/tool/cost.py @@ -154,7 +154,7 @@ class Cost(bonsai.core.tool.Cost): device = aud.Device() # chaching.mp3 is by Lucish_ CC-BY-3.0 https://freesound.org/people/Lucish_/sounds/554841/ filepath = tool.Blender.get_data_dir_path("chaching.mp3").__str__() - sound = aud.Sound(filepath) + sound = aud.Sound(filepath) # ty:ignore[too-many-positional-arguments] device.play(sound) @classmethod diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index b8ec26fd37..86113b9c95 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -603,6 +603,10 @@ class Drawing(bonsai.core.tool.Drawing): props = tool.Drawing.get_text_props(obj) for literal_props in props.literals: literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes) + alignment = literal_props.align_vertical + "-" + literal_props.align_horizontal + if alignment == "middle-middle": + alignment = "center" + literal_data["BoxAlignment"] = alignment literals.append(literal_data) return literals @@ -853,6 +857,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def edit_text_literals(cls, obj: bpy.types.Object, literal_attributes: dict) -> None: + if not literal_attributes: + return assert (element := tool.Ifc.get_entity(obj)) assert (rep := cls.get_annotation_representation(element)) to_remove = [i for i in rep.Items if i.is_a("IfcTextLiteral")] @@ -1176,22 +1182,26 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def import_text_attributes(cls, obj: bpy.types.Object) -> None: - from bonsai.bim.module.drawing.prop import BOX_ALIGNMENT_POSITIONS - props = cls.get_text_props(obj) props.literals.clear() ifc_literals = cls.get_text_literal(obj, return_list=True) assert isinstance(ifc_literals, list) + + if ifc_literals: + first_alignment = getattr(ifc_literals[0], "BoxAlignment", None) or "bottom-left" + if first_alignment == "center": + first_alignment = "middle-middle" + props.align_vertical, props.align_horizontal = first_alignment.split("-") + for ifc_literal in ifc_literals: literal_props = props.literals.add() bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes) - box_alignment_mask = [False] * 9 - position_string = literal_props.attributes["BoxAlignment"].string_value - box_alignment_mask[BOX_ALIGNMENT_POSITIONS.index(position_string)] = True - - literal_props.box_alignment = box_alignment_mask # pyright: ignore[reportAttributeAccessIssue] + alignment = getattr(ifc_literal, "BoxAlignment", None) or "bottom-left" + if alignment == "center": + alignment = "middle-middle" + literal_props.align_vertical, literal_props.align_horizontal = alignment.split("-") literal_props.ifc_definition_id = ifc_literal.id() from bonsai.bim.module.drawing.data import DecoratorData @@ -1280,6 +1290,12 @@ class Drawing(bonsai.core.tool.Drawing): def get_representation(cls, element, context): return ifcopenshell.util.representation.get_representation(element, context) + @classmethod + def set_camera_name(cls, drawing: ifcopenshell.entity_instance, name: str) -> None: + camera = tool.Ifc.get_object(drawing) + if camera and camera.name != name: + camera.name = name + @classmethod def set_drawing_collection_name( cls, drawing: ifcopenshell.entity_instance, collection: bpy.types.Collection diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index e24152d642..0d690d0308 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -1407,8 +1407,6 @@ class Geometry(bonsai.core.tool.Geometry): :param representation_item: item to remove. :param element: item's element. Is used to unmark manual booleans. """ - # NOTE: we assume it's not the last representation item - # otherwise we probably would need to remove representation too # NOTE: a lot of shared code with `geometry.remove_representation` ifc_file = tool.Ifc.get() shape_aspects: list[ifcopenshell.entity_instance] = [] @@ -1467,7 +1465,10 @@ class Geometry(bonsai.core.tool.Geometry): cls.remove_representation_items_from_shape_aspect([representation_item], shape_aspect) if representation: - representation.Items = tuple(set(representation.Items) - {representation_item}) + new_items = tuple(set(representation.Items) - {representation_item}) + if not new_items: + return + representation.Items = new_items also_consider = list(consider_inverses) ifcopenshell.util.element.remove_deep2(ifc_file, representation_item, also_consider=also_consider) diff --git a/src/bonsai/bonsai/tool/ifc.py b/src/bonsai/bonsai/tool/ifc.py index 8cbd2a112b..6d79ce7582 100644 --- a/src/bonsai/bonsai/tool/ifc.py +++ b/src/bonsai/bonsai/tool/ifc.py @@ -197,12 +197,16 @@ class Ifc(bonsai.core.tool.Ifc): if not cls.get(): return + # Clear all per-object msgbus subscriptions at once using the dedicated + # owner. After undo/redo, per-object Python wrappers have new + # identities so clearing by individual obj would miss stale + # subscriptions registered with the old wrappers. + bpy.msgbus.clear_by_owner(bonsai.bim.handler.object_subscription_owner) + for obj in bpy.data.objects: if obj.library: continue - bpy.msgbus.clear_by_owner(obj) - element = cls.get_entity(obj) if not element: continue @@ -217,8 +221,6 @@ class Ifc(bonsai.core.tool.Ifc): if obj.library: continue - bpy.msgbus.clear_by_owner(obj) - style = cls.get_entity(obj) if not style: continue diff --git a/src/bonsai/bonsai/tool/ifcgit.py b/src/bonsai/bonsai/tool/ifcgit.py index db557542bc..1843716f58 100644 --- a/src/bonsai/bonsai/tool/ifcgit.py +++ b/src/bonsai/bonsai/tool/ifcgit.py @@ -24,7 +24,7 @@ import re import subprocess import tempfile from pathlib import Path -from typing import TYPE_CHECKING, Any, Literal, Union +from typing import TYPE_CHECKING, Any, Union import bpy @@ -128,39 +128,27 @@ class IfcGit: cls.dos2unix(path_file) repo.index.add(os.path.normpath(path_file)) repo.index.commit(message="Added " + os.path.relpath(path_file, repo.working_dir)) - bpy.ops.ifcgit.refresh() @classmethod def git_checkout(cls, path_file: str) -> None: IfcGitRepo.repo.git.checkout(path_file) @classmethod - def checkout_new_branch(cls, path_file: str) -> None: + def checkout_new_branch(cls, path_file: str, branch_name: str) -> None: """Create a branch and move uncommitted changes to this branch""" - props = cls.get_ifcgit_props() - if props.new_branch_name: - IfcGitRepo.repo.git.checkout(b=props.new_branch_name) - props.display_branch = props.new_branch_name - props.new_branch_name = "" - bpy.ops.ifcgit.refresh() + IfcGitRepo.repo.git.checkout(b=branch_name) @classmethod - def git_commit(cls, path_file: str) -> None: - props = cls.get_ifcgit_props() + def git_commit(cls, path_file: str, commit_message: str) -> None: repo = IfcGitRepo.repo if os.name == "nt": cls.dos2unix(path_file) repo.index.add(os.path.normpath(path_file)) - repo.index.commit(message=props.commit_message) - props.commit_message = "" + repo.index.commit(message=commit_message) @classmethod - def add_tag(cls, repo: git.Repo) -> None: - props = cls.get_ifcgit_props() - item = props.ifcgit_commits[props.commit_index] - repo.create_tag(props.new_tag_name, ref=item.hexsha, message=props.new_tag_message) - props.new_tag_name = "" - props.new_tag_message = "" + def add_tag(cls, repo: git.Repo, hexsha: str, tag_name: str, tag_message: str = "") -> None: + repo.create_tag(tag_name, ref=hexsha, message=tag_message) @classmethod def delete_tag(cls, repo: git.Repo, tag_name: git.TagReference) -> None: @@ -168,20 +156,13 @@ class IfcGit: repo.delete_tag(tag_name) @classmethod - def add_remote(cls, repo: git.Repo) -> None: - props = cls.get_ifcgit_props() - repo.create_remote(name=props.remote_name, url=props.remote_url) - props.remote_name = "" - props.remote_url = "" + def add_remote(cls, repo: git.Repo, remote_name: str, remote_url: str) -> None: + repo.create_remote(name=remote_name, url=remote_url) @classmethod - def delete_remote(cls, repo: git.Repo) -> None: - props = cls.get_ifcgit_props() - remote_name = props.select_remote + def delete_remote(cls, repo: git.Repo, remote_name: str) -> None: if remote_name in repo.remotes: repo.delete_remote(remote_name) - if repo.remotes: - props.select_remote = repo.remotes[0].name @classmethod def push(cls, repo: git.Repo, remote_name: str, branch_name: str) -> Union[str, None]: @@ -193,16 +174,25 @@ class IfcGit: return exc.stderr @classmethod - def create_new_branch(cls) -> None: - """Convert a detached HEAD into a branch""" - props = cls.get_ifcgit_props() - repo = IfcGitRepo.repo - new_branch = repo.create_head(props.new_branch_name) - new_branch.checkout() - props.display_branch = props.new_branch_name - props.new_branch_name = "" + def is_head_detached(cls) -> bool: + return bool(IfcGitRepo.repo.head.is_detached) - bpy.ops.ifcgit.refresh() + @classmethod + def repo_has_commits(cls) -> bool: + if IfcGitRepo.repo: + return bool(IfcGitRepo.repo.heads) + return False + + @classmethod + def get_active_branch_name(cls) -> str: + return IfcGitRepo.repo.active_branch.name + + @classmethod + def create_new_branch(cls, branch_name: str) -> None: + """Convert a detached HEAD into a branch""" + repo = IfcGitRepo.repo + new_branch = repo.create_head(branch_name) + new_branch.checkout() @classmethod def clear_commits_list(cls) -> None: @@ -243,6 +233,7 @@ class IfcGit: list_item.message = commit.message list_item.author_name = commit.author.name list_item.author_email = commit.author.email + list_item.committed_date = int(commit.committed_date) if commit in commits_relevant: list_item.relevant = True if commit.hexsha in lookup: @@ -284,7 +275,11 @@ class IfcGit: if re.match("^Ifc", obj.name): bpy.data.objects.remove(obj, do_unlink=True) - bpy.data.orphans_purge(do_recursive=True) + bpy.data.orphans_purge(do_recursive=True) # ty:ignore[unknown-argument] + + from bonsai.bim.module.root.data import IfcClassData + + IfcClassData.is_loaded = False settings = import_ifc.IfcImportSettings.factory(bpy.context, path_ifc, logging.getLogger("ImportIFC")) settings.should_setup_viewport_camera = False @@ -469,15 +464,6 @@ class IfcGit: bpy.data.objects.remove(obj, do_unlink=True) bpy.data.collections.remove(blender_collection) - @classmethod - def is_valid_branch_name(cls, new_branch_name: str): - """Check if a branch name is valid and doesn't conflict with existing branches""" - if not cls.is_valid_ref_format(new_branch_name): - return False - if new_branch_name in [branch.name for branch in IfcGitRepo.repo.branches]: - return False - return True - @classmethod def config_ifcmerge(cls) -> None: config_reader = IfcGitRepo.repo.config_reader() @@ -519,49 +505,65 @@ class IfcGit: output.write(line + b"\n") @classmethod - def execute_merge(cls, path_ifc: str, operator: bpy.types.Operator) -> Union[None, Literal[False]]: + def get_selected_branch(cls) -> Union[str, None]: + """Return the name of the branch at the selected commit matching display_branch, or None.""" props = cls.get_ifcgit_props() repo = IfcGitRepo.repo item = props.ifcgit_commits[props.commit_index] lookup = cls.branches_by_hexsha(repo) - if item.hexsha in lookup: - for branch in lookup[item.hexsha]: - if branch.name == props.display_branch: - # this is a branch! - if re.match("^(origin/)?(HEAD|main|master)$", branch.name): - # preserve remote IDs in origin/main or main - mergetool = "ifcmerge" - else: - # rewrite remote IDs - mergetool = "ifcmerge-forward" - try: - # NOTE this is calling the git binary in a subprocess - repo.git.merge(branch) - except git.exc.GitCommandError: - # merge is expected to fail, run ifcmerge - try: - repo.git.mergetool(tool=mergetool) - except git.exc.GitCommandError as exc: - message = re.sub("( stderr: '|')", "", exc.stderr) - # ifcmerge failed, rollback - repo.git.merge(abort=True) + if item.hexsha not in lookup: + return None + for branch in lookup[item.hexsha]: + if branch.name == props.display_branch: + return branch.name + return None - operator.report({"ERROR"}, "IFC Merge failed:" + message) - return False - else: - if os.name == "nt": - cls.dos2unix(path_ifc) - repo.index.add(os.path.normpath(path_ifc)) - repo.git.commit("--no-edit") - except git.exc.GitError: - operator.report({"ERROR"}, "Unknown IFC Merge failure") - return False + @classmethod + def get_merge_tool(cls, branch_name: str) -> str: + if re.match("^(origin/)?(HEAD|main|master)$", branch_name): + return "ifcmerge" + return "ifcmerge-forward" - props.display_branch = repo.active_branch.name + @classmethod + def git_merge(cls, branch_name: str) -> Union[str, None]: + """Attempt a git merge. Returns None on clean merge, 'conflict' on expected + GitCommandError, or 'error' on an unknown GitError.""" + repo = IfcGitRepo.repo + branch = repo.branches[branch_name] + try: + repo.git.merge(branch) + return None + except git.exc.GitCommandError: + return "conflict" + except git.exc.GitError: + return "error" - cls.load_project(path_ifc) - cls.refresh_revision_list(path_ifc) - cls.decolourise() + @classmethod + def git_mergetool(cls, mergetool: str) -> Union[str, None]: + """Run ifcmerge tool. Returns None on success, error message string on failure.""" + repo = IfcGitRepo.repo + try: + repo.git.mergetool(tool=mergetool) + return None + except git.exc.GitCommandError as exc: + return re.sub("( stdout: '|')", "", exc.stdout) + + @classmethod + def git_merge_abort(cls) -> None: + IfcGitRepo.repo.git.merge(abort=True) + + @classmethod + def commit_merge(cls, path_ifc: str) -> None: + repo = IfcGitRepo.repo + if os.name == "nt": + cls.dos2unix(path_ifc) + repo.index.add(os.path.normpath(path_ifc)) + repo.git.commit("--no-edit") + + @classmethod + def set_display_branch(cls) -> None: + props = cls.get_ifcgit_props() + props.display_branch = IfcGitRepo.repo.active_branch.name @classmethod def entity_log(cls, path_ifc: str, step_id: int) -> str: @@ -589,6 +591,18 @@ class IfcGit: except FileNotFoundError: operator.report({"ERROR"}, "Winget is not available. Make sure Windows Package Manager is installed.") + @classmethod + def select_first_remote(cls) -> None: + props = cls.get_ifcgit_props() + repo = IfcGitRepo.repo + if repo and repo.remotes: + props.select_remote = repo.remotes[0].name + + @classmethod + def fetch(cls, remote_name: str) -> None: + repo = IfcGitRepo.repo + repo.remotes[remote_name].fetch() + @classmethod def run_git_diff(cls, operator: bpy.types.Operator, save_to_temp: bool) -> None: path = tool.Ifc.get_path() diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index 94b15e73b7..6d30192485 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -1070,11 +1070,6 @@ class Loader(bonsai.core.tool.Loader): layer_set = material.ForLayerSet offset = usage.OffsetFromReferenceLine * cls.unit_scale sense_factor = 1 if usage.DirectionSense == "POSITIVE" else -1 - elif material.is_a("IfcMaterialLayerSet"): - usage = None - layer_set = material - offset = 0 - sense_factor = 1 else: return mesh if len(layer_set.MaterialLayers) == 1: @@ -1082,11 +1077,7 @@ class Loader(bonsai.core.tool.Loader): bm = bmesh.new() bm.from_mesh(mesh) prev_co = None - if not usage: - sense_factor = 1 # Assume the extrusion vector points in the direction sense - no = cls.get_extrusion_vector(element).normalized() - co = Vector((0.0, 0.0, offset)) - elif usage.LayerSetDirection == "AXIS2": + if usage.LayerSetDirection == "AXIS2": co = Vector((0.0, offset, 0.0)) no = cls.get_extrusion_vector(element).normalized() no = no.cross(Vector([1.0, 0.0, 0.0])) diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index e09462a8c2..4c55123a9c 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -226,6 +226,10 @@ class Material(bonsai.core.tool.Material): "IfcMaterialProfileSet", ] + @classmethod + def is_type_product(cls, element: ifcopenshell.entity_instance) -> bool: + return element.is_a("IfcTypeProduct") + @classmethod def add_material_to_set( cls, material_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance diff --git a/src/bonsai/bonsai/tool/misc.py b/src/bonsai/bonsai/tool/misc.py index 890e8aac9b..5676e8f76c 100644 --- a/src/bonsai/bonsai/tool/misc.py +++ b/src/bonsai/bonsai/tool/misc.py @@ -16,7 +16,10 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -from typing import Union +from __future__ import annotations + +import ctypes +from typing import TYPE_CHECKING, Any, NamedTuple, Union import bmesh import bpy @@ -30,8 +33,164 @@ import bonsai.core.root import bonsai.core.tool import bonsai.tool as tool +if TYPE_CHECKING: + from bonsai.bim.module.misc.prop import BIMMiscProperties + class Misc(bonsai.core.tool.Misc): + + class BlenderCStructs: + + class ListBase(ctypes.Structure): + _fields_ = [("first", ctypes.c_void_p), ("last", ctypes.c_void_p)] + + class bUserMenu(ctypes.Structure): + pass + + bUserMenu._fields_ = [ + ("next", ctypes.c_void_p), + ("prev", ctypes.c_void_p), + ("space_type", ctypes.c_int8), + ("_pad0", ctypes.c_int8 * 7), + ("context", ctypes.c_char * 64), + ("items", ListBase), + ] + + class bUserMenuItem(ctypes.Structure): + _fields_ = [ + ("next", ctypes.c_void_p), + ("prev", ctypes.c_void_p), + ("ui_name", ctypes.c_char * 64), + ("type", ctypes.c_int8), + ("_pad0", ctypes.c_int8 * 7), + ] + + class bUserMenuItem_Op(ctypes.Structure): + pass + + bUserMenuItem_Op._fields_ = [ + ("item", bUserMenuItem), + ("op_idname", ctypes.c_char * 64), + ("prop", ctypes.c_void_p), + ("op_prop_enum", ctypes.c_char * 64), + ("opcontext", ctypes.c_int8), + ("_pad0", ctypes.c_int8 * 7), + ] + + class IDPropertyData(ctypes.Structure): + pass + + IDPropertyData._fields_ = [ + ("pointer", ctypes.c_void_p), + ("group", ListBase), + ("children_map", ctypes.c_void_p), + ("val", ctypes.c_int), + ("val2", ctypes.c_int), + ] + + class IDProperty(ctypes.Structure): + pass + + IDProperty._fields_ = [ + ("next", ctypes.c_void_p), + ("prev", ctypes.c_void_p), + ("type", ctypes.c_int8), + ("subtype", ctypes.c_int8), + ("flag", ctypes.c_int16), + ("name", ctypes.c_char * 64), + ("_pad0", ctypes.c_int8 * 4), + ("data", IDPropertyData), + ("len", ctypes.c_int), + ("totallen", ctypes.c_int), + ("ui_data", ctypes.c_void_p), + ] + + class QuickFavorites: + """Blender doesn't provide a good way to access or manage Quick Favorites + from the Python API. We use c-structs (ctypes) to read data directly from memory. + This is fragile and can break between Blender versions. We only use this for + reading data and never writing, to avoid the possibility of corrupting user preferences. + """ + + OFFSET_USER_MENUS: dict[tuple[int, int], int] = { + (4, 5): 10032, + (5, 0): 10032, + (5, 1): 10032, + } + + @classmethod + def _read_idprop_value(cls, prop_ptr: int) -> Any: + IDP_STRING = 0 + IDP_INT = 1 + IDP_FLOAT = 2 + IDP_BOOLEAN = 10 + + p = Misc.BlenderCStructs.IDProperty.from_address(prop_ptr) + if p.type == IDP_INT: + return p.data.val + elif p.type == IDP_BOOLEAN: + return bool(p.data.val) + elif p.type == IDP_FLOAT: + return ctypes.c_float.from_buffer_copy(ctypes.c_int(p.data.val)).value + elif p.type == IDP_STRING: + return ctypes.string_at(p.data.pointer).decode() + return f"" + + @classmethod + def _read_idprop_group(cls, group_ptr: int) -> dict[str, Any]: + root = Misc.BlenderCStructs.IDProperty.from_address(group_ptr) + result: dict[str, Any] = {} + child_ptr = root.data.group.first + while child_ptr: + child = Misc.BlenderCStructs.IDProperty.from_address(child_ptr) + result[child.name.decode()] = cls._read_idprop_value(child_ptr) + child_ptr = child.next + return result + + class QuickFavoritesOperator(NamedTuple): + ui_name: str + op_idname_py: str + props: dict[str, Any] + + @classmethod + def get_quick_favorites(cls) -> list[QuickFavoritesOperator]: + assert bpy.context.preferences + blender_version = bpy.app.version[:2] + offset = cls.OFFSET_USER_MENUS[blender_version] + prefs_address = bpy.context.preferences.as_pointer() + user_menus = Misc.BlenderCStructs.ListBase.from_address(prefs_address + offset) + + result: list[cls.QuickFavoritesOperator] = [] + SPACE_VIEW3D = 4 + + node = user_menus.first + while node: + user_menu = Misc.BlenderCStructs.bUserMenu.from_address(node) + if user_menu.space_type != SPACE_VIEW3D: + node = user_menu.next + continue + item_ptr = user_menu.items.first + while item_ptr: + umi = Misc.BlenderCStructs.bUserMenuItem.from_address(item_ptr) + if umi.type == 2: # OPERATOR + op = Misc.BlenderCStructs.bUserMenuItem_Op.from_address(item_ptr) + props = cls._read_idprop_group(op.prop) if op.prop else {} + result.append( + cls.QuickFavoritesOperator( + ui_name=op.item.ui_name.decode(), + op_idname_py=tool.Blender.operator_idname_to_py(op.op_idname.decode()), + props=props, + ) + ) + item_ptr = umi.next + node = user_menu.next + + return result + + @classmethod + def get_misc_props(cls) -> BIMMiscProperties: + return bpy.context.scene.BIMMiscProperties + @classmethod def get_object_storey(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: element = tool.Ifc.get_entity(obj) diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 23c0071b1b..60f79cc26e 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -76,6 +76,7 @@ if TYPE_CHECKING: BIMRailingProperties, BIMRoofProperties, BIMStairProperties, + BIMSverchokProperties, BIMWindowProperties, ) @@ -87,23 +88,27 @@ class Model(bonsai.core.tool.Model): @classmethod def get_door_props(cls, obj: bpy.types.Object) -> BIMDoorProperties: - return obj.BIMDoorProperties + return obj.BIMDoorProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_window_props(cls, obj: bpy.types.Object) -> BIMWindowProperties: - return obj.BIMWindowProperties + return obj.BIMWindowProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_stair_props(cls, obj: bpy.types.Object) -> BIMStairProperties: - return obj.BIMStairProperties + return obj.BIMStairProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_roof_props(cls, obj: bpy.types.Object) -> BIMRoofProperties: - return obj.BIMRoofProperties + return obj.BIMRoofProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_railing_props(cls, obj: bpy.types.Object) -> BIMRailingProperties: - return obj.BIMRailingProperties + return obj.BIMRailingProperties # pyright: ignore[reportAttributeAccessIssue] + + @classmethod + def get_sverchok_props(cls, obj: bpy.types.Object) -> BIMSverchokProperties: + return obj.BIMSverchokProperties # pyright: ignore[reportAttributeAccessIssue] @classmethod def get_array_props(cls, obj: bpy.types.Object) -> BIMArrayProperties: diff --git a/src/bonsai/bonsai/tool/nest.py b/src/bonsai/bonsai/tool/nest.py index a386e402ec..5a1b2c83b2 100644 --- a/src/bonsai/bonsai/tool/nest.py +++ b/src/bonsai/bonsai/tool/nest.py @@ -45,9 +45,23 @@ class Nest(bonsai.core.tool.Nest): related_object = tool.Ifc.get_entity(related_obj) if not relating_object or not related_object: return False - if relating_object.is_a("IfcElement") and related_object.is_a("IfcElement"): - return True - return False + if relating_object == related_object: + return False + is_compatible_class = relating_object.is_a("IfcElement") and related_object.is_a("IfcElement") + if not is_compatible_class: + return False + # Prevent cyclic references: walk up the full hierarchy from the + # proposed parent and reject if we encounter the proposed child. + ancestor = ifcopenshell.util.element.get_parent(relating_object) + seen = {relating_object} + while ancestor: + if ancestor == related_object: + return False + if ancestor in seen: + break + seen.add(ancestor) + ancestor = ifcopenshell.util.element.get_parent(ancestor) + return True @classmethod def disable_editing(cls, obj: bpy.types.Object) -> None: @@ -123,11 +137,11 @@ class Nest(bonsai.core.tool.Nest): return {"FINISHED"} @classmethod - def disable_nest_mode(cls): - context = bpy.context - props = context.scene.BIMNestProperties + def disable_nest_mode(cls) -> None: + props = cls.get_nest_props() for obj_prop in props.not_editing_objects: obj = obj_prop.obj + assert obj and obj.original obj.original.display_type = obj_prop.previous_display_type element = tool.Ifc.get_entity(obj) if not element: @@ -135,7 +149,7 @@ class Nest(bonsai.core.tool.Nest): components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(props.editing_nest)) objs = [tool.Ifc.get_object(component) for component in components] - if context.space_data.local_view: + if bpy.context.space_data.local_view: bpy.ops.view3d.localview() props.in_nest_mode = False diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index 5a91fc79b8..542bc23005 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -533,9 +533,12 @@ class Polyline(bonsai.core.tool.Polyline): polyline_data = polyline_data[0] polyline_points = polyline_data.polyline_points if polyline_points: - # Avoids creating two points at the same location - for point in polyline_points[1:]: # The first can be repeated to form a wall loop + # Avoids creating two points at the same location. + # The only exception is repeating the first point to close a loop (requires >= 3 existing points). + for i, point in enumerate(polyline_points): if (x, y, z) == (point.x, point.y, point.z): + if i == 0 and len(polyline_points) >= 3: + continue return "Cannot create two points at the same location" # Avoids creating overlapping edges if len(polyline_points) > 1: diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index d92bf19cf5..1bfb239726 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -24,7 +24,15 @@ import shutil from collections import defaultdict from math import radians from pathlib import Path -from typing import TYPE_CHECKING, Any, NamedTuple, Optional +from typing import ( + TYPE_CHECKING, + Any, + Literal, + NamedTuple, + NotRequired, + Optional, + TypedDict, +) import bpy import ifcopenshell @@ -35,7 +43,7 @@ import ifcopenshell.util.shape_builder import numpy as np import numpy.typing as npt from ifcopenshell.api.project.append_asset import APPENDABLE_ASSET_TYPES -from mathutils import Matrix +from mathutils import Matrix, Vector import bonsai.bim.schema import bonsai.core.aggregate @@ -600,6 +608,49 @@ class Project(bonsai.core.tool.Project): class Link: """Tools for working with linked models.""" + class LinkedObjectChunk(TypedDict): + """There's actually no dictionary with those keys, + just using this class to document what keys we do assign to the objects + that represent chunks of the linked models. + """ + + guids: list[str] + """List of guids present in the object.""" + + guid_ids: list[int] + """Number of faces that belong to each guid. + + E.g. if chunk consists of two 12 tris cubes: + ``` + guids = ["aaa", "bbb"] + # Meaning object has 24 polygons + # [0;11] is part of "aaa", [12:23] is part of "bbb". + guid_ids = [12, 24] + ``` + """ + + db: str + """Absolute filepath to .ifc.cache.sqlite.""" + + ifc_filepath: str + """Absolute filepath to .ifc.""" + + # Only added when object is queried. + selected_vertices: NotRequired[list[tuple[int, int, int]]] + selected_edges: NotRequired[list[tuple[int, int]]] + selected_tris: NotRequired[list[tuple[int, int, int]]] + + hidden_indices: NotRequired[list[int]] + """List of hidden indices in "guids". + Note that entire object also can be hidden by ``hide_viewport`` and then "hidden_indices" won't be set. + + ``` + guids = ["aaa", "bbb", "ccc"] + # guid "bbb" is hidden. + hidden_indices = [1] + ``` + """ + @classmethod def is_linked_element(cls, obj: bpy.types.Object) -> bool: return "guids" in obj @@ -670,11 +721,17 @@ class Project(bonsai.core.tool.Project): return slice(guid_start_index, guid_end_index) @classmethod - def hide_linked_element(cls, obj: bpy.types.Object, guid: str) -> None: - verts = tool.Project.Link.get_linked_element_verts(obj, guid) - + def setup_hide_modifier( + cls, + obj: bpy.types.Object, + hide_type: Literal["hide_selected", "hide_unselected"], + ) -> bpy.types.VertexGroup: # `MeshPolygon.hide` works only in EDIT mode, # so we use vertex groups + Mask modifier. + # But since for hiding we're modifying object in a linked model, + # then those changes are ephemeral and not fully supported by Blender + # e.g. they're not tracked by UNDO system. + MODIFIER_VG_NAME = "BBIM_HIDE_LINKED_GEOMETRY" vertex_groups = obj.vertex_groups @@ -688,8 +745,15 @@ class Project(bonsai.core.tool.Project): modifier = modifiers.new(MODIFIER_VG_NAME, "MASK") assert isinstance(modifier, bpy.types.MaskModifier) modifier.vertex_group = MODIFIER_VG_NAME - modifier.invert_vertex_group = True + # Mask modifier by default shows only geometry from the provided vertex group. + modifier.invert_vertex_group = hide_type == "hide_selected" + return vertex_group + @classmethod + def hide_linked_element(cls, obj: bpy.types.Object, guid: str) -> None: + verts = tool.Project.Link.get_linked_element_verts(obj, guid) + + vertex_group = cls.setup_hide_modifier(obj, "hide_selected") vertex_group.add(verts, 1.0, "REPLACE") hidden_indices: list[int] = list(obj.get("hidden_indices") or []) @@ -706,12 +770,45 @@ class Project(bonsai.core.tool.Project): assert col for obj_ in col.objects: + obj_.hide_viewport = False + if "hidden_indices" not in obj_: continue obj_.vertex_groups.clear() obj_.modifiers.clear() del obj_["hidden_indices"] + @classmethod + def hide_all_elements_except(cls, link: Link, queried_obj: bpy.types.Object, queried_guid: str) -> None: + handle = tool.Project.get_link_empty_handle(link) + assert handle + col = handle.instance_collection + assert col + + for obj_ in col.objects: + if "guids" not in obj_: + continue + if obj_ != queried_obj: + # Just hide the entire chunk, if queried guid is not part of it. + obj_.hide_viewport = True + continue + + guids: list[str] = obj_["guids"] + queried_guid_index = guids.index(queried_guid) + + # Get vertices for the queried element before modifying hidden_indices. + queried_verts = cls.get_linked_element_verts(obj_, queried_guid) + vertex_group = cls.setup_hide_modifier(obj_, "hide_unselected") + + assert isinstance(mesh := obj_.data, bpy.types.Mesh) + # Clean up possible previously hidden elements. + vertex_group.remove(range(len(mesh.vertices))) + + vertex_group.add(queried_verts, 1.0, "REPLACE") + + # Mark all guids as hidden except the queried one. + obj_["hidden_indices"] = [i for i in range(len(guids)) if i != queried_guid_index] + @classmethod def select_linked_element_geom(cls, obj: bpy.types.Object, guid: str) -> None: slice_ = cls.get_linked_element_geom_slice(obj, guid) diff --git a/src/bonsai/bonsai/tool/raycast.py b/src/bonsai/bonsai/tool/raycast.py index d98aaf26ab..c16bc55564 100644 --- a/src/bonsai/bonsai/tool/raycast.py +++ b/src/bonsai/bonsai/tool/raycast.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations + +import math from typing import Union import bmesh @@ -24,9 +27,11 @@ import mathutils import numpy as np from mathutils import Vector +from bpy_extras import view3d_utils + import bonsai.core.tool import bonsai.tool as tool - +from bpy_extras import view3d_utils class Raycast(bonsai.core.tool.Raycast): offset = 10 @@ -41,6 +46,7 @@ class Raycast(bonsai.core.tool.Raycast): (0, -offset), (offset, -offset), ) + snap_objs = [] @classmethod def get_visible_objects(cls, context: bpy.types.Context): @@ -69,8 +75,10 @@ class Raycast(bonsai.core.tool.Raycast): rv3d = context.region_data assert rv3d view_location = rv3d.view_matrix.inverted().translation + view_normal = rv3d.view_rotation @ mathutils.Vector((0.0, 0.0, -1.0)) obj_matrix = obj.matrix_world.copy() bbox = [obj_matrix @ Vector(v) for v in obj.bound_box] + bbox_edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)] transposed_bbox: list[Vector] = [] bbox_2d: list[float] = [] @@ -94,8 +102,25 @@ class Raycast(bonsai.core.tool.Raycast): for v in bbox: coord_2d = tool.Cad.location_3d_to_region_2d_np(context.region, context.space_data.region_3d, v) - if coord_2d is not None: - transposed_bbox.append(coord_2d) + transposed_bbox.append(coord_2d) + + if not any(transposed_bbox): + transposed_bbox = [] + # If there are None values in transposed_bbox it means that there are vertices behind the camera + # so we get the intersection of the edge with the region border + # new_bbox = [] + if any(transposed_bbox) and not all(transposed_bbox): + new_bbox = transposed_bbox.copy() + new_bbox = [x for x in new_bbox if x is not None] + for edge in bbox_edges: + if (transposed_bbox[edge[0]] is None) ^ (transposed_bbox[edge[1]] is None): + point, _ = cls.intersect_edge_region_border( + context.region, context.space_data, rv3d, bbox[edge[0]], bbox[edge[1]] + ) + if point: + new_bbox.append(point) + if new_bbox: + transposed_bbox = new_bbox region = context.region borders = (0, region.width, 0, region.height) @@ -117,6 +142,96 @@ class Raycast(bonsai.core.tool.Raycast): return (obj, bbox_2d) return None + def intersect_edge_region_border(region, space, rv3d, v1, v2): + def segment_intersect_near_plane(view_matrix, clip_start, p_world_a, p_world_b): + a_view = view_matrix @ p_world_a + b_view = view_matrix @ p_world_b + z_near = -clip_start + za = a_view.z + zb = b_view.z + denom = zb - za + if denom == 0.0: + return None, None + t = (z_near - za) / denom + if t < 0.0 or t > 1.0: + return None, None + p_view = a_view.lerp(b_view, t) + cam_world = view_matrix.inverted() + p_world = cam_world @ p_view + return p_world, t + + def is_inside_region(pt2d, region): + return 0.0 <= pt2d.x <= region.width and 0.0 <= pt2d.y <= region.height + + def clamp_to_region_border(point2d, region): + x, y = point2d + x_clamped = max(0.0, min(region.width, x)) + y_clamped = max(0.0, min(region.height, y)) + return Vector((x_clamped, y_clamped)) + + def find_nearby_onscreen_point(region, rv3d, p1, p2, initial_t_on_segment, max_iters=40, step=0.05): + """ + Use iterative approach: move t toward 0. Returns the first point that is inside region border + """ + t = initial_t_on_segment + for i in range(max_iters): + test_3d = p1.lerp(p2, t) + test_2d = view3d_utils.location_3d_to_region_2d(region, rv3d, test_3d) + if test_2d is not None and is_inside_region(test_2d, region): + return test_3d, test_2d, t + # move t toward 0 by reducing it by a fraction of its current value + t -= step + # if t is already very small, break + if t <= 1e-6: + break + + return None, None, None + + # Ensures that all the calculation uses the same direction based on which point is on the screen + if view3d_utils.location_3d_to_region_2d(region, rv3d, v1): + onscreen_vert = v1 + offscreen_vert = v2 + else: + onscreen_vert = v2 + offscreen_vert = v1 + # v2, v1 = v1, v2 + + clip_start = space.clip_start + view_mat = rv3d.view_matrix + inter_world, t_on_ab = segment_intersect_near_plane(view_mat, clip_start, onscreen_vert, offscreen_vert) + + if inter_world is None: + print("No intersection with viewport near plane found for the segment.") + return None, None + + init_2d = view3d_utils.location_3d_to_region_2d(region, rv3d, inter_world) + + if init_2d is not None and is_inside_region(init_2d, region): + final_world = inter_world + final_2d = init_2d + final_t = t_on_ab + else: + found_world, found_2d, found_t = find_nearby_onscreen_point( + region, rv3d, onscreen_vert, offscreen_vert, t_on_ab, max_iters=600, step=0.01 + ) + if found_world is None: + if init_2d is None: + print("Initial projection invalid and iterative search failed.") + return None, None + # fallback: clamp projected point to border via manual mapping + final_2d = clamp_to_region_border(init_2d, region) + final_world = None + final_t = None + # print("Iterative search failed; using clamped 2D:", final_2d) + else: + final_world = found_world + final_2d = found_2d + final_t = found_t + # print(f"Found onscreen point at t={final_t:.4f}") + + # print("Final 2D:", final_2d) + return final_2d, v2 + @classmethod def intersect_mouse_2d_bounding_box(cls, mouse_pos: tuple[int, int], bbox: list[float, float, float, float]): x, y = mouse_pos @@ -232,6 +347,161 @@ class Raycast(bonsai.core.tool.Raycast): else: return None, None, None + @classmethod + def ray_cast_by_proximity_2d( + cls, + context: bpy.types.Context, + event: bpy.types.Event, + snap_obj: SnapObj, + ): + + def divide_vector(start, end, n): + points = [] + delta = (end - start) / n + for i in range(1, n): + point = start + i * delta + points.append(point) + return points + + region = context.region + rv3d = context.region_data + mouse_pos = event.mouse_region_x, event.mouse_region_y + ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event) + points = [] + + try: + loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction) + except: + loc = Vector((0, 0, 0)) + + verts_2d = [ + view3d_utils.location_3d_to_region_2d(region, rv3d, v) for v in snap_obj.verts_3d + ] # Numpy version is worst in performance + + intersected = snap_obj.raycast_boxes( + context, event, snap_obj.root, intersected=[], rays=(ray_origin, ray_direction) + ) + edges = [] + for it in intersected: + edges.extend(it.edges) + edges = set(edges) + + edge_verts = {} + for e in edges: + verts_idx = tuple(snap_obj.obj.data.edges[e].vertices) + verts = snap_obj.obj.data.vertices + v1 = snap_obj.obj.matrix_world @ verts[verts_idx[0]].co + v1_2d = verts_2d[verts_idx[0]] + v2 = snap_obj.obj.matrix_world @ verts[verts_idx[1]].co + v2_2d = verts_2d[verts_idx[1]] + if (v1_2d is None) ^ (v2_2d is None): + point, _ = cls.intersect_edge_region_border(region, context.space_data, rv3d, v1, v2) + if v1_2d is None: + edge_verts[e] = (point, v2_2d) + else: + edge_verts[e] = (v1_2d, point) + else: + edge_verts[e] = (v1_2d, v2_2d) + + snap_threshold = 10.0 + + for i, point in enumerate(verts_2d): + if not point: + continue + distance = (Vector(mouse_pos) - point).length + if distance <= snap_threshold: + snap_point = { + "object": snap_obj.obj, + "type": "Vertex", + "point": snap_obj.verts_3d[i], + "distance": distance / 10, + } + points.append(snap_point) + + count = 0 + selected_edges = {} + for e in edges: + p0, p1 = edge_verts[e] + p0x, p0y = p0 + p1x, p1y = p1 + px, py = mouse_pos + + # segment vector = p1 - p0 + sx = p1x - p0x + sy = p1y - p0y + + # seg length squared + seg_len_sq = sx * sx + sy * sy + + if seg_len_sq == 0.0: + # degenerate segment: return distance to p0 + dx = px - p0x + dy = py - p0y + dist = math.hypot(dx, dy) + return dist, (p0x, p0y), 0.0 + + # project (p - p0) onto seg: t = dot(p-p0, seg) / |seg|^2 + apx = px - p0x + apy = py - p0y + t = (apx * sx + apy * sy) / seg_len_sq + + # clamp to segment + if t <= 0.0: + t_clamped = 0.0 + cx, cy = p0x, p0y + elif t >= 1.0: + t_clamped = 1.0 + cx, cy = p1x, p1y + else: + t_clamped = t + cx = p0x + sx * t_clamped + cy = p0y + sy * t_clamped + + dx = px - cx + dy = py - cy + dist = math.hypot(dx, dy) + if dist <= snap_threshold: + selected_edges[dist] = e + + if selected_edges: + min_dist = float("inf") + for key in selected_edges: + if key < min_dist: + min_dist = key + + idx = snap_obj.obj.data.edges[selected_edges[min_dist]].vertices + edge_verts = (snap_obj.verts_3d[idx[0]], snap_obj.verts_3d[idx[1]]) + division_points = divide_vector( + edge_verts[0], edge_verts[1], 2 + ) # TODO Make it work for different divisions + for division_point in division_points: + intersection = tool.Cad.point_on_edge(division_point, (ray_target, loc)) + distance = (division_point - intersection).length + if distance < snap_threshold: + snap_point = { + "object": snap_obj.obj, + "type": "Edge Center", + "point": division_point.copy(), + "distance": distance, + } + points.append(snap_point) + + intersection = tool.Cad.intersect_edges_v2((ray_target, loc), edge_verts) + if intersection[0]: + if tool.Cad.is_point_on_edge(intersection[1], edge_verts): + distance = (intersection[1] - intersection[0]).length + if distance < snap_threshold: + snap_point = { + "object": snap_obj.obj, + "type": "Edge", + "point": intersection[1].copy(), + "edge_verts": edge_verts, + "distance": distance, + } + points.append(snap_point) + + return points + @classmethod def ray_cast_by_proximity( cls, @@ -457,7 +727,9 @@ class Raycast(bonsai.core.tool.Raycast): if bbox_2d: if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d): if tool.Raycast.object_is_visible_in_clipping_plane(obj): - objs_to_raycast.append(obj) + snap_obj = cls.create_snap_obj(obj) + if snap_obj is not None: + objs_to_raycast.append(snap_obj) return objs_to_raycast @@ -474,12 +746,6 @@ class Raycast(bonsai.core.tool.Raycast): face_index = None # Wireframes if obj.type in {"EMPTY", "CURVE"} or (hasattr(obj.data, "polygons") and len(obj.data.polygons) == 0): - snap_points = tool.Raycast.ray_cast_by_proximity(context, event, obj) - if snap_points: - hit = sorted(snap_points, key=lambda x: x["distance"])[0]["point"] - if hit: - hit_world = obj.original.matrix_world @ hit - return obj, hit_world, face_index return None, None, None # Meshes else: @@ -514,19 +780,20 @@ class Raycast(bonsai.core.tool.Raycast): ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event) - for obj in objs_to_raycast: + for snap_obj in objs_to_raycast: if not include_wireframes and ( - obj.type in {"EMPTY", "CURVE"} or (hasattr(obj.data, "polygons") and len(obj.data.polygons) == 0) + snap_obj.obj.type in {"EMPTY", "CURVE"} + or (hasattr(snap_obj.obj.data, "polygons") and len(snap_obj.obj.data.polygons) == 0) ): continue - snap_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, obj) + hit_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, snap_obj.obj) if hit is not None: length_squared = (hit - ray_origin).length_squared if best_obj is None or length_squared < best_length_squared: best_length_squared = length_squared - best_obj = snap_obj + best_obj = hit_obj best_hit = hit best_face_index = face_index @@ -536,6 +803,79 @@ class Raycast(bonsai.core.tool.Raycast): else: return None, None, None + @classmethod + def ray_cast_and_get_closest_to_camera_snaps( + cls, + context: bpy.types.Context, + event: bpy.types.Event, + objs_to_raycast: list[bpy.types.Object], + ) -> Union[tuple[bpy.types.Object, Vector, int], tuple[None, None, None]]: + closest_length_squared = 1.0 + closest_obj = None + closest_hit = None + closest_face_index = None + + ray_origin, ray_target, ray_direction = cls.get_viewport_ray_data(context, event) + + closest_snaps = [] + hit = None + + for snap_obj in objs_to_raycast: + if snap_obj.obj.type in {"EMPTY", "CURVE"} or ( + hasattr(snap_obj.obj.data, "polygons") and len(snap_obj.obj.data.polygons) == 0 + ): + # For wireframe objects we have to test all the snaps to see which is closer + snap_points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj) + closest_wf_hit = None + closest_wf_length_squared = 1.0 + closest_wf_point = None + if snap_points: + for point in snap_points: + point["group"] = "Wireframe" + closest_snaps.append(point) + length = (point["point"] - ray_origin).length_squared + if closest_wf_hit is None or length < closest_wf_length_squared: + closest_wf_length_squared = length + closest_wf_hit = point["point"] + closest_wf_point = point + + if closest_wf_point: + hit_obj = closest_wf_point["object"] + hit = closest_wf_point["point"] + face_index = None + + else: + # Solid objects + hit_obj, hit, face_index = cls.cast_rays_to_single_object(context, event, snap_obj.obj) + + if hit: + snap_point = { + "point": hit, + "type": "Face", + "group": "Object", + "object": hit_obj, + "face_index": face_index, + "distance": 9, # High value so it has low priority + } + closest_snaps.append(snap_point) + + # Here we test which is closer, including wireframe and solid objects + if hit is not None: + length_squared = (hit - ray_origin).length_squared + if closest_obj is None or length_squared < closest_length_squared: + closest_length_squared = length_squared + closest_obj = hit_obj + closest_hit = hit + closest_face_index = face_index + + # Label snaps from the closest object + if closest_obj is not None: + for snap in closest_snaps: + if snap["object"] == closest_obj: + snap["is_closest_to_camera"] = True + + return closest_snaps + @classmethod def calculate_snap_threshold(cls, view_distance): snap_threshold = view_distance / 100 @@ -547,3 +887,262 @@ class Raycast(bonsai.core.tool.Raycast): if lens < 50: snap_threshold *= value return snap_threshold + + @classmethod + def create_snap_obj(cls, obj): + if obj.data is None or not isinstance(obj.data, bpy.types.Mesh): + return None + for snap_obj in cls.snap_objs: + if obj.name == snap_obj.obj.name: + return snap_obj + snap_obj = SnapObj(obj) + cls.snap_objs.append(snap_obj) + return snap_obj + + @classmethod + def clear_snap_objs(cls): + TreeNode.__clear_all__() + SnapObj.__clear_all__() + cls.snap_objs.clear() + + +class TreeNode: + all = [] + + def __init__(self, box: tuple): + self.__class__.all.append(self) + self.box = box + self.child_a = None + self.child_b = None + self.edges = [] + + def __clear_all__(): + for instance in TreeNode.all: + del instance + TreeNode.all.clear() + + +class SnapObj: + max_depth = 9 + all = [] + + def __init__(self, obj: bpy.types.Object): + self.__class__.all.append(self) + self.obj = obj + self.root = self._create_root_node() + self.root.edges = [e.index for e in obj.data.edges] + self.split_box(self.root, 0) + self.verts_3d = [obj.matrix_world @ v.co for v in obj.data.vertices] + self.snap_points = [] + + def __clear_all__(): + for instance in SnapObj.all: + del instance + SnapObj.all.clear() + + def _create_root_node(self) -> TreeNode: + bbox = tool.Blender.get_object_bounding_box(self.obj) + min_point = self.obj.matrix_world @ bbox["min_point"] + max_point = self.obj.matrix_world @ bbox["max_point"] + new_bbox = self.expand_bounding_box((min_point, max_point)) + return TreeNode(new_bbox) + + def divide_bounding_box_along_longest_axis( + self, min_pt: Vector, max_pt: Vector + ) -> Union[tuple[Vector, Vector], tuple[Vector, Vector]]: + """ + Divide a bounding box into two equal parts along the axis with the longest dimension. + + Args: + min_pt: The minimum point of the bounding box. + max_pt: The maximum point of the bounding box. + + Returns: + list: A list of two tuples, each containing the minimum and maximum points of the divided boxes. + """ + + # Calculate the dimensions of the box + dx = max_pt.x - min_pt.x + dy = max_pt.y - min_pt.y + dz = max_pt.z - min_pt.z + + # Determine the axis with the longest dimension + if dx >= dy and dx >= dz: + # Divide along the x-axis + mid_x = min_pt.x + dx / 2 + box1 = (min_pt, Vector((mid_x, max_pt.y, max_pt.z))) + box2 = (Vector((mid_x, min_pt.y, min_pt.z)), max_pt) + elif dy >= dx and dy >= dz: + # Divide along the y-axis + mid_y = min_pt.y + dy / 2 + box1 = (min_pt, Vector((max_pt.x, mid_y, max_pt.z))) + box2 = (Vector((min_pt.x, mid_y, min_pt.z)), max_pt) + else: + # Divide along the z-axis + mid_z = min_pt.z + dz / 2 + box1 = (min_pt, Vector((max_pt.x, max_pt.y, mid_z))) + box2 = (Vector((min_pt.x, min_pt.y, mid_z)), max_pt) + + return [box1, box2] + + def expand_bounding_box(self, box: tuple[Vector, Vector], offset: float = 0.1) -> tuple[Vector, Vector]: + """ + Expand a 3D bounding box by a given offset. + + Args: + min_pt: The minimum point of the bounding box. + max_pt: The maximum point of the bounding box. + offset: The offset to expand the bounding box by. + + Returns: + tuple: A tuple containing the new minimum and maximum points of the expanded bounding box. + """ + + min_pt, max_pt = box + # Calculate the new minimum and maximum points + new_min_pt = Vector((min_pt.x - offset, min_pt.y - offset, min_pt.z - offset)) + new_max_pt = Vector((max_pt.x + offset, max_pt.y + offset, max_pt.z + offset)) + + return new_min_pt, new_max_pt + + def split_box(self, parent: TreeNode, depth: int): + """ + Splits the bounding box creating two child nodes to compose a BVH Tree recursively. + + Args: + parent: the TreeNode instance that represents the parent node of a BVH Tree. + depth: the depth of the BVH Tree no be used in recursion. + """ + if depth > self.max_depth: + return + box_a, box_b = self.divide_bounding_box_along_longest_axis(parent.box[0], parent.box[1]) + parent.child_a = TreeNode(box_a) + parent.child_b = TreeNode(box_b) + edges_a = [] + edges_b = [] + for e in parent.edges: + verts_idx = [v for v in self.obj.data.edges[e].vertices] + verts_coords = [] + for idx in verts_idx: + if idx < len(self.obj.data.vertices): + verts_coords.append(self.obj.matrix_world @ self.obj.data.vertices[idx].co) + if self.line_intersects_box(verts_coords[0], verts_coords[1], parent.child_a.box): + edges_a.append(e) + if self.line_intersects_box(verts_coords[0], verts_coords[1], parent.child_b.box): + edges_b.append(e) + parent.child_a.edges = edges_a + parent.child_b.edges = edges_b + self.split_box(parent.child_a, depth + 1) + self.split_box(parent.child_b, depth + 1) + + def raycast_box( + self, context: bpy.types.Context, event: bpy.types.Event, node: TreeNode, rays: tuple[Vector, Vector] + ) -> bool: + """ + Raycast bounding box. + + Args: + context: Blender context. + event: Blender event. + node: a TreeNode instance. + rays: tuple containing ray origin and ray direction + + Returns: + True if hits the box or False otherwise. + """ + box = node.box + min_v = box[0] + max_v = box[1] + t_min = 0.0 + t_max = float("inf") + ray_origin, ray_dir = rays + inv_dir = Vector((1.0 / r if r != 0.0 else 1e32) for r in (ray_dir.x, ray_dir.y, ray_dir.z)) + # X + tx1 = (min_v.x - ray_origin.x) * inv_dir[0] + tx2 = (max_v.x - ray_origin.x) * inv_dir[0] + tmin = min(tx1, tx2) + tmax = max(tx1, tx2) + # Y + ty1 = (min_v.y - ray_origin.y) * inv_dir[1] + ty2 = (max_v.y - ray_origin.y) * inv_dir[1] + tmin = max(tmin, min(ty1, ty2)) + tmax = min(tmax, max(ty1, ty2)) + # Z + tz1 = (min_v.z - ray_origin.z) * inv_dir[2] + tz2 = (max_v.z - ray_origin.z) * inv_dir[2] + tmin = max(tmin, min(tz1, tz2)) + tmax = min(tmax, max(tz1, tz2)) + return (tmax >= max(tmin, t_min)) and (tmin <= t_max) + + def line_intersects_box(self, v1: mathutils.Vector, v2: mathutils.Vector, box: tuple) -> bool: + """ + Check if a line segment intersects an axis-aligned bounding box (AABB). + + Args: + v1: The first endpoint of the line segment as a mathutils.Vector. + v2: The second endpoint of the line segment as a mathutils.Vector. + box: A tuple containing the minimum and maximum points of the AABB, where each point is a mathutils.Vector. + + Returns: + bool: True if the segment [v1, v2] intersects the AABB; otherwise, False. + """ + bmin, bmax = box + dir = v2 - v1 + tmin = 0.0 + tmax = 1.0 + + for i in range(3): + if abs(dir[i]) < 1e-12: + # Line is parallel to slab. If origin not within slab -> no hit. + if v1[i] < bmin[i] or v1[i] > bmax[i]: + return False + else: + ood = 1.0 / dir[i] + t1 = (bmin[i] - v1[i]) * ood + t2 = (bmax[i] - v1[i]) * ood + if t1 > t2: + t1, t2 = t2, t1 + if t1 > tmin: + tmin = t1 + if t2 < tmax: + tmax = t2 + if tmin > tmax: + return False + + # If any overlap in [0,1] exists, there's intersection + return (tmax >= 0.0) and (tmin <= 1.0) + + def raycast_boxes( + self, + context: bpy.types.Context, + event: bpy.Types.Event, + node: TreeNode, + intersected: Union[TreeNode] = [], + rays: tuple[Vector, Vector] = (), + ) -> Union[TreeNode]: + """ + Raycast bounding box subdivisions recursively. + + Args: + context: Blender context. + event: Blender event. + node: a TreeNode instance. + intersected: list of intersected boxes to use in recursion. + rays: tuple containing ray origin and ray direction + + Returns: + tuple: a list of TreeNode instances that represent the subdivided boxes hit by the ray cast. + """ + if not node.child_a: + intersected.append(node) + return intersected + + intersects_a = self.raycast_box(context, event, node.child_a, rays) + intersects_b = self.raycast_box(context, event, node.child_b, rays) + if intersects_a: + intersected = self.raycast_boxes(context, event, node.child_a, intersected, rays) + + if intersects_b: + intersected = self.raycast_boxes(context, event, node.child_b, intersected, rays) + + return intersected diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index 02ddbe9745..8880a168fe 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -93,38 +93,16 @@ class Root(bonsai.core.tool.Root): elif dest.is_a("IfcTypeProduct"): if not source.RepresentationMaps: return copied_entities - - # Copy representation maps while preserving mapped representation structures - new_maps = [] - for i, rep_map in enumerate(source.RepresentationMaps): - source_rep = rep_map.MappedRepresentation - - # Copy the map itself - new_map = ifcopenshell.util.element.copy(tool.Ifc.get(), rep_map) - - # Handle the mapped representation - preserve mapping structure if present - if ( - source_rep.RepresentationType == "MappedRepresentation" - and len(source_rep.Items) == 1 - and source_rep.Items[0].is_a("IfcMappedItem") - ): - # This is a mapped representation - preserve the structure - new_rep = ifcopenshell.util.element.copy(tool.Ifc.get(), source_rep) - new_rep.Items = [ifcopenshell.util.element.copy(tool.Ifc.get(), item) for item in source_rep.Items] - new_map.MappedRepresentation = new_rep - else: - # Not a mapped representation - use copy_deep as before - new_map.MappedRepresentation = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), - source_rep, - exclude=["IfcGeometricRepresentationContext"], - exclude_callback=exclude_callback, - copied_entities=copied_entities, - ) - - new_maps.append(new_map) - - dest.RepresentationMaps = new_maps + dest.RepresentationMaps = [ + ifcopenshell.util.element.copy_deep( + tool.Ifc.get(), + m, + exclude=["IfcGeometricRepresentationContext"], + exclude_callback=exclude_callback, + copied_entities=copied_entities, + ) + for m in source.RepresentationMaps + ] return copied_entities @classmethod diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 02f484d388..5755c02ba7 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -388,57 +388,36 @@ class Snap(bonsai.core.tool.Snap): # Objects objs_to_raycast = tool.Raycast.filter_objects_to_raycast(context, event, objs_2d_bbox) - # Wireframes - # For wireframe we have to get all the objects so we can further calculate edge intersection - for snap_obj in objs_to_raycast: - if snap_obj.type in {"EMPTY", "CURVE"} or (snap_obj.type == "MESH" and len(snap_obj.data.polygons) == 0): - snap_points = tool.Raycast.ray_cast_by_proximity(context, event, snap_obj) - if snap_points: - for point in snap_points: - point["group"] = "Wireframe" - detected_snaps.append(point) + closest_snaps = tool.Raycast.ray_cast_and_get_closest_to_camera_snaps(context, event, objs_to_raycast) + detected_snaps.extend(closest_snaps) - if (space.shading.type == "SOLID" and space.shading.show_xray) or ( + xray_mode = (space.shading.type == "SOLID" and space.shading.show_xray) or ( space.shading.type == "WIREFRAME" and space.shading.show_xray_wireframe - ): - results = [] - for obj in objs_to_raycast: - results.append(tool.Raycast.cast_rays_to_single_object(context, event, obj)) - else: - results = [] - results.append(tool.Raycast.cast_rays_and_get_best_object(context, event, objs_to_raycast)) + ) - for result in results: - snap_obj = result[0] - hit = result[1] - face_index = result[2] - if hit is not None: - # Wireframes - if snap_obj.type in {"EMPTY", "CURVE"} or ( - snap_obj.type == "MESH" and len(snap_obj.data.polygons) == 0 - ): - continue - # Meshes - else: - # Add face snap - snap_point = { - "point": hit, - "type": "Face", - "group": "Object", - "object": snap_obj, - "face_index": face_index, - "distance": 9, # High value so it has low priority - } - detected_snaps.append(snap_point) - - # Add vertex and edge snap - snap_points = tool.Raycast.ray_cast_by_proximity( - context, event, snap_obj, snap_obj.data.polygons[face_index] - ) - if snap_points: - for point in snap_points: - point["group"] = "Object" - detected_snaps.append(point) + for snap_obj in objs_to_raycast: + for snap in closest_snaps: + if snap_obj.obj == snap["object"]: + if xray_mode: + if "face_index" in snap and snap["face_index"] is not None: + snap_points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj) + for point in snap_points: + point["group"] = "Object" + detected_snaps.append(point) + else: + # If it is a solid object that is closest to camera it ignores all the rest + if ( + "is_closest_to_camera" in snap + and snap["is_closest_to_camera"] + and snap["group"] == "Object" + ): + closest_snap = [snap] # discards objects that aren't the closest + if "face_index" in snap and snap["face_index"] is not None: + snap_points = tool.Raycast.ray_cast_by_proximity_2d(context, event, snap_obj) + for point in snap_points: + point["group"] = "Object" + closest_snap.append(point) + detected_snaps = closest_snap # snap to cut geometry (e.g. in plan view) if CutDecorator.installed: diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index b185b9ab59..11a41672bc 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -21,13 +21,13 @@ from __future__ import annotations import json from collections import defaultdict from collections.abc import Generator, Iterable -from math import pi from typing import TYPE_CHECKING, Any, Literal, Optional, Union import bmesh import bpy import ifcopenshell import ifcopenshell.api.attribute +import ifcopenshell.api.geometry import ifcopenshell.api.type import ifcopenshell.geom import ifcopenshell.util.classification @@ -991,114 +991,104 @@ class Spatial(bonsai.core.tool.Spatial): return poly @classmethod - def get_bmesh_from_polygon(cls, poly: Polygon, h: float, polygon_is_si: bool = False) -> bmesh.types.BMesh: - """ - :param h: Height, in meters. - :param polygon_is_si: Should be True if `poly` is defined in meters. - """ - mat = Matrix() - bm = bmesh.new() - bm.verts.index_update() - bm.edges.index_update() - - mat_invert = mat.inverted() - si_conversion = 1.0 if polygon_is_si else ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - new_verts = [ - # bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion)) for v in poly.exterior.coords[0:-1] - bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion)) - for v in shapely.get_exterior_ring(poly).coords[0:-1] - ] - [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] - bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) - - bm.verts.index_update() - bm.edges.index_update() - - bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5) - bmesh.ops.triangle_fill(bm, edges=bm.edges) - bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges) - - if h != 0: - extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces) - extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)] - bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts) - - bmesh.ops.recalc_face_normals(bm, faces=bm.faces) - - return bm - - @classmethod - def get_named_obj_from_bmesh(cls, name: str, bmesh: bmesh.types.BMesh) -> bpy.types.Object: - mesh = cls.get_named_mesh_from_bmesh(name, bmesh) - obj = cls.get_named_obj_from_mesh(name, mesh) - return obj - - @classmethod - def get_named_obj_from_mesh(cls, name: str, mesh: bpy.types.Mesh) -> bpy.types.Object: + def create_object(cls, name: str) -> bpy.types.Object: + mesh = bpy.data.meshes.new(name=name) obj = bpy.data.objects.new(name, mesh) return obj @classmethod - def get_named_mesh_from_bmesh(cls, name: str, bmesh: bmesh.types.BMesh) -> bpy.types.Mesh: - mesh = bpy.data.meshes.new(name=name) - bmesh.to_mesh(mesh) - bmesh.free() - return mesh + def set_obj_origin_to_polygon_center(cls, obj: bpy.types.Object, poly: Polygon, polygon_is_si: bool = True) -> None: + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + centroid = poly.centroid + if polygon_is_si: + obj.location = Vector((centroid.x, centroid.y, 0)) + else: + obj.location = Vector((centroid.x * unit_scale, centroid.y * unit_scale, 0)) @classmethod - def get_transformed_mesh_from_local_to_global(cls, mesh: bpy.types.Mesh) -> bpy.types.Mesh: - active_obj = cls.get_active_obj() - mat = active_obj.matrix_world - mesh.transform(mat.inverted()) - mesh.update() - return mesh + def get_2d_vertices_from_polygon( + cls, + poly: Polygon, + obj: bpy.types.Object, + polygon_is_si: bool = True, + ) -> list[list[float]]: + """Convert a world-space shapely polygon to 2D vertices in obj's local space, in IFC file units. + + :param poly: The polygon in world space. + :param obj: The Blender object whose local space is used. + :param polygon_is_si: True if polygon coords are in SI, False if in IFC file units. + :return: List of [x, y] coordinates (not closed). + """ + ifc_file = tool.Ifc.get() + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) + bpy.context.view_layer.update() + mat_inv = obj.matrix_world.inverted() + coords_2d = [] + for v in shapely.get_exterior_ring(poly).coords[:-1]: + world_si = Vector((v[0], v[1], 0)) + if not polygon_is_si: + world_si = world_si * unit_scale + local_si = mat_inv @ world_si + coords_2d.append([local_si.x / unit_scale, local_si.y / unit_scale]) + return coords_2d @classmethod - def edit_active_space_obj_from_mesh(cls, mesh: bpy.types.Mesh) -> None: - active_obj = bpy.context.active_object - old_mesh = active_obj.data - old_mesh_name = old_mesh.name - assert active_obj and isinstance(old_mesh, bpy.types.Mesh) - tool.Geometry.get_mesh_props(mesh).ifc_definition_id = tool.Geometry.get_mesh_props(old_mesh).ifc_definition_id - tool.Geometry.change_object_data(active_obj, mesh, is_global=True) - tool.Ifc.edit(active_obj) - tool.Blender.remove_data_block(old_mesh) - # Rename after old mesh is removed to avoid .001 suffix. - mesh.name = old_mesh_name + def set_extrusion_representation_from_polygon( + cls, + obj: bpy.types.Object, + element: ifcopenshell.entity_instance, + poly: Polygon, + depth_ifc: float, + polygon_is_si: bool = True, + ) -> None: + """Create or replace the IFC body representation from a polygon extrusion. + + :param obj: The Blender object. + :param element: The IFC product entity. + :param poly: The polygon in world space. + :param depth_ifc: The extrusion depth in IFC file units. + :param polygon_is_si: True if polygon coords are in SI, False if in IFC file units. + """ + ifc_file = tool.Ifc.get() + builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file) + + coords_2d = cls.get_2d_vertices_from_polygon(poly, obj, polygon_is_si) + + curve = builder.polyline(coords_2d, closed=True) + item = builder.extrude(curve, magnitude=depth_ifc) + + old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + if old_body: + context = old_body.ContextOfItems + ifcopenshell.api.geometry.unassign_representation(ifc_file, product=element, representation=old_body) + ifcopenshell.api.geometry.remove_representation(ifc_file, representation=old_body) + else: + context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW") + + new_body = builder.get_representation(context, item) + ifcopenshell.api.geometry.assign_representation(ifc_file, product=element, representation=new_body) + bonsai.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=new_body, + ) @classmethod - def set_obj_origin_to_bboxcenter(cls, obj: bpy.types.Object) -> None: - mat = obj.matrix_world - inverted = mat.inverted() - local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) - global_bbox_center = mat @ local_bbox_center + def set_space_representation_from_polygon( + cls, + obj: bpy.types.Object, + element: ifcopenshell.entity_instance, + poly: Polygon, + h: float, + polygon_is_si: bool = True, + ) -> None: + """Create or replace the IFC body representation of a space from a polygon. - oldLoc = obj.location - newLoc = global_bbox_center - diff = newLoc - oldLoc - for vert in obj.data.vertices: - aux_vector = mat @ vert.co - aux_vector = aux_vector - diff - vert.co = inverted @ aux_vector - obj.location = newLoc - - @classmethod - def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj: bpy.types.Object) -> None: - mat = obj.matrix_world - inverted = mat.inverted() - local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector()) - global_bbox_center = mat @ local_bbox_center - global_obj_origin = global_bbox_center - global_obj_origin.z = 0 - - oldLoc = obj.location - newLoc = global_obj_origin - diff = newLoc - oldLoc - for vert in obj.data.vertices: - aux_vector = mat @ vert.co - aux_vector = aux_vector - diff - vert.co = inverted @ aux_vector - obj.location = newLoc + :param h: The height in SI (meters). + """ + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + cls.set_extrusion_representation_from_polygon(obj, element, poly, h / unit_scale, polygon_is_si) @classmethod def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj: bpy.types.Object) -> None: @@ -1145,65 +1135,53 @@ class Spatial(bonsai.core.tool.Spatial): if z != 0: obj.location = obj.location + Vector((0, 0, z)) - @classmethod - def get_2d_vertices_from_obj(cls, obj: bpy.types.Object) -> list[tuple]: - points = [] - vectors = [v.co for v in obj.data.vertices.values()] - for vector in vectors: - points.append(vector.xy) - - points.append(vectors[0].xy) - return points - - @classmethod - def get_scaled_2d_vertices(cls, points: list[Vector]) -> list[tuple[float, float]]: - model = tool.Ifc.get() - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(model) - _points = [] - for p in points: - _p = list(p) - _p[0] /= unit_scale - _p[1] /= unit_scale - _points.append(_p) - return _points - - @classmethod - def assign_swept_area_outer_curve_from_2d_vertices(cls, obj: bpy.types.Object, vertices: list[Vector]) -> None: - body = cls.get_body_representation(obj) - model = tool.Ifc.get() - extrusion = tool.Model.get_extrusion(body) - area = extrusion.SweptArea - old_area = area.OuterCurve - - builder = ifcopenshell.util.shape_builder.ShapeBuilder(model) - outer_curve = builder.polyline(vertices, closed=True) - - area.OuterCurve = outer_curve - ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_area) - - @classmethod - def get_body_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]: - element = tool.Ifc.get_entity(obj) - return ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") - @classmethod def assign_ifcspace_class_to_obj(cls, obj: bpy.types.Object) -> None: - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace") + bonsai.core.root.assign_class( + tool.Ifc, + tool.Collector, + tool.Root, + obj=obj, + ifc_class="IfcSpace", + should_add_representation=False, + ) @classmethod def assign_type_to_obj(cls, obj: bpy.types.Object) -> None: - # TODO this code looks in the wrong spot and suspicious props = tool.Model.get_model_props() ifc_file = tool.Ifc.get() relating_type_id = props.relating_type_id - relating_type = tool.Ifc.get().by_id(int(relating_type_id)) + relating_type = ifc_file.by_id(int(relating_type_id)) ifc_class = relating_type.is_a() instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, ifc_file.schema)[0] - bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class) + bonsai.core.root.assign_class( + tool.Ifc, + tool.Collector, + tool.Root, + obj=obj, + ifc_class=instance_class, + should_add_representation=False, + ) element = tool.Ifc.get_entity(obj) assert element ifcopenshell.api.type.assign_type(ifc_file, related_objects=[element], relating_type=relating_type) + @classmethod + def set_covering_representation_from_polygon( + cls, + obj: bpy.types.Object, + poly: Polygon, + polygon_is_si: bool = True, + ) -> None: + """Create the covering body representation from a polygon, extruded by the type's material layer thickness.""" + element = tool.Ifc.get_entity(obj) + relating_type = ifcopenshell.util.element.get_type(element) + material = ifcopenshell.util.element.get_material(relating_type, should_skip_usage=True) + depth = 0.0 + if material and material.is_a("IfcMaterialLayerSet"): + depth = sum(layer.LayerThickness for layer in material.MaterialLayers) + cls.set_extrusion_representation_from_polygon(obj, element, poly, depth, polygon_is_si) + @classmethod def assign_relating_type_to_element( cls, @@ -1214,15 +1192,6 @@ class Spatial(bonsai.core.tool.Spatial): ) -> None: bonsai.core.type.assign_type(ifc, tool.Model, type, element=element, type=relating_type) - @classmethod - def regen_obj_representation(cls, obj: bpy.types.Object, body: ifcopenshell.entity_instance) -> None: - bonsai.core.geometry.switch_representation( - tool.Ifc, - tool.Geometry, - obj=obj, - representation=body, - ) - @classmethod def set_space_visibility(cls, is_visible: bool) -> None: if tool.Ifc.get().schema == "IFC2X3": diff --git a/src/bonsai/bonsai/tool/type.py b/src/bonsai/bonsai/tool/type.py index 84a349f288..882c4b4618 100644 --- a/src/bonsai/bonsai/tool/type.py +++ b/src/bonsai/bonsai/tool/type.py @@ -75,16 +75,7 @@ class Type(bonsai.core.tool.Type): @classmethod def get_model_types(cls) -> list[ifcopenshell.entity_instance]: - ifc_file = tool.Ifc.get() - types = ifc_file.by_type("IfcElementType") - if tool.Ifc.get_schema() == "IFC2X3": - types += ifc_file.by_type("IfcWindowStyle") - types += ifc_file.by_type("IfcDoorStyle") - types += ifc_file.by_type("IfcSpatialStructureElementType") - else: - types += ifc_file.by_type("IfcSpatialElementType") - types += ifc_file.by_type("IfcTypeProduct", include_subtypes=False) - return types + return tool.Ifc.get().by_type("IfcTypeProduct") @classmethod def get_object_data(cls, obj: bpy.types.Object) -> Union[bpy.types.ID, None]: diff --git a/src/bonsai/bonsai/tool/unit.py b/src/bonsai/bonsai/tool/unit.py index 5825c6f382..4c3e45b794 100644 --- a/src/bonsai/bonsai/tool/unit.py +++ b/src/bonsai/bonsai/tool/unit.py @@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Any, Literal, Union import bpy import ifcopenshell +import ifcopenshell.util.unit from lark import Lark, Transformer import bonsai.bim.helper diff --git a/src/bonsai/docs/guides/development/index.rst b/src/bonsai/docs/guides/development/index.rst index 8852892361..d969791bf6 100644 --- a/src/bonsai/docs/guides/development/index.rst +++ b/src/bonsai/docs/guides/development/index.rst @@ -19,4 +19,5 @@ This chapter covers how you can help contribute to Bonsai. undo_system writing_docs debugging + maintenance ide/index diff --git a/src/bonsai/docs/guides/development/maintenance.rst b/src/bonsai/docs/guides/development/maintenance.rst new file mode 100644 index 0000000000..e1728fd7a5 --- /dev/null +++ b/src/bonsai/docs/guides/development/maintenance.rst @@ -0,0 +1,65 @@ +Maintenance +=========== + +This page documents what needs to be updated in various maintenance scenarios. + +Python Version Added or Removed +-------------------------------- + +When adding or removing a supported Python version, update the following: + +.. list-table:: + :header-rows: 1 + + * - File + - What to update + * - ``.github/workflows/ci-black-formatting.yaml`` + - ``MIN_IOS_PY_VERSION`` + * - ``.github/workflows/ci-ifcopenshell-python-pypi.yml`` + - ``pyver`` matrix + * - ``.github/workflows/ci-ifcopenshell-python.yml`` + - ``pyver`` matrix + * - ``nix/build-all.py`` + - ``PYTHON_VERSIONS`` list + * - ``src/bsdd/pyproject.toml`` + - ``requires-python`` + * - ``src/ifcopenshell-python/docs/ifcopenshell-python/installation.rst`` + - add or remove the row in the ZIP packages table + * - ``src/ifcopenshell-python/Makefile`` + - ``SUPPORTED_PYVERSIONS`` + * - ``src/ifcopenshell-python/pyproject.toml`` + - ``requires-python`` + * - ``src/ifcopenshell-python/test/test_package.py`` + - ``SUPPORTED_PY_VERSIONS`` tuple + * - ``win/build-all-win.py`` + - ``PYTHON_VERSIONS`` list + +Blender Version Updated +----------------------- + +When a new Blender version is released and supported: + +.. list-table:: + :header-rows: 1 + + * - File + - What to update + * - ``.github/workflows/ci-bonsai-daily.yml`` + - Blender download URL + +Blender's Bundled Python Version Updated +----------------------------------------- + +When Blender ships with a new Python version: + +.. list-table:: + :header-rows: 1 + + * - File + - What to update + * - ``.github/workflows/ci-black-formatting.yaml`` + - ``MIN_BLENDER_PY_VERSION`` + * - ``src/bonsai/Makefile`` + - ``SUPPORTED_PYVERSIONS`` + * - ``src/bonsai/scripts/dev_environment.py`` + - ``PYTHON_VERSION`` mapping (Blender version, bundled Python version) diff --git a/src/bonsai/scripts/bonsai_deps.py b/src/bonsai/scripts/bonsai_deps.py new file mode 100644 index 0000000000..678fa49bb1 --- /dev/null +++ b/src/bonsai/scripts/bonsai_deps.py @@ -0,0 +1,23 @@ +"""Clone or update Bonsai external dependencies. + +Must be run from the repository root. +""" + +import subprocess +from pathlib import Path + +DEPS = [ + ("https://projects.blender.org/pioverfour/sun_position.git", "sun_position"), + ("https://github.com/kevancress/MeasureIt_ARCH", "MeasureIt_ARCH"), + ("https://github.com/nortikin/sverchok.git", "sverchok"), +] + +base = Path("src/bonsai/external_dependencies") +base.mkdir(parents=True, exist_ok=True) + +for url, name in DEPS: + path = base / name + if not path.exists(): + subprocess.check_call(["git", "clone", url, str(path)]) + else: + subprocess.check_call(["git", "-C", str(path), "pull", "--rebase"]) diff --git a/src/bonsai/scripts/bonsai_translations.py b/src/bonsai/scripts/bonsai_translations.py index 1597eaabcb..7b6ae960db 100644 --- a/src/bonsai/scripts/bonsai_translations.py +++ b/src/bonsai/scripts/bonsai_translations.py @@ -273,10 +273,10 @@ if BPY_IS_LOADED: f"Couldn't find locale path in the source directory, creating dummy directory: {source_locale_path}.", ) - from ui_translate.settings import ( - settings as ui_translate_settings, # pyright: ignore[reportMissingImports] + from ui_translate.settings import ( # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] + settings as ui_translate_settings, ) - from ui_translate.update_ui import ( # pyright: ignore[reportMissingImports] + from ui_translate.update_ui import ( # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] UI_OT_i18n_updatetranslation_init_settings, ) diff --git a/src/bonsai/scripts/gbxml.py b/src/bonsai/scripts/gbxml.py index 7bd8063a15..6cf104f2c1 100644 --- a/src/bonsai/scripts/gbxml.py +++ b/src/bonsai/scripts/gbxml.py @@ -23,7 +23,9 @@ import bpy # sys.path.append('C:\Program Files\Python37\Lib\site-packages') import lxml.etree -from bspy import Gbxml # pyright: ignore[reportMissingImports] +from bspy import ( # ty: ignore[unresolved-import] + Gbxml, # pyright: ignore[reportMissingImports] +) class GbxmlExporter: diff --git a/src/bonsai/scripts/generate_steel_profiles_library.py b/src/bonsai/scripts/generate_steel_profiles_library.py index 255a448127..ca45648122 100644 --- a/src/bonsai/scripts/generate_steel_profiles_library.py +++ b/src/bonsai/scripts/generate_steel_profiles_library.py @@ -22,7 +22,7 @@ from math import pi from pathlib import Path -import boltspy as bolts # pyright: ignore[reportMissingImports] +import boltspy as bolts # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import ifcopenshell.api import ifcopenshell.api.material import ifcopenshell.api.project diff --git a/src/bonsai/scripts/obj2ifc-meshlab.py b/src/bonsai/scripts/obj2ifc-meshlab.py index 70849e11d9..d707c8bda7 100644 --- a/src/bonsai/scripts/obj2ifc-meshlab.py +++ b/src/bonsai/scripts/obj2ifc-meshlab.py @@ -31,7 +31,7 @@ import ifcopenshell.api.spatial import ifcopenshell.api.unit import ifcopenshell.guid import numpy as np -import pymeshlab # pyright: ignore[reportMissingImports] +import pymeshlab # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] class Obj2Ifc: diff --git a/src/bonsai/scripts/obj2ifc.py b/src/bonsai/scripts/obj2ifc.py index ec5459c4a3..0b3252cd1c 100644 --- a/src/bonsai/scripts/obj2ifc.py +++ b/src/bonsai/scripts/obj2ifc.py @@ -31,7 +31,7 @@ import ifcopenshell.api.spatial import ifcopenshell.api.unit import ifcopenshell.guid import numpy as np -import pywavefront # pyright: ignore[reportMissingImports] +import pywavefront # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] class Obj2Ifc: diff --git a/src/bonsai/test/bim/feature/covering.feature b/src/bonsai/test/bim/feature/covering.feature index 4eb97649da..cefa554d41 100644 --- a/src/bonsai/test/bim/feature/covering.feature +++ b/src/bonsai/test/bim/feature/covering.feature @@ -2,7 +2,7 @@ Feature: Covering Covers covering tool. -Scenario: Execute generate flooring coverings from walls +Scenario: Add flooring from walls Given an empty IFC project And I load the demo construction library And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" @@ -24,9 +24,9 @@ Scenario: Execute generate flooring coverings from walls And the cursor is at "0,2.0,0" And I set "scene.BIMModelProperties.length" to "1.9" And I press "bim.add_occurrence" - # add_instance_flooring_coverings_from_walls is expecting FLOORING predefined type. + # Set COV30 predefined type to FLOORING. And the object "IfcCoveringType/COV30" is selected - And I look at the "Attributes" panel + And I look at the "Object Attributes" panel And I click "Edit" And I set the "PredefinedType" property to "FLOORING" And I click "Save Attributes" @@ -42,3 +42,108 @@ Scenario: Execute generate flooring coverings from walls Then the object "IfcCovering/Covering0" exists And the object "IfcCovering/Covering0" is at "1.8,1.05,0.0" And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03" + +Scenario: Add ceiling from walls + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And the object "IfcWall/Wall" is selected + And I press "bim.change_layer_length(length=3.6)" + And the cursor is at "3.6,0.1,3" + And I set "scene.BIMModelProperties.length" to "2.0" + And I press "bim.add_occurrence" + And the cursor is at "3.5,2.1,3" + And I set "scene.BIMModelProperties.length" to "3.5" + And I press "bim.add_occurrence" + And the cursor is at "0,2.0,0" + And I set "scene.BIMModelProperties.length" to "1.9" + And I press "bim.add_occurrence" + # Set COV30 predefined type to CEILING. + And the object "IfcCoveringType/COV30" is selected + And I look at the "Object Attributes" panel + And I click "Edit" + And I set the "PredefinedType" property to "CEILING" + And I click "Save Attributes" + # Run the operator with ceiling height = 2.7 (default). + When the object "IfcWall/Wall" is selected + And additionally the object "IfcWall/Wall.001" is selected + And additionally the object "IfcWall/Wall.002" is selected + And additionally the object "IfcWall/Wall.003" is selected + And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_instance_ceiling_coverings_from_walls" + Then the object "IfcCovering/Covering0" exists + And the object "IfcCovering/Covering0" is at "1.8,1.05,2.7" + And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03" + +Scenario: Add flooring from cursor + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And the cursor is at "1.1,0,0" + And I press "bim.add_occurrence" + And the object "IfcWall/Wall.001" is selected + And I press "bim.hotkey(hotkey='S_R')" + And the cursor is at "0,.9,0" + And I press "bim.add_occurrence" + And the cursor is at "-1,0,0" + And I press "bim.add_occurrence" + And the object "IfcWall/Wall.003" is selected + And I press "bim.hotkey(hotkey='S_R')" + And the object "IfcWall/Wall.003" is moved to "0,0,0" + # Set COV30 predefined type to FLOORING. + And the object "IfcCoveringType/COV30" is selected + And I look at the "Object Attributes" panel + And I click "Edit" + And I set the "PredefinedType" property to "FLOORING" + And I click "Save Attributes" + # Generate covering from cursor inside the room. + When the cursor is at "0.5,0.5,0" + And I deselect all objects + And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_instance_flooring_covering_from_cursor" + Then the object "IfcCovering/Covering" exists + And the object "IfcCovering/Covering" dimensions are "1,0.8,0.03" + +Scenario: Add ceiling from cursor + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And the cursor is at "1.1,0,0" + And I press "bim.add_occurrence" + And the object "IfcWall/Wall.001" is selected + And I press "bim.hotkey(hotkey='S_R')" + And the cursor is at "0,.9,0" + And I press "bim.add_occurrence" + And the cursor is at "-1,0,0" + And I press "bim.add_occurrence" + And the object "IfcWall/Wall.003" is selected + And I press "bim.hotkey(hotkey='S_R')" + And the object "IfcWall/Wall.003" is moved to "0,0,0" + # Set COV30 predefined type to CEILING. + And the object "IfcCoveringType/COV30" is selected + And I look at the "Object Attributes" panel + And I click "Edit" + And I set the "PredefinedType" property to "CEILING" + And I click "Save Attributes" + # Generate covering from cursor inside the room. + When the cursor is at "0.5,0.5,0" + And I deselect all objects + And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_instance_ceiling_covering_from_cursor" + Then the object "IfcCovering/Covering" exists + And the object "IfcCovering/Covering" dimensions are "1,0.8,0.03" diff --git a/src/bonsai/test/bim/feature/drawing.feature b/src/bonsai/test/bim/feature/drawing.feature index b7b78e748d..0f0c5a3b17 100644 --- a/src/bonsai/test/bim/feature/drawing.feature +++ b/src/bonsai/test/bim/feature/drawing.feature @@ -310,9 +310,129 @@ Scenario: Create sheet - with a drawing added to it When I click "OUTPUT" Then the file "{ifc_dir}/sheets/A01 - UNTITLED.svg" should contain "IfcWall" +Scenario: Enable editing text + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + When I click "Enable Editing Text" + Then I see "Literals:" + And I don't see "FontSize" + +Scenario: Disable editing text + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + And I click "Enable Editing Text" + When I click "CANCEL" + Then I see "FontSize" + And I don't see "Literals:" + +Scenario: Edit text - no changes + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + And I click "Enable Editing Text" + When I click "Edit Text" + Then I see "FontSize" + And I don't see "Literals:" + +Scenario: Edit text - change literal + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + And I click "Enable Editing Text" + And I set the "Literal" property to "Hello World" + When I click "Edit Text" + Then I see "Hello World" + +Scenario: Add text literal + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + And I click "Enable Editing Text" + And I click the "ADD" after the text "Literals:" + And I set the "2nd Literal" property to "New Literal" + When I click "Edit Text" + Then I see "New Literal" + +Scenario: Remove text literal + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I save IFC project + And I look at the "Drawings" panel + And I click "IMPORT" + And I click "ADD" + And I press "bim.toggle_target_view(option="EXPAND", target_view='PLAN_VIEW')" + And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list + And I click "VIEW_CAMERA_UNSELECTED" in the row where I see "PLAN_VIEW" in the "1st" list + And I press "bim.add_annotation" + And the object "IfcAnnotation/TEXT" is selected + And I look at the "BIM_PT_text" panel + And I click "Enable Editing Text" + And I set the "Literal" property to "Keep This" + And I click the "ADD" after the text "Literals:" + And I set the "2nd Literal" property to "Remove This" + And I click "Edit Text" + And I click "Enable Editing Text" + When I click the "2nd" "X" + And I click "Edit Text" + Then I see "Keep This" + And I don't see "Remove This" + Scenario: Add reference image Given an empty IFC project And I save IFC project - When I press "bim.add_reference_image(filepath='{cwd}/test/files/image.jpg')" + When I press "bim.add_reference_image(filepath='{cwd}/test/files/image.jpg', x_length=1, y_length=0.565)" Then the object "IfcAnnotation/image" exists And the object "IfcAnnotation/image" dimensions are "1.0,0.565,0." diff --git a/src/bonsai/test/bim/feature/material.feature b/src/bonsai/test/bim/feature/material.feature index 2f34cbb893..67525cd8f3 100644 --- a/src/bonsai/test/bim/feature/material.feature +++ b/src/bonsai/test/bim/feature/material.feature @@ -422,6 +422,24 @@ Scenario: Enable editing material set item When I press "bim.enable_editing_material_set_item(material_set_item={material_profile})" Then nothing happens +Scenario: Edit layer item defaults null IsVentilated to FALSE in UI + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I look at the "Class" panel + And I set the "Products" property to "IfcElement" + And I set the "Class" property to "IfcWall" + And I click "Assign IFC Class" + And I press "bim.add_material()" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "layer" is "{ifc}.by_type('IfcMaterialLayer')[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={layer})" + When I evaluate expression "attrs = bpy.context.active_object.BIMObjectMaterialProperties.material_set_item_attributes; is_vent = next(a for a in attrs if a.name == 'IsVentilated'); assert is_vent.enum_value == 'FALSE'; assert is_vent.is_null is True" + And I press "bim.edit_material_set_item(material_set_item={layer})" + Then I evaluate expression "assert {ifc}.by_id({layer}).IsVentilated is None" + Scenario: Add material set layer Given an empty IFC project And I add a cube diff --git a/src/bonsai/test/bim/feature/model.feature b/src/bonsai/test/bim/feature/model.feature index 9ca843c1c5..064620a574 100644 --- a/src/bonsai/test/bim/feature/model.feature +++ b/src/bonsai/test/bim/feature/model.feature @@ -57,7 +57,7 @@ Scenario: Add one type from the Construction Type Browser Scenario: Add grid Given an empty IFC project - When I press "mesh.add_grid" + When I press "bim.add_grid" Then the object "IfcGrid/Grid" is an "IfcGrid" And the object "IfcGridAxis/A" is an "IfcGridAxis" And the object "IfcGridAxis/B" is an "IfcGridAxis" @@ -396,6 +396,44 @@ Scenario: Add a slab And the object "IfcSlab/Slab" bottom left corner is at "0,0,0" And the object "IfcSlab/Slab" top right corner is at "1,1,0.2" +Scenario: Extend walls to underside + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And I set "scene.BIMModelProperties.ifc_class" to "IfcSlabType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcSlabType') if e.Name == 'FLR200'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + And the object "IfcSlab/Slab" is moved to "0,0,2.5" + When the object "IfcWall/Wall" is selected + And additionally the object "IfcSlab/Slab" is selected + And I look at the tool header + And I click "Extend To Underside" + Then the object "IfcWall/Wall" dimensions are "1,0.1,2.5" + +Scenario: Extend walls to underside - extending to a tessellated gable roof + Given an empty IFC project + And I load the demo construction library + And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType" + And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()" + And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}" + And I press "bim.add_occurrence" + # Create gable roof: a cube turned into a prism with a ridge. + And I add a cube of size "1" at "0.5,0.05,3" + And the object "Cube" is selected + And I evaluate expression "obj = bpy.context.active_object; [setattr(v.co, 'y', 0) for v in obj.data.vertices if v.co.z > 0]" + And I set "scene.BIMRootProperties.ifc_product" to "IfcElement" + And I set "scene.BIMRootProperties.ifc_class" to "IfcRoof" + And I press "bim.assign_class" + When the object "IfcWall/Wall" is selected + And additionally the object "IfcRoof/Cube" is selected + And I look at the tool header + And I click "Extend To Underside" + Then the object "IfcWall/Wall" dimensions are "1,0.1,2.5" + Scenario: Enable editing a slab profile Given an empty IFC project And I load the demo construction library diff --git a/src/bonsai/test/bim/feature/project.feature b/src/bonsai/test/bim/feature/project.feature index f536a17220..0db6041b56 100644 --- a/src/bonsai/test/bim/feature/project.feature +++ b/src/bonsai/test/bim/feature/project.feature @@ -914,7 +914,7 @@ Scenario: Export IFC - with moved object location synchronised Scenario: Export IFC - with moved grid axis location synchronised Given an empty IFC project - And I press "mesh.add_grid" + And I press "bim.add_grid" When the object "IfcGridAxis/01" is moved to "1,0,0" And I save IFC project And I load previously saved IFC project diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 262dce65ed..79e8494441 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -133,7 +133,11 @@ class PanelSpy: self.spied_labels.append(kwargs["text"]) return self elif self.spied_attr == "prop": - props, name = args + if args: + props, name = args + else: + props = kwargs.get("data") + name = kwargs.get("property") props: bpy.types.bpy_struct text = kwargs.get("text", props.bl_rna.properties[name].name) icon = kwargs.get("icon", None) @@ -242,7 +246,7 @@ class TemplateListItemSpy(PanelSpy): self.spied_props: list[dict[str, Any]] = [] self.spied_operators: list[dict[str, Any]] = [] if len(signature(blender_panel.draw_item).parameters) == 8: - blender_panel.draw_item( + blender_panel.draw_item( # ty:ignore[missing-argument] self, bpy.context, self, @@ -378,7 +382,7 @@ def i_look_at_the_panel_panel(panel: str) -> None: # Option to provide explicit panel name if panel names overlap. panel_class = getattr(bpy.types, panel, None) - if panel_class is None: + if panel_class is None or panel_class.bl_rna.base.name not in ("Panel", "Operator", "Menu", "UIList"): global ui_name_cache create_ui_name_cache() if panel not in ui_name_cache: @@ -390,6 +394,32 @@ def i_look_at_the_panel_panel(panel: str) -> None: panel_spy.refresh_spy() +@given(parsers.parse("I look at the tool header")) +@when(parsers.parse("I look at the tool header")) +@then(parsers.parse("I look at the tool header")) +def i_look_at_the_tool_header() -> None: + from bonsai.bim.module.model.workspace import EditObjectUI + + class MockRegion: + type = "UI" + + class MockContext: + def __getattr__(self, name): + if name == "region": + return MockRegion() + return getattr(bpy.context, name) + + global panel_spy + panel_spy = PanelSpy(EditObjectUI) + panel_spy.is_spy_dirty = False + panel_spy.spied_attr = None + panel_spy.spied_labels = [] + panel_spy.spied_props = [] + panel_spy.spied_operators = [] + panel_spy.spied_lists = [] + EditObjectUI.draw(MockContext(), panel_spy) + + @given(parsers.parse('I open the "{name}" menu')) @when(parsers.parse('I open the "{name}" menu')) @then(parsers.parse('I open the "{name}" menu')) @@ -610,8 +640,9 @@ def i_see_the_prop_property_is_value(prop, value): @then(parsers.parse('I set the "{prop}" property to "{value}"')) def i_set_the_prop_property_to_value(prop: str, value: str): """ - :param prop: Could be either property name, property text, property icon - or property index (e.g. "1st", "2nd", "5th"). + :param prop: Could be either property name, property text, property icon, + property index (e.g. "1st", "2nd", "5th"), or Nth named property + (e.g. "2nd Literal" for the 2nd property called "Literal"). :param value: For boolean propeties - 'TRUE' or 'FALSE'. """ @@ -619,12 +650,28 @@ def i_set_the_prop_property_to_value(prop: str, value: str): assert panel_spy panel_spy.refresh_spy() is_nth = False - if prop[0].isnumeric() and prop.endswith(("st", "nd", "th")): + is_nth_named = False + nth_target = 0 + prop_name = prop + if " " in prop and prop[0].isnumeric(): + parts = prop.split(" ", 1) + if parts[0].endswith(("st", "nd", "th")): + is_nth_named = True + nth_target = int(parts[0][:-2]) - 1 + prop_name = parts[1] + elif prop[0].isnumeric() and prop.endswith(("st", "nd", "th")): is_nth = True + named_count = 0 for nth, spied_prop in enumerate(panel_spy.spied_props): if is_nth and nth != int(prop[:-2]) - 1: continue - if not is_nth and prop not in (spied_prop["name"], spied_prop["text"], spied_prop["icon"]): + if is_nth_named: + if prop_name not in (spied_prop["name"], spied_prop["text"], spied_prop["icon"]): + continue + if named_count != nth_target: + named_count += 1 + continue + elif not is_nth and prop not in (spied_prop["name"], spied_prop["text"], spied_prop["icon"]): continue if spied_prop["prop_type"] == "BOOLEAN": if value == "TRUE": @@ -873,6 +920,29 @@ def i_click_button(button): _i_click_button_on_panel(button, panel_spy) +@given(parsers.parse('I click the "{nth}" "{button}"')) +@when(parsers.parse('I click the "{nth}" "{button}"')) +@then(parsers.parse('I click the "{nth}" "{button}"')) +def i_click_the_nth_button(nth, button): + """ + :param nth: Ordinal like "1st", "2nd", "3rd" to select the Nth matching button. + :param button: The text or icon of the button to click. + """ + assert panel_spy + panel_spy.refresh_spy() + target = int(nth[:-2]) - 1 + count = 0 + for spied_operator in panel_spy.spied_operators: + if spied_operator["text"] == button or spied_operator["icon"] == button: + if count == target: + spied_operator["operator"]("INVOKE_DEFAULT", **spied_operator["kwargs"]) + panel_spy.is_spy_dirty = True + return + count += 1 + debug = "\n".join([f"{i} {v}" for i, v in enumerate(panel_spy.spied_operators)]) + assert False, f"Could not find {nth} {button}:\n{debug}" + + @given(parsers.parse('I click the "{button}" after the text "{text}"')) @when(parsers.parse('I click the "{button}" after the text "{text}"')) @then(parsers.parse('I click the "{button}" after the text "{text}"')) diff --git a/src/bonsai/test/core/bootstrap.py b/src/bonsai/test/core/bootstrap.py index 057ee9ffab..cd8371e1c3 100644 --- a/src/bonsai/test/core/bootstrap.py +++ b/src/bonsai/test/core/bootstrap.py @@ -102,6 +102,13 @@ def geometry(): prophet.verify() +@pytest.fixture +def ifcgit(): + prophet = Prophecy(bonsai.core.tool.IfcGit) + yield prophet + prophet.verify() + + @pytest.fixture def georeference(): prophet = Prophecy(bonsai.core.tool.Georeference) diff --git a/src/bonsai/test/core/test_drawing.py b/src/bonsai/test/core/test_drawing.py index a83f026ad3..52822aa754 100644 --- a/src/bonsai/test/core/test_drawing.py +++ b/src/bonsai/test/core/test_drawing.py @@ -35,9 +35,14 @@ class TestDisableEditingText: class TestEditText: def test_run(self, drawing): - drawing.synchronise_ifc_and_text_attributes("obj").should_be_called() - drawing.update_text_size_pset("obj").should_be_called() - drawing.update_text_annotation_properties("obj").should_be_called() + drawing.export_text_literal_attributes("obj").should_be_called().will_return("literal_attributes") + drawing.export_font_size("obj").should_be_called().will_return("font_size") + drawing.edit_text_font_size("obj", "font_size").should_be_called() + drawing.export_wrap_length("obj").should_be_called().will_return("wrap_length") + drawing.edit_text_wrap_length("obj", "wrap_length").should_be_called() + drawing.export_symbol("obj").should_be_called().will_return("symbol") + drawing.edit_text_symbol("obj", "symbol").should_be_called() + drawing.edit_text_literals("obj", "literal_attributes").should_be_called() drawing.disable_editing_text("obj").should_be_called() subject.edit_text(drawing, obj="obj") @@ -466,6 +471,7 @@ class TestRemoveDrawing: class TestUpdateDrawingName: def test_do_not_update_if_name_unchanged(self, ifc, drawing): drawing.get_name("drawing").should_be_called().will_return("name") + drawing.set_camera_name("drawing", "name").should_be_called() drawing.get_drawing_group("drawing").should_be_called().will_return("group") drawing.get_name("group").should_be_called().will_return("name") drawing.get_drawing_collection("drawing").should_be_called().will_return("collection") @@ -482,6 +488,7 @@ class TestUpdateDrawingName: def test_run(self, ifc, drawing): drawing.get_name("drawing").should_be_called().will_return("oldname") ifc.run("attribute.edit_attributes", product="drawing", attributes={"Name": "name"}).should_be_called() + drawing.set_camera_name("drawing", "name").should_be_called() drawing.get_drawing_group("drawing").should_be_called().will_return("group") drawing.get_name("group").should_be_called().will_return("oldname") ifc.run("attribute.edit_attributes", product="group", attributes={"Name": "name"}).should_be_called() diff --git a/src/bonsai/test/core/test_georeference.py b/src/bonsai/test/core/test_georeference.py index b9b330d816..e95236ec3f 100644 --- a/src/bonsai/test/core/test_georeference.py +++ b/src/bonsai/test/core/test_georeference.py @@ -23,6 +23,7 @@ from test.core.bootstrap import georeference, ifc class TestAddGeoreferencing: def test_run(self, georeference): georeference.add_georeferencing().should_be_called() + georeference.set_model_origin().should_be_called() subject.add_georeferencing(georeference) @@ -35,9 +36,10 @@ class TestEnableEditingGeoreferencing: class TestRemoveGeoreferencing: - def test_run(self, ifc): + def test_run(self, ifc, georeference): ifc.run("georeference.remove_georeferencing").should_be_called() - subject.remove_georeferencing(ifc) + georeference.set_model_origin().should_be_called() + subject.remove_georeferencing(ifc, georeference) class TestDisableEditingGeoreferencing: diff --git a/src/bonsai/test/core/test_ifcgit.py b/src/bonsai/test/core/test_ifcgit.py new file mode 100644 index 0000000000..b4d80985b2 --- /dev/null +++ b/src/bonsai/test/core/test_ifcgit.py @@ -0,0 +1,274 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2025 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import pytest + +import bonsai.core.ifcgit as subject +from test.core.bootstrap import ifcgit, ifc + + +class MockOperator: + def __init__(self): + self.reports = [] + + def report(self, level, message): + self.reports.append((level, message)) + + +class TestCreateRepo: + def test_run(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.get_path_dir("path/to/model.ifc").should_be_called().will_return("path/to") + ifcgit.init_repo("path/to").should_be_called() + subject.create_repo(ifcgit, ifc) + + +class TestAddFile: + def test_run(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.repo_from_path("path/to/model.ifc").should_be_called().will_return("repo") + ifcgit.add_file_to_repo("repo", "path/to/model.ifc").should_be_called() + subject.add_file(ifcgit, ifc) + + +class TestCloneRepo: + def test_successful_clone(self, ifcgit): + ifcgit.clone_repo("http://example.com/repo.git", "/local/folder").should_be_called().will_return("repo") + ifcgit.load_anyifc("repo").should_be_called() + op = MockOperator() + subject.clone_repo(ifcgit, "http://example.com/repo.git", "/local/folder", operator=op) + assert op.reports == [({"INFO"}, "Repository cloned")] + + def test_failed_clone_reports_error(self, ifcgit): + ifcgit.clone_repo("http://example.com/repo.git", "/local/folder").should_be_called().will_return(None) + op = MockOperator() + subject.clone_repo(ifcgit, "http://example.com/repo.git", "/local/folder", operator=op) + assert op.reports == [({"ERROR"}, "Clone failed")] + + +class TestDiscardUncommitted: + def test_run(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.git_checkout("path/to/model.ifc").should_be_called() + ifcgit.load_project("path/to/model.ifc").should_be_called() + subject.discard_uncommitted(ifcgit, ifc) + + +class TestCommitChanges: + def test_commit_on_branch_without_new_branch(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.is_head_detached().should_be_called().will_return(False) + ifcgit.git_commit("path/to/model.ifc", "my message").should_be_called() + subject.commit_changes(ifcgit, ifc, "my message", "") + + def test_commit_on_branch_with_new_branch(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.is_head_detached().should_be_called().will_return(False) + ifcgit.checkout_new_branch("path/to/model.ifc", "feature").should_be_called() + ifcgit.git_commit("path/to/model.ifc", "my message").should_be_called() + subject.commit_changes(ifcgit, ifc, "my message", "feature") + + def test_commit_on_detached_head(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.is_head_detached().should_be_called().will_return(True) + ifcgit.git_commit("path/to/model.ifc", "my message").should_be_called() + ifcgit.create_new_branch("feature").should_be_called() + subject.commit_changes(ifcgit, ifc, "my message", "feature") + + +class TestAddTag: + def test_run(self, ifcgit): + ifcgit.add_tag("repo", "abc123", "v1.0", "Release notes").should_be_called() + subject.add_tag(ifcgit, "repo", "abc123", "v1.0", "Release notes") + + +class TestDeleteTag: + def test_run(self, ifcgit): + ifcgit.delete_tag("repo", "v1.0").should_be_called() + subject.delete_tag(ifcgit, "repo", "v1.0") + + +class TestAddRemote: + def test_run(self, ifcgit): + ifcgit.add_remote("repo", "origin", "http://example.com").should_be_called() + subject.add_remote(ifcgit, "repo", "origin", "http://example.com") + + +class TestDeleteRemote: + def test_run(self, ifcgit): + ifcgit.delete_remote("repo", "origin").should_be_called() + subject.delete_remote(ifcgit, "repo", "origin") + + +class TestPush: + def test_push_succeeds_silently(self, ifcgit): + ifcgit.get_active_branch_name().should_be_called().will_return("main") + ifcgit.push("repo", "origin", "main").should_be_called().will_return(None) + subject.push(ifcgit, "repo", "origin", operator=None) + + def test_push_failure_reports_error(self, ifcgit): + ifcgit.get_active_branch_name().should_be_called().will_return("main") + ifcgit.push("repo", "origin", "main").should_be_called().will_return("stderr: rejected") + op = MockOperator() + subject.push(ifcgit, "repo", "origin", operator=op) + assert op.reports == [({"ERROR"}, "stderr: rejected")] + + +class TestRefreshRevisionList: + def test_refreshes_when_repo_has_heads(self, ifcgit, ifc): + ifcgit.repo_has_commits().should_be_called().will_return(True) + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called() + subject.refresh_revision_list(ifcgit, ifc) + + def test_skips_when_repo_has_no_heads(self, ifcgit, ifc): + ifcgit.repo_has_commits().should_be_called().will_return(False) + subject.refresh_revision_list(ifcgit, ifc) + # nothing else should be called — Prophecy will verify + + +class TestColouriseRevision: + def test_skips_when_no_step_ids(self, ifcgit): + ifcgit.get_revisions_step_ids().should_be_called().will_return(None) + subject.colourise_revision(ifcgit) + + def test_colourises_with_step_ids(self, ifcgit): + ifcgit.get_revisions_step_ids().should_be_called().will_return("step_ids") + ifcgit.get_modified_step_ids("step_ids").should_be_called().will_return("modified_step_ids") + ifcgit.update_step_ids("step_ids", "modified_step_ids").should_be_called().will_return("final_step_ids") + ifcgit.colourise("final_step_ids").should_be_called() + subject.colourise_revision(ifcgit) + + +class TestColouriseUncommitted: + def test_skips_when_no_step_ids(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.ifc_diff_ids("repo", None, "HEAD", "path/to/model.ifc").should_be_called().will_return(None) + subject.colourise_uncommitted(ifcgit, ifc, "repo") + + def test_colourises_with_step_ids(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.ifc_diff_ids("repo", None, "HEAD", "path/to/model.ifc").should_be_called().will_return("step_ids") + ifcgit.get_modified_step_ids("step_ids").should_be_called().will_return("modified_step_ids") + ifcgit.update_step_ids("step_ids", "modified_step_ids").should_be_called().will_return("final_step_ids") + ifcgit.colourise("final_step_ids").should_be_called() + subject.colourise_uncommitted(ifcgit, ifc, "repo") + + +class TestSwitchRevision: + def test_run(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.switch_to_revision_item().should_be_called() + ifcgit.load_project("path/to/model.ifc").should_be_called() + ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called() + ifcgit.decolourise().should_be_called() + subject.switch_revision(ifcgit, ifc) + + +class TestMergeBranch: + def test_no_branch_at_selected_commit(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.config_ifcmerge().should_be_called() + ifcgit.get_selected_branch().should_be_called().will_return(None) + subject.merge_branch(ifcgit, ifc, operator=None) + + def test_clean_merge(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.config_ifcmerge().should_be_called() + ifcgit.get_selected_branch().should_be_called().will_return("feature") + ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward") + ifcgit.git_merge("feature").should_be_called().will_return(None) + ifcgit.set_display_branch().should_be_called() + ifcgit.load_project("path/to/model.ifc").should_be_called() + ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called() + ifcgit.decolourise().should_be_called() + subject.merge_branch(ifcgit, ifc, operator=None) + + def test_conflict_mergetool_success(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.config_ifcmerge().should_be_called() + ifcgit.get_selected_branch().should_be_called().will_return("feature") + ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward") + ifcgit.git_merge("feature").should_be_called().will_return("conflict") + ifcgit.git_mergetool("ifcmerge-forward").should_be_called().will_return(None) + ifcgit.commit_merge("path/to/model.ifc").should_be_called() + ifcgit.set_display_branch().should_be_called() + ifcgit.load_project("path/to/model.ifc").should_be_called() + ifcgit.refresh_revision_list("path/to/model.ifc").should_be_called() + ifcgit.decolourise().should_be_called() + subject.merge_branch(ifcgit, ifc, operator=None) + + def test_conflict_mergetool_failure(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.config_ifcmerge().should_be_called() + ifcgit.get_selected_branch().should_be_called().will_return("feature") + ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward") + ifcgit.git_merge("feature").should_be_called().will_return("conflict") + ifcgit.git_mergetool("ifcmerge-forward").should_be_called().will_return("merge error") + ifcgit.git_merge_abort().should_be_called() + op = MockOperator() + subject.merge_branch(ifcgit, ifc, op) + assert op.reports == [({"ERROR"}, "IFC Merge failed:merge error")] + + def test_unknown_merge_error(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.config_ifcmerge().should_be_called() + ifcgit.get_selected_branch().should_be_called().will_return("feature") + ifcgit.get_merge_tool("feature").should_be_called().will_return("ifcmerge-forward") + ifcgit.git_merge("feature").should_be_called().will_return("error") + op = MockOperator() + subject.merge_branch(ifcgit, ifc, op) + assert op.reports == [({"ERROR"}, "Unknown IFC Merge failure")] + + +class TestEntityLog: + def test_run(self, ifcgit, ifc): + ifc.get_path().should_be_called().will_return("path/to/model.ifc") + ifcgit.entity_log("path/to/model.ifc", 42).should_be_called().will_return("log text") + op = MockOperator() + subject.entity_log(ifcgit, ifc, 42, op) + assert op.reports == [({"ERROR"}, "log text")] + + +class TestInstallGit: + def test_windows(self, ifcgit): + import unittest.mock as mock + + with mock.patch("platform.system", return_value="Windows"): + ifcgit.install_git_windows(operator="op").should_be_called() + subject.install_git(ifcgit, "op") + + def test_non_windows_does_nothing(self, ifcgit): + import unittest.mock as mock + + with mock.patch("platform.system", return_value="Linux"): + subject.install_git(ifcgit, "op") + # no tool method should be called — Prophecy will verify + + +class TestFetch: + def test_run(self, ifcgit): + ifcgit.fetch("origin").should_be_called() + subject.fetch(ifcgit, "origin") + + +class TestRunGitDiff: + def test_run(self, ifcgit): + ifcgit.run_git_diff("operator", False).should_be_called() + subject.run_git_diff(ifcgit, "operator", False) diff --git a/src/bonsai/test/core/test_type.py b/src/bonsai/test/core/test_type.py index e46cf8168f..031e414f95 100644 --- a/src/bonsai/test/core/test_type.py +++ b/src/bonsai/test/core/test_type.py @@ -22,8 +22,9 @@ from test.core.bootstrap import geometry, ifc, model, type class TestAssignType: def test_assigning_and_switching_to_an_existing_type_data(self, ifc, model, type): + type.record_material_usage_attributes("element").should_be_called().will_return(None) ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called() - type.has_material_usage("element").should_be_called().will_return(False) + model.get_usage_type("type").should_be_called(2).will_return(None) ifc.get_object("type").should_be_called().will_return("type_obj") type.get_object_data("type_obj").should_be_called().will_return("type_obj_data") type.change_object_data("obj", "type_obj_data", is_global=False).should_be_called() @@ -31,9 +32,10 @@ class TestAssignType: type.disable_editing("obj").should_be_called() subject.assign_type(ifc, model, type, element="element", type="type") - def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, type): + def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, model, type): + type.record_material_usage_attributes("element").should_be_called().will_return(None) ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called() - type.has_material_usage("element").should_be_called().will_return(False) + model.get_usage_type("type").should_be_called(2).will_return(None) ifc.get_object("type").should_be_called().will_return("type_obj") type.get_object_data("type_obj").should_be_called().will_return(None) ifc.get_object("element").should_be_called().will_return("obj") diff --git a/src/bonsai/test/tool/test_aggregate.py b/src/bonsai/test/tool/test_aggregate.py index bab71b2c4a..4fd9388c13 100644 --- a/src/bonsai/test/tool/test_aggregate.py +++ b/src/bonsai/test/tool/test_aggregate.py @@ -18,6 +18,7 @@ import bpy import ifcopenshell +import ifcopenshell.api.aggregate import ifcopenshell.api.context import ifcopenshell.api.geometry import ifcopenshell.api.root @@ -99,6 +100,42 @@ class TestCanAggregate(NewFile): subelement_obj = bpy.data.objects.new("Object", None) assert subject.can_aggregate(element_obj, subelement_obj) is False + def test_element_cannot_aggregate_to_itself(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcElementAssembly() + element_obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(element, element_obj) + assert subject.can_aggregate(element_obj, element_obj) is False + + def test_cyclic_aggregation_is_prevented(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + assembly_a = ifc.createIfcElementAssembly() + assembly_a_obj = bpy.data.objects.new("AssemblyA", None) + tool.Ifc.link(assembly_a, assembly_a_obj) + beam = ifc.createIfcBeam() + beam_obj = bpy.data.objects.new("Beam", None) + tool.Ifc.link(beam, beam_obj) + ifcopenshell.api.aggregate.assign_object(ifc, products=[beam], relating_object=assembly_a) + assert subject.can_aggregate(beam_obj, assembly_a_obj) is False + + def test_deep_cyclic_aggregation_is_prevented(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + assembly_a = ifc.createIfcElementAssembly() + assembly_a_obj = bpy.data.objects.new("AssemblyA", None) + tool.Ifc.link(assembly_a, assembly_a_obj) + assembly_b = ifc.createIfcElementAssembly() + assembly_b_obj = bpy.data.objects.new("AssemblyB", None) + tool.Ifc.link(assembly_b, assembly_b_obj) + beam = ifc.createIfcBeam() + beam_obj = bpy.data.objects.new("Beam", None) + tool.Ifc.link(beam, beam_obj) + ifcopenshell.api.aggregate.assign_object(ifc, products=[assembly_b], relating_object=assembly_a) + ifcopenshell.api.aggregate.assign_object(ifc, products=[beam], relating_object=assembly_b) + assert subject.can_aggregate(beam_obj, assembly_a_obj) is False + class TestHasPhysicalBodyRepresentation(NewFile): def test_run(self): diff --git a/src/bonsai/test/tool/test_blender.py b/src/bonsai/test/tool/test_blender.py index cd155b5dee..cb5d06bc71 100644 --- a/src/bonsai/test/tool/test_blender.py +++ b/src/bonsai/test/tool/test_blender.py @@ -24,6 +24,7 @@ import bpy import ifcopenshell import pytest +import bonsai import bonsai.core.tool import bonsai.tool as tool from bonsai.tool.blender import Blender as subject @@ -144,3 +145,25 @@ class TestGetSelectedFiles(NewFile): assert subject.get_selected_files(Path(g.name).parent, [file], use_relative_path=True) == [ Path(g.name).name ] + + +class TestGetDebugInfo(NewFile): + # Only keys that are safe to set if Bonsai fails to load. + EXPECTED_KEYS = { + "os", + "os_version", + "python_version", + "architecture", + "machine", + "processor", + "blender_version", + "bonsai_version", + "bonsai_commit_hash", + "bonsai_commit_date", + "last_actions", + "last_error", + } + + def test_failed_to_load_returns_only_base_keys(self): + info = bonsai.get_debug_info(bonsai_failed_to_load=True) + assert set(info.keys()) == self.EXPECTED_KEYS diff --git a/src/bonsai/test/tool/test_classification.py b/src/bonsai/test/tool/test_classification.py index 7bfe2a44d7..a48df50db4 100644 --- a/src/bonsai/test/tool/test_classification.py +++ b/src/bonsai/test/tool/test_classification.py @@ -42,6 +42,7 @@ class TestAddClassificationReferenceFromBSDD(NewFile): bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4)) obj = bpy.data.objects["Cube"] bpy.ops.bim.assign_class(ifc_class="IfcSpace", predefined_type="SPACE", userdefined_type="") + tool.Blender.set_active_object(obj) element = tool.Ifc.get_entity(obj) assert element @@ -66,6 +67,7 @@ class TestAddClassificationReferenceFromBSDD(NewFile): bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4)) obj = bpy.data.objects["Cube"] bpy.ops.bim.assign_class(ifc_class="IfcSpace", predefined_type="SPACE", userdefined_type="") + tool.Blender.set_active_object(obj) element = tool.Ifc.get_entity(obj) assert element @@ -110,6 +112,7 @@ class TestAddClassificationReferenceFromBSDD(NewFile): bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4)) obj = bpy.data.objects["Cube"] bpy.ops.bim.assign_class(ifc_class="IfcSpace", predefined_type="SPACE", userdefined_type="") + tool.Blender.set_active_object(obj) element = tool.Ifc.get_entity(obj) assert element diff --git a/src/bonsai/test/tool/test_drawing.py b/src/bonsai/test/tool/test_drawing.py index 06091ff8c4..e3de6ba2d9 100644 --- a/src/bonsai/test/tool/test_drawing.py +++ b/src/bonsai/test/tool/test_drawing.py @@ -666,10 +666,13 @@ class TestImportTextAttributes(NewFile): literal_props = props.literals[0] assert literal_props.ifc_definition_id == item.id() - assert literal_props.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2) assert literal_props.attributes["Literal"].string_value == "Literal" assert literal_props.attributes["Path"].enum_value == "RIGHT" assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left" + assert literal_props.align_vertical == "bottom" + assert literal_props.align_horizontal == "left" + assert props.align_vertical == "bottom" + assert props.align_horizontal == "left" class TestReplaceTextLiteralVariables(NewFile): @@ -957,4 +960,4 @@ class TestAddReferenceImage(NewFile): assert texture_filepath == filepath uv_node = material_nodes["Texture Coordinate"] - assert len(uv_node.outputs["UV"].links[:]) == 1 + assert len(uv_node.outputs["Generated"].links[:]) == 1 diff --git a/src/bonsai/test/tool/test_ifcgit.py b/src/bonsai/test/tool/test_ifcgit.py new file mode 100644 index 0000000000..5cd051aa5f --- /dev/null +++ b/src/bonsai/test/tool/test_ifcgit.py @@ -0,0 +1,456 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2025 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import os +import tempfile + +import bonsai.core.tool +from bonsai.tool.ifcgit import IfcGit, IfcGitRepo +from test.bim.bootstrap import NewFile + +try: + import git + import git.exc + + HAS_GIT = True +except ImportError: + HAS_GIT = False + +import pytest + +requires_git = pytest.mark.skipif(not HAS_GIT, reason="GitPython not available") + + +def _make_repo(tmpdir: str) -> "git.Repo": + """Initialise a git repo with user config (needed for commits).""" + repo = git.Repo.init(tmpdir) + with repo.config_writer() as cfg: + cfg.set_value("user", "name", "Test User") + cfg.set_value("user", "email", "test@example.com") + return repo + + +def _commit_ifc(repo: "git.Repo", tmpdir: str, content: str, message: str) -> str: + """Write content to model.ifc, stage and commit it. Returns commit hexsha.""" + ifc_path = os.path.join(tmpdir, "model.ifc") + with open(ifc_path, "w") as f: + f.write(content) + repo.index.add([os.path.normpath(ifc_path)]) + commit = repo.index.commit(message) + return commit.hexsha + + +# --------------------------------------------------------------------------- +# Interface conformance +# --------------------------------------------------------------------------- + + +class TestImplementsTool(NewFile): + def test_run(self): + assert isinstance(IfcGit(), bonsai.core.tool.IfcGit) + + +# --------------------------------------------------------------------------- +# Pure-logic (no git required) +# --------------------------------------------------------------------------- + + +class TestIsValidRefFormat(NewFile): + def test_simple_name(self): + assert IfcGit.is_valid_ref_format("main") + + def test_name_with_slash(self): + assert IfcGit.is_valid_ref_format("feature/my-feature") + + def test_name_with_numbers(self): + assert IfcGit.is_valid_ref_format("release-2024") + + def test_rejects_leading_dot(self): + assert not IfcGit.is_valid_ref_format(".hidden") + + def test_rejects_leading_dash(self): + assert not IfcGit.is_valid_ref_format("-branch") + + def test_rejects_space(self): + assert not IfcGit.is_valid_ref_format("my branch") + + def test_rejects_double_dot(self): + assert not IfcGit.is_valid_ref_format("my..branch") + + def test_rejects_tilde(self): + assert not IfcGit.is_valid_ref_format("my~branch") + + def test_rejects_caret(self): + assert not IfcGit.is_valid_ref_format("my^branch") + + def test_rejects_trailing_dot(self): + assert not IfcGit.is_valid_ref_format("branch.") + + def test_rejects_trailing_slash(self): + assert not IfcGit.is_valid_ref_format("branch/") + + def test_rejects_dot_lock_suffix(self): + assert not IfcGit.is_valid_ref_format("branch.lock") + + def test_rejects_empty_string(self): + assert not IfcGit.is_valid_ref_format("") + + def test_rejects_at_brace(self): + assert not IfcGit.is_valid_ref_format("branch@{upstream}") + + +class TestGetPathDir(NewFile): + def test_returns_parent_directory(self): + assert IfcGit.get_path_dir("/some/path/model.ifc") == "/some/path" + + def test_handles_nested_path(self): + assert IfcGit.get_path_dir("/a/b/c/d.ifc") == "/a/b/c" + + def test_returns_absolute_path(self): + result = IfcGit.get_path_dir("/a/b/model.ifc") + assert os.path.isabs(result) + + +# --------------------------------------------------------------------------- +# File I/O +# --------------------------------------------------------------------------- + + +class TestDos2Unix(NewFile): + def test_converts_crlf_to_lf(self): + with tempfile.NamedTemporaryFile(suffix=".ifc", delete=False, mode="wb") as f: + f.write(b"line1\r\nline2\r\nline3\r\n") + path = f.name + try: + IfcGit.dos2unix(path) + with open(path, "rb") as f: + assert f.read() == b"line1\nline2\nline3\n" + finally: + os.unlink(path) + + def test_lf_only_file_is_unchanged(self): + with tempfile.NamedTemporaryFile(suffix=".ifc", delete=False, mode="wb") as f: + f.write(b"line1\nline2\nline3\n") + path = f.name + try: + IfcGit.dos2unix(path) + with open(path, "rb") as f: + assert f.read() == b"line1\nline2\nline3\n" + finally: + os.unlink(path) + + def test_empty_file_unchanged(self): + with tempfile.NamedTemporaryFile(suffix=".ifc", delete=False, mode="wb") as f: + f.write(b"") + path = f.name + try: + IfcGit.dos2unix(path) + with open(path, "rb") as f: + assert f.read() == b"" + finally: + os.unlink(path) + + +# --------------------------------------------------------------------------- +# Git repo initialisation +# --------------------------------------------------------------------------- + + +class TestInitRepo(NewFile): + @requires_git + def test_creates_git_repo(self): + with tempfile.TemporaryDirectory() as tmpdir: + IfcGitRepo.repo = None + IfcGit.init_repo(tmpdir) + assert IfcGitRepo.repo is not None + assert os.path.isdir(IfcGitRepo.repo.git_dir) + IfcGitRepo.repo = None + + @requires_git + def test_creates_info_attributes_file(self): + with tempfile.TemporaryDirectory() as tmpdir: + IfcGitRepo.repo = None + IfcGit.init_repo(tmpdir) + attrs_path = os.path.join(IfcGitRepo.repo.git_dir, "info", "attributes") + assert os.path.isfile(attrs_path) + with open(attrs_path) as f: + assert "*.ifc text" in f.read() + IfcGitRepo.repo = None + + +class TestRepoFromPath(NewFile): + @requires_git + def test_finds_repo_from_file_in_root(self): + with tempfile.TemporaryDirectory() as tmpdir: + IfcGitRepo.repo = None + repo = _make_repo(tmpdir) + ifc_path = os.path.join(tmpdir, "model.ifc") + open(ifc_path, "w").close() + result = IfcGit.repo_from_path(ifc_path) + assert result is not None + assert os.path.abspath(result.working_dir) == os.path.abspath(tmpdir) + IfcGitRepo.repo = None + + @requires_git + def test_finds_repo_from_subdirectory(self): + with tempfile.TemporaryDirectory() as tmpdir: + IfcGitRepo.repo = None + _make_repo(tmpdir) + subdir = os.path.join(tmpdir, "sub", "dir") + os.makedirs(subdir) + result = IfcGit.repo_from_path(subdir) + assert result is not None + IfcGitRepo.repo = None + + @requires_git + def test_returns_none_for_path_outside_any_repo(self): + with tempfile.TemporaryDirectory() as tmpdir: + IfcGitRepo.repo = None + # a plain directory with no .git anywhere above it (using /tmp directly + # is safe since /tmp is not a git repo on this system) + result = IfcGit.repo_from_path("/nonexistent/path/that/does/not/exist") + assert result is None + IfcGitRepo.repo = None + + +# --------------------------------------------------------------------------- +# Git repo configuration +# --------------------------------------------------------------------------- + + +class TestConfigInfoAttributes(NewFile): + @requires_git + def test_creates_attributes_file(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + IfcGit.config_info_attributes(repo) + attrs_path = os.path.join(repo.git_dir, "info", "attributes") + assert os.path.isfile(attrs_path) + with open(attrs_path) as f: + assert "*.ifc text" in f.read() + + @requires_git + def test_does_not_overwrite_existing_attributes(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + attrs_path = os.path.join(repo.git_dir, "info", "attributes") + os.makedirs(os.path.dirname(attrs_path), exist_ok=True) + with open(attrs_path, "w") as f: + f.write("*.png binary\n") + IfcGit.config_info_attributes(repo) + with open(attrs_path) as f: + content = f.read() + assert "*.png binary" in content # original content preserved + + +class TestConfigPush(NewFile): + @requires_git + def test_sets_push_defaults(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + IfcGit.config_push(repo) + reader = repo.config_reader() + assert reader.get_value("push", "default") == "current" + assert reader.get_value("push", "autoSetupRemote") is True + + @requires_git + def test_does_not_overwrite_existing_push_section(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + with repo.config_writer() as w: + w.set_value("push", "default", "simple") + IfcGit.config_push(repo) + reader = repo.config_reader() + assert reader.get_value("push", "default") == "simple" + + +# --------------------------------------------------------------------------- +# Branch / tag lookups +# --------------------------------------------------------------------------- + + +class TestBranchesByHexsha(NewFile): + @requires_git + def test_maps_head_commit_to_active_branch(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + result = IfcGit.branches_by_hexsha(repo) + head_sha = repo.head.commit.hexsha + assert head_sha in result + names = [b.name for b in result[head_sha]] + assert repo.active_branch.name in names + + @requires_git + def test_returns_empty_dict_when_no_commits(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + result = IfcGit.branches_by_hexsha(repo) + assert result == {} + + @requires_git + def test_includes_both_branches_when_pointing_to_same_commit(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + repo.create_head("feature") + result = IfcGit.branches_by_hexsha(repo) + head_sha = repo.head.commit.hexsha + names = [b.name for b in result[head_sha]] + assert len(names) == 2 + + +class TestTagsByHexsha(NewFile): + @requires_git + def test_returns_empty_dict_when_no_tags(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + assert IfcGit.tags_by_hexsha(repo) == {} + + @requires_git + def test_maps_commit_to_annotated_tag(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + repo.create_tag("v1.0", message="Release 1.0") + result = IfcGit.tags_by_hexsha(repo) + head_sha = repo.head.commit.hexsha + assert head_sha in result + assert result[head_sha][0].name == "v1.0" + + @requires_git + def test_maps_commit_to_lightweight_tag(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + repo.create_tag("v0.1") + result = IfcGit.tags_by_hexsha(repo) + head_sha = repo.head.commit.hexsha + assert head_sha in result + + +class TestDeleteTag(NewFile): + @requires_git + def test_removes_existing_tag(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + repo.create_tag("v1.0") + IfcGit.delete_tag(repo, "v1.0") + assert "v1.0" not in [t.name for t in repo.tags] + + @requires_git + def test_does_not_raise_for_nonexistent_tag(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + IfcGit.delete_tag(repo, "does-not-exist") # must not raise + + +# --------------------------------------------------------------------------- +# IFC diff parsing +# --------------------------------------------------------------------------- + + +class TestIfcDiffIds(NewFile): + @requires_git + def test_detects_modified_entity(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + sha_a = _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + ifc_path = os.path.join(tmpdir, "model.ifc") + sha_b = _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('xyz',$,$,$,$,$,$,$,$);\n", "update") + result = IfcGit.ifc_diff_ids(repo, sha_a, sha_b, ifc_path) + assert 1 in result["modified"] + assert result["added"] == set() + assert result["removed"] == set() + + @requires_git + def test_detects_added_entity(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + sha_a = _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + ifc_path = os.path.join(tmpdir, "model.ifc") + sha_b = _commit_ifc( + repo, + tmpdir, + "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n#2=IFCSITE('new',$,$,$,$,$,$,$,$,$,$,$,$);\n", + "add site", + ) + result = IfcGit.ifc_diff_ids(repo, sha_a, sha_b, ifc_path) + assert 2 in result["added"] + assert 1 not in result["modified"] + assert result["removed"] == set() + + @requires_git + def test_detects_removed_entity(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + sha_a = _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + ifc_path = os.path.join(tmpdir, "model.ifc") + sha_b = _commit_ifc(repo, tmpdir, "", "clear") + result = IfcGit.ifc_diff_ids(repo, sha_a, sha_b, ifc_path) + assert 1 in result["removed"] + assert result["added"] == set() + assert result["modified"] == set() + + @requires_git + def test_no_changes_between_identical_commits(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + sha = _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + ifc_path = os.path.join(tmpdir, "model.ifc") + result = IfcGit.ifc_diff_ids(repo, sha, sha, ifc_path) + assert result["modified"] == set() + assert result["added"] == set() + assert result["removed"] == set() + + @requires_git + def test_diff_against_working_tree_with_none_hash_a(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + _commit_ifc(repo, tmpdir, "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n", "init") + ifc_path = os.path.join(tmpdir, "model.ifc") + # Make an uncommitted change in working tree + with open(ifc_path, "w") as f: + f.write("#1=IFCPROJECT('xyz',$,$,$,$,$,$,$,$);\n") + result = IfcGit.ifc_diff_ids(repo, None, "HEAD", ifc_path) + assert 1 in result["modified"] + + @requires_git + def test_handles_multiple_changed_entities(self): + with tempfile.TemporaryDirectory() as tmpdir: + repo = _make_repo(tmpdir) + sha_a = _commit_ifc( + repo, + tmpdir, + "#1=IFCPROJECT('abc',$,$,$,$,$,$,$,$);\n#2=IFCSITE('s',$,$,$,$,$,$,$,$,$,$,$,$);\n", + "init", + ) + ifc_path = os.path.join(tmpdir, "model.ifc") + sha_b = _commit_ifc( + repo, + tmpdir, + "#1=IFCPROJECT('xyz',$,$,$,$,$,$,$,$);\n#2=IFCSITE('t',$,$,$,$,$,$,$,$,$,$,$,$);\n", + "update both", + ) + result = IfcGit.ifc_diff_ids(repo, sha_a, sha_b, ifc_path) + assert 1 in result["modified"] + assert 2 in result["modified"] diff --git a/src/bonsai/test/tool/test_misc.py b/src/bonsai/test/tool/test_misc.py index c3d3cec2fe..21c1fd341c 100644 --- a/src/bonsai/test/tool/test_misc.py +++ b/src/bonsai/test/tool/test_misc.py @@ -171,6 +171,12 @@ class TestScaleObjectToHeight(test.bim.bootstrap.NewFile): assert list(obj.scale) == [1.0, 1.0, 1.0] +class TestQuickFavoritesOffsetUserMenus(test.bim.bootstrap.NewFile): + def test_current_blender_version_is_supported(self): + version = bpy.app.version[:2] + assert version in subject.QuickFavorites.OFFSET_USER_MENUS + + class TestSplitObjectsWithCutter(test.bim.bootstrap.NewFile): def test_run(self): bpy.ops.mesh.primitive_cube_add() diff --git a/src/bonsai/test/tool/test_model.py b/src/bonsai/test/tool/test_model.py index 756268cbee..30782b8a15 100644 --- a/src/bonsai/test/tool/test_model.py +++ b/src/bonsai/test/tool/test_model.py @@ -176,6 +176,7 @@ class TestStairCalculatedParams(NewFile): pset_data = pset_data_base.copy() calculated_data = calculated_data_base.copy() pset_data["custom_first_last_tread_run"] = (0.1, 0.4) + pset_data["custom_tread_lock"] = False calculated_data["Length"] += -0.2 + 0.1 self.compare_data(pset_data, calculated_data) @@ -183,6 +184,7 @@ class TestStairCalculatedParams(NewFile): pset_data = pset_data_base.copy() calculated_data = calculated_data_base.copy() pset_data["custom_first_last_tread_run"] = (0.0, None) + pset_data["custom_tread_lock"] = False calculated_data["Length"] = 0.9 # Only 3 treads at 0.3 each self.compare_data(pset_data, calculated_data) @@ -190,6 +192,7 @@ class TestStairCalculatedParams(NewFile): pset_data = pset_data_base.copy() calculated_data = calculated_data_base.copy() pset_data["custom_first_last_tread_run"] = (None, 0.0) + pset_data["custom_tread_lock"] = False calculated_data["Length"] = 0.9 # Only 3 treads at 0.3 each self.compare_data(pset_data, calculated_data) @@ -197,6 +200,7 @@ class TestStairCalculatedParams(NewFile): pset_data = pset_data_base.copy() calculated_data = calculated_data_base.copy() pset_data["custom_first_last_tread_run"] = (0.0, 0.0) + pset_data["custom_tread_lock"] = False calculated_data["Length"] = 0.6 # Only 2 middle treads at 0.3 each self.compare_data(pset_data, calculated_data) diff --git a/src/bonsai/test/tool/test_nest.py b/src/bonsai/test/tool/test_nest.py index 14acfa18bc..368ea9a923 100644 --- a/src/bonsai/test/tool/test_nest.py +++ b/src/bonsai/test/tool/test_nest.py @@ -19,6 +19,7 @@ import bpy import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.nest import ifcopenshell.api.spatial import bonsai.core.tool @@ -51,6 +52,42 @@ class TestCanNest(NewFile): subelement_obj = bpy.data.objects.new("Object", None) assert subject.can_nest(element_obj, subelement_obj) is False + def test_element_cannot_nest_to_itself(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcWall() + element_obj = bpy.data.objects.new("Object", None) + tool.Ifc.link(element, element_obj) + assert subject.can_nest(element_obj, element_obj) is False + + def test_cyclic_nesting_is_prevented(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall_a = ifc.createIfcWall() + wall_a_obj = bpy.data.objects.new("WallA", None) + tool.Ifc.link(wall_a, wall_a_obj) + wall_b = ifc.createIfcWall() + wall_b_obj = bpy.data.objects.new("WallB", None) + tool.Ifc.link(wall_b, wall_b_obj) + ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_b], relating_object=wall_a) + assert subject.can_nest(wall_b_obj, wall_a_obj) is False + + def test_deep_cyclic_nesting_is_prevented(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + wall_a = ifc.createIfcWall() + wall_a_obj = bpy.data.objects.new("WallA", None) + tool.Ifc.link(wall_a, wall_a_obj) + wall_b = ifc.createIfcWall() + wall_b_obj = bpy.data.objects.new("WallB", None) + tool.Ifc.link(wall_b, wall_b_obj) + wall_c = ifc.createIfcWall() + wall_c_obj = bpy.data.objects.new("WallC", None) + tool.Ifc.link(wall_c, wall_c_obj) + ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_b], relating_object=wall_a) + ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_c], relating_object=wall_b) + assert subject.can_nest(wall_c_obj, wall_a_obj) is False + class TestDisableEditing(NewFile): def test_run(self): diff --git a/src/bonsai/test/tool/test_project.py b/src/bonsai/test/tool/test_project.py index c6d1c410de..e9e11f4656 100644 --- a/src/bonsai/test/tool/test_project.py +++ b/src/bonsai/test/tool/test_project.py @@ -365,7 +365,7 @@ class TestLoadingIfcSqlite(NewFile): sql_type="SQLite", ) patcher.patch() - tmp_file = Path(tempfile.mktemp(suffix=".ifcsqlite")) + tmp_file = Path(tempfile.mkstemp(suffix=".ifcsqlite")[1]) ifcpatch.write(patcher.get_output(), tmp_file) elements_with_meshes = [ @@ -403,13 +403,6 @@ class TestLoadingIfcSqlite(NewFile): class TestGettingLinkedElementGeomSlice: - def __init__(self): - self.test_get_first_element() - self.test_get_middle_element() - self.test_skip_hidden_first_element() - self.test_skip_hidden_middle_element() - self.test_handle_hidden_non_first_element() - TEST_OBJ = { "guids": ["aaa", "bbb", "ccc"], "guid_ids": [5, 10, 15], diff --git a/src/bonsai/test/tool/test_system.py b/src/bonsai/test/tool/test_system.py index 3067ccfa09..5c18ad623f 100644 --- a/src/bonsai/test/tool/test_system.py +++ b/src/bonsai/test/tool/test_system.py @@ -23,6 +23,7 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.api.root import ifcopenshell.api.system +import ifcopenshell.util.system import ifcopenshell.util.unit import numpy as np from mathutils import Euler, Matrix, Vector diff --git a/src/bonsai/type-check-requirements.txt b/src/bonsai/type-check-requirements.txt new file mode 100644 index 0000000000..1a8419c7d3 --- /dev/null +++ b/src/bonsai/type-check-requirements.txt @@ -0,0 +1,41 @@ +aiohttp +beautifulsoup4 +boto3 +botocore +brickschema +cjio >=0.8, <0.10 +debugpy +ezdxf +fake-bpy-module-latest +git+https://github.com/prochitecture/bpypolyskel +git+https://github.com/Andrej730/IFC2JSON_python.git@pyproject_toml +gitpython +isodate +lark +lxml +lxml-stubs +markdown-it-py +natsort +numpy +odfpy +openpyxl +pandas +pillow +platformdirs +pygments +pyradiance +pystache +pytest +pytest_bdd +pytest_blender +python-dateutil +python-socketio +pytz +rdflib +requests +shapely +svgwrite +typing-extensions +typst +tzfpy +xsdata diff --git a/src/bsdd/bsdd_json.py b/src/bsdd/bsdd_json.py index becaac810b..6a89d19753 100644 --- a/src/bsdd/bsdd_json.py +++ b/src/bsdd/bsdd_json.py @@ -7,7 +7,7 @@ from typing import Literal, Optional from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator -from .type_hints import * +from type_hints import * def _lower_first(s: str) -> str: diff --git a/src/common.mk b/src/common.mk index 24537d06de..765655339b 100644 --- a/src/common.mk +++ b/src/common.mk @@ -28,6 +28,7 @@ dist: mkdir -p dist cp -r $(PACKAGE_NAME) build/ cp pyproject.toml build/ + if [ -f README.md ]; then cp README.md build/; fi ifeq ($(IS_STABLE), TRUE) $(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml ifdef IS_MODULE diff --git a/src/ifcchat/README.md b/src/ifcchat/README.md new file mode 100644 index 0000000000..f20aa8788d --- /dev/null +++ b/src/ifcchat/README.md @@ -0,0 +1,10 @@ +IfcOpenShell AI Assistant +========================= + +A web-based client-side (pyodide + OpenAI API) model interrogation and generation API based on: ifcedit, ifcquery and ifcmcp (ifcopenshell-mcp) packaged in a HTML+JS application. + +### Setup instructions + +``` +pip download ifcquery==0.8.5 ifcopenshell-mcp==0.8.5 ifcedit==0.8.5 lark==1.3.1 isodate==0.7.2 --no-deps -d ./dist +``` diff --git a/src/ifcchat/app.js b/src/ifcchat/app.js new file mode 100644 index 0000000000..516ec8f846 --- /dev/null +++ b/src/ifcchat/app.js @@ -0,0 +1,320 @@ +// app.js +const $ = (id) => document.getElementById(id); + +const statusEl = $("status"); +const msgsEl = $("msgs"); +const sendBtn = $("send"); +const inputEl = $("input"); +const apiKeyEl = $("apiKey"); +const modelEl = $("model"); +const ifcFileEl = $("ifcFile"); +const newBtn = $("newModel"); +const downloadBtn = $("downloadIfc"); + +function setBusy(isBusy, reason = "") { + const controls = [ + $("send"), + $("newModel"), + $("downloadIfc"), + $("ifcFile"), + ]; + + for (const el of controls) el.disabled = isBusy; + + $("input").disabled = isBusy; + + const browseBtn = $("browseBtn"); + if (browseBtn) { + browseBtn.classList.toggle("disabled", isBusy); + browseBtn.setAttribute("aria-disabled", isBusy ? "true" : "false"); + browseBtn.tabIndex = isBusy ? -1 : 0; + } + + setStatus(isBusy ? (reason || "Working…") : "Ready"); +} + +function addMessage(role, text) { + if (text.ok) { + text = text.data; + } + const wrap = document.createElement("div"); + wrap.className = `msg ${role}`; + wrap.innerHTML = ` +
${role}
+
`; + const bubble = wrap.querySelector(".bubble"); + bubble.textContent = text; + bubble.onclick = function () { + if (bubble.scrollHeight > 100 && role === "tool") { + bubble.style.maxHeight = bubble.style.maxHeight == 'none' ? '' : 'none'; + bubble.style.borderBottom = bubble.style.borderBottom == '' ? 'dotted 2px gray' : ''; + } + } + msgsEl.appendChild(wrap); + msgsEl.scrollTop = msgsEl.scrollHeight; +} + +function setStatus(text) { + statusEl.textContent = text; +} + +const worker = new Worker("./ifc_worker.js", { type: "module" }); + +function callWorker(type, payload = {}) { + return new Promise((resolve, reject) => { + const id = crypto.randomUUID(); + const onMsg = (ev) => { + const msg = ev.data; + if (!msg || msg.id !== id) return; + worker.removeEventListener("message", onMsg); + if (msg.ok) resolve(msg); + else reject(new Error(msg.error || "Worker error")); + }; + worker.addEventListener("message", onMsg); + worker.postMessage({ id, type, payload }); + }); +} + +// ---- OpenAI Responses API tool schemas (should match ifcmcp.core openai_tools()) ---- +// Docs show Responses API function_call items + function_call_output loop. :contentReference[oaicite:4]{index=4} +const tools = [ + { + type: "function", name: "ifc_new", description: "Create a new empty IFC model in memory.", + parameters: { type: "object", properties: { schema: { type: "string" } }, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_summary", description: "Get a concise overview of the loaded IFC model.", + parameters: { type: "object", properties: {}, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_tree", description: "Get the full spatial hierarchy tree.", + parameters: { type: "object", properties: {}, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_select", description: "Select elements using ifcopenshell selector syntax (e.g. 'IfcWall').", + parameters: { type: "object", properties: { query: { type: "string" } }, required: ["query"], additionalProperties: false } + }, + { + type: "function", name: "ifc_info", description: "Inspect an entity by STEP id.", + parameters: { type: "object", properties: { element_id: { type: "integer" } }, required: ["element_id"], additionalProperties: false } + }, + { + type: "function", name: "ifc_relations", description: "Get relationships for an element. traverse='up' walks to IfcProject.", + parameters: { + type: "object", properties: { element_id: { type: "integer" }, traverse: { type: "string" } }, + required: ["element_id"], additionalProperties: false + } + }, + { + type: "function", name: "ifc_clash", description: "Run clash/clearance checks for an element.", + parameters: { + type: "object", properties: { element_id: { type: "integer" }, clearance: { type: "number" }, tolerance: { type: "number" }, scope: { type: "string" } }, + required: ["element_id"], additionalProperties: false + } + }, + { + type: "function", name: "ifc_list", description: "List ifcopenshell.api modules or functions within a module.", + parameters: { type: "object", properties: { module: { type: "string" } }, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_docs", description: "Get documentation for an ifcopenshell.api function, 'module.function'.", + parameters: { type: "object", properties: { function_path: { type: "string" } }, required: ["function_path"], additionalProperties: false } + }, + { + type: "function", name: "ifc_edit", description: "Execute an ifcopenshell.api mutation; params is a JSON string of stringly-typed kwargs.", + parameters: { type: "object", properties: { function_path: { type: "string" }, params: { type: "string" } }, required: ["function_path"], additionalProperties: false } + }, + { + type: "function", name: "ifc_validate", description: "Validate the loaded model. Returns valid bool and list of issues.", + parameters: { type: "object", properties: { express_rules: { type: "boolean" } }, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_schedule", description: "List work schedules and nested tasks. Use max_depth=1 for top-level phases only on large projects.", + parameters: { type: "object", properties: { max_depth: { type: "integer" } }, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_cost", description: "List cost schedules and nested cost items. Use max_depth=1 for top-level sections only on large BoQs.", + parameters: { type: "object", properties: { max_depth: { type: "integer" } }, required: [], additionalProperties: false } + }, + { + type: "function", name: "ifc_schema", description: "Return IFC class documentation for an entity type.", + parameters: { type: "object", properties: { entity_type: { type: "string" } }, required: ["entity_type"], additionalProperties: false } + }, + { + type: "function", name: "ifc_quantify", description: "Run quantity take-off (QTO) on the model. Modifies model in-place; call ifc_save() after.", + parameters: { type: "object", properties: { rule: { type: "string" }, selector: { type: "string" } }, required: ["rule"], additionalProperties: false } + }, +]; + +const SYSTEM_INSTRUCTIONS = ` +You are an IFC copilot running in a browser. You can call tools to inspect or modify the currently loaded IFC model. +Rules: +- If the user asks about model contents (counts, lists, properties, hierarchy), use tools like ifc_summary/ifc_select/ifc_info/ifc_tree. +- If the user asks to change the model, prefer: (1) ifc_list to find candidate API modules, (2) ifc_docs for the exact function signature, then (3) ifc_edit. +- If there is no model and the user wants to create one, call ifc_new. +- After edits, explain what changed and suggest downloading the IFC. +Be concise. Avoid dumping huge trees unless asked. +`; + +let inputItems = []; // running conversation state (Responses API style) + +async function openAIResponsesCreate({ apiKey, model, input, tools }) { + const res = await fetch("https://api.openai.com/v1/responses", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model, + instructions: SYSTEM_INSTRUCTIONS, + tools, + input, + }), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`OpenAI error ${res.status}: ${text}`); + } + return await res.json(); +} + +function extractAssistantText(response) { + const out = []; + for (const item of response.output ?? []) { + if (item.type === "message" && item.role === "assistant") { + for (const c of item.content ?? []) { + if (c.type === "output_text") out.push(c.text); + } + } + } + return out.join("\n").trim(); +} + +async function runAgentTurn(userText) { + const apiKey = apiKeyEl.value.trim(); + if (!apiKey) throw new Error("Missing API key"); + + // Add user message + inputItems.push({ role: "user", content: userText }); + + // Tool-calling loop (Responses API): append response.output, execute function_call items, append function_call_output. + for (let i = 0; i < 64; i++) { + const response = await openAIResponsesCreate({ + apiKey, + model: modelEl.value, + input: inputItems, + tools, + }); + + // Keep ALL output items (incl reasoning/tool calls) in the running state. + inputItems.push(...(response.output ?? [])); + + // Show any assistant text immediately + const text = extractAssistantText(response); + if (text) addMessage("assistant", text); + + const calls = (response.output ?? []).filter((x) => x.type === "function_call"); + if (calls.length === 0) return; + + for (const call of calls) { + let args = {}; + try { args = call.arguments ? JSON.parse(call.arguments) : {}; } + catch { args = {}; } + + addMessage("tool", `→ ${call.name}(${JSON.stringify(args)})`); + + const toolRes = await callWorker("toolCall", { name: call.name, args }); + + // Feed tool result back to the model + inputItems.push({ + type: "function_call_output", + call_id: call.call_id, + output: JSON.stringify(toolRes.result), + }); + + addMessage("tool", `← ${call.name}: ${JSON.stringify(toolRes.result, null, 2)}`); + } + } + + addMessage("assistant", "I hit the tool-call loop limit. Try narrowing your request."); +} + +sendBtn.onclick = async () => { + const text = inputEl.value.trim(); + if (!text) return; + inputEl.value = ""; + addMessage("user", text); + try { + setBusy(true, "Thinking…"); + await runAgentTurn(text); + setBusy(false, "Ready"); + } catch (e) { + setBusy(true, "Error"); + addMessage("assistant", `Error: ${e.message}`); + } +}; + +inputEl.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + sendBtn.click(); + } +}); + +ifcFileEl.onchange = async () => { + const f = ifcFileEl.files?.[0]; + if (!f) return; + setBusy(true, "Loading IFC into Pyodide…"); + const buf = await f.arrayBuffer(); + try { + const r = await callWorker("loadIfc", { filename: f.name, bytes: buf }, [buf]); + addMessage("assistant", r.result); + setBusy(false, "Ready"); + } catch (e) { + setStatus(true, "Error"); + addMessage("assistant", `Load error: ${e.message}`); + } +}; + +newBtn.onclick = async () => { + try { + setBusy(true, "Creating new model…"); + const r = await callWorker("toolCall", { name: "ifc_new", args: { schema: "IFC4" } }); + addMessage("assistant", `New model: ${JSON.stringify(r.result)}`); + setBusy(false, "Ready"); + } catch (e) { + setBusy(true, "Error"); + addMessage("assistant", `Error: ${e.message}`); + } +}; + +downloadBtn.onclick = async () => { + try { + setBusy(true, "Exporting IFC…"); + const r = await callWorker("exportIfc", {}); + const blob = new Blob([r.bytes], { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = r.filename || "model.ifc"; + a.click(); + URL.revokeObjectURL(url); + setBusy(false, "Ready"); + } catch (e) { + setBusy(true, "Error"); + addMessage("assistant", `Export error: ${e.message}`); + } +}; + +(async () => { + try { + setBusy(true, "Initializing Pyodide and IfcOpenShell for in-memory IFC access…"); + await callWorker("init", {}); + setBusy(false, "Ready"); + } catch (e) { + setBusy(true, "Error"); + addMessage("assistant", `Worker init failed: ${e.message}`); + } +})(); \ No newline at end of file diff --git a/src/ifcchat/ifc_worker.js b/src/ifcchat/ifc_worker.js new file mode 100644 index 0000000000..517bc4b24c --- /dev/null +++ b/src/ifcchat/ifc_worker.js @@ -0,0 +1,110 @@ +// ifc_worker.js (MODULE WORKER) +import { loadPyodide } from "https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.mjs"; + +let pyodide = null; +let callToolPy = null; +let initPromise = null; + +function ok(id, extra = {}, transfer = []) { + self.postMessage({ id, ok: true, ...extra }, transfer); +} +function fail(id, error) { + self.postMessage({ id, ok: false, error: String(error?.message || error) }); +} + +async function ensurePyodide() { + if (initPromise) return initPromise; + + initPromise = (async () => { + // Passing indexURL avoids some environments failing to infer it from the module URL. :contentReference[oaicite:3]{index=3} + pyodide = await loadPyodide({ + indexURL: "https://cdn.jsdelivr.net/pyodide/v0.29.3/full/", + }); + + await pyodide.loadPackage("micropip"); + await pyodide.loadPackage("numpy"); + await pyodide.loadPackage("shapely"); + await pyodide.loadPackage("typing-extensions"); + + const micropip = pyodide.pyimport("micropip"); + micropip.install("python-dateutil") + + // Detect python minor version (3.12 vs 3.13) and pick a matching wheel. + const pyVer = pyodide.runPython(` +import sys +f"{sys.version_info.major}.{sys.version_info.minor}" + `); + + const wheelUrl = + pyVer === "3.13" + ? "https://ifcopenshell.github.io/wasm-wheels/ifcopenshell-0.8.3+34a1bc6-cp313-cp313-emscripten_4_0_9_wasm32.whl" + : "https://ifcopenshell.github.io/wasm-wheels/ifcopenshell-0.8.2+d50e806-cp312-cp312-emscripten_3_1_58_wasm32.whl"; + + await micropip.install(wheelUrl); + + await micropip.install([ + "./dist/ifcquery-0.8.5-py3-none-any.whl", + "./dist/ifcedit-0.8.5-py3-none-any.whl", + "./dist/ifcopenshell_mcp-0.8.5-py3-none-any.whl", + "./dist/lark-1.3.1-py3-none-any.whl", + "./dist/isodate-0.7.2-py3-none-any.whl", + ]) + await pyodide.runPythonAsync(` +from ifcmcp.embedded import call_tool as _call_tool + `); + callToolPy = pyodide.globals.get("_call_tool"); + })(); + + return initPromise; +} + +function callTool(name, args) { + const pyArgs = pyodide.toPy(args); + const res = callToolPy(name, pyArgs); + pyArgs.destroy(); + const resJs = res.toJs({ dict_converter: Object.fromEntries }); + res.destroy(); + return resJs; +} + +self.onmessage = async (ev) => { + const { id, type, payload } = ev.data || {}; + try { + if (type === "init") { + await ensurePyodide(); + ok(id, { result: "ok" }); + return; + } + + await ensurePyodide(); + + if (type === "loadIfc") { + const { filename, bytes } = payload; + const path = `/tmp/${filename || "model.ifc"}`; + pyodide.FS.mkdirTree("/tmp"); + pyodide.FS.writeFile(path, new Uint8Array(bytes)); + const result = callTool("ifc_load", { path }); + ok(id, { result }); + return; + } + + if (type === "exportIfc") { + const path = "/tmp/export.ifc"; + const result = callTool("ifc_save", { path }); + const data = pyodide.FS.readFile(path); + ok(id, { result, filename: "export.ifc", bytes: data }, [data.buffer]); + return; + } + + if (type === "toolCall") { + const { name, args } = payload; + const result = callTool(name, args || {}); + ok(id, { result }); + return; + } + + throw new Error(`Unknown message type: ${type}`); + } catch (e) { + fail(id, e); + } +}; \ No newline at end of file diff --git a/src/ifcchat/index.html b/src/ifcchat/index.html new file mode 100644 index 0000000000..ac878ae615 --- /dev/null +++ b/src/ifcchat/index.html @@ -0,0 +1,322 @@ + + + + + + + IfcOpenShell AI Assistant + + + + + +
+
+
+ + +
+ +
+ +
+ + +
+ + + +
+ + +
+ +
+ +
+ +
+ +
+ +
+ • Upload an IFC, then ask “Summarize the model” or “List all IfcWalls”.
+ • Try “Add a new site and building named X” (will use ifc_edit). +
+
+ +
+ +
+ Chat with your IFC model. This app uses ifcopenshell-mcp to interact with an IFC model. IFC is in its raw-serialized form provides little textual context on the meaning of attributes, the intent behind element instances and the resulting geometrical form. By using the IfcOpenShell high-level API through an MCP-like interface (Model Context Protocol; allows for structured interaction with APIs and tools) these limitations are addressed and the model can be interrogated on a higher semantic abstraction level. You can also use ifcopenshell-mcp on your local machine with any LLM provider that supports the MCP protocol. +
+
+
+ +
+
+
+ + + + IfcOpenShell AI Assistant + +
+
+ + Status: Booting… +
+
+ +
+
+
+ + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/ifcclash/pyproject.toml b/src/ifcclash/pyproject.toml index dc27c49efc..3b510d4ea5 100644 --- a/src/ifcclash/pyproject.toml +++ b/src/ifcclash/pyproject.toml @@ -19,6 +19,11 @@ dependencies = [ "ifcopenshell", ] +[project.optional-dependencies] +advanced = [ + "scikit-learn", +] + [project.urls] Homepage = "http://ifcopenshell.org" Documentation = "https://docs.ifcopenshell.org" diff --git a/src/ifcedit/Makefile b/src/ifcedit/Makefile new file mode 100644 index 0000000000..114e5f9ddc --- /dev/null +++ b/src/ifcedit/Makefile @@ -0,0 +1,10 @@ +PACKAGE_NAME:=ifcedit +include ../common.mk + +.PHONY: test +test: + pytest tests + +.PHONY: qa +qa: + black . diff --git a/src/ifcedit/README.md b/src/ifcedit/README.md new file mode 100644 index 0000000000..19b1ec8e7a --- /dev/null +++ b/src/ifcedit/README.md @@ -0,0 +1,305 @@ + +# ifcedit + +A CLI wrapper that exposes all 350+ `ifcopenshell.api` mutation functions as +shell commands. Functions are auto-discovered at runtime via introspection -- +no hardcoded list to maintain. + +## Installation + +```bash +pip install ifcedit +``` + +Requires `ifcopenshell`. + +## Usage + +``` +ifcedit [options] [--format json|text] +``` + +Three subcommands: `list` to discover functions, `docs` to read their +documentation, and `run` to execute them. + +## Subcommands + +### list + +Discover available API modules and their functions. + +**List all modules:** + +```bash +ifcedit list +``` + +```json +[ + { + "module": "root", + "description": "Functions for creating project-level entities", + "functions": ["create_entity", "remove_product", "copy_class"], + "count": 3 + }, + { + "module": "spatial", + "description": "Functions for managing spatial relationships", + "functions": ["assign_container", "unassign_container"], + "count": 2 + } +] +``` + +**List functions in a module:** + +```bash +ifcedit list root +``` + +```json +[ + { + "name": "create_entity", + "description": "Create an IFC entity with optional initial attributes", + "params": [ + {"name": "ifc_class", "type": "str", "required": true}, + {"name": "name", "type": "Optional[str]"} + ] + } +] +``` + +### docs + +Show full documentation for a specific function, including parameter +descriptions from docstrings and return type. + +```bash +ifcedit docs root.create_entity +``` + +```json +{ + "module": "root", + "function": "create_entity", + "description": "Create an IFC entity with optional initial attributes", + "long_description": "This function creates a new entity instance...", + "params": [ + { + "name": "ifc_class", + "type": "str", + "required": true, + "description": "The IFC class name (e.g. 'IfcWall', 'IfcProject')" + }, + { + "name": "name", + "type": "Optional[str]", + "description": "Optional name attribute" + } + ], + "return_type": "ifcopenshell.entity_instance", + "return_description": "The newly created entity instance" +} +``` + +### run + +Execute an API function against an IFC file. Parameters are passed as +`--key value` pairs after the function name. + +```bash +ifcedit run model.ifc root.create_entity --ifc_class IfcWall --name "My Wall" +``` + +```json +{ + "ok": true, + "result": {"id": 42, "type": "IfcWall", "name": "My Wall"} +} +``` + +**Options:** + +- `-o, --output ` -- write to a different file instead of overwriting the input +- `--dry-run` -- validate parameters without executing or saving + +```bash +# Save to a new file +ifcedit run model.ifc root.create_entity -o out.ifc --ifc_class IfcWall + +# Validate without executing +ifcedit run model.ifc root.create_entity --dry-run --ifc_class IfcWall +``` + +Dry-run output shows the resolved parameters: + +```json +{ + "ok": true, + "dry_run": true, + "module": "root", + "function": "create_entity", + "args": {"ifc_class": "IfcWall", "name": "My Wall"} +} +``` + +## Parameter type coercion + +CLI strings are automatically converted to the types expected by each API +function, using the function's type annotations: + +| Type | CLI input | Python value | +|------|-----------|--------------| +| `str` | `"hello"` | `"hello"` | +| `int` | `"42"` or `"#42"` | `42` | +| `float` | `"3.14"` | `3.14` | +| `bool` | `"true"`, `"1"`, `"yes"` | `True` | +| `Optional[X]` | `"none"` | `None` | +| `entity_instance` | `"42"` or `"#42"` | resolved from model by step ID | +| `list[entity_instance]` | `"5,6,7"` or `"[5, 6, 7]"` | list of resolved entities | +| `dict` | `'{"key": "val"}'` | parsed JSON object | +| `Literal["A", "B"]` | `"A"` | validated against allowed values | + +## Examples + +```bash +# Create a project +ifcedit run model.ifc root.create_entity --ifc_class IfcProject --name "My Project" + +# Assign an element to a storey +ifcedit run model.ifc spatial.assign_container --products 10 --relating_structure 4 + +# Assign multiple elements at once +ifcedit run model.ifc aggregate.assign_object --products "5,6,7" --relating_object 1 + +# Add a property set +ifcedit run model.ifc pset.add_pset --product 10 --name "Pset_WallCommon" + +# Edit properties +ifcedit run model.ifc pset.edit_pset --pset 15 \ + --properties '{"IsExternal": true, "FireRating": "2HR"}' +``` + +### foreach + +Apply an API function to each element in a JSON array read from stdin. +`{field}` placeholders in argument values are substituted with fields from +each JSON object. The model is opened once and saved once regardless of how +many elements are processed. + +```bash +ifcquery model.ifc select 'IfcWindow' | ifcedit foreach model.ifc root.remove_product --product {id} +``` + +```json +{"ok": true, "count": 36, "errors": []} +``` + +Placeholder tokens match the fields emitted by `ifcquery` — typically `{id}`, +`{type}`, and `{name}`: + +```bash +ifcquery model.ifc select 'IfcDoor' | ifcedit foreach model.ifc attribute.edit_attributes \ + --product {id} --attributes '{"Name": "Door"}' +``` + +**Options:** + +- `-o, --output ` -- write to a different file instead of overwriting the input + +**Output:** + +- `count` -- number of elements successfully processed +- `errors` -- list of per-element failures, each with `index`, `item`, and `error`; processing continues past errors + +```json +{ + "ok": false, + "count": 34, + "errors": [ + {"index": 2, "item": {"id": 55, "type": "IfcWindow", "name": "W03"}, "error": "Entity #55 not found in model"} + ] +} +``` + +Exit code is 1 if any element failed. + +### quantify + +Run quantity take-off (QTO) on an IFC file, computing physical measurements +(volume, area, length, count, weight) and writing them back as +`IfcElementQuantity` property sets. Uses `ifc5d` rules. + +**List available rules:** + +```bash +ifcedit quantify list +``` + +```json +[ + {"name": "IFC4QtoBaseQuantities"}, + {"name": "IFC4X3QtoBaseQuantities"} +] +``` + +**Run QTO on a file:** + +```bash +ifcedit quantify run model.ifc IFC4QtoBaseQuantities +ifcedit quantify run model.ifc IFC4QtoBaseQuantities --selector IfcWall +ifcedit quantify run model.ifc IFC4QtoBaseQuantities -o model_qto.ifc +``` + +```json +{"ok": true, "rule": "IFC4QtoBaseQuantities", "elements_quantified": 42} +``` + +Options: + +- `--selector ` -- ifcopenshell selector to restrict elements (default: all `IfcElement`) +- `-o, --output ` -- write to a different file instead of overwriting the input + +Note: `quantify run` writes geometry-based measurements and requires the +IfcOpenShell C++ geometry bindings for elements with computed quantities. + +## Error handling + +Errors are reported in the JSON response: + +```json +{ + "ok": false, + "error": "Entity #999 not found in model" +} +``` + +Exit code is 0 on success, 1 on error. + +## Relationship to ifcquery + +`ifcedit` and `ifcquery` are complementary tools: + +- **ifcquery** reads and inspects IFC models (summary, tree, info, select, relations, clash, validate, schedule, cost, schema, contexts, materials, plot, render) +- **ifcedit** modifies IFC models by wrapping `ifcopenshell.api` functions, and runs QTO via `quantify` + +A typical workflow: inspect with `ifcquery`, look up the right API function +with `ifcedit docs`, then apply changes with `ifcedit run`. + +The two tools also compose directly in shell scripts. Use `ifcquery --format ids` +to feed a list of IDs into a `run` parameter, or pipe `ifcquery select` JSON +into `ifcedit foreach` to apply an operation to every matching element: + +```bash +# Aggregate — pass all IDs as a list parameter +ifcedit run model.ifc spatial.unassign_container \ + --products "$(ifcquery model.ifc --format ids select 'IfcWall')" + +# Fan-out — one operation per element, model opened and saved once +ifcquery model.ifc select 'IfcWindow' | ifcedit foreach model.ifc root.remove_product --product {id} +``` + +## License + +LGPLv3+ -- see the IfcOpenShell project license. diff --git a/src/ifcedit/ifcedit/__init__.py b/src/ifcedit/ifcedit/__init__.py new file mode 100644 index 0000000000..eac2eac799 --- /dev/null +++ b/src/ifcedit/ifcedit/__init__.py @@ -0,0 +1,20 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +__version__ = version = "0.0.0" diff --git a/src/ifcedit/ifcedit/__main__.py b/src/ifcedit/ifcedit/__main__.py new file mode 100644 index 0000000000..28292f378e --- /dev/null +++ b/src/ifcedit/ifcedit/__main__.py @@ -0,0 +1,265 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +from __future__ import annotations + +import argparse +import json +import sys + +import ifcopenshell + +from ifcedit.discover import function_docs, list_functions, list_modules +from ifcedit.foreach import run_foreach +from ifcedit.quantify import list_rules, run_quantify +from ifcedit.run import run_api + + +def format_output(data, fmt: str) -> str: + if fmt == "json": + return json.dumps(data, indent=2, ensure_ascii=False) + elif fmt == "text": + return _format_text(data) + return json.dumps(data, indent=2, ensure_ascii=False) + + +def _format_text(data, indent: int = 0) -> str: + prefix = " " * indent + lines = [] + if isinstance(data, dict): + for key, value in data.items(): + if isinstance(value, (dict, list)): + lines.append(f"{prefix}{key}:") + lines.append(_format_text(value, indent + 1)) + else: + lines.append(f"{prefix}{key}: {value}") + elif isinstance(data, list): + for item in data: + if isinstance(item, dict): + lines.append(_format_text(item, indent)) + lines.append("") + else: + lines.append(f"{prefix}- {item}") + else: + lines.append(f"{prefix}{data}") + return "\n".join(lines) + + +def cmd_list(args): + if args.module: + try: + functions = list_functions(args.module) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + print(format_output(functions, args.output_format)) + else: + modules = list_modules() + print(format_output(modules, args.output_format)) + + +def cmd_docs(args): + parts = args.function_path.split(".") + if len(parts) != 2: + print("Error: function path must be 'module.function' (e.g. root.create_entity)", file=sys.stderr) + sys.exit(1) + module, function = parts + try: + docs = function_docs(module, function) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + print(format_output(docs, args.output_format)) + + +def cmd_run(args, extra_args): + try: + model = ifcopenshell.open(args.ifc_file) + except Exception as e: + print(f"Error: Could not open IFC file: {e}", file=sys.stderr) + sys.exit(1) + + parts = args.function_path.split(".") + if len(parts) != 2: + print("Error: function path must be 'module.function' (e.g. root.create_entity)", file=sys.stderr) + sys.exit(1) + module, function = parts + + # Parse extra --key value arguments into a dict + raw_kwargs = _parse_extra_args(extra_args) + + if args.dry_run: + result = {"ok": True, "dry_run": True, "module": module, "function": function, "args": raw_kwargs} + else: + result = run_api(model, module, function, raw_kwargs) + + if result["ok"]: + output_path = args.output or args.ifc_file + model.write(output_path) + + print(format_output(result, args.output_format)) + if not result["ok"]: + sys.exit(1) + + +def _parse_extra_args(extra: list[str]) -> dict[str, str]: + """Parse a list of ['--key', 'value', ...] into a dict.""" + kwargs = {} + i = 0 + while i < len(extra): + arg = extra[i] + if arg.startswith("--"): + key = arg[2:] + if i + 1 < len(extra) and not extra[i + 1].startswith("--"): + kwargs[key] = extra[i + 1] + i += 2 + else: + # Flag without value — treat as "true" + kwargs[key] = "true" + i += 1 + else: + print(f"Error: Unexpected argument: {arg}", file=sys.stderr) + sys.exit(1) + return kwargs + + +def cmd_foreach(args, extra_args): + try: + model = ifcopenshell.open(args.ifc_file) + except Exception as e: + print(f"Error: Could not open IFC file: {e}", file=sys.stderr) + sys.exit(1) + + parts = args.function_path.split(".") + if len(parts) != 2: + print("Error: function path must be 'module.function' (e.g. root.create_entity)", file=sys.stderr) + sys.exit(1) + module, function = parts + + raw_kwargs_template = _parse_extra_args(extra_args) + + try: + stdin_data = json.load(sys.stdin) + except json.JSONDecodeError as e: + print(f"Error: Could not parse JSON from stdin: {e}", file=sys.stderr) + sys.exit(1) + + if not isinstance(stdin_data, list): + print("Error: stdin must be a JSON array", file=sys.stderr) + sys.exit(1) + + result = run_foreach(model, module, function, raw_kwargs_template, stdin_data) + + if result["ok"]: + output_path = args.output or args.ifc_file + model.write(output_path) + + print(format_output(result, args.output_format)) + if not result["ok"]: + sys.exit(1) + + +def cmd_quantify(args, extra_args): + if args.quantify_command == "list": + result = list_rules() + print(format_output(result, args.output_format)) + elif args.quantify_command == "run": + try: + model = ifcopenshell.open(args.ifc_file) + except Exception as e: + print(f"Error: Could not open IFC file: {e}", file=sys.stderr) + sys.exit(1) + selector = args.selector or None + result = run_quantify(model, args.rule_name, selector=selector) + if result["ok"]: + output_path = args.output or args.ifc_file + model.write(output_path) + print(format_output(result, args.output_format)) + if not result["ok"]: + sys.exit(1) + else: + print("Error: quantify requires a subcommand: list or run", file=sys.stderr) + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser( + prog="ifcedit", + description="CLI wrapper for ifcopenshell.api IFC model mutation functions", + ) + parser.add_argument( + "--format", + choices=["json", "text"], + default="json", + dest="output_format", + help="Output format (default: json)", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + # list + list_parser = subparsers.add_parser("list", help="List API modules or functions in a module") + list_parser.add_argument("module", nargs="?", help="Module name (omit to list all modules)") + + # docs + docs_parser = subparsers.add_parser("docs", help="Show full documentation for an API function") + docs_parser.add_argument("function_path", help="module.function (e.g. root.create_entity)") + + # run + run_parser = subparsers.add_parser("run", help="Execute an API function on an IFC file") + run_parser.add_argument("ifc_file", help="Path to the IFC file") + run_parser.add_argument("function_path", help="module.function (e.g. root.create_entity)") + run_parser.add_argument("-o", "--output", help="Output file path (default: overwrite input)") + run_parser.add_argument("--dry-run", action="store_true", help="Validate without executing or saving") + + # foreach + foreach_parser = subparsers.add_parser( + "foreach", + help="Apply an API function to each element in a JSON array read from stdin", + ) + foreach_parser.add_argument("ifc_file", help="Path to the IFC file") + foreach_parser.add_argument("function_path", help="module.function (e.g. attribute.edit_attributes)") + foreach_parser.add_argument("-o", "--output", help="Output file path (default: overwrite input)") + + # quantify + quantify_parser = subparsers.add_parser("quantify", help="Quantity take-off (QTO) using ifc5d rules") + quantify_sub = quantify_parser.add_subparsers(dest="quantify_command") + quantify_sub.add_parser("list", help="List available QTO rule names") + qrun_parser = quantify_sub.add_parser("run", help="Run QTO on an IFC file") + qrun_parser.add_argument("ifc_file", help="Path to the IFC file") + qrun_parser.add_argument("rule_name", help="QTO rule name (e.g. IFC4QtoBaseQuantities)") + qrun_parser.add_argument("--selector", help="ifcopenshell selector to restrict elements (default: all IfcElement)") + qrun_parser.add_argument("-o", "--output", help="Output file path (default: overwrite input)") + + args, extra = parser.parse_known_args() + + if args.command == "list": + cmd_list(args) + elif args.command == "docs": + cmd_docs(args) + elif args.command == "run": + cmd_run(args, extra) + elif args.command == "foreach": + cmd_foreach(args, extra) + elif args.command == "quantify": + cmd_quantify(args, extra) + + +if __name__ == "__main__": + main() diff --git a/src/ifcedit/ifcedit/coerce.py b/src/ifcedit/ifcedit/coerce.py new file mode 100644 index 0000000000..5a410ec186 --- /dev/null +++ b/src/ifcedit/ifcedit/coerce.py @@ -0,0 +1,180 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +from __future__ import annotations + +import json +import typing + +import ifcopenshell + + +def coerce_value( + value_str: str, + type_hint, + model: ifcopenshell.file | None = None, + lookup_file: ifcopenshell.file | None = None, +): + """Convert a CLI string argument to the proper Python type based on a type hint. + + Args: + value_str: The raw string from the CLI. + type_hint: The type annotation from the function signature. + model: The main open IFC model, needed to resolve entity instance references by ID. + lookup_file: Override file for entity resolution (e.g. a library file for + project.append_asset). When provided, entity IDs are looked up here instead + of in model. + + Returns: + The converted Python value. + + Raises: + ValueError: If the value cannot be converted. + TypeError: If the type hint is not supported. + """ + # When a library file has been opened, entity IDs are resolved from it, not the main model. + effective_lookup = lookup_file if lookup_file is not None else model + + if type_hint is None: + return value_str + + origin = typing.get_origin(type_hint) + args = typing.get_args(type_hint) + + # Union / Optional + if origin is typing.Union: + non_none_types = [a for a in args if a is not type(None)] + if value_str.lower() == "none": + if type(None) in args: + return None + # Try each non-None type in order + for t in non_none_types: + try: + return coerce_value(value_str, t, model, lookup_file) + except (ValueError, TypeError): + continue + raise ValueError(f"Cannot convert '{value_str}' to any of {non_none_types}") + + # Literal + if origin is typing.Literal: + allowed = args + if value_str in [str(a) for a in allowed]: + # return the actual literal value with proper type + for a in allowed: + if str(a) == value_str: + return a + raise ValueError(f"'{value_str}' is not one of: {', '.join(repr(a) for a in allowed)}") + + # list types + if origin is list: + if args and _is_entity_type(args[0]): + return _coerce_entity_list(value_str, effective_lookup) + if args: + items = _split_list(value_str) + return [coerce_value(item.strip(), args[0], model, lookup_file) for item in items] + return _split_list(value_str) + + # dict types + if origin is dict: + return _floatify_numeric_lists(json.loads(value_str)) + + # Simple types + if type_hint is str: + return value_str + if type_hint is int: + return int(value_str.lstrip("#")) + if type_hint is float: + return float(value_str) + if type_hint is bool: + return value_str.lower() in ("true", "1", "yes") + + # ifcopenshell.file — open from path string + if type_hint is ifcopenshell.file: + return ifcopenshell.open(value_str) + + # entity_instance + if _is_entity_type(type_hint): + return _coerce_entity(value_str, effective_lookup) + + # Fallback: try json.loads for complex types, then plain string + try: + return json.loads(value_str) + except (json.JSONDecodeError, TypeError): + return value_str + + +def _is_entity_type(hint) -> bool: + """Check if a type hint refers to ifcopenshell.entity_instance.""" + if hint is ifcopenshell.entity_instance: + return True + if isinstance(hint, type) and issubclass(hint, ifcopenshell.entity_instance): + return True + return False + + +def _coerce_entity(value_str: str | int, lookup_file: ifcopenshell.file | None) -> ifcopenshell.entity_instance: + """Resolve a step ID string like '123' or '#123' to an entity instance.""" + if lookup_file is None: + raise ValueError("Cannot resolve entity reference without an IFC model") + if isinstance(value_str, int): + entity_id = value_str + else: + entity_id = int(value_str.strip().lstrip("#")) + try: + return lookup_file.by_id(entity_id) + except RuntimeError: + raise ValueError(f"Entity #{entity_id} not found in model") + + +def _coerce_entity_list(value_str: str, lookup_file: ifcopenshell.file | None) -> list[ifcopenshell.entity_instance]: + """Resolve a comma-separated list of step IDs to entity instances.""" + items = _split_list(value_str) + return [_coerce_entity(item.strip(), lookup_file) for item in items] + + +def _floatify_numeric_lists(obj): + """Recursively convert lists of numbers to lists of floats. + + IFC C++ bindings require Python floats (not ints) for AGGREGATE OF DOUBLE + attributes (e.g. DirectionRatios, Coordinates). JSON parsing produces ints + for whole numbers like 0, which causes a TypeError at the binding level. + """ + if isinstance(obj, dict): + return {k: _floatify_numeric_lists(v) for k, v in obj.items()} + if ( + isinstance(obj, list) + and obj + and all(isinstance(v, (int, float)) for v in obj) + and any(isinstance(v, float) for v in obj) + ): + return [float(v) for v in obj] + return obj + + +def _split_list(value_str: str) -> list[str]: + """Split a comma-separated string, handling JSON arrays too.""" + value_str = value_str.strip() + if value_str.startswith("["): + try: + parsed = json.loads(value_str) + if isinstance(parsed, list): + return [json.dumps(item) if isinstance(item, (dict, list)) else str(item) for item in parsed] + except json.JSONDecodeError: + pass + return [item.strip() for item in value_str.split(",") if item.strip()] diff --git a/src/ifcedit/ifcedit/discover.py b/src/ifcedit/ifcedit/discover.py new file mode 100644 index 0000000000..b26a93f6c6 --- /dev/null +++ b/src/ifcedit/ifcedit/discover.py @@ -0,0 +1,282 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +from __future__ import annotations + +import importlib +import inspect +import re +import typing +from pathlib import Path + + +def _api_package_path() -> Path: + """Return the filesystem path to the ifcopenshell.api package.""" + import ifcopenshell.api + + return Path(ifcopenshell.api.__file__).parent + + +def list_modules() -> list[dict]: + """List all API modules with their function counts and descriptions. + + Returns a list of dicts: [{"module": "root", "description": "...", "functions": [...], "count": 4}, ...] + """ + api_path = _api_package_path() + modules = [] + for child in sorted(api_path.iterdir()): + if not child.is_dir() or child.name.startswith("_"): + continue + init_file = child / "__init__.py" + if not init_file.exists(): + continue + try: + mod = importlib.import_module(f"ifcopenshell.api.{child.name}") + except Exception: + continue + all_names = getattr(mod, "__all__", []) + if not all_names: + continue + description = "" + if mod.__doc__: + description = mod.__doc__.strip().split("\n")[0] + modules.append( + { + "module": child.name, + "description": description, + "functions": list(all_names), + "count": len(all_names), + } + ) + return modules + + +def list_functions(module: str) -> list[dict]: + """List functions in an API module with one-line descriptions and parameter info. + + Returns a list of dicts: [{"name": "create_entity", "description": "...", "params": [...]}] + """ + mod = importlib.import_module(f"ifcopenshell.api.{module}") + all_names = getattr(mod, "__all__", []) + functions = [] + for name in all_names: + fn = _get_underlying_function(module, name) + if fn is None: + continue + description = "" + if fn.__doc__: + description = fn.__doc__.strip().split("\n")[0] + params = _extract_params(fn) + functions.append( + { + "name": name, + "description": description, + "params": params, + } + ) + return functions + + +def function_docs(module: str, function: str) -> dict: + """Full documentation for a single API function. + + Returns a dict with: module, function, description, params (with types/defaults/descriptions), return_type + """ + fn = _get_underlying_function(module, function) + if fn is None: + raise ValueError(f"Function '{module}.{function}' not found") + + description = "" + long_description = "" + if fn.__doc__: + description, long_description = _parse_docstring_body(fn.__doc__) + + params = _extract_params(fn) + param_descriptions = _parse_param_docs(fn.__doc__ or "") + for param in params: + if param["name"] in param_descriptions: + param["description"] = param_descriptions[param["name"]] + + return_type = _format_type_hint(typing.get_type_hints(fn).get("return")) + return_description = _parse_return_doc(fn.__doc__ or "") + + result = { + "module": module, + "function": function, + "description": description, + "long_description": long_description, + "params": params, + } + if return_type: + result["return_type"] = return_type + if return_description: + result["return_description"] = return_description + return result + + +def _get_underlying_function(module: str, function: str): + """Get the actual function object (unwrapping the listener wrapper if needed).""" + try: + fn_module = importlib.import_module(f"ifcopenshell.api.{module}.{function}") + fn = getattr(fn_module, function, None) + return fn + except (ImportError, AttributeError): + return None + + +def _extract_params(fn) -> list[dict]: + """Extract parameter info from a function's signature and type hints.""" + sig = inspect.signature(fn) + try: + hints = typing.get_type_hints(fn) + except Exception: + hints = {} + + params = [] + for name, param in sig.parameters.items(): + if name == "file" or name == "self": + continue + info = {"name": name} + if name in hints: + info["type"] = _format_type_hint(hints[name]) + if param.default is not inspect.Parameter.empty: + info["default"] = _serialize_default(param.default) + else: + info["required"] = True + params.append(info) + return params + + +def _format_type_hint(hint) -> str | None: + """Format a type hint to a readable string.""" + import ifcopenshell + + if hint is None: + return None + if hint is type(None): + return "None" + # ifcopenshell.file params are passed as a file path string + if hint is ifcopenshell.file: + return "file_path" + origin = typing.get_origin(hint) + args = typing.get_args(hint) + + # Union (including Optional) + if origin is typing.Union: + formatted = [_format_type_hint(a) for a in args] + # Optional[X] is Union[X, None] — render as "Optional[X]" + if len(formatted) == 2 and "None" in formatted: + inner = next(f for f in formatted if f != "None") + return f"Optional[{inner}]" + return " | ".join(formatted) + + # Literal + if origin is typing.Literal: + values = ", ".join(repr(a) for a in args) + return f"Literal[{values}]" + + # Generic types (list, dict, etc.) + if origin is not None: + origin_name = getattr(origin, "__name__", str(origin)) + if args: + inner = ", ".join(_format_type_hint(a) for a in args) + return f"{origin_name}[{inner}]" + return origin_name + + # Simple types + return getattr(hint, "__name__", str(hint)) + + +def _serialize_default(value): + """Serialize a default value to something JSON-friendly.""" + if value is None: + return None + if isinstance(value, (str, int, float, bool)): + return value + return repr(value) + + +def _parse_docstring_body(docstring: str) -> tuple[str, str]: + """Parse the summary and long description from a docstring.""" + lines = docstring.strip().split("\n") + summary = lines[0].strip() if lines else "" + body_lines = [] + in_body = False + for line in lines[1:]: + stripped = line.strip() + if stripped.startswith(":param") or stripped.startswith(":return"): + break + if stripped.startswith("Example"): + break + if not in_body and not stripped: + in_body = True + continue + if in_body: + body_lines.append(stripped) + + long_description = " ".join(body_lines).strip() + # collapse multiple spaces + long_description = re.sub(r"\s+", " ", long_description) + return summary, long_description + + +_FIELD_MARKER = re.compile(r":(?:param|returns?|rtype|type|raises?)\b") + + +def _parse_param_docs(docstring: str) -> dict[str, str]: + """Extract :param name: description lines from a docstring.""" + params = {} + current_param = None + current_lines = [] + for line in docstring.split("\n"): + stripped = line.strip() + match = re.match(r":param\s+(\w+):\s*(.*)", stripped) + if match: + if current_param: + params[current_param] = " ".join(current_lines).strip() + current_param = match.group(1) + current_lines = [match.group(2)] + elif current_param and stripped and not _FIELD_MARKER.match(stripped): + current_lines.append(stripped) + elif _FIELD_MARKER.match(stripped) or (stripped == "" and current_param): + if current_param: + params[current_param] = " ".join(current_lines).strip() + current_param = None + current_lines = [] + if current_param: + params[current_param] = " ".join(current_lines).strip() + # collapse whitespace + return {k: re.sub(r"\s+", " ", v) for k, v in params.items()} + + +def _parse_return_doc(docstring: str) -> str: + """Extract :return: description from a docstring.""" + lines = [] + in_return = False + for line in docstring.split("\n"): + stripped = line.strip() + match = re.match(r":return:\s*(.*)", stripped) + if match: + in_return = True + lines = [match.group(1)] + elif in_return: + if _FIELD_MARKER.match(stripped) or stripped == "": + break + lines.append(stripped) + return re.sub(r"\s+", " ", " ".join(lines).strip()) diff --git a/src/ifcedit/ifcedit/foreach.py b/src/ifcedit/ifcedit/foreach.py new file mode 100644 index 0000000000..587d611c3f --- /dev/null +++ b/src/ifcedit/ifcedit/foreach.py @@ -0,0 +1,76 @@ +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +from __future__ import annotations + +import ifcopenshell + +from ifcedit.run import run_api + + +def _substitute(template: str, item: dict) -> str: + """Replace {key} placeholders in template with values from item.""" + for key, value in item.items(): + template = template.replace(f"{{{key}}}", str(value)) + return template + + +def run_foreach( + model: ifcopenshell.file, + module: str, + function: str, + raw_kwargs_template: dict[str, str], + items: list[dict], +) -> dict: + """Apply an API function to each item in a list, substituting {field} placeholders. + + Opens the model once, applies the mutation for every item, and returns a summary. + The caller is responsible for saving the model. + + Args: + model: The open IFC model (mutated in place). + module: API module name (e.g. "root"). + function: Function name (e.g. "remove_product"). + raw_kwargs_template: Arg templates with {field} placeholders, e.g. {"product": "{id}"}. + items: List of dicts (e.g. from ifcquery select output). + + Returns: + {"ok": True, "count": N, "errors": []} on full success, + {"ok": False, "count": N, "errors": [{...}]} if any item failed. + """ + errors = [] + count = 0 + + for i, item in enumerate(items): + if not isinstance(item, dict): + errors.append({"index": i, "item": item, "error": "item is not a dict"}) + continue + + substituted = {k: _substitute(v, item) for k, v in raw_kwargs_template.items()} + result = run_api(model, module, function, substituted) + + if result["ok"]: + count += 1 + else: + errors.append({"index": i, "item": item, "error": result["error"]}) + + return { + "ok": len(errors) == 0, + "count": count, + "errors": errors, + } diff --git a/src/ifcedit/ifcedit/quantify.py b/src/ifcedit/ifcedit/quantify.py new file mode 100644 index 0000000000..f85475e22c --- /dev/null +++ b/src/ifcedit/ifcedit/quantify.py @@ -0,0 +1,37 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from typing import Any + +import ifcopenshell + +AVAILABLE_RULES = ["IFC4QtoBaseQuantities", "IFC4X3QtoBaseQuantities"] + + +def list_rules() -> list[dict[str, str]]: + """Return a list of available quantification rule names.""" + return [{"name": name} for name in AVAILABLE_RULES] + + +def run_quantify(model: ifcopenshell.file, rule: str, selector: str | None = None) -> dict[str, Any]: + """Run quantity take-off on the model using the named rule. + + Modifies the model in-place by adding/updating IfcElementQuantity psets. + Returns a summary dict with ok, rule, and elements_quantified. + """ + from ifc5d.qto import edit_qtos, quantify + from ifc5d.qto import rules as rule_sets + + if rule not in rule_sets: + return {"ok": False, "error": f"Unknown rule: {rule}. Available: {list(rule_sets.keys())}"} + + import ifcopenshell.util.selector + + if selector: + elements = set(ifcopenshell.util.selector.filter_elements(model, selector)) + else: + elements = set(model.by_type("IfcElement")) + + results = quantify(model, elements, rule_sets[rule]) + edit_qtos(model, results) + return {"ok": True, "rule": rule, "elements_quantified": len(results)} diff --git a/src/ifcedit/ifcedit/run.py b/src/ifcedit/ifcedit/run.py new file mode 100644 index 0000000000..91d8be6774 --- /dev/null +++ b/src/ifcedit/ifcedit/run.py @@ -0,0 +1,148 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcEdit - CLI wrapper for ifcopenshell.api mutation functions +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcEdit. +# +# IfcEdit is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcEdit is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcEdit. If not, see . + +from __future__ import annotations + +import importlib +import inspect +import typing + +import ifcopenshell + +from ifcedit.coerce import coerce_value + + +def _is_file_type(hint) -> bool: + """Check if a type hint refers to ifcopenshell.file (or Optional[ifcopenshell.file]).""" + if hint is ifcopenshell.file: + return True + origin = typing.get_origin(hint) + args = typing.get_args(hint) + if origin is typing.Union and ifcopenshell.file in args: + return True + return False + + +def run_api( + model: ifcopenshell.file, + module: str, + function: str, + raw_kwargs: dict[str, str], +) -> dict: + """Execute an ifcopenshell.api function with CLI-provided string arguments. + + Args: + model: The open IFC model. + module: API module name (e.g. "root"). + function: Function name (e.g. "create_entity"). + raw_kwargs: String keyword arguments from the CLI. + + Returns: + A dict with {"ok": True, "result": ...} on success, + or {"ok": False, "error": "..."} on failure. + """ + try: + fn = _import_function(module, function) + except (ImportError, AttributeError) as e: + return {"ok": False, "error": f"Cannot find function '{module}.{function}': {e}"} + + try: + hints = typing.get_type_hints(fn) + except Exception: + hints = {} + + sig = inspect.signature(fn) + coerced_kwargs = {} + + # Pass 1: coerce ifcopenshell.file-typed params first (e.g. library= in append_asset). + # The opened file is then used as the lookup file for entity resolution in pass 2. + opened_files: list[ifcopenshell.file] = [] + for name, value_str in raw_kwargs.items(): + if name not in sig.parameters: + return {"ok": False, "error": f"Unknown parameter '{name}' for {module}.{function}"} + hint = hints.get(name) + if not _is_file_type(hint): + continue + try: + coerced = coerce_value(value_str, hint, model) + coerced_kwargs[name] = coerced + if isinstance(coerced, ifcopenshell.file): + opened_files.append(coerced) + except (ValueError, TypeError) as e: + return {"ok": False, "error": f"Cannot convert parameter '{name}': {e}"} + + # Pass 2: coerce remaining params. Entity instance IDs are resolved from the opened + # library file (if any), since you are always appending from another file, never + # from the current model. + lookup_file = opened_files[0] if opened_files else None + for name, value_str in raw_kwargs.items(): + if name in coerced_kwargs: + continue + if name not in sig.parameters: + return {"ok": False, "error": f"Unknown parameter '{name}' for {module}.{function}"} + hint = hints.get(name) + try: + coerced_kwargs[name] = coerce_value(value_str, hint, model, lookup_file=lookup_file) + except (ValueError, TypeError) as e: + return {"ok": False, "error": f"Cannot convert parameter '{name}': {e}"} + + # Determine if the function takes 'file' as its first parameter + first_param = next(iter(sig.parameters), None) + try: + if first_param == "file": + result = fn(model, **coerced_kwargs) + else: + result = fn(**coerced_kwargs) + except Exception as e: + return {"ok": False, "error": f"{type(e).__name__}: {e}"} + + return {"ok": True, "result": serialize_result(result)} + + +def _import_function(module: str, function: str): + """Import and return the underlying function from ifcopenshell.api.""" + fn_module = importlib.import_module(f"ifcopenshell.api.{module}.{function}") + fn = getattr(fn_module, function) + return fn + + +def serialize_result(value) -> object: + """Serialize an API result to a JSON-friendly structure.""" + if value is None: + return None + if isinstance(value, ifcopenshell.entity_instance): + return _serialize_entity(value) + if isinstance(value, (list, tuple, set, frozenset)): + return [serialize_result(item) for item in value] + if isinstance(value, dict): + return {str(k): serialize_result(v) for k, v in value.items()} + if isinstance(value, (str, int, float, bool)): + return value + return str(value) + + +def _serialize_entity(entity: ifcopenshell.entity_instance) -> dict: + """Serialize an entity instance to a summary dict.""" + result = { + "id": entity.id(), + "type": entity.is_a(), + } + if hasattr(entity, "Name") and entity.Name: + result["name"] = entity.Name + return result diff --git a/src/ifcedit/pyproject.toml b/src/ifcedit/pyproject.toml new file mode 100644 index 0000000000..f50c0b3cc4 --- /dev/null +++ b/src/ifcedit/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ifcedit" +version = "0.0.0" +authors = [ + { name="Bruno Postle", email="bruno@postle.net" }, +] +description = "CLI wrapper for ifcopenshell.api IFC model mutation functions" +readme = "README.md" +keywords = ["IFC", "BIM", "API"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", +] +dependencies = ["ifcopenshell", "ifc5d"] + +[project.scripts] +ifcedit = "ifcedit.__main__:main" + +[project.urls] +Homepage = "http://ifcopenshell.org" +Documentation = "https://docs.ifcopenshell.org" +Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues" + +[tool.setuptools.packages.find] +include = ["ifcedit*"] +exclude = ["test*"] + +[tool.ruff] +extend = "../../pyproject.toml" diff --git a/src/ifcedit/tests/__init__.py b/src/ifcedit/tests/__init__.py new file mode 100644 index 0000000000..0a3bc271a0 --- /dev/null +++ b/src/ifcedit/tests/__init__.py @@ -0,0 +1 @@ +# This file was generated with the assistance of an AI coding tool. diff --git a/src/ifcedit/tests/conftest.py b/src/ifcedit/tests/conftest.py new file mode 100644 index 0000000000..241220b145 --- /dev/null +++ b/src/ifcedit/tests/conftest.py @@ -0,0 +1,63 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.material +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.pset +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + + +@pytest.fixture +def model(): + """Create an IFC4 model with a spatial hierarchy and a wall.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + return f + + +@pytest.fixture +def model_file(model, tmp_path): + """Write the model fixture to a temp file and return the path.""" + path = tmp_path / "test.ifc" + model.write(str(path)) + return str(path) + + +@pytest.fixture +def library(): + """Create an IFC4 library with a single IfcWallType asset.""" + lib = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + ifcopenshell.api.root.create_entity(lib, ifc_class="IfcProject", name="TestLibrary") + ifcopenshell.api.unit.assign_unit(lib) + ifcopenshell.api.root.create_entity(lib, ifc_class="IfcWallType", name="WAL01") + return lib + + +@pytest.fixture +def library_file(library, tmp_path): + """Write the library fixture to a temp file and return the path.""" + path = tmp_path / "library.ifc" + library.write(str(path)) + return str(path) diff --git a/src/ifcedit/tests/test_coerce.py b/src/ifcedit/tests/test_coerce.py new file mode 100644 index 0000000000..4ee6895717 --- /dev/null +++ b/src/ifcedit/tests/test_coerce.py @@ -0,0 +1,158 @@ +# This file was generated with the assistance of an AI coding tool. +import json +from typing import Literal, Optional, Union + +import ifcopenshell +import ifcopenshell.api.project +import pytest + +from ifcedit.coerce import coerce_value + + +class TestStringCoercion: + def test_plain_string(self): + assert coerce_value("hello", str) == "hello" + + def test_empty_string(self): + assert coerce_value("", str) == "" + + +class TestIntCoercion: + def test_plain_int(self): + assert coerce_value("42", int) == 42 + + def test_hash_prefix(self): + assert coerce_value("#42", int) == 42 + + def test_negative(self): + assert coerce_value("-5", int) == -5 + + +class TestFloatCoercion: + def test_plain_float(self): + assert coerce_value("3.14", float) == pytest.approx(3.14) + + def test_integer_as_float(self): + assert coerce_value("5", float) == 5.0 + + +class TestBoolCoercion: + def test_true_values(self): + for val in ("true", "True", "TRUE", "1", "yes"): + assert coerce_value(val, bool) is True + + def test_false_values(self): + for val in ("false", "False", "0", "no"): + assert coerce_value(val, bool) is False + + +class TestOptionalCoercion: + def test_optional_string(self): + assert coerce_value("hello", Optional[str]) == "hello" + + def test_optional_none(self): + assert coerce_value("none", Optional[str]) is None + assert coerce_value("None", Optional[str]) is None + + def test_optional_int(self): + assert coerce_value("42", Optional[int]) == 42 + + +class TestUnionCoercion: + def test_union_str_int(self): + # Tries str first (or int first depending on order), both work + result = coerce_value("hello", Union[str, int]) + assert result == "hello" + + def test_union_int_none(self): + result = coerce_value("42", Union[int, None]) + assert result == 42 + + +class TestLiteralCoercion: + def test_valid_literal(self): + assert coerce_value("IFC4", Literal["IFC2X3", "IFC4", "IFC4X3"]) == "IFC4" + + def test_invalid_literal(self): + with pytest.raises(ValueError, match="not one of"): + coerce_value("IFC5", Literal["IFC2X3", "IFC4", "IFC4X3"]) + + +class TestDictCoercion: + def test_json_dict(self): + result = coerce_value('{"IsExternal": true, "FireRating": "2HR"}', dict[str, object]) + assert result == {"IsExternal": True, "FireRating": "2HR"} + + def test_mixed_float_int_list_coerced_to_float(self): + # [0.419, 0, 0.908] — JSON integer 0 mixed with floats must become float + # so ifcopenshell AGGREGATE OF DOUBLE attributes (e.g. DirectionRatios) don't reject the list + result = coerce_value('{"DirectionRatios": [0.419, 0, 0.908]}', dict[str, object]) + assert result["DirectionRatios"] == pytest.approx([0.419, 0.0, 0.908]) + assert all(isinstance(v, float) for v in result["DirectionRatios"]) + + def test_pure_int_list_not_coerced(self): + # All-integer lists (e.g. face indices) must stay as ints + result = coerce_value('{"CoordIndex": [0, 1, 2]}', dict[str, object]) + assert result["CoordIndex"] == [0, 1, 2] + assert all(isinstance(v, int) for v in result["CoordIndex"]) + + +class TestListCoercion: + def test_comma_separated(self): + result = coerce_value("a,b,c", list[str]) + assert result == ["a", "b", "c"] + + def test_json_array(self): + result = coerce_value("[1, 2, 3]", list[int]) + assert result == [1, 2, 3] + + +class TestEntityCoercion: + def test_entity_by_id(self, model): + wall = model.by_type("IfcWall")[0] + result = coerce_value(str(wall.id()), ifcopenshell.entity_instance, model) + assert result == wall + + def test_entity_with_hash(self, model): + wall = model.by_type("IfcWall")[0] + result = coerce_value(f"#{wall.id()}", ifcopenshell.entity_instance, model) + assert result == wall + + def test_entity_not_found(self, model): + with pytest.raises(ValueError, match="not found"): + coerce_value("999999", ifcopenshell.entity_instance, model) + + def test_entity_list(self, model): + wall = model.by_type("IfcWall")[0] + result = coerce_value(str(wall.id()), list[ifcopenshell.entity_instance], model) + assert len(result) == 1 + assert result[0] == wall + + def test_entity_list_multiple(self, model): + wall = model.by_type("IfcWall")[0] + storey = model.by_type("IfcBuildingStorey")[0] + result = coerce_value(f"{wall.id()},{storey.id()}", list[ifcopenshell.entity_instance], model) + assert len(result) == 2 + + def test_entity_no_model(self): + with pytest.raises(ValueError, match="without an IFC model"): + coerce_value("42", ifcopenshell.entity_instance, None) + + +class TestFileCoercion: + def test_opens_file_from_path(self, model_file): + result = coerce_value(model_file, ifcopenshell.file) + assert isinstance(result, ifcopenshell.file) + + def test_entity_from_lookup_file(self, model_file): + lib = ifcopenshell.open(model_file) + wall = lib.by_type("IfcWall")[0] + empty_model = ifcopenshell.api.project.create_file() + result = coerce_value(str(wall.id()), ifcopenshell.entity_instance, empty_model, lookup_file=lib) + assert result.id() == wall.id() + assert result.is_a("IfcWall") + + +class TestFallback: + def test_no_type_hint(self): + assert coerce_value("hello", None) == "hello" diff --git a/src/ifcedit/tests/test_discover.py b/src/ifcedit/tests/test_discover.py new file mode 100644 index 0000000000..7b77cb5b7e --- /dev/null +++ b/src/ifcedit/tests/test_discover.py @@ -0,0 +1,104 @@ +# This file was generated with the assistance of an AI coding tool. +from ifcedit.discover import function_docs, list_functions, list_modules + + +class TestListModules: + def test_returns_list(self): + result = list_modules() + assert isinstance(result, list) + assert len(result) > 0 + + def test_module_structure(self): + result = list_modules() + for entry in result: + assert "module" in entry + assert "description" in entry + assert "functions" in entry + assert "count" in entry + assert isinstance(entry["functions"], list) + assert entry["count"] == len(entry["functions"]) + + def test_known_modules_present(self): + result = list_modules() + module_names = [m["module"] for m in result] + for expected in ("root", "spatial", "pset", "aggregate", "unit"): + assert expected in module_names + + def test_root_module_has_functions(self): + result = list_modules() + root = next(m for m in result if m["module"] == "root") + assert "create_entity" in root["functions"] + assert root["count"] >= 3 + + +class TestListFunctions: + def test_root_functions(self): + result = list_functions("root") + assert isinstance(result, list) + names = [f["name"] for f in result] + assert "create_entity" in names + + def test_function_structure(self): + result = list_functions("root") + for fn in result: + assert "name" in fn + assert "description" in fn + assert "params" in fn + + def test_create_entity_params(self): + result = list_functions("root") + create = next(f for f in result if f["name"] == "create_entity") + param_names = [p["name"] for p in create["params"]] + assert "ifc_class" in param_names + assert "name" in param_names + + def test_pset_functions(self): + result = list_functions("pset") + names = [f["name"] for f in result] + assert "add_pset" in names + assert "edit_pset" in names + + +class TestFunctionDocs: + def test_create_entity_docs(self): + result = function_docs("root", "create_entity") + assert result["module"] == "root" + assert result["function"] == "create_entity" + assert result["description"] + assert isinstance(result["params"], list) + assert len(result["params"]) > 0 + + def test_params_have_types(self): + result = function_docs("root", "create_entity") + for param in result["params"]: + assert "name" in param + assert "type" in param + + def test_params_have_descriptions(self): + result = function_docs("root", "create_entity") + ifc_class = next(p for p in result["params"] if p["name"] == "ifc_class") + assert "description" in ifc_class + assert len(ifc_class["description"]) > 0 + + def test_return_type(self): + result = function_docs("root", "create_entity") + assert "return_type" in result + + def test_assign_container_docs(self): + result = function_docs("spatial", "assign_container") + assert result["module"] == "spatial" + param_names = [p["name"] for p in result["params"]] + assert "products" in param_names + assert "relating_structure" in param_names + + def test_unknown_function_raises(self): + import pytest + + with pytest.raises(ValueError, match="not found"): + function_docs("root", "nonexistent_function") + + def test_edit_pset_docs(self): + result = function_docs("pset", "edit_pset") + param_names = [p["name"] for p in result["params"]] + assert "pset" in param_names + assert "properties" in param_names diff --git a/src/ifcedit/tests/test_foreach.py b/src/ifcedit/tests/test_foreach.py new file mode 100644 index 0000000000..d464fedcf3 --- /dev/null +++ b/src/ifcedit/tests/test_foreach.py @@ -0,0 +1,85 @@ +# Tests for ifcedit.foreach +import ifcopenshell +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + +from ifcedit.foreach import _substitute, run_foreach + + +@pytest.fixture +def model(model): + return model + + +class TestSubstitute: + def test_single_field(self): + assert _substitute("--product {id}", {"id": 42}) == "--product 42" + + def test_multiple_fields(self): + result = _substitute("{type} #{id} ({name})", {"id": 5, "type": "IfcWall", "name": "W1"}) + assert result == "IfcWall #5 (W1)" + + def test_no_placeholder(self): + assert _substitute("hello", {"id": 1}) == "hello" + + def test_unknown_placeholder_unchanged(self): + assert _substitute("{unknown}", {"id": 1}) == "{unknown}" + + +class TestRunForeach: + def _items(self, model, ifc_class): + return [{"id": e.id(), "type": e.is_a(), "name": e.Name} for e in model.by_type(ifc_class)] + + def test_rename_single(self, model): + items = self._items(model, "IfcWall") + result = run_foreach( + model, "attribute", "edit_attributes", {"product": "{id}", "attributes": '{"Name": "R"}'}, items + ) + assert result["ok"] is True + assert result["count"] == 1 + assert result["errors"] == [] + assert model.by_type("IfcWall")[0].Name == "R" + + def test_rename_multiple(self, model): + items = self._items(model, "IfcElement") + result = run_foreach( + model, "attribute", "edit_attributes", {"product": "{id}", "attributes": '{"Name": "X"}'}, items + ) + assert result["ok"] is True + assert result["count"] == len(items) + + def test_empty_list(self, model): + result = run_foreach(model, "root", "remove_product", {"product": "{id}"}, []) + assert result["ok"] is True + assert result["count"] == 0 + assert result["errors"] == [] + + def test_bad_id_collects_error(self, model): + items = [{"id": 999999, "type": "IfcWall", "name": "X"}] + result = run_foreach(model, "root", "remove_product", {"product": "{id}"}, items) + assert result["ok"] is False + assert result["count"] == 0 + assert len(result["errors"]) == 1 + assert result["errors"][0]["index"] == 0 + + def test_non_dict_item_collects_error(self, model): + result = run_foreach(model, "root", "remove_product", {"product": "{id}"}, ["not_a_dict"]) + assert result["ok"] is False + assert len(result["errors"]) == 1 + + def test_partial_failure_counts_successes(self, model): + wall_id = model.by_type("IfcWall")[0].id() + items = [ + {"id": wall_id, "type": "IfcWall", "name": "W"}, + {"id": 999999, "type": "IfcWall", "name": "Bad"}, + ] + result = run_foreach( + model, "attribute", "edit_attributes", {"product": "{id}", "attributes": '{"Name": "Ok"}'}, items + ) + assert result["ok"] is False + assert result["count"] == 1 + assert len(result["errors"]) == 1 diff --git a/src/ifcedit/tests/test_main.py b/src/ifcedit/tests/test_main.py new file mode 100644 index 0000000000..00585d5172 --- /dev/null +++ b/src/ifcedit/tests/test_main.py @@ -0,0 +1,211 @@ +# This file was generated with the assistance of an AI coding tool. +import json +import subprocess +import sys + +import ifcopenshell +import pytest + + +def run_ifcedit(*args, stdin=None): + """Run ifcedit as a subprocess and return (stdout, stderr, returncode).""" + result = subprocess.run( + [sys.executable, "-m", "ifcedit", *args], + capture_output=True, + text=True, + input=stdin, + ) + return result.stdout, result.stderr, result.returncode + + +class TestListCommand: + def test_list_all_modules(self): + stdout, stderr, rc = run_ifcedit("list") + assert rc == 0 + data = json.loads(stdout) + assert isinstance(data, list) + module_names = [m["module"] for m in data] + assert "root" in module_names + assert "spatial" in module_names + + def test_list_module_functions(self): + stdout, stderr, rc = run_ifcedit("list", "root") + assert rc == 0 + data = json.loads(stdout) + assert isinstance(data, list) + names = [f["name"] for f in data] + assert "create_entity" in names + + def test_list_text_format(self): + stdout, stderr, rc = run_ifcedit("--format", "text", "list") + assert rc == 0 + assert "root" in stdout + + +class TestDocsCommand: + def test_docs_create_entity(self): + stdout, stderr, rc = run_ifcedit("docs", "root.create_entity") + assert rc == 0 + data = json.loads(stdout) + assert data["module"] == "root" + assert data["function"] == "create_entity" + assert "params" in data + + def test_docs_invalid_path(self): + stdout, stderr, rc = run_ifcedit("docs", "invalid_path") + assert rc != 0 + assert "module.function" in stderr + + def test_docs_unknown_function(self): + stdout, stderr, rc = run_ifcedit("docs", "root.nonexistent") + assert rc != 0 + + +class TestRunCommand: + def test_create_entity(self, model_file): + stdout, stderr, rc = run_ifcedit( + "run", model_file, "root.create_entity", "--ifc_class", "IfcWall", "--name", "CLIWall" + ) + assert rc == 0, f"stderr: {stderr}" + data = json.loads(stdout) + assert data["ok"] is True + assert data["result"]["type"] == "IfcWall" + assert data["result"]["name"] == "CLIWall" + + def test_dry_run(self, model_file): + stdout, stderr, rc = run_ifcedit("run", model_file, "root.create_entity", "--dry-run", "--ifc_class", "IfcWall") + assert rc == 0 + data = json.loads(stdout) + assert data["ok"] is True + assert data["dry_run"] is True + + def test_output_to_different_file(self, model_file, tmp_path): + output = str(tmp_path / "output.ifc") + stdout, stderr, rc = run_ifcedit( + "run", model_file, "root.create_entity", "-o", output, "--ifc_class", "IfcSlab" + ) + assert rc == 0, f"stderr: {stderr}" + data = json.loads(stdout) + assert data["ok"] is True + + import os + + assert os.path.exists(output) + + def test_run_error_bad_function(self, model_file): + stdout, stderr, rc = run_ifcedit("run", model_file, "root.nonexistent") + assert rc != 0 + + def test_run_invalid_function_path(self, model_file): + stdout, stderr, rc = run_ifcedit("run", model_file, "invalid_path") + assert rc != 0 + assert "module.function" in stderr + + +class TestForeachCommand: + def _select_json(self, model, ifc_class): + """Build a JSON array like ifcquery select would produce.""" + elements = model.by_type(ifc_class) + return json.dumps([{"id": e.id(), "type": e.is_a(), "name": getattr(e, "Name", None)} for e in elements]) + + def test_foreach_rename(self, model, model_file): + walls_json = self._select_json(model, "IfcWall") + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "attribute.edit_attributes", + "--product", + "{id}", + "--attributes", + '{"Name": "Renamed"}', + stdin=walls_json, + ) + assert rc == 0, f"stderr: {stderr}" + data = json.loads(stdout) + assert data["ok"] is True + assert data["count"] == 1 + assert data["errors"] == [] + updated = ifcopenshell.open(model_file) + assert updated.by_type("IfcWall")[0].Name == "Renamed" + + def test_foreach_multiple_elements(self, model, model_file): + # Build a two-item list by selecting all IfcObject (includes spatial structure + elements) + elements_json = self._select_json(model, "IfcObject") + items = json.loads(elements_json) + assert len(items) >= 2 + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "attribute.edit_attributes", + "--product", + "{id}", + "--attributes", + '{"Name": "Bulk"}', + stdin=elements_json, + ) + assert rc == 0, f"stderr: {stderr}" + data = json.loads(stdout) + assert data["ok"] is True + assert data["count"] == len(items) + + def test_foreach_empty_list(self, model_file): + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "attribute.edit_attributes", + "--product", + "{id}", + "--attributes", + '{"Name": "X"}', + stdin="[]", + ) + assert rc == 0 + data = json.loads(stdout) + assert data["ok"] is True + assert data["count"] == 0 + + def test_foreach_invalid_json_stdin(self, model_file): + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "root.remove_product", + "--product", + "{id}", + stdin="not json", + ) + assert rc != 0 + assert "Error" in stderr + + def test_foreach_not_array_stdin(self, model_file): + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "root.remove_product", + "--product", + "{id}", + stdin='{"id": 1}', + ) + assert rc != 0 + assert "Error" in stderr + + def test_foreach_output_to_different_file(self, model, model_file, tmp_path): + import os + + output = str(tmp_path / "out.ifc") + walls_json = self._select_json(model, "IfcWall") + stdout, stderr, rc = run_ifcedit( + "foreach", + model_file, + "attribute.edit_attributes", + "-o", + output, + "--product", + "{id}", + "--attributes", + '{"Name": "OutFile"}', + stdin=walls_json, + ) + assert rc == 0, f"stderr: {stderr}" + assert os.path.exists(output) + updated = ifcopenshell.open(output) + assert updated.by_type("IfcWall")[0].Name == "OutFile" diff --git a/src/ifcedit/tests/test_quantify.py b/src/ifcedit/tests/test_quantify.py new file mode 100644 index 0000000000..f4335fb2a7 --- /dev/null +++ b/src/ifcedit/tests/test_quantify.py @@ -0,0 +1,87 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + +from ifcedit.quantify import AVAILABLE_RULES, list_rules, run_quantify + + +class TestListRules: + def test_returns_list(self): + result = list_rules() + assert isinstance(result, list) + + def test_each_entry_has_name(self): + result = list_rules() + for entry in result: + assert "name" in entry + + def test_ifc4_rule_present(self): + result = list_rules() + names = [r["name"] for r in result] + assert "IFC4QtoBaseQuantities" in names + + def test_ifc4x3_rule_present(self): + result = list_rules() + names = [r["name"] for r in result] + assert "IFC4X3QtoBaseQuantities" in names + + +@pytest.fixture +def quantify_model(): + """Create an IFC4 model with a wall element.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + return f + + +class TestRunQuantify: + def test_returns_ok_true(self, quantify_model): + result = run_quantify(quantify_model, "IFC4QtoBaseQuantities") + assert result["ok"] is True + + def test_returns_rule_name(self, quantify_model): + result = run_quantify(quantify_model, "IFC4QtoBaseQuantities") + assert result["rule"] == "IFC4QtoBaseQuantities" + + def test_returns_elements_quantified(self, quantify_model): + result = run_quantify(quantify_model, "IFC4QtoBaseQuantities") + assert "elements_quantified" in result + assert isinstance(result["elements_quantified"], int) + + def test_unknown_rule_returns_error(self, quantify_model): + result = run_quantify(quantify_model, "NonExistentRule") + assert result["ok"] is False + assert "error" in result + + def test_selector_restricts_elements(self, quantify_model): + result = run_quantify(quantify_model, "IFC4QtoBaseQuantities", selector="IfcWall") + assert result["ok"] is True + assert result["rule"] == "IFC4QtoBaseQuantities" + + def test_empty_selector_runs_on_all(self, quantify_model): + result = run_quantify(quantify_model, "IFC4QtoBaseQuantities", selector=None) + assert result["ok"] is True diff --git a/src/ifcedit/tests/test_run.py b/src/ifcedit/tests/test_run.py new file mode 100644 index 0000000000..80881e879f --- /dev/null +++ b/src/ifcedit/tests/test_run.py @@ -0,0 +1,97 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.project +import ifcopenshell.api.pset +import ifcopenshell.api.root + +from ifcedit.run import run_api, serialize_result + + +class TestRunApi: + def test_create_entity(self, model): + result = run_api(model, "root", "create_entity", {"ifc_class": "IfcWall", "name": "NewWall"}) + assert result["ok"] is True + assert result["result"]["type"] == "IfcWall" + assert result["result"]["name"] == "NewWall" + assert isinstance(result["result"]["id"], int) + + def test_create_entity_default_class(self, model): + result = run_api(model, "root", "create_entity", {}) + assert result["ok"] is True + assert result["result"]["type"] == "IfcBuildingElementProxy" + + def test_assign_container(self, model): + wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall", name="TestWall2") + storey = model.by_type("IfcBuildingStorey")[0] + result = run_api( + model, + "spatial", + "assign_container", + {"products": str(wall.id()), "relating_structure": str(storey.id())}, + ) + assert result["ok"] is True + assert result["result"]["type"] == "IfcRelContainedInSpatialStructure" + + def test_add_pset(self, model): + wall = model.by_type("IfcWall")[0] + result = run_api(model, "pset", "add_pset", {"product": str(wall.id()), "name": "Pset_WallCommon"}) + assert result["ok"] is True + assert result["result"]["type"] == "IfcPropertySet" + + def test_unknown_function(self, model): + result = run_api(model, "root", "nonexistent", {}) + assert result["ok"] is False + assert "Cannot find" in result["error"] + + def test_unknown_parameter(self, model): + result = run_api(model, "root", "create_entity", {"bogus_param": "value"}) + assert result["ok"] is False + assert "Unknown parameter" in result["error"] + + def test_bad_entity_reference(self, model): + result = run_api(model, "pset", "add_pset", {"product": "999999", "name": "Pset_WallCommon"}) + assert result["ok"] is False + assert "not found" in result["error"] + + +class TestAppendAsset: + def test_append_asset_from_library(self, model, library_file): + lib = ifcopenshell.open(library_file) + wall_type = lib.by_type("IfcWallType")[0] + result = run_api( + model, + "project", + "append_asset", + {"library": library_file, "element": str(wall_type.id())}, + ) + assert result["ok"] is True + assert result["result"]["type"] == "IfcWallType" + assert model.by_type("IfcWallType"), "wall type should have been appended to the model" + + +class TestSerializeResult: + def test_none(self): + assert serialize_result(None) is None + + def test_string(self): + assert serialize_result("hello") == "hello" + + def test_int(self): + assert serialize_result(42) == 42 + + def test_entity(self, model): + wall = model.by_type("IfcWall")[0] + result = serialize_result(wall) + assert result["id"] == wall.id() + assert result["type"] == "IfcWall" + assert result["name"] == "Wall001" + + def test_list(self, model): + walls = model.by_type("IfcWall") + result = serialize_result(walls) + assert isinstance(result, list) + assert all(isinstance(r, dict) for r in result) + + def test_dict(self): + result = serialize_result({"key": "value"}) + assert result == {"key": "value"} diff --git a/src/ifcfm/ifcfm/cobie24.py b/src/ifcfm/ifcfm/cobie24.py index f4848e1654..b83a4abe08 100644 --- a/src/ifcfm/ifcfm/cobie24.py +++ b/src/ifcfm/ifcfm/cobie24.py @@ -955,7 +955,7 @@ def get_unit_type_name(ifc_file: ifcopenshell.file, unit_type: str) -> Union[str return val(unit.Currency) -def get_unit_name(ifc_file: ifcopenshell.entity_instance, unit: ifcopenshell.entity_instance) -> Union[str, None]: +def get_unit_name(unit: ifcopenshell.entity_instance) -> Union[str, None]: if unit.is_a("IfcNamedUnit"): return val(unit.Name) diff --git a/src/ifcfm/ifcfm/cobie24legacy.py b/src/ifcfm/ifcfm/cobie24legacy.py index 078529e892..b3aa069e75 100644 --- a/src/ifcfm/ifcfm/cobie24legacy.py +++ b/src/ifcfm/ifcfm/cobie24legacy.py @@ -953,7 +953,7 @@ def get_unit_type_name(ifc_file: ifcopenshell.file, unit_type: str) -> Union[str return val(unit.Currency) -def get_unit_name(ifc_file: ifcopenshell.entity_instance, unit: ifcopenshell.entity_instance) -> Union[str, None]: +def get_unit_name(unit: ifcopenshell.entity_instance) -> Union[str, None]: if unit.is_a("IfcNamedUnit"): return val(unit.Name) diff --git a/src/ifcgeom/Iterator.cpp b/src/ifcgeom/Iterator.cpp index a7f700f4de..4db7db8946 100644 --- a/src/ifcgeom/Iterator.cpp +++ b/src/ifcgeom/Iterator.cpp @@ -620,17 +620,7 @@ const IfcGeom::Element* IfcGeom::Iterator::get_object(int id) { } } catch (const std::exception& e) { Logger::Error(e); - } -#ifdef IFOPSH_WITH_OPENCASCADE - catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error returning product"); - } - } -#endif - catch (...) { + } catch (...) { Logger::Error("Unknown error returning product"); } @@ -645,18 +635,7 @@ const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() { } catch (const std::exception& e) { Logger::Error(e); had_error_processing_elements_ = true; - } -#ifdef IFOPSH_WITH_OPENCASCADE - catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating geometry"); - } - had_error_processing_elements_ = true; - } -#endif - catch (...) { + } catch (...) { Logger::Error("Unknown error creating geometry"); had_error_processing_elements_ = true; } diff --git a/src/ifcgeom/Iterator.h b/src/ifcgeom/Iterator.h index 3f5f90a350..fc9e542d60 100644 --- a/src/ifcgeom/Iterator.h +++ b/src/ifcgeom/Iterator.h @@ -68,10 +68,6 @@ #include "../ifcgeom/abstract_mapping.h" #include "../ifcgeom/GeometrySerializer.h" -#ifdef IFOPSH_WITH_OPENCASCADE -#include -#endif - #include #include diff --git a/src/ifcgeom/infra_sweep_helper.cpp b/src/ifcgeom/infra_sweep_helper.cpp index 58f6d3108a..123e13b063 100644 --- a/src/ifcgeom/infra_sweep_helper.cpp +++ b/src/ifcgeom/infra_sweep_helper.cpp @@ -160,12 +160,12 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, } auto interpolated_offset = lerp(offset_a, offset_b, relative_dist_along); - if (rotation_a == rotation_b && rotation_a) { - // @todo we don't support an overridden rotation on only one of the placements + if (rotation_a.has_value() && rotation_b.has_value() ) { + // @todo we don't support an overridden rotation on only one of the placements // in which case we would need to lerp with the rotation component below in m4b. interpolated_rotation = lerp(*rotation_a, *rotation_b, relative_dist_along); } else if (rotation_a != rotation_b) { - Logger::Error("Direction vectors on cross section placements only supported when used consistently"); + Logger::Error("Direction vectors on cross section placements only supported when used consistently"); } taxonomy::loop::ptr w1, w2; diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp index a5a9d94149..e8b672906e 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.cpp @@ -255,1126 +255,28 @@ bool IfcGeom::OpenCascadeKernel::unify_shapes(const IfcGeom::ConversionResults& } bool IfcGeom::OpenCascadeKernel::convert_impl(const taxonomy::revolve::ptr r, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + gp_Ax1 ax( + convert_xyz(*r->axis_origin), + convert_xyz(*r->direction)); + TopoDS_Shape face; + if (!convert(taxonomy::cast(r->basis), face)) { + return false; + } - gp_Ax1 ax( - convert_xyz(*r->axis_origin), - convert_xyz(*r->direction)); + TopoDS_Shape shape; + if (r->angle) { + shape = BRepPrimAPI_MakeRevol(face, ax, *r->angle); + } else { + shape = BRepPrimAPI_MakeRevol(face, ax); + } - TopoDS_Shape face; - if (!convert(taxonomy::cast(r->basis), face)) { - return false; - } - - TopoDS_Shape shape; - if (r->angle) { - shape = BRepPrimAPI_MakeRevol(face, ax, *r->angle); - } else { - shape = BRepPrimAPI_MakeRevol(face, ax); - } - - results.emplace_back(ConversionResult( - r->instance->as()->id(), - r->matrix, - new OpenCascadeShape(shape), - r->surface_style - )); - return true; + results.emplace_back(ConversionResult( + r->instance->as()->id(), + r->matrix, + new OpenCascadeShape(shape), + r->surface_style)); + return true; + }); } - -// IfcSchema::IfcRelVoidsElement::list::ptr IfcGeom::Kernel::find_openings(IfcSchema::IfcProduct* product) { -// std::vector rs; -// -// if (product->declaration().is(IfcSchema::IfcElement::Class()) && !product->declaration().is(IfcSchema::IfcOpeningElement::Class())) { -// IfcSchema::IfcElement* element = (IfcSchema::IfcElement*)product; -// auto rels = element->HasOpenings(); -// rs.insert(rs.end(), rels->begin(), rels->end()); -// } -// -// // Is the IfcElement a decomposition of an IfcElement with any IfcOpeningElements? -// IfcSchema::IfcObjectDefinition* obdef = product->as(); -// for (;;) { -// auto decomposes = obdef->Decomposes(); -// if (decomposes->size() != 1) break; -// IfcSchema::IfcObjectDefinition* rel_obdef = (*decomposes->begin())->RelatingObject(); -// if (rel_obdef->declaration().is(IfcSchema::IfcElement::Class()) && !rel_obdef->declaration().is(IfcSchema::IfcOpeningElement::Class())) { -// IfcSchema::IfcElement* element = (IfcSchema::IfcElement*)rel_obdef; -// auto rels = element->HasOpenings(); -// rs.insert(rs.end(), rels->begin(), rels->end()); -// } -// -// obdef = rel_obdef; -// } -// -// // Filter openings in Reference view, solely marked as Reference. -// IfcSchema::IfcRelVoidsElement::list::ptr openings(new IfcSchema::IfcRelVoidsElement::list); -// std::for_each(rs.begin(), rs.end(), [&openings](IfcSchema::IfcRelVoidsElement* rel) { -// if (rel->RelatedOpeningElement()->ObjectPlacement() && rel->RelatedOpeningElement()->Representation()) { -// auto reps = rel->RelatedOpeningElement()->Representation()->Representations(); -// if (!(reps->size() == 1 && (*reps->begin())->RepresentationIdentifier().get_value_or("") == "Reference")) { -// openings->push(rel); -// } -// } -// }); -// -// return openings; -// } -// -// const IfcSchema::IfcMaterial* IfcGeom::Kernel::get_single_material_association(const IfcSchema::IfcProduct* product) { -// IfcSchema::IfcMaterial* single_material = 0; -// IfcSchema::IfcRelAssociatesMaterial::list::ptr associated_materials = product->HasAssociations()->as(); -// if (associated_materials->size() == 1) { -// IfcSchema::IfcMaterialSelect* associated_material = (*associated_materials->begin())->RelatingMaterial(); -// single_material = associated_material->as(); -// -// // NB: IfcMaterialLayerSets are also considered, regardless of --enable-layerset-slicing. Picking -// // the first material (in accordance with other viewers) when layerset-slicing is disabled. -// if (!single_material && associated_material->as()) { -// IfcSchema::IfcMaterialLayerSet* layerset = associated_material->as()->ForLayerSet(); -// if (getValue(GV_LAYERSET_FIRST) > 0.0 ? layerset->MaterialLayers()->size() >= 1 : layerset->MaterialLayers()->size() == 1) { -// IfcSchema::IfcMaterialLayer* layer = (*layerset->MaterialLayers()->begin()); -// if (layer->Material()) { -// single_material = layer->Material(); -// } -// } -// } -// } -// return single_material; -// } -// -// IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and_product( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product) -// { -// std::stringstream representation_id_builder; -// -// representation_id_builder << representation->data().id(); -// -// IfcGeom::Representation::BRep* shape; -// IfcGeom::ConversionResults shapes, shapes2; -// -// if (!convert_shapes(representation, shapes)) { -// return 0; -// } -// -// if (settings.get(IteratorSettings::APPLY_LAYERSETS)) { -// TopoDS_Shape merge; -// if (util::flatten_shape_list(shapes, merge, false, getValue(GV_PRECISION))) { -// if (util::count(merge, TopAbs_FACE) > 0) { -// std::vector thickness; -// std::vector layers; -// std::vector< std::vector > folded_layers; -// std::vector> styles; -// if (convert_layerset(product, layers, styles, thickness)) { -// -// IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations(); -// for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) { -// IfcSchema::IfcRelAssociatesMaterial* associates_material = (**it).as(); -// if (associates_material) { -// unsigned layerset_id = associates_material->RelatingMaterial()->data().id(); -// representation_id_builder << "-layerset-" << layerset_id; -// break; -// } -// } -// -// if (styles.size() > 1) { -// // If there's only a single layer there is no need to manipulate geometries. -// bool success = true; -// if (product->as() && fold_layers(product->as(), shapes, layers, thickness, folded_layers)) { -// if (util::apply_folded_layerset(shapes, folded_layers, styles, shapes2, getValue(GV_PRECISION))) { -// std::swap(shapes, shapes2); -// success = true; -// } -// } else { -// if (util::apply_layerset(shapes, layers, styles, shapes2, getValue(GV_PRECISION))) { -// std::swap(shapes, shapes2); -// success = true; -// } -// } -// -// if (!success) { -// Logger::Error("Failed processing layerset"); -// } -// } -// } -// } -// } -// } -// -// bool material_style_applied = false; -// -// const IfcSchema::IfcMaterial* single_material = get_single_material_association(product); -// if (single_material) { -// auto s = get_style(single_material); -// for (IfcGeom::ConversionResults::iterator it = shapes.begin(); it != shapes.end(); ++it) { -// if (!it->hasStyle() && s) { -// it->setStyle(s); -// material_style_applied = true; -// } -// } -// } else { -// bool some_items_without_style = false; -// for (IfcGeom::ConversionResults::iterator it = shapes.begin(); it != shapes.end(); ++it) { -// if (!it->hasStyle() && util::count(it->Shape(), TopAbs_FACE)) { -// some_items_without_style = true; -// break; -// } -// } -// if (some_items_without_style) { -// Logger::Warning("No material and surface styles for:", product); -// } -// } -// -// if (material_style_applied) { -// representation_id_builder << "-material-" << single_material->data().id(); -// } -// -// if (settings.force_space_transparency() >= 0. && product->declaration().is("IfcSpace")) { -// for (auto& s : shapes) { -// if (s.hasStyle()) { -// for (auto& p : style_cache) { -// if (p.second == s.StylePtr()) { -// std::const_pointer_cast(p.second)->Transparency() = settings.force_space_transparency(); -// } -// } -// } -// } -// } -// -// int parent_id = -1; -// try { -// IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product); -// if (parent_object && parent_object->as()) { -// parent_id = parent_object->data().id(); -// } -// } catch (const std::exception& e) { -// Logger::Error(e); -// } -// -// const std::string name = product->Name().get_value_or(""); -// const std::string guid = product->GlobalId(); -// -// gp_Trsf trsf; -// try { -// if (product->ObjectPlacement()) { -// convert(product->ObjectPlacement(), trsf); -// } -// } catch (const std::exception& e) { -// Logger::Error(e); -// } catch (...) { -// Logger::Error("Failed to construct placement"); -// } -// -// // Does the IfcElement have any IfcOpenings? -// // Note that openings for IfcOpeningElements are not processed -// IfcSchema::IfcRelVoidsElement::list::ptr openings = find_openings(product); -// -// const std::string product_type = product->declaration().name(); -// ElementSettings element_settings(settings, getValue(GV_LENGTH_UNIT), product_type); -// -// if (!settings.get(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS) && openings && openings->size()) { -// representation_id_builder << "-openings"; -// for (IfcSchema::IfcRelVoidsElement::list::it it = openings->begin(); it != openings->end(); ++it) { -// representation_id_builder << "-" << (*it)->data().id(); -// } -// -// IfcGeom::ConversionResults opened_shapes; -// bool caught_error = false; -// try { -// convert_openings(product, openings, shapes, trsf, opened_shapes); -// } catch (const std::exception& e) { -// Logger::Message(Logger::LOG_ERROR, std::string("Error processing openings for: ") + e.what() + ":", product); -// caught_error = true; -// } catch (...) { -// Logger::Message(Logger::LOG_ERROR, "Error processing openings for:", product); -// } -// -// if (caught_error && opened_shapes.size() < shapes.size()) { -// opened_shapes = shapes; -// } -// -// if (settings.get(IteratorSettings::USE_WORLD_COORDS)) { -// for (IfcGeom::ConversionResults::iterator it = opened_shapes.begin(); it != opened_shapes.end(); ++it) { -// it->prepend(trsf); -// } -// trsf = gp_Trsf(); -// representation_id_builder << "-world-coords"; -// } -// shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), opened_shapes); -// } else if (settings.get(IteratorSettings::USE_WORLD_COORDS)) { -// for (IfcGeom::ConversionResults::iterator it = shapes.begin(); it != shapes.end(); ++it) { -// it->prepend(trsf); -// } -// trsf = gp_Trsf(); -// representation_id_builder << "-world-coords"; -// shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), shapes); -// } else { -// shape = new IfcGeom::Representation::BRep(element_settings, representation_id_builder.str(), shapes); -// } -// -// std::string context_string = ""; -// if (representation->RepresentationIdentifier()) { -// context_string = *representation->RepresentationIdentifier(); -// } else if (representation->ContextOfItems()->ContextType()) { -// context_string = *representation->ContextOfItems()->ContextType(); -// } -// -// auto elem = new BRepElement( -// product->data().id(), -// parent_id, -// name, -// product_type, -// guid, -// context_string, -// trsf, -// boost::shared_ptr(shape), -// product -// ); -// -// if (settings.get(IteratorSettings::VALIDATE_QUANTITIES)) { -// auto rels = product->IsDefinedBy(); -// for (auto& rel : *rels) { -// if (rel->as()) { -// auto pdef = rel->as()->RelatingPropertyDefinition(); -// if (pdef->as()) { -// std::string organization_name; -// try { -// // A couple of files are not according to the schema here. -// organization_name = pdef->as()->OwnerHistory()->OwningApplication()->ApplicationDeveloper()->Name(); -// } catch (...) {} -// if (organization_name == "IfcOpenShell") { -// auto qs = pdef->as()->Quantities(); -// for (auto& q : *qs) { -// if (q->as() && q->Name() == "Total Surface Area") { -// double a_calc; -// double a_file = q->as()->AreaValue(); -// if (elem->geometry().calculate_surface_area(a_calc)) { -// double diff = std::abs(a_calc - a_file); -// if (diff / std::sqrt(a_file) > getValue(GV_PRECISION)) { -// Logger::Error("Validation of surface area failed for:", product); -// } else { -// Logger::Notice("Validation of surface area succeeded for:", product); -// } -// } else { -// Logger::Error("Validation of surface area failed for:", product); -// } -// } else if (q->as() && q->Name() == "Volume") { -// double v_calc; -// double v_file = q->as()->VolumeValue(); -// if (elem->geometry().calculate_volume(v_calc)) { -// double diff = std::abs(v_calc - v_file); -// if (diff / std::sqrt(v_file) > getValue(GV_PRECISION)) { -// Logger::Error("Validation of volume failed for:", product); -// } else { -// Logger::Notice("Validation of volume succeeded for:", product); -// } -// } else { -// Logger::Error("Validation of volume failed for:", product); -// } -// } else if (q->as() && q->Name() == "Shape Validation Properties") { -// auto qs2 = q->as()->HasQuantities(); -// bool all_succeeded = qs2->size() > 0; -// for (auto& q2 : *qs2) { -// if (q2->as() && q2->Name() == "Surface Genus" && q2->Description()) { -// int item_id = boost::lexical_cast((*q2->Description()).substr(1)); -// int genus = (int)q2->as()->CountValue(); -// for (auto& part : elem->geometry()) { -// if (part.ItemId() == item_id) { -// if (util::surface_genus(part.Shape()) != genus) { -// all_succeeded = false; -// } -// } -// } -// } -// } -// if (!all_succeeded) { -// Logger::Error("Validation of surface genus failed for:", product); -// } else { -// Logger::Notice("Validation of surface genus succeeded for:", product); -// } -// } -// } -// } -// } -// } -// } -// } -// -// return elem; -// } -// -// IfcSchema::IfcRepresentation* IfcGeom::Kernel::representation_mapped_to(const IfcSchema::IfcRepresentation* representation) { -// IfcSchema::IfcRepresentation* representation_mapped_to = 0; -// try { -// IfcSchema::IfcRepresentationItem::list::ptr items = representation->Items(); -// if (items->size() == 1) { -// IfcSchema::IfcRepresentationptr item = *items->begin(); -// if (item->declaration().is(IfcSchema::IfcMappedItem::Class())) { -// if (item->StyledByItem()->size() == 0) { -// IfcSchema::IfcMappedptr mapped_item = item->as(); -// if (is_identity_transform(mapped_item->MappingTarget())) { -// IfcSchema::IfcRepresentationMap* map = mapped_item->MappingSource(); -// if (is_identity_transform(map->MappingOrigin())) { -// representation_mapped_to = map->MappedRepresentation(); -// } -// } -// } -// } -// } -// } catch (const IfcParse::IfcException& e) { -// Logger::Error(e); -// // @todo reset representation_mapped_to to zero? -// } -// return representation_mapped_to; -// } -// -// IfcSchema::IfcProduct::list::ptr IfcGeom::Kernel::products_represented_by(const IfcSchema::IfcRepresentation* representation) { -// IfcSchema::IfcProduct::list::ptr products(new IfcSchema::IfcProduct::list); -// -// IfcSchema::IfcProductRepresentation::list::ptr prodreps = representation->OfProductRepresentation(); -// -// for (IfcSchema::IfcProductRepresentation::list::it it = prodreps->begin(); it != prodreps->end(); ++it) { -// // http://buildingsmart-tech.org/ifc/IFC2x3/TC1/html/ifcrepresentationresource/lexical/ifcproductrepresentation.htm -// // IFC2x Edition 3 NOTE Users should not instantiate the entity IfcProductRepresentation from IFC2x Edition 3 onwards. -// // It will be changed into an ABSTRACT supertype in future releases of IFC. -// -// // IfcProductRepresentation also lacks the INVERSE relation to IfcProduct -// // Let's find the IfcProducts that reference the IfcProductRepresentation anyway -// products->push((*it)->data().getInverse((&IfcSchema::IfcProduct::Class()), -1)->as()); -// } -// -// IfcSchema::IfcRepresentationMap::list::ptr maps = representation->RepresentationMap(); -// -// if (products->size() && maps->size()) { -// Logger::Warning("Representation used by IfcRepresentationMap and IfcProductDefinitionShape", representation); -// } -// -// if (prodreps->size() > 1) { -// Logger::Warning("Multiple IfcProductDefinitionShapes for representation", representation); -// } -// -// if (maps->size() > 1) { -// Logger::Warning("Multiple IfcRepresentationMaps for representation", representation); -// } -// -// if (maps->size() == 1) { -// IfcSchema::IfcRepresentationMap* map = *maps->begin(); -// if (is_identity_transform(map->MappingOrigin())) { -// IfcSchema::IfcMappedItem::list::ptr items = map->MapUsage(); -// for (IfcSchema::IfcMappedItem::list::it it = items->begin(); it != items->end(); ++it) { -// IfcSchema::IfcMappedptr item = *it; -// if (item->StyledByItem()->size() != 0) continue; -// -// if (!is_identity_transform(item->MappingTarget())) { -// continue; -// } -// -// IfcSchema::IfcRepresentation::list::ptr reps = item->data().getInverse((&IfcSchema::IfcRepresentation::Class()), -1)->as(); -// for (IfcSchema::IfcRepresentation::list::it jt = reps->begin(); jt != reps->end(); ++jt) { -// IfcSchema::IfcRepresentation* rep = *jt; -// if (rep->Items()->size() != 1) continue; -// IfcSchema::IfcProductRepresentation::list::ptr prodreps_mapped = rep->OfProductRepresentation(); -// for (IfcSchema::IfcProductRepresentation::list::it kt = prodreps_mapped->begin(); kt != prodreps_mapped->end(); ++kt) { -// IfcSchema::IfcProduct::list::ptr ps = (*kt)->data().getInverse((&IfcSchema::IfcProduct::Class()), -1)->as(); -// products->push(ps); -// } -// } -// } -// } -// } -// -// return products; -// } -// -// IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_processed_representation( -// const IteratorSettings& /*settings*/, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, -// IfcGeom::BRepElement* brep) -// { -// int parent_id = -1; -// try { -// IfcUtil::IfcBaseEntity* parent_object = get_decomposing_entity(product); -// if (parent_object && parent_object->as()) { -// parent_id = parent_object->data().id(); -// } -// } catch (const std::exception& e) { -// Logger::Error(e); -// } -// -// const std::string name = product->Name().get_value_or(""); -// const std::string guid = product->GlobalId(); -// -// gp_Trsf trsf; -// try { -// if (product->ObjectPlacement()) { -// convert(product->ObjectPlacement(), trsf); -// } -// } catch (const std::exception& e) { -// Logger::Error(e); -// } catch (...) { -// Logger::Error("Failed to construct placement"); -// } -// -// std::string context_string = ""; -// if (representation->RepresentationIdentifier()) { -// context_string = *representation->RepresentationIdentifier(); -// } else if (representation->ContextOfItems()->ContextType()) { -// context_string = *representation->ContextOfItems()->ContextType(); -// } -// -// const std::string product_type = product->declaration().name(); -// -// return new BRepElement( -// product->data().id(), -// parent_id, -// name, -// product_type, -// guid, -// context_string, -// trsf, -// brep->geometry_pointer(), -// product -// ); -// } -// -// bool IfcGeom::Kernel::convert_layerset(const IfcSchema::IfcProduct* product, std::vector& surfaces, std::vector>& styles, std::vector& thicknesses) { -// -// } -// -// bool IfcGeom::Kernel::find_wall_end_points(const IfcSchema::IfcWall* wall, gp_Pnt& start, gp_Pnt& end) { -// IfcSchema::IfcRepresentation* axis_representation = find_representation(wall, "Axis"); -// if (!axis_representation) { -// return false; -// } -// -// ConversionResults items; -// { -// Kernel temp = *this; -// temp.setValue(GV_DIMENSIONALITY, -1.); -// temp.convert_shapes(axis_representation, items); -// } -// -// TopoDS_Vertex a, b; -// for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { -// TopExp_Explorer exp(it->Shape(), TopAbs_VERTEX); -// for (; exp.More(); exp.Next()) { -// b = TopoDS::Vertex(exp.Current()); -// if (a.IsNull()) { -// a = b; -// } -// } -// } -// -// if (a.IsNull() || b.IsNull()) { -// return false; -// } -// -// start = BRep_Tool::Pnt(a); -// end = BRep_Tool::Pnt(b); -// -// return true; -// } -// -// bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const ConversionResults& items, const std::vector& surfaces, const std::vector& thicknesses, std::vector< std::vector >& result) { -// /* -// * @todo isn't it easier to do this based on the non-folded surfaces of -// * the connected walls and fold both pairs of layersets simultaneously? -// */ -// -// bool folds_made = false; -// -// IfcSchema::IfcRelConnectsPathElements::list::ptr connections(new IfcSchema::IfcRelConnectsPathElements::list); -// connections->push(wall->ConnectedFrom()->as()); -// connections->push(wall->ConnectedTo()->as()); -// -// typedef std::vector surfaces_t; -// typedef std::pair curve_on_surface; -// typedef std::vector curves_on_surfaces_t; -// typedef std::vector< std::pair< std::pair, const IfcSchema::IfcProduct*> > endpoint_connections_t; -// typedef std::vector< std::vector > result_t; -// endpoint_connections_t endpoint_connections; -// -// // Find the semantic connections to other wall elements when they are not connected 'AT_PATH' because -// // in that latter case no folds need to be made. -// for (IfcSchema::IfcRelConnectsPathElements::list::it it = connections->begin(); it != connections->end(); ++it) { -// IfcSchema::IfcRelConnectsPathElements* connection = *it; -// IfcSchema::IfcConnectionTypeEnum::Value own_type = connection->RelatedElement() == wall -// ? connection->RelatedConnectionType() -// : connection->RelatingConnectionType(); -// IfcSchema::IfcConnectionTypeEnum::Value other_type = connection->RelatedElement() == wall -// ? connection->RelatingConnectionType() -// : connection->RelatedConnectionType(); -// if (other_type != IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATPATH && -// (own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND || -// own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART)) { -// IfcSchema::IfcElement* other = connection->RelatedElement() == wall -// ? connection->RelatingElement() -// : connection->RelatedElement(); -// if (other->as()) { -// endpoint_connections.push_back(std::make_pair(std::make_pair(own_type, other_type), other)); -// } -// } -// } -// -// if (endpoint_connections.size() == 0) { -// return false; -// } -// -// // Count how many connections are made AT_START and AT_END respectively -// int connection_type_count[2] = { 0,0 }; -// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { -// const int idx = it->first.first == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; -// connection_type_count[idx] ++; -// } -// -// gp_Trsf local; -// if (wall->ObjectPlacement()) { -// if (!convert(wall->ObjectPlacement(), local)) { -// return false; -// } -// } -// local.Invert(); -// -// { -// // Copy the unfolded surfaces -// result.resize(surfaces.size()); -// std::vector< std::vector >::iterator result_it = result.begin() + 1; -// std::vector::const_iterator input_it = surfaces.begin() + 1; -// for (; input_it != surfaces.end() - 1; ++result_it, ++input_it) { -// result_it->push_back(*input_it); -// } -// } -// -// const double total_thickness = std::accumulate(thicknesses.begin(), thicknesses.end(), 0.); -// -// gp_Pnt own_axis_start, own_axis_end; -// find_wall_end_points(wall, own_axis_start, own_axis_end); -// -// // Sometimes duplicate IfcRelConnectsPathElements exist. These are detected -// // and the counts of connections are decremented accordingly. -// for (int idx = 0; idx < 2; ++idx) { -// if (connection_type_count[idx] <= 1) { -// continue; -// } -// -// /* -// IfcSchema::IfcConnectionTypeEnum::Value connection_type = idx == 1 -// ? IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART -// : IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND; -// */ -// -// std::set others; -// endpoint_connections_t::iterator it = endpoint_connections.begin(); -// while (it != endpoint_connections.end()) { -// const IfcSchema::IfcProduct* other = it->second; -// if (others.find(other) != others.end()) { -// it = endpoint_connections.erase(it); -// --connection_type_count[idx]; -// } else { -// others.insert(other); -// ++it; -// } -// } -// } -// -// // Check whether the end points are of the wall are really ~1 LayerThickness away from each other -// /* -// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { -// IfcSchema::IfcConnectionTypeEnum::Value own_type = it->first.first; -// IfcSchema::IfcConnectionTypeEnum::Value other_type = it->first.second; -// -// gp_Pnt other_axis_start, other_axis_end; -// find_wall_end_points(it->second->as(), other_axis_start, other_axis_end); -// -// gp_Trsf other; -// if (!convert(it->second->ObjectPlacement(), other)) { -// continue; -// } -// -// other.Transforms(other_axis_start.ChangeCoord()); -// local.Transforms(other_axis_start.ChangeCoord()); -// other.Transforms(other_axis_end.ChangeCoord()); -// local.Transforms(other_axis_end.ChangeCoord()); -// -// const gp_Pnt& a = own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART -// ? own_axis_start -// : own_axis_end; -// -// const gp_Pnt& b = other_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART -// ? other_axis_start -// : other_axis_end; -// -// const double d = a.Distance(b); -// } -// */ -// -// const double length_required = endpoint_connections.size() * total_thickness; -// // @todo this is not precisely the distance in case of curved walls. Also, it's safer -// // to first reproject the body onto the axis to get the precise curve parametrization -// // range. It's only a safeguard though, so can probably be approximated. -// const double axis_length = own_axis_start.Distance(own_axis_end); -// if (length_required > axis_length) { -// Logger::Warning("The wall axis is not long enough to accommodate the fold points"); -// return false; -// } -// -// for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { -// IfcSchema::IfcConnectionTypeEnum::Value connection_type = it->first.first; -// -// // If more than one wall connects to this start/end -point assume layers do not need to be folded -// const int idx = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; -// if (connection_type_count[idx] > 1) continue; -// -// // Pick the corresponding point from the axis -// const gp_Pnt& own_end_point = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND -// ? own_axis_end -// : own_axis_start; -// const IfcSchema::IfcProduct* other_wall = it->second; -// -// gp_Trsf other; -// if (other_wall->ObjectPlacement()) { -// if (!convert(other_wall->ObjectPlacement(), other)) { -// Logger::Error("Failed to convert placement", other_wall); -// continue; -// } -// } -// -// IfcSchema::IfcRepresentation* axis_representation = find_representation(other_wall, "Axis"); -// -// if (!axis_representation) { -// Logger::Warning("Joined wall has no axis representation", other_wall); -// continue; -// } -// -// ConversionResults axis_items; -// { -// Kernel temp = *this; -// temp.setValue(GV_DIMENSIONALITY, -1.); -// temp.convert_shapes(axis_representation, axis_items); -// } -// -// TopoDS_Shape axis_shape; -// util::flatten_shape_list(axis_items, axis_shape, false, getValue(GV_PRECISION)); -// -// // local and other are IfcLocalPlacements and therefore have a unit -// // scale factor that can be applied by means of TopoDS_Shape::Move() -// axis_shape.Move(other); -// axis_shape.Move(local); -// -// TopoDS_Shape body_shape; -// util::flatten_shape_list(items, body_shape, false, getValue(GV_PRECISION)); -// -// // Create a single paremetric range over a single curve -// // that represents the entire 1d domain of the other wall -// // Sometimes there are multiple edges in the Axis shape -// // but it is assumed these are colinear. -// Handle_Geom_Curve other_axis_curve; -// double axis_u1, axis_u2; -// { -// TopExp_Explorer exp(axis_shape, TopAbs_EDGE); -// if (!exp.More()) { -// return false; -// } -// -// TopoDS_Edge axis_edge = TopoDS::Edge(exp.Current()); -// other_axis_curve = BRep_Tool::Curve(axis_edge, axis_u1, axis_u2); -// -// gp_Pnt other_a_1, other_a_2; -// other_axis_curve->D0(axis_u1, other_a_1); -// other_axis_curve->D0(axis_u2, other_a_2); -// -// if (axis_u2 < axis_u1) { -// std::swap(axis_u1, axis_u2); -// } -// exp.Next(); -// -// for (; exp.More(); exp.Next()) { -// TopoDS_Edge axis_edge2 = TopoDS::Edge(exp.Current()); -// TopExp_Explorer exp2(axis_edge2, TopAbs_VERTEX); -// for (; exp2.More(); exp2.Next()) { -// gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp2.Current())); -// gp_Pnt pp; -// double u, d; -// if (util::project(other_axis_curve, p, pp, u, d)) { -// if (u < axis_u1) axis_u1 = u; -// if (u > axis_u2) axis_u2 = u; -// } -// } -// } -// } -// -// double layer_offset = 0; -// -// std::vector::const_iterator thickness = thicknesses.begin(); -// result_t::iterator result_vector = result.begin() + 1; -// -// // nb The first layer is never folded, because it corresponds -// // to one of the longitudinal faces of the wall. Hence the +1 -// for (surfaces_t::const_iterator jt = surfaces.begin() + 1; jt != surfaces.end() - 1; ++jt, ++result_vector) { -// layer_offset += *thickness++; -// -// bool found_intersection = false, parallel = false; -// boost::optional point_outside_param_range; -// -// const Handle_Geom_Surface& surface = *jt; -// -// // Find the intersection point between the layerset surface -// // and the other axis curve. If it's within the parametric -// // range of the other wall it means the walls are connected -// // with an angle. -// GeomAPI_IntCS intersections(other_axis_curve, surface); -// if (intersections.IsDone() && intersections.NbPoints() == 1) { -// const gp_Pnt& p = intersections.Point(1); -// -// double u, v, w; -// intersections.Parameters(1, u, v, w); -// -// gp_Pnt Pc, Ps; -// gp_Vec Vc, Vs1, Vs2; -// other_axis_curve->D1(w, Pc, Vc); -// surface->D1(u, v, Ps, Vs1, Vs2); -// Vs1.Cross(Vs2); -// -// if (Vs1.IsNormal(Vc, 1.e-5)) { -// Logger::Warning("Connected walls are parallel"); -// parallel = true; -// } else if (w < axis_u1 || w > axis_u2) { -// point_outside_param_range = p; -// } else { -// // Found an intersection. Layer end point is covered by connecting wall -// found_intersection = true; -// break; -// } -// } -// -// if (!parallel && !found_intersection && point_outside_param_range) { -// -// /* -// Is there a bug in Open Cascade related to the intersection -// of offset surfaces constructed from linear extrusions? -// Handle_Geom_Surface xy = new Geom_Plane(gp::Origin(), gp::DZ()); -// // Handle_Geom_Surface yz = new Geom_Plane(gp::Origin(), gp::DX()); -// // Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); -// Handle_Geom_Curve ln = new Geom_Line(gp::Origin(), gp::DX()); -// Handle_Geom_Surface yz = new Geom_SurfaceOfLinearExtrusion(ln, gp::DZ()); -// Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); -// intersect(xy, yz2); -// */ -// -// Handle_Geom_Surface plane = new Geom_Plane(*point_outside_param_range, gp::DZ()); -// -// // vertical edges at wall end point face. -// curves_on_surfaces_t layer_ends; -// util::intersect(surface, body_shape, layer_ends); -// -// Handle_Geom_Curve layer_body_intersection; -// Handle_Geom_Surface body_surface; -// double mind = std::numeric_limits::infinity(); -// for (curves_on_surfaces_t::const_iterator kt = layer_ends.begin(); kt != layer_ends.end(); ++kt) { -// gp_Pnt p; -// gp_Vec v; -// double u, d; -// kt->second->D1(0., p, v); -// if (ALMOST_THE_SAME(0., v.Dot(gp::DZ()))) { -// // Filter horizontal curves -// continue; -// } -// // Find vertical wall end point edge closest to end point associated with semantic connection -// if (util::project(kt->second, own_end_point, p, u, d)) { -// // In addition to closest, there is a length threshold based on thickness. -// // @todo ideally, first, the point closest to end-point is selected, and -// // after that the parallel check is performed. But threshold probably -// // functions good enough. -// if (d < total_thickness * 3 && d < mind) { -// GeomAdaptor_Curve GAC(other_axis_curve); -// GeomAdaptor_Surface GAS(kt->first); -// -// Extrema_ExtCS x(GAC, GAS, getValue(GV_PRECISION), getValue(GV_PRECISION)); -// -// if (x.IsParallel()) { -// body_surface = kt->first; -// layer_body_intersection = kt->second; -// mind = d; -// } -// } -// } -// } -// -// if (body_surface.IsNull()) { -// continue; -// } -// -// // Intersect vertical edge with ground plane for point. -// GeomAPI_IntCS intersection2(layer_body_intersection, plane); -// if (intersection2.IsDone() && intersection2.NbPoints() == 1) { -// const gp_Pnt& layer_end_point = intersection2.Point(1); -// -// // Intersect layerset surface with ground plane -// GeomAPI_IntSS intersection3(surface, plane, 1.e-7); -// if (intersection3.IsDone() && intersection3.NbLines() == 1) { -// Handle_Geom_Curve layer_line = intersection3.Line(1); -// GeomAdaptor_Curve layer_line_adaptor(layer_line); -// ShapeAnalysis_Curve sac; -// gp_Pnt layer_end_point_projected; double layer_end_point_param; -// sac.Project(layer_line, layer_end_point, 1e-3, layer_end_point_projected, layer_end_point_param, false); -// -// // Move point inwards by distance from other layerset -// GCPnts_AbscissaPoint dst(layer_line_adaptor, layer_offset, layer_end_point_param); -// if (dst.IsDone()) { -// // Convert parameter to point -// gp_Pnt layer_fold_point; -// layer_line->D0(dst.Parameter(), layer_fold_point); -// -// GeomAPI_IntSS intersection4(body_surface, plane, 1.e-7); -// if (intersection4.IsDone() && intersection4.NbLines() == 1) { -// Handle_Geom_Curve body_trim_curve = intersection4.Line(1); -// ShapeAnalysis_Curve sac2; -// gp_Pnt layer_fold_point_projected; double layer_fold_point_param; -// sac2.Project(body_trim_curve, layer_fold_point, 1.e-7, layer_fold_point_projected, layer_fold_point_param, false); -// Handle_Geom_Curve fold_curve = new Geom_OffsetCurve(body_trim_curve->Reversed(), layer_fold_point_projected.Distance(layer_fold_point), gp::DZ()); -// -// Handle_Geom_Surface fold_surface = new Geom_SurfaceOfLinearExtrusion(fold_curve, gp::DZ()); -// result_vector->push_back(fold_surface); -// folds_made = true; -// } -// } -// } -// } -// -// } -// -// } -// } -// -// return folds_made; -// } -// -// IfcSchema::IfcRepresentation* IfcGeom::Kernel::find_representation(const IfcSchema::IfcProduct* product, const std::string& identifier) { -// if (!product->Representation()) return 0; -// IfcSchema::IfcProductRepresentation* prod_rep = product->Representation(); -// IfcSchema::IfcRepresentation::list::ptr reps = prod_rep->Representations(); -// for (IfcSchema::IfcRepresentation::list::it it = reps->begin(); it != reps->end(); ++it) { -// if ((**it).RepresentationIdentifier() && (*(**it).RepresentationIdentifier()) == identifier) { -// return *it; -// } -// } -// return 0; -// } -// -// const IfcSchema::IfcRepresentationptr IfcGeom::Kernel::find_item_carrying_style(const IfcSchema::IfcRepresentationptr item) { -// if (item->StyledByItem()->size()) { -// return item; -// } -// -// while (item->declaration().is(IfcSchema::IfcBooleanResult::Class())) { -// // All instantiations of IfcBooleanOperand (type of FirstOperand) are subtypes of -// // IfcGeometricRepresentationItem -// item = item->as()->FirstOperand()->as(); -// if (item && item->StyledByItem()->size()) { -// return item; -// } -// } -// -// // TODO: Ideally this would be done for other entities (such as IfcCsgSolid) as well. -// // But neither are these very prevalent, nor does the current IfcOpenShell style -// // mechanism enable to conveniently style subshapes, which would be necessary for -// // distinctly styled union operands. -// -// return item; -// } -// -// bool IfcGeom::Kernel::is_identity_transform(IfcUtil::IfcBaseInterface* l) { -// IfcSchema::IfcAxis2Placement2D* ax2d; -// IfcSchema::IfcAxis2Placement3D* ax3d; -// -// IfcSchema::IfcCartesianTransformationOperator2D* op2d; -// IfcSchema::IfcCartesianTransformationOperator3D* op3d; -// IfcSchema::IfcCartesianTransformationOperator2DnonUniform* op2dnonu; -// IfcSchema::IfcCartesianTransformationOperator3DnonUniform* op3dnonu; -// -// if ((op2dnonu = l->as()) != 0) { -// gp_GTrsf2d gtrsf2d; -// convert(op2dnonu, gtrsf2d); -// return gtrsf2d.Form() == gp_Identity; -// } else if ((op2d = l->as()) != 0) { -// gp_Trsf2d trsf2d; -// convert(op2d, trsf2d); -// return trsf2d.Form() == gp_Identity; -// } else if ((op3dnonu = l->as()) != 0) { -// gp_GTrsf gtrsf; -// convert(op3dnonu, gtrsf); -// return gtrsf.Form() == gp_Identity; -// } else if ((op3d = l->as()) != 0) { -// gp_Trsf trsf; -// convert(op3d, trsf); -// return trsf.Form() == gp_Identity; -// } else if ((ax2d = l->as()) != 0) { -// gp_Trsf2d trsf2d; -// convert(ax2d, trsf2d); -// return trsf2d.Form() == gp_Identity; -// } else if ((ax3d = l->as()) != 0) { -// gp_Trsf trsf; -// convert(ax3d, trsf); -// return trsf.Form() == gp_Identity; -// } else { -// throw IfcParse::IfcException("Invalid valuation for IfcAxis2Placement / IfcCartesianTransformationOperator"); -// } -// } -// -// void IfcGeom::Kernel::set_conversion_placement_rel_to_type(const IfcParse::declaration* type) { -// placement_rel_to_type_ = type; -// } -// -// void IfcGeom::Kernel::set_conversion_placement_rel_to_instance(const IfcUtil::IfcBaseEntity* instance) { -// placement_rel_to_instance_ = instance; -// } -// -// -// namespace { -// -// bool process_colour(IfcSchema::IfcColourRgb* colour, double* rgb) { -// if (colour != 0) { -// rgb[0] = colour->Red(); -// rgb[1] = colour->Green(); -// rgb[2] = colour->Blue(); -// } -// return colour != 0; -// } -// -// bool process_colour(IfcSchema::IfcNormalisedRatioMeasure* factor, double* rgb) { -// if (factor != 0) { -// const double f = *factor; -// rgb[0] = rgb[1] = rgb[2] = f; -// } -// return factor != 0; -// } -// -// bool process_colour(IfcSchema::IfcColourOrFactor* colour_or_factor, double* rgb) { -// if (colour_or_factor == 0) { -// return false; -// } else if (colour_or_factor->declaration().is(IfcSchema::IfcColourRgb::Class())) { -// return process_colour(static_cast(colour_or_factor), rgb); -// } else if (colour_or_factor->declaration().is(IfcSchema::IfcNormalisedRatioMeasure::Class())) { -// return process_colour(static_cast(colour_or_factor), rgb); -// } else { -// return false; -// } -// } -// -// } -// -// #define Kernel POSTFIX_SCHEMA(Kernel) -// -// std::shared_ptr IfcGeom::Kernel::internalize_surface_style(const std::pair& shading_styles) { -// if (shading_styles.second == 0) { -// return 0; -// } -// int surface_style_id = shading_styles.first->data().id(); -// auto it = style_cache.find(surface_style_id); -// if (it != style_cache.end()) { -// return it->second; -// } -// -// -// IfcSchema::IfcSurfaceStyle* style = shading_styles.first->as(); -// IfcSchema::IfcSurfaceStyleShading* shading = shading_styles.second->as(); -// -// std::shared_ptr surface_style_ptr; -// -// if (style->Name()) { -// surface_style_ptr.reset(new SurfaceStyle(surface_style_id, *style->Name())); -// } else { -// surface_style_ptr.reset(new SurfaceStyle(surface_style_id)); -// } -// -// std::shared_ptr surface_style_ptr_const = std::const_pointer_cast(surface_style_ptr); -// SurfaceStyle& surface_style = *surface_style_ptr; -// -// double rgb[3]; -// if (process_colour(shading->SurfaceColour(), rgb)) { -// surface_style.Diffuse().reset(SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2])); -// } -// if (shading_styles.second->declaration().is(IfcSchema::IfcSurfaceStyleRendering::Class())) { -// IfcSchema::IfcSurfaceStyleRendering* rendering_style = static_cast(shading_styles.second); -// if (rendering_style->DiffuseColour() && process_colour(rendering_style->DiffuseColour(), rgb)) { -// SurfaceStyle::ColorComponent diffuse = surface_style.Diffuse().get_value_or(SurfaceStyle::ColorComponent(1, 1, 1)); -// surface_style.Diffuse().reset(SurfaceStyle::ColorComponent(diffuse.R() * rgb[0], diffuse.G() * rgb[1], diffuse.B() * rgb[2])); -// } -// if (rendering_style->DiffuseTransmissionColour()) { -// // Not supported -// } -// if (rendering_style->ReflectionColour()) { -// // Not supported -// } -// if (rendering_style->SpecularColour() && process_colour(rendering_style->SpecularColour(), rgb)) { -// surface_style.Specular().reset(SurfaceStyle::ColorComponent(rgb[0], rgb[1], rgb[2])); -// } -// if (rendering_style->SpecularHighlight()) { -// IfcSchema::IfcSpecularHighlightSelect* highlight = rendering_style->SpecularHighlight(); -// if (highlight->declaration().is(IfcSchema::IfcSpecularRoughness::Class())) { -// double roughness = *((IfcSchema::IfcSpecularRoughness*)highlight); -// if (roughness >= 1e-9) { -// surface_style.Specularity().reset(1.0 / roughness); -// } -// } else if (highlight->declaration().is(IfcSchema::IfcSpecularExponent::Class())) { -// surface_style.Specularity().reset(*((IfcSchema::IfcSpecularExponent*)highlight)); -// } -// } -// if (rendering_style->TransmissionColour()) { -// // Not supported -// } -// if (rendering_style->Transparency()) { -// const double d = *rendering_style->Transparency(); -// surface_style.Transparency().reset(d); -// } -// } -// return style_cache[surface_style_id] = surface_style_ptr_const; -// } -// -// std::shared_ptr IfcGeom::Kernel::get_style(const IfcSchema::IfcRepresentationptr item) { -// return internalize_surface_style(get_surface_style(item)); -// } -// -// std::shared_ptr IfcGeom::Kernel::get_style(const IfcSchema::IfcMaterial* material) { -// IfcSchema::IfcMaterialDefinitionRepresentation::list::ptr defs = material->HasRepresentation(); -// for (IfcSchema::IfcMaterialDefinitionRepresentation::list::it jt = defs->begin(); jt != defs->end(); ++jt) { -// IfcSchema::IfcRepresentation::list::ptr reps = (*jt)->Representations(); -// IfcSchema::IfcStyledItem::list::ptr styles(new IfcSchema::IfcStyledItem::list); -// for (IfcSchema::IfcRepresentation::list::it it = reps->begin(); it != reps->end(); ++it) { -// styles->push((**it).Items()->as()); -// } -// for (IfcSchema::IfcStyledItem::list::it it = styles->begin(); it != styles->end(); ++it) { -// const std::pair ss = get_surface_style(*it); -// if (ss.second) { -// return internalize_surface_style(ss); -// } -// } -// } -// auto material_style = std::make_shared(material->data().id(), material->Name()); -// return style_cache[material->data().id()] = material_style; -// } -// -// void IfcGeom::Kernel::apply_layerset(IfcGeom::ConversionResults& r, const ifcopenshell::geometry::layerset_information& info) { -// convert(info.layers); -// -// if (info.layers.empty()) { -// return; -// } -// -// if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Line)) { -// Handle_Geom_Line axis_line = Handle_Geom_Line::DownCast(axis_curve); -// // @todo note that this creates an offset into the wrong order, the cross product arguments should be -// // reversed. This causes some inversions later on, e.g. if(positive) { reverse(); } -// reference_surface = new Geom_Plane(axis_line->Lin().Location(), axis_line->Lin().Direction() ^ gp::DZ()); -// } else if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Circle)) { -// // @todo note that in this branch this inversion does not seem to take place. -// Handle_Geom_Circle axis_line = Handle_Geom_Circle::DownCast(axis_curve); -// reference_surface = new Geom_CylindricalSurface(axis_li->Position(), axis_line->Radius()); -// } else { -// Logger::Message(Logger::LOG_ERROR, "Unsupported underlying curve of Axis representation:", product); -// return false; -// } -// -// IfcGeom::ConversionResults r2; -// if (IfcGeom::util::apply_layerset(r, const std::vector&, ConversionResults& r2, double tol)) { -// std::swap(r, r2) -// } -// } \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h index 61b1a2c548..add0c15a25 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h @@ -56,6 +56,21 @@ #include "../../../ifcgeom/taxonomy.h" #include "../../../ifcgeom/ConversionSettings.h" +namespace { +template +bool handle_occt_exception(Fn&& fn) { + try { + return std::forward(fn)(); + } catch (const Standard_Failure& e) { + if (e.GetMessageString() && strlen(e.GetMessageString())) { + throw std::runtime_error(e.GetMessageString()); + } else { + throw std::runtime_error("Unknown error creating geometry"); + } + } +} +} + namespace IfcGeom { class IFC_GEOMLIBRARY_API OpenCascadeKernel : public ifcopenshell::geometry::kernels::AbstractKernel { diff --git a/src/ifcgeom/kernels/opencascade/boolean_result.cpp b/src/ifcgeom/kernels/opencascade/boolean_result.cpp index 6750108a90..64b65f5f2a 100644 --- a/src/ifcgeom/kernels/opencascade/boolean_result.cpp +++ b/src/ifcgeom/kernels/opencascade/boolean_result.cpp @@ -84,6 +84,7 @@ namespace { } bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, ConversionResults& results) { + return handle_occt_exception([&]() -> bool { bool valid_result = false; bool first = true; const double tol = settings_.get().get(); @@ -196,4 +197,5 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, Con )); return true; + }); } diff --git a/src/ifcgeom/kernels/opencascade/extrusion.cpp b/src/ifcgeom/kernels/opencascade/extrusion.cpp index b1152a4158..631c382bf8 100644 --- a/src/ifcgeom/kernels/opencascade/extrusion.cpp +++ b/src/ifcgeom/kernels/opencascade/extrusion.cpp @@ -72,6 +72,8 @@ bool OpenCascadeKernel::convert(const taxonomy::extrusion::ptr extrusion, TopoDS } bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; if (!convert(extrusion, shape)) { return false; @@ -84,4 +86,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, I extrusion->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/face.cpp b/src/ifcgeom/kernels/opencascade/face.cpp index b2313487c1..661b725c1d 100644 --- a/src/ifcgeom/kernels/opencascade/face.cpp +++ b/src/ifcgeom/kernels/opencascade/face.cpp @@ -599,6 +599,8 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re } bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; if (!convert(face, shape)) { return false; @@ -609,4 +611,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::Co face->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/loft.cpp b/src/ifcgeom/kernels/opencascade/loft.cpp index b055b7743b..719e089278 100644 --- a/src/ifcgeom/kernels/opencascade/loft.cpp +++ b/src/ifcgeom/kernels/opencascade/loft.cpp @@ -427,6 +427,8 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re } bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; if (!convert(loft, shape)) { return false; @@ -438,4 +440,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::Co loft->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/loop.cpp b/src/ifcgeom/kernels/opencascade/loop.cpp index 581cbf8567..5d8a538716 100644 --- a/src/ifcgeom/kernels/opencascade/loop.cpp +++ b/src/ifcgeom/kernels/opencascade/loop.cpp @@ -378,6 +378,8 @@ bool OpenCascadeKernel::convert(const taxonomy::loop::ptr loop, TopoDS_Wire& wir } bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Wire shape; if (!convert(loop, shape)) { return false; @@ -389,9 +391,13 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::Co loop->surface_style )); return true; + + }); } bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Wire shape = boost::get(convert_curve(edge)); results.emplace_back(ConversionResult( @@ -400,4 +406,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::Co edge->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/shell.cpp b/src/ifcgeom/kernels/opencascade/shell.cpp index eacc87e12b..4456895b12 100644 --- a/src/ifcgeom/kernels/opencascade/shell.cpp +++ b/src/ifcgeom/kernels/opencascade/shell.cpp @@ -107,6 +107,8 @@ bool OpenCascadeKernel::convert(const taxonomy::shell::ptr l, TopoDS_Shape& shap } bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; if (!convert(shell, shape)) { return false; @@ -118,4 +120,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom:: shell->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/solid.cpp b/src/ifcgeom/kernels/opencascade/solid.cpp index 4f308b0b06..fb38c29789 100644 --- a/src/ifcgeom/kernels/opencascade/solid.cpp +++ b/src/ifcgeom/kernels/opencascade/solid.cpp @@ -102,6 +102,8 @@ bool OpenCascadeKernel::convert(const taxonomy::solid::ptr solid, TopoDS_Shape& } bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; if (!convert(solid, shape)) { return false; @@ -113,4 +115,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom:: solid->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp b/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp index c971291868..510c8f182d 100644 --- a/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp +++ b/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp @@ -308,6 +308,8 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo } bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs, IfcGeom::ConversionResults& results) { + return handle_occt_exception([&]() -> bool { + TopoDS_Shape shape; // For tiny radii occt will fail building the sweep, in which case we enlarge the inputs to occt, and add a scale matrix to the output bool enlarged = false; @@ -352,4 +354,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs, scs->surface_style )); return true; + + }); } diff --git a/src/ifcgeom/taxonomy.h b/src/ifcgeom/taxonomy.h index 5d70d4490c..c1162eebb0 100644 --- a/src/ifcgeom/taxonomy.h +++ b/src/ifcgeom/taxonomy.h @@ -1068,8 +1068,10 @@ typedef item const* ptr; virtual kinds kind() const { return LOFT; } virtual void print_impl(std::ostream& o, int indent) const { - o << std::string(indent, ' ') << "axis" << std::endl; - axis->print(o, indent + 4); + if (axis) { + o << std::string(indent, ' ') << "axis" << std::endl; + axis->print(o, indent + 4); + } } virtual size_t calc_hash() const { diff --git a/src/ifcmcp/Makefile b/src/ifcmcp/Makefile new file mode 100644 index 0000000000..3dbcbb4e93 --- /dev/null +++ b/src/ifcmcp/Makefile @@ -0,0 +1,10 @@ +PACKAGE_NAME:=ifcmcp +include ../common.mk + +.PHONY: test +test: + pytest tests + +.PHONY: qa +qa: + black . diff --git a/src/ifcmcp/README.md b/src/ifcmcp/README.md new file mode 100644 index 0000000000..a0fe01c8ea --- /dev/null +++ b/src/ifcmcp/README.md @@ -0,0 +1,400 @@ + +# ifcmcp + +An MCP (Model Context Protocol) server that wraps `ifcquery` and `ifcedit`, +holding the IFC model in memory across tool calls for fast interactive editing +sessions. + +## Installation + +```bash +pip install ifcmcp +``` + +Requires `ifcopenshell`, `ifcquery`, and `ifcedit`. The `mcp` package is an optional dependency needed to run the server; install it with `pip install ifcmcp[mcp]` or add `mcp` separately. + +## Running the server + +```bash +ifcmcp +``` + +This starts the server on stdio transport, suitable for use with Claude Code +or any MCP client. + +### Claude Code configuration + +Use the `claude mcp add` command: + +```bash +claude mcp add --transport stdio ifc -- ifcmcp +``` + +Or create a `.mcp.json` file in your project root: + +```json +{ + "mcpServers": { + "ifc": { + "type": "stdio", + "command": "ifcmcp" + } + } +} +``` + +After adding the server, restart Claude Code for the tools to become available. +Then load a model by asking Claude to use `ifc_load`: + +``` +load model.ifc using ifc_load +``` + +## Tools + +### Session + +#### ifc_new + +Create a new empty IFC model in memory, replacing any currently loaded model. + +``` +ifc_new() +ifc_new(schema="IFC4X3") +``` + +Default schema is `IFC4`. + +#### ifc_load + +Open an IFC file into memory. + +``` +ifc_load(path="/path/to/model.ifc") +-> "Loaded /path/to/model.ifc: schema IFC4, 1847 entities" +``` + +#### ifc_reset + +Unload the current model from memory, freeing all session state. + +``` +ifc_reset() +``` + +#### ifc_save + +Write the in-memory model to disk. Empty path overwrites the original file. + +``` +ifc_save() +ifc_save(path="/path/to/output.ifc") +``` + +### Query tools + +All query tools require a model to be loaded first via `ifc_load`. + +#### ifc_summary + +Model overview: schema, entity counts, project info. + +```json +{ + "schema": "IFC4", + "total_entities": 1847, + "project": {"id": 1, "name": "Office Building"}, + "types": {"IfcWall": 42, "IfcSlab": 12, "IfcWindow": 36} +} +``` + +#### ifc_tree + +Full spatial hierarchy from IfcProject down through sites, buildings, storeys, +and contained elements. + +```json +{ + "id": 1, + "type": "IfcProject", + "name": "Office Building", + "children": [ + { + "id": 2, + "type": "IfcSite", + "children": [{"id": 3, "type": "IfcBuilding", "children": ["..."]}] + } + ] +} +``` + +#### ifc_info + +Deep inspection of an entity by step ID: attributes, property sets, type, +material, container, and 4x4 placement matrix. + +``` +ifc_info(element_id=10) +``` + +#### ifc_select + +Filter elements using ifcopenshell selector syntax. + +``` +ifc_select(query="IfcWall") +ifc_select(query="IfcWindow") +``` + +Returns a sorted list of `{"id", "type", "name"}` references. + +#### ifc_relations + +Show all relationships for an element: hierarchy, children, type, groups, +systems, material, connections. + +``` +ifc_relations(element_id=10) +ifc_relations(element_id=10, traverse="up") +``` + +With `traverse="up"`, walks the hierarchy from element up to IfcProject. + +#### ifc_contexts + +List all geometric representation contexts and subcontexts in the loaded model. + +``` +ifc_contexts() +``` + +#### ifc_materials + +List all materials and material sets in the loaded model, with their assigned elements. + +``` +ifc_materials() +``` + +#### ifc_clash + +Check an element for geometric intersections and clearance violations. + +``` +ifc_clash(element_id=10) +ifc_clash(element_id=10, clearance=0.5, scope="all") +``` + +Parameters: + +- `clearance` -- minimum clearance distance in meters (0.0 = no clearance check) +- `tolerance` -- intersection tolerance in meters (default: 0.002) +- `scope` -- `"storey"` or `"all"` (default: `"storey"`) + +#### ifc_validate + +Check the model for schema and constraint violations. + +``` +ifc_validate() +ifc_validate(express_rules=True) +``` + +Returns `{"valid": true, "issues": []}` or `{"valid": false, "issues": [{"level": "ERROR", "message": "..."}]}`. + +#### ifc_schedule + +List all work schedules and their nested task trees. + +``` +ifc_schedule() +ifc_schedule(max_depth=1) # top-level phases only +``` + +`max_depth` limits subtask expansion. At the cutoff, `subtasks` is replaced +with `{"truncated": true, "count": N}` so you know children exist without +fetching them all. Omit for unlimited depth. + +#### ifc_cost + +List all cost schedules and their nested cost item trees. + +``` +ifc_cost() +ifc_cost(max_depth=2) # top two levels of the BoQ +``` + +`max_depth` limits cost item expansion, same truncation convention as +`ifc_schedule`. + +#### ifc_schema + +Return IFC class documentation for any entity type, using the loaded model's +schema version. + +``` +ifc_schema(entity_type="IfcWall") +ifc_schema(entity_type="IfcBuildingStorey") +``` + +Returns description, predefined types, spec URL, and attribute descriptions. +Returns `{"error": "Unknown entity: Foo"}` for unrecognised types. + +#### ifc_quantify + +Run quantity take-off (QTO) on the loaded model using an `ifc5d` rule. +Computes physical measurements (volume, area, length, count, weight) and +writes them back as `IfcElementQuantity` property sets. Modifies the model +in-place -- call `ifc_save()` when done. + +``` +ifc_quantify(rule="IFC4QtoBaseQuantities") +ifc_quantify(rule="IFC4QtoBaseQuantities", selector="IfcWall") +``` + +Available rules: `IFC4QtoBaseQuantities`, `IFC4X3QtoBaseQuantities`. + +`selector` is an optional ifcopenshell selector to restrict which elements +are quantified (default: all `IfcElement`). + +Returns `{"ok": true, "rule": "...", "elements_quantified": 42}`. + +### Drawing and rendering tools + +#### ifc_plot + +Generate a 2D technical drawing of the loaded model and return it as an inline image. + +``` +ifc_plot() +ifc_plot(selector="IfcWall", view="floorplan", scale=0.01, output_path="/tmp/plan.svg") +ifc_plot(element_ids=[10, 11], view="floorplan") +``` + +Parameters: + +- `selector` -- ifcopenshell selector to restrict plotted elements +- `element_ids` -- step IDs of elements to highlight; others are faded +- `view` -- `"floorplan"` (default), `"elevation"`, `"section"`, or `"auto"` +- `width_mm`, `height_mm` -- paper size in mm (default: 297 x 420) +- `scale` -- model-to-paper ratio (default: 0.01 = 1:100) +- `png_width`, `png_height` -- raster output size in pixels (default: 1024 x 1024) +- `output_path` -- optional path to also save to disk (`.svg` for vector, otherwise PNG) + +Returns an inline PNG the LLM can inspect. Requires `ifcopenshell.draw`. + +#### ifc_render + +Render the loaded model to a 3D PNG image. + +``` +ifc_render() +ifc_render(selector="IfcWall", view="iso", output_path="/tmp/model.png") +ifc_render(element_ids=[10, 11], view="south") +``` + +Parameters: + +- `selector` -- ifcopenshell selector to restrict rendered elements +- `element_ids` -- step IDs of elements to highlight; others are shown translucent +- `view` -- `"iso"` (default), `"top"`, `"south"`, `"north"`, `"east"`, or `"west"` +- `output_path` -- optional path to save the PNG to disk + +Returns an inline PNG. Requires `pyvista` and the IfcOpenShell C++ geometry bindings. + +### Shape builder tools + +#### ifc_shape_list + +List all available `ShapeBuilder` methods with brief descriptions. + +``` +ifc_shape_list() +``` + +#### ifc_shape_docs + +Show full documentation for a specific `ShapeBuilder` method. + +``` +ifc_shape_docs(method="extrude") +ifc_shape_docs(method="create_ellipse") +``` + +#### ifc_shape + +Execute a `ShapeBuilder` method on the loaded model. + +``` +ifc_shape(method="extrude", params='{"profile": "42", "magnitude": 3.0}') +``` + +`params` is a JSON string; entity references are resolved by step ID (same coercion as `ifc_edit`). + +### Edit discovery tools + +#### ifc_list + +List all API modules, or functions within a specific module. + +``` +ifc_list() # all modules +ifc_list(module="root") # functions in the root module +``` + +#### ifc_docs + +Show full documentation for an API function including parameters, types, +defaults, and descriptions. + +``` +ifc_docs(function_path="root.create_entity") +``` + +### Edit execution + +#### ifc_edit + +Execute an `ifcopenshell.api` mutation function. Parameters are passed as a +JSON string with string values that get coerced by ifcedit's type system. + +``` +ifc_edit( + function_path="root.create_entity", + params='{"ifc_class": "IfcWall", "name": "My Wall"}' +) +``` + +Returns `{"ok": true, "result": ...}` or `{"ok": false, "error": "..."}`. + +Does NOT auto-save -- call `ifc_save()` when ready to write changes to disk. + +**Parameter coercion:** + +| Type | JSON value | Python value | +|------|------------|--------------| +| `entity_instance` | `"42"` | resolved from model by step ID | +| `list[entity_instance]` | `"5,6,7"` | list of resolved entities | +| `dict` | `'{"key": "val"}'` | parsed JSON object | +| `bool` | `"true"` | `True` | +| `Optional[X]` | `"none"` | `None` | + +## Typical workflow + +1. **Load** a model: `ifc_load` +2. **Inspect** with query tools: `ifc_summary`, `ifc_tree`, `ifc_select`, `ifc_info`, `ifc_relations` +3. **Validate** if needed: `ifc_validate` +4. **Browse schedules / costs**: `ifc_schedule`, `ifc_cost` (use `max_depth=1` first on large projects) +5. **Look up IFC classes**: `ifc_schema` +6. **Find** the right API function: `ifc_list`, `ifc_docs` +7. **Edit** the model: `ifc_edit` +8. **Quantify** elements: `ifc_quantify` (writes QTO psets in-place) +9. **Verify** changes with query tools +10. **Save** when satisfied: `ifc_save` + +The model stays in memory across all calls, so multi-step editing sessions +are fast -- no file I/O between operations. + +## License + +LGPLv3+ -- see the IfcOpenShell project license. diff --git a/src/ifcmcp/ifcmcp/__init__.py b/src/ifcmcp/ifcmcp/__init__.py new file mode 100644 index 0000000000..92e1c230b6 --- /dev/null +++ b/src/ifcmcp/ifcmcp/__init__.py @@ -0,0 +1,20 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcMCP - MCP server for IFC building models +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcMCP. +# +# IfcMCP is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcMCP is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcMCP. If not, see . + +__version__ = version = "0.0.0" diff --git a/src/ifcmcp/ifcmcp/__main__.py b/src/ifcmcp/ifcmcp/__main__.py new file mode 100644 index 0000000000..da3f1deab7 --- /dev/null +++ b/src/ifcmcp/ifcmcp/__main__.py @@ -0,0 +1,48 @@ +# This file was generated with the assistance of an AI coding tool. +import argparse + +from ifcmcp import __version__ + + +def main(): + parser = argparse.ArgumentParser( + prog="python3 -m ifcmcp", + description=( + "ifcmcp — MCP server for IFC building models.\n\n" + "Runs a Model Context Protocol server over stdio so that MCP clients\n" + "can query and edit IFC files without writing them to disk between\n" + "operations.\n\n" + "Add to .mcp.json to configure:\n" + ' {"mcpServers": {"ifc": {"type": "stdio", "command": "python3", "args": ["-m", "ifcmcp"]}}}' + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("--version", action="version", version=f"ifcmcp {__version__}") + parser.add_argument( + "--transport", + choices=["stdio", "sse", "streamable-http"], + default="stdio", + help="MCP transport to use (default: stdio)", + ) + + args = parser.parse_args() + + try: + from mcp.server.fastmcp import FastMCP # noqa: F401 + except ImportError: + import sys + + print( + "error: the 'mcp' package is required to run the server.\n" "Install it with: pip install mcp", + file=sys.stderr, + ) + sys.exit(1) + + from ifcmcp.server import build_server + + server = build_server() + server.run(transport=args.transport) + + +if __name__ == "__main__": + main() diff --git a/src/ifcmcp/ifcmcp/core.py b/src/ifcmcp/ifcmcp/core.py new file mode 100644 index 0000000000..ed9f91c3c9 --- /dev/null +++ b/src/ifcmcp/ifcmcp/core.py @@ -0,0 +1,731 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +# inside ifcmcp/core.py +import json +from collections.abc import Callable # noqa: F401 — Callable used in helpers below +from dataclasses import dataclass +from typing import Any + +import ifcopenshell +from ifcedit.discover import function_docs, list_functions, list_modules +from ifcedit.quantify import run_quantify +from ifcedit.run import run_api +from ifcquery import clash as clash_mod +from ifcquery import contexts as contexts_mod +from ifcquery import cost as cost_mod +from ifcquery import ( + info, + relations, + schedule, + schema, + select, + summary, + tree, +) +from ifcquery import ( + materials as materials_mod, +) +from ifcquery import ( + plot as plot_mod, +) +from ifcquery import ( + render as render_mod, +) +from ifcquery import validate as validate_mod + + +def _jsonify(x: Any) -> Any: + """Convert IfcOpenShell objects / iterables into JSON-safe primitives.""" + if x is None or isinstance(x, (str, int, float, bool)): + return x + + # numpy arrays (and any array-like with tolist) + if hasattr(x, "tolist"): + return x.tolist() + + # IfcOpenShell entity instances: normalize + if isinstance(x, ifcopenshell.entity_instance): + return { + "id": int(x.id()), + "type": x.is_a(), + "repr": str(x), + "name": getattr(x, "Name", None), + } + + if isinstance(x, dict): + return {str(k): _jsonify(v) for k, v in x.items()} + + if isinstance(x, (list, tuple, set)): + return [_jsonify(v) for v in x] + + # Try JSON as-is, else fallback to string + try: + json.dumps(x) + return x + except Exception: + return str(x) + + +# --------------------------------------------------------------------------- +# Shape builder helpers +# --------------------------------------------------------------------------- + + +def _list_shape_methods() -> list[dict]: + """Introspect ShapeBuilder and return a summary of all public methods.""" + import inspect + + from ifcedit.discover import _extract_params + from ifcopenshell.util.shape_builder import ShapeBuilder + + results = [] + for name, fn in inspect.getmembers(ShapeBuilder, predicate=inspect.isfunction): + if name.startswith("_"): + continue + doc = fn.__doc__ or "" + description = doc.strip().split("\n")[0] if doc.strip() else "" + results.append({"method": name, "description": description, "params": _extract_params(fn)}) + return results + + +def _shape_method_docs(method_name: str) -> dict: + """Return full documentation for a single ShapeBuilder method.""" + import typing + + from ifcedit.discover import ( + _extract_params, + _format_type_hint, + _parse_docstring_body, + _parse_param_docs, + _parse_return_doc, + ) + from ifcopenshell.util.shape_builder import ShapeBuilder + + if method_name.startswith("_"): + raise ValueError(f"ShapeBuilder has no method '{method_name}'") + fn = getattr(ShapeBuilder, method_name, None) + if fn is None: + raise ValueError(f"ShapeBuilder has no method '{method_name}'") + + doc = fn.__doc__ or "" + description, long_description = _parse_docstring_body(doc) + params = _extract_params(fn) + for param in params: + param_desc = _parse_param_docs(doc) + if param["name"] in param_desc: + param["description"] = param_desc[param["name"]] + + try: + hints = typing.get_type_hints(fn) + except Exception: + hints = {} + + result: dict[str, Any] = { + "method": method_name, + "description": description, + "long_description": long_description, + "params": params, + } + return_type = _format_type_hint(hints.get("return")) + if return_type: + result["return_type"] = return_type + return_description = _parse_return_doc(doc) + if return_description: + result["return_description"] = return_description + return result + + +def _coerce_shape_params(fn: Callable, raw_kwargs: dict, model: ifcopenshell.file) -> dict: + """Coerce JSON-parsed kwargs to proper Python types for a ShapeBuilder method.""" + import inspect + import typing + + sig = inspect.signature(fn) + try: + hints = typing.get_type_hints(fn) + except Exception: + hints = {} + + return { + key: _coerce_shape_value(value, hints.get(key), model) + for key, value in raw_kwargs.items() + if key in sig.parameters and key != "self" + } + + +def _coerce_shape_value(value: Any, hint: Any, model: ifcopenshell.file) -> Any: + """Convert a single JSON-parsed value to the correct Python type.""" + import typing + + if hint is None or value is None: + return value + + origin = typing.get_origin(hint) + args = typing.get_args(hint) + + # Optional[X] / Union — try each non-None branch in order + if origin is typing.Union: + if value is None: + return None + for t in (a for a in args if a is not type(None)): + try: + return _coerce_shape_value(value, t, model) + except (ValueError, TypeError): + continue + return value + + # entity_instance: resolve integer or "#N" string step ID + if hint is ifcopenshell.entity_instance or ( + isinstance(hint, type) and issubclass(hint, ifcopenshell.entity_instance) + ): + entity_id = int(str(value).lstrip("#")) + entity = model.by_id(entity_id) + if entity is None: + raise ValueError(f"Entity #{entity_id} not found in model") + return entity + + # Sequence[entity_instance]: resolve each element in the list + import collections.abc + + if origin is not None and issubclass(origin, collections.abc.Sequence) and not isinstance(value, str): + if args and ( + args[0] is ifcopenshell.entity_instance + or (isinstance(args[0], type) and issubclass(args[0], ifcopenshell.entity_instance)) + ): + if isinstance(value, (list, tuple)): + return [_coerce_shape_value(v, args[0], model) for v in value] + + # bool: JSON gives actual bools; also accept string representations + if hint is bool: + if isinstance(value, bool): + return value + return str(value).lower() in ("true", "1", "yes") + + # Everything else (float, int, VectorType lists, dicts, Literals) passes through + return value + + +class IfcSessionError(RuntimeError): + pass + + +@dataclass +class IfcSession: + """In-memory IFC session (no FastMCP dependency). + + Designed to work in: + - FastMCP server (single global session) + - Embedded runtimes like Pyodide (one session per browser tab/worker) + """ + + model: ifcopenshell.file | None = None + model_path: str | None = None + + # ----------------- + # Session lifecycle + # ----------------- + def _require_model(self) -> ifcopenshell.file: + if self.model is None: + raise IfcSessionError("No model loaded. Call ifc_load() or ifc_new() first.") + return self.model + + def ifc_new(self, schema: str = "IFC4") -> dict[str, Any]: + """Create a new empty IFC model in memory.""" + self.model = ifcopenshell.file(schema=schema) + self.model_path = None + return {"ok": True, "schema": self.model.schema, "entities": sum(1 for _ in self.model)} + + def ifc_load(self, path: str) -> str: + """Open an IFC file into memory. Returns confirmation string.""" + self.model = ifcopenshell.open(path) + self.model_path = path + count = sum(1 for _ in self.model) + return f"Loaded {path}: schema {self.model.schema}, {count} entities" + + def ifc_save(self, path: str = "") -> str: + """Write the in-memory model to disk. Empty path overwrites the original file.""" + model = self._require_model() + target = path if path else self.model_path + if not target: + raise IfcSessionError("No path specified and no original path available.") + model.write(target) + return f"Saved to {target}" + + def ifc_reset(self) -> dict[str, Any]: + """Drop the in-memory model.""" + self.model = None + self.model_path = None + return {"ok": True} + + # ------------- + # Query tools + # ------------- + def ifc_summary(self) -> dict[str, Any]: + """Model overview: schema, entity counts, project info.""" + return summary.summary(self._require_model()) + + def ifc_tree(self) -> dict[str, Any] | list[dict[str, Any]]: + """Full spatial hierarchy tree (Project -> Site -> Building -> Storeys -> Elements).""" + return tree.tree(self._require_model()) + + def ifc_info(self, element_id: int) -> dict[str, Any]: + """Deep inspection of an entity by step ID (attributes, psets, placement, type, material).""" + model = self._require_model() + element = model.by_id(element_id) + if element is None: + raise IfcSessionError(f"Element #{element_id} not found.") + return info.info(model, element) + + def ifc_select(self, query: str) -> list[dict[str, Any]]: + """Filter elements using ifcopenshell selector syntax (e.g. 'IfcWall', 'IfcWindow').""" + return select.select(self._require_model(), query) + + def ifc_relations(self, element_id: int, traverse: str = "") -> dict[str, Any] | list[dict[str, Any]]: + """Show relationships for an element. Set traverse='up' to walk hierarchy to IfcProject.""" + model = self._require_model() + element = model.by_id(element_id) + if element is None: + raise IfcSessionError(f"Element #{element_id} not found.") + return relations.relations(model, element, traverse=traverse if traverse else None) + + def ifc_clash( + self, + element_id: int, + clearance: float = 0.0, + tolerance: float = 0.002, + scope: str = "storey", + ) -> dict[str, Any]: + """Check element for geometric clashes. clearance=0.0 means no clearance check.""" + model = self._require_model() + element = model.by_id(element_id) + if element is None: + raise IfcSessionError(f"Element #{element_id} not found.") + return clash_mod.clash( + model, + element, + clearance=clearance if clearance and clearance > 0.0 else None, + tolerance=tolerance, + scope=scope, + ) + + def ifc_contexts(self) -> list[dict[str, Any]]: + """List all geometric representation contexts and subcontexts with their step IDs.""" + return contexts_mod.contexts(self._require_model()) + + def ifc_materials(self) -> list[dict[str, Any]]: + """List all materials and material sets (layers, constituents, profiles).""" + return materials_mod.materials(self._require_model()) + + # ------------------------ + # Edit discovery + execute + # ------------------------ + def ifc_list(self, module: str = "") -> list[dict]: + """List all API modules, or functions within a module. Empty module = all modules.""" + return list_functions(module) if module else list_modules() + + def ifc_docs(self, function_path: str) -> dict: + """Show full documentation for an API function. Input format: 'module.function'.""" + module, function = function_path.split(".", 1) + return function_docs(module, function) + + def ifc_edit(self, function_path: str, params: Any = "{}") -> dict: + """Execute an ifcopenshell.api mutation. + + params may be: + - JSON string + - dict (from tool calling / JS) + - JsProxy (handled upstream in embedded.py) + """ + model = self._require_model() + module, function = function_path.split(".", 1) + + if isinstance(params, str): + raw_kwargs = json.loads(params) if params.strip() else {} + elif isinstance(params, dict): + raw_kwargs = params + else: + # e.g. list/None/etc + raw_kwargs = dict(params) if params is not None else {} + + res = run_api(model, module, function, raw_kwargs) + return _jsonify(res) + + # ------------------------ + # Extended query + edit tools + # ------------------------ + def ifc_validate(self, express_rules: bool = False) -> dict[str, Any]: + """Validate the loaded model. Returns {'valid': bool, 'issues': [...]}.""" + return validate_mod.validate(self._require_model(), express_rules=express_rules) + + def ifc_schedule(self, max_depth: int | None = None) -> list[dict[str, Any]]: + """List work schedules and nested tasks from the model. + + max_depth limits subtask expansion (None = unlimited). At the cutoff, + subtasks is replaced with {"truncated": True, "count": N}. + """ + return schedule.schedule(self._require_model(), max_depth=max_depth) + + def ifc_cost(self, max_depth: int | None = None) -> list[dict[str, Any]]: + """List cost schedules and nested cost items from the model. + + max_depth limits cost item expansion (None = unlimited). At the cutoff, + subitems is replaced with {"truncated": True, "count": N}. + """ + return cost_mod.cost(self._require_model(), max_depth=max_depth) + + def ifc_schema(self, entity_type: str) -> dict[str, Any]: + """Return IFC class documentation for entity_type using the model's schema version.""" + return schema.schema(self._require_model(), entity_type) + + def ifc_plot( + self, + selector: str = "", + element_ids: list[int] | None = None, + view: str = "floorplan", + width_mm: float = 297.0, + height_mm: float = 420.0, + scale: float = 1.0 / 100.0, + png_width: int = 1024, + png_height: int = 1024, + output_format: str = "png", + ) -> bytes: + """Generate a 2D technical drawing (floor plan, elevation, or section) and return image bytes. + + Uses ifcopenshell.draw to produce SVG output which is rasterised to PNG via CairoSVG + when output_format is 'png'. + + :param selector: ifcopenshell selector to restrict plotted elements + (e.g. ``'IfcWall'``). Omit to plot the whole model. + :param element_ids: Step IDs of elements to highlight. Other elements + are faded to 10% opacity so the subject stands out. + :param view: Drawing view — ``floorplan`` (default), ``elevation``, + ``section``, or ``auto``. + :param width_mm: Paper width in mm (default 297 = A4). + :param height_mm: Paper height in mm (default 420 = A4). + :param scale: Model-to-paper scale ratio (default 0.01 = 1:100). + :param png_width: Raster output width in pixels (default 1024). + :param png_height: Raster output height in pixels (default 1024). + :param output_format: ``'svg'`` or ``'png'`` (default ``'png'``). + :return: SVG or PNG bytes depending on output_format. + """ + model = self._require_model() + return plot_mod.plot( + model, + output_format=output_format, + selector=selector if selector else None, + element_ids=element_ids, + view=view, + width_mm=width_mm, + height_mm=height_mm, + scale=scale, + png_width=png_width, + png_height=png_height, + ) + + def ifc_render( + self, + selector: str = "", + element_ids: list[int] | None = None, + view: str = "iso", + ) -> bytes: + """Render the loaded model to a PNG image and return raw bytes. + + :param selector: ifcopenshell selector to restrict rendered elements + (e.g. ``'IfcWall'``). Omit to render the whole model. + :param element_ids: Step IDs of elements to highlight. Other elements + are rendered in translucent grey. + :param view: Camera angle: ``iso``, ``top``, ``south``, ``north``, + ``east``, or ``west``. + :return: PNG image as raw bytes. + """ + model = self._require_model() + return render_mod.render( + model, + selector=selector if selector else None, + element_ids=element_ids, + view=view, + ) + + # ------------------------ + # Shape builder tools + # ------------------------ + def ifc_shape_list(self) -> list[dict]: + """List all ShapeBuilder geometry methods with one-line descriptions and parameter names.""" + return _list_shape_methods() + + def ifc_shape_docs(self, method: str) -> dict: + """Full documentation for a ShapeBuilder method: params, types, return value.""" + return _shape_method_docs(method) + + def ifc_shape(self, method: str, params: Any = "{}") -> dict: + """Call a ShapeBuilder method by name. Returns the created entity's step ID. + + params is a JSON string of keyword arguments. Pass entity references as integer + step IDs; vectors as JSON arrays (e.g. [1.0, 0.0, 0.0]). + """ + model = self._require_model() + + from ifcopenshell.util.shape_builder import ShapeBuilder + + if method.startswith("_"): + raise IfcSessionError(f"Private method '{method}' is not accessible") + fn = getattr(ShapeBuilder, method, None) + if fn is None: + return {"ok": False, "error": f"ShapeBuilder has no method '{method}'"} + + if isinstance(params, str): + raw_kwargs = json.loads(params) if params.strip() else {} + elif isinstance(params, dict): + raw_kwargs = params + else: + raw_kwargs = {} + + try: + coerced = _coerce_shape_params(fn, raw_kwargs, model) + result = fn(ShapeBuilder(model), **coerced) + return {"ok": True, "result": _jsonify(result)} + except Exception as e: + return {"ok": False, "error": f"{type(e).__name__}: {e}"} + + def ifc_quantify(self, rule: str, selector: str = "") -> dict[str, Any]: + """Run quantity take-off on the model using the named rule. + + Modifies the model in-place; call ifc_save() after. + """ + model = self._require_model() + return run_quantify(model, rule, selector=selector if selector else None) + + # ------------------------ + # Generic dispatcher + tool specs for LLMs + # ------------------------ + def dispatch(self, name: str, args: dict[str, Any] | None = None) -> Any: + args = args or {} + fn = getattr(self, name, None) + if not callable(fn): + raise IfcSessionError(f"Unknown tool: {name}") + return _jsonify(fn(**args)) + + def openai_tools(self) -> list[dict[str, Any]]: + """Tool schemas in the OpenAI 'Responses API' format (type=function).""" + # Keep schemas tight so the model calls tools correctly. + return [ + { + "type": "function", + "name": "ifc_new", + "description": "Create a new empty IFC model in memory.", + "parameters": { + "type": "object", + "properties": {"schema": {"type": "string", "description": "IFC schema, e.g. IFC4"}}, + "required": [], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_summary", + "description": "Get a concise overview of the loaded IFC model.", + "parameters": {"type": "object", "properties": {}, "required": [], "additionalProperties": False}, + }, + { + "type": "function", + "name": "ifc_tree", + "description": "Get the full spatial hierarchy tree.", + "parameters": {"type": "object", "properties": {}, "required": [], "additionalProperties": False}, + }, + { + "type": "function", + "name": "ifc_select", + "description": "Select elements using ifcopenshell selector syntax (e.g. 'IfcWall').", + "parameters": { + "type": "object", + "properties": {"query": {"type": "string"}}, + "required": ["query"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_info", + "description": "Inspect an entity by STEP id.", + "parameters": { + "type": "object", + "properties": {"element_id": {"type": "integer"}}, + "required": ["element_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_relations", + "description": "Get relationships for an element. traverse='up' walks to IfcProject.", + "parameters": { + "type": "object", + "properties": {"element_id": {"type": "integer"}, "traverse": {"type": "string"}}, + "required": ["element_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_clash", + "description": "Run clash/clearance checks for an element.", + "parameters": { + "type": "object", + "properties": { + "element_id": {"type": "integer"}, + "clearance": {"type": "number"}, + "tolerance": {"type": "number"}, + "scope": {"type": "string", "description": "storey or all"}, + }, + "required": ["element_id"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_contexts", + "description": "List all geometric representation contexts and subcontexts with their step IDs, context type, identifier, and target view. Use this to find the context ID required for geometry-creation API calls.", + "parameters": {"type": "object", "properties": {}, "required": [], "additionalProperties": False}, + }, + { + "type": "function", + "name": "ifc_materials", + "description": "List all materials and material sets (IfcMaterial, IfcMaterialLayerSet, IfcMaterialConstituentSet, IfcMaterialProfileSet) with their layers, constituents, or profiles.", + "parameters": {"type": "object", "properties": {}, "required": [], "additionalProperties": False}, + }, + { + "type": "function", + "name": "ifc_list", + "description": "List ifcopenshell.api modules or functions within a module.", + "parameters": { + "type": "object", + "properties": {"module": {"type": "string"}}, + "required": [], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_docs", + "description": "Get documentation for an ifcopenshell.api function, 'module.function'.", + "parameters": { + "type": "object", + "properties": {"function_path": {"type": "string"}}, + "required": ["function_path"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_edit", + "description": "Execute an ifcopenshell.api mutation; params is a JSON string of stringly-typed kwargs.", + "parameters": { + "type": "object", + "properties": {"function_path": {"type": "string"}, "params": {"type": "string"}}, + "required": ["function_path"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_validate", + "description": "Validate the loaded model. Returns valid bool and list of issues.", + "parameters": { + "type": "object", + "properties": { + "express_rules": {"type": "boolean", "description": "Also check EXPRESS rules (slower)"} + }, + "required": [], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_schedule", + "description": "List work schedules and nested tasks. Use max_depth=1 for top-level phases only on large projects.", + "parameters": { + "type": "object", + "properties": { + "max_depth": { + "type": "integer", + "description": "Max levels of subtask expansion (omit for unlimited)", + } + }, + "required": [], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_cost", + "description": "List cost schedules and nested cost items. Use max_depth=1 for top-level sections only on large BoQs.", + "parameters": { + "type": "object", + "properties": { + "max_depth": { + "type": "integer", + "description": "Max levels of cost item expansion (omit for unlimited)", + } + }, + "required": [], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_schema", + "description": "Return IFC class documentation for an entity type.", + "parameters": { + "type": "object", + "properties": {"entity_type": {"type": "string", "description": "IFC entity type, e.g. IfcWall"}}, + "required": ["entity_type"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_quantify", + "description": "Run quantity take-off (QTO) on the model. Modifies model in-place; call ifc_save() after.", + "parameters": { + "type": "object", + "properties": { + "rule": {"type": "string", "description": "QTO rule name, e.g. IFC4QtoBaseQuantities"}, + "selector": { + "type": "string", + "description": "ifcopenshell selector to restrict elements (default: all IfcElement)", + }, + }, + "required": ["rule"], + "additionalProperties": False, + }, + }, + { + "type": "function", + "name": "ifc_render", + "description": ( + "Render the loaded IFC model to a PNG image for visual inspection. " + "Use selector to restrict which elements are rendered (e.g. a single storey). " + "Use element_ids to highlight elements against a greyed-out background. " + "Returns base64-encoded PNG bytes." + ), + "parameters": { + "type": "object", + "properties": { + "selector": {"type": "string", "description": "ifcopenshell selector (default: whole model)"}, + "element_ids": { + "type": "array", + "items": {"type": "integer"}, + "description": "Step IDs of elements to highlight", + }, + "view": { + "type": "string", + "enum": ["iso", "top", "south", "north", "east", "west"], + "description": "Camera angle (default: iso)", + }, + }, + "required": [], + "additionalProperties": False, + }, + }, + ] diff --git a/src/ifcmcp/ifcmcp/embedded.py b/src/ifcmcp/ifcmcp/embedded.py new file mode 100644 index 0000000000..4ad99ba1fa --- /dev/null +++ b/src/ifcmcp/ifcmcp/embedded.py @@ -0,0 +1,60 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from ifcmcp.core import IfcSession + +session = IfcSession() + +# Optional imports only available under Pyodide +try: + from pyodide.ffi import JsProxy, to_py # type: ignore +except Exception: # pragma: no cover + JsProxy = None # type: ignore + to_py = None # type: ignore + + +def _coerce_args(args: Any) -> dict[str, Any]: + """Convert JS objects / JsProxy / mappings into a real Python dict.""" + if args is None: + return {} + + # Pyodide: JS object arrives as JsProxy; convert recursively to Python. + if JsProxy is not None and isinstance(args, JsProxy): + # dict_converter=dict ensures JS object -> Python dict (not Map) + return to_py(args, dict_converter=dict) + + # Already a Python dict + if isinstance(args, dict): + return args + + # Any Mapping-like object + if isinstance(args, Mapping): + return dict(args) + + # Last resort: try dict() coercion + try: + return dict(args) + except Exception as e: + raise TypeError(f"Tool args must be a mapping/dict; got {type(args)}") from e + + +def tools_openai() -> list[dict[str, Any]]: + return session.openai_tools() + + +def call_tool(name: str, args: Any = None) -> dict[str, Any]: + """ + Non-throwing tool dispatcher. + Always returns: {"ok": bool, "data": ...} or {"ok": false, "error": "...", "error_type": "...", ...} + """ + try: + py_args = _coerce_args(args) + data = session.dispatch(name, py_args) + return {"ok": True, "data": data} + + except Exception as e: + # Keep it short; avoid full tracebacks in tool output unless debugging. + return {"ok": False, "error_type": type(e).__name__, "error": str(e)} diff --git a/src/ifcmcp/ifcmcp/server.py b/src/ifcmcp/ifcmcp/server.py new file mode 100644 index 0000000000..08ba86a2cb --- /dev/null +++ b/src/ifcmcp/ifcmcp/server.py @@ -0,0 +1,231 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import base64 +from typing import Any + +from ifcmcp.core import IfcSession + +try: + from mcp.server.fastmcp import FastMCP # type: ignore + from mcp.types import ImageContent # type: ignore +except Exception: # pragma: no cover + FastMCP = None # type: ignore + ImageContent = None # type: ignore + + +def build_server() -> Any: + """Create the FastMCP server if the dependency is available.""" + if FastMCP is None: + raise ImportError( + "FastMCP is not installed. Install with: pip install ifcmcp[mcp] " "(or add 'mcp' to your environment)." + ) + + session = IfcSession() + + server = FastMCP( + name="ifc-mcp", + instructions=( + "MCP server for querying and editing IFC building models. " + "Load a file first with ifc_load, then use query/edit tools. " + "Save changes with ifc_save." + ), + ) + + # ---- Lifecycle ---- + @server.tool() + def ifc_new(schema: str = "IFC4") -> dict[str, Any]: + return session.ifc_new(schema=schema) + + @server.tool() + def ifc_load(path: str) -> str: + return session.ifc_load(path) + + @server.tool() + def ifc_save(path: str = "") -> str: + return session.ifc_save(path) + + @server.tool() + def ifc_reset() -> dict[str, Any]: + return session.ifc_reset() + + # ---- Query ---- + @server.tool() + def ifc_summary() -> dict[str, Any]: + return session.ifc_summary() + + @server.tool() + def ifc_tree() -> dict[str, Any] | list[dict[str, Any]]: + return session.ifc_tree() + + @server.tool() + def ifc_info(element_id: int) -> dict[str, Any]: + return session.ifc_info(element_id) + + @server.tool() + def ifc_select(query: str) -> list[dict[str, Any]]: + return session.ifc_select(query) + + @server.tool() + def ifc_relations(element_id: int, traverse: str = "") -> dict[str, Any] | list[dict[str, Any]]: + return session.ifc_relations(element_id, traverse=traverse) + + @server.tool() + def ifc_clash( + element_id: int, + clearance: float = 0.0, + tolerance: float = 0.002, + scope: str = "storey", + ) -> dict[str, Any]: + return session.ifc_clash( + element_id=element_id, + clearance=clearance, + tolerance=tolerance, + scope=scope, + ) + + @server.tool() + def ifc_contexts() -> list[dict[str, Any]]: + return session.ifc_contexts() + + @server.tool() + def ifc_materials() -> list[dict[str, Any]]: + return session.ifc_materials() + + # ---- Edit ---- + @server.tool() + def ifc_list(module: str = "") -> list[dict]: + return session.ifc_list(module=module) + + @server.tool() + def ifc_docs(function_path: str) -> dict: + return session.ifc_docs(function_path=function_path) + + @server.tool() + def ifc_edit(function_path: str, params: str = "{}") -> dict: + return session.ifc_edit(function_path=function_path, params=params) + + # ---- Extended query + edit ---- + @server.tool() + def ifc_validate(express_rules: bool = False) -> dict[str, Any]: + return session.ifc_validate(express_rules=express_rules) + + @server.tool() + def ifc_schedule(max_depth: int | None = None) -> list[dict[str, Any]]: + return session.ifc_schedule(max_depth=max_depth) + + @server.tool() + def ifc_cost(max_depth: int | None = None) -> list[dict[str, Any]]: + return session.ifc_cost(max_depth=max_depth) + + @server.tool() + def ifc_schema(entity_type: str) -> dict[str, Any]: + return session.ifc_schema(entity_type=entity_type) + + @server.tool() + def ifc_quantify(rule: str, selector: str = "") -> dict[str, Any]: + return session.ifc_quantify(rule=rule, selector=selector) + + # ---- Shape builder ---- + @server.tool() + def ifc_shape_list() -> list[dict]: + return session.ifc_shape_list() + + @server.tool() + def ifc_shape_docs(method: str) -> dict: + return session.ifc_shape_docs(method=method) + + @server.tool() + def ifc_shape(method: str, params: str = "{}") -> dict: + return session.ifc_shape(method=method, params=params) + + @server.tool(structured_output=False) + def ifc_plot( + selector: str = "", + element_ids: list[int] | None = None, + view: str = "floorplan", + width_mm: float = 297.0, + height_mm: float = 420.0, + scale: float = 1.0 / 100.0, + png_width: int = 1024, + png_height: int = 1024, + output_path: str = "", + ) -> list[ImageContent]: + """Generate a 2D technical drawing of the loaded IFC model. + + Returns an inline PNG image (floor plan, elevation, or section) that the + LLM can inspect to understand the 2D layout of the model. If + ``output_path`` is provided the drawing is also saved to disk — as SVG + when the path ends in ``.svg``, otherwise as PNG. + + :param selector: ifcopenshell selector to restrict plotted elements + (e.g. ``'IfcWall'``). Omit to plot the whole model. + :param element_ids: Step IDs of elements to highlight. Other elements + are faded so the subject stands out. + :param view: Drawing view — ``floorplan`` (default), ``elevation``, + ``section``, or ``auto``. + :param width_mm: Paper width in mm (default 297 = A4 landscape width). + :param height_mm: Paper height in mm (default 420 = A4 landscape height). + :param scale: Model-to-paper scale ratio (default 0.01 = 1:100). + :param png_width: Raster output width in pixels (default 1024). + :param png_height: Raster output height in pixels (default 1024). + :param output_path: Optional file path to save the drawing to disk. + """ + png_bytes = session.ifc_plot( + selector=selector, + element_ids=element_ids, + view=view, + width_mm=width_mm, + height_mm=height_mm, + scale=scale, + png_width=png_width, + png_height=png_height, + output_format="png", + ) + if output_path: + if output_path.endswith(".svg"): + svg_bytes = session.ifc_plot( + selector=selector, + element_ids=element_ids, + view=view, + width_mm=width_mm, + height_mm=height_mm, + scale=scale, + output_format="svg", + ) + with open(output_path, "wb") as f: + f.write(svg_bytes) + else: + with open(output_path, "wb") as f: + f.write(png_bytes) + return [ImageContent(type="image", data=base64.b64encode(png_bytes).decode(), mimeType="image/png")] + + @server.tool(structured_output=False) + def ifc_render( + selector: str = "", + element_ids: list[int] | None = None, + view: str = "iso", + output_path: str = "", + ) -> list[ImageContent]: + """Render the loaded IFC model to a PNG image. + + Returns an inline image the LLM can inspect to understand the spatial + layout of the model or a specific element in context. If + ``output_path`` is provided the PNG is also saved to that file path. + + :param selector: ifcopenshell selector to restrict rendered elements + (e.g. ``'IfcWall'``, ``'IfcBuildingStorey[Name="0"]'``). + Omit to render the whole model. + :param element_ids: Step IDs of elements to highlight. Other elements + are rendered in translucent grey so the subject stands out. + :param view: Camera angle — ``iso`` (default), ``top``, ``south``, + ``north``, ``east``, or ``west``. + :param output_path: Optional file path to save the PNG to disk. + """ + png_bytes = session.ifc_render(selector=selector, element_ids=element_ids, view=view) + if output_path: + with open(output_path, "wb") as f: + f.write(png_bytes) + return [ImageContent(type="image", data=base64.b64encode(png_bytes).decode(), mimeType="image/png")] + + return server diff --git a/src/ifcmcp/pyproject.toml b/src/ifcmcp/pyproject.toml new file mode 100644 index 0000000000..295954d0c5 --- /dev/null +++ b/src/ifcmcp/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ifcopenshell-mcp" +version = "0.0.0" +authors = [ + { name="Bruno Postle", email="bruno@postle.net" }, +] +description = "MCP server for querying and editing IFC building models" +readme = "README.md" +keywords = ["IFC", "BIM", "MCP"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", +] +dependencies = ["ifcopenshell", "ifcquery", "ifcedit"] + +[project.optional-dependencies] +mcp = ["mcp"] + +[project.scripts] +ifcmcp = "ifcmcp.__main__:main" + +[project.urls] +Homepage = "http://ifcopenshell.org" +Documentation = "https://docs.ifcopenshell.org" +Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues" + +[tool.setuptools.packages.find] +include = ["ifcmcp*"] +exclude = ["test*"] + +[tool.ruff] +extend = "../../pyproject.toml" diff --git a/src/ifcmcp/tests/__init__.py b/src/ifcmcp/tests/__init__.py new file mode 100644 index 0000000000..0a3bc271a0 --- /dev/null +++ b/src/ifcmcp/tests/__init__.py @@ -0,0 +1 @@ +# This file was generated with the assistance of an AI coding tool. diff --git a/src/ifcmcp/tests/conftest.py b/src/ifcmcp/tests/conftest.py new file mode 100644 index 0000000000..9fae4aef92 --- /dev/null +++ b/src/ifcmcp/tests/conftest.py @@ -0,0 +1,59 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + +from ifcmcp.core import IfcSession + + +@pytest.fixture +def session(): + return IfcSession() + + +@pytest.fixture +def model(): + """IFC4 model with a spatial hierarchy, a wall, and a slab.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + slab = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSlab", name="Slab001") + ifcopenshell.api.spatial.assign_container(f, products=[slab], relating_structure=storey) + + return f + + +@pytest.fixture +def model_file(model, tmp_path): + """Write the model fixture to a temp file and return the path.""" + path = tmp_path / "test.ifc" + model.write(str(path)) + return str(path) + + +@pytest.fixture +def loaded_session(model): + """An IfcSession with an in-memory model already loaded (no file path).""" + s = IfcSession() + s.model = model + return s diff --git a/src/ifcmcp/tests/test_edit.py b/src/ifcmcp/tests/test_edit.py new file mode 100644 index 0000000000..778b232249 --- /dev/null +++ b/src/ifcmcp/tests/test_edit.py @@ -0,0 +1,96 @@ +# This file was generated with the assistance of an AI coding tool. +import json + +import ifcopenshell +import pytest + +from ifcmcp.core import IfcSession, IfcSessionError + + +class TestNoModel: + def test_edit_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_edit("root.create_entity") + + +class TestList: + def test_list_all_modules(self, loaded_session): + result = loaded_session.ifc_list() + assert isinstance(result, list) + assert len(result) > 0 + modules = [m["module"] for m in result] + assert "root" in modules + assert "spatial" in modules + + def test_list_module_functions(self, loaded_session): + result = loaded_session.ifc_list(module="root") + assert isinstance(result, list) + names = [f["name"] for f in result] + assert "create_entity" in names + + def test_list_empty_string_returns_modules(self, loaded_session): + result = loaded_session.ifc_list(module="") + assert isinstance(result, list) + assert any(m["module"] == "root" for m in result) + + +class TestDocs: + def test_docs_create_entity(self, loaded_session): + result = loaded_session.ifc_docs("root.create_entity") + assert result["module"] == "root" + assert result["function"] == "create_entity" + assert "params" in result + + def test_docs_bad_format(self, loaded_session): + with pytest.raises(ValueError): + loaded_session.ifc_docs("no_dot_here") + + +class TestEdit: + def test_create_entity(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "NewWall"})) + assert result["ok"] is True + assert result["result"]["type"] == "IfcWall" + assert result["result"]["name"] == "NewWall" + + def test_create_entity_default_params(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", "{}") + assert result["ok"] is True + + def test_unknown_function(self, loaded_session): + result = loaded_session.ifc_edit("root.nonexistent", "{}") + assert result["ok"] is False + assert "Cannot find" in result["error"] + + def test_unknown_parameter(self, loaded_session): + result = loaded_session.ifc_edit("root.create_entity", json.dumps({"bogus": "value"})) + assert result["ok"] is False + assert "Unknown parameter" in result["error"] + + def test_bad_json(self, loaded_session): + with pytest.raises(json.JSONDecodeError): + loaded_session.ifc_edit("root.create_entity", "not json") + + def test_edit_does_not_save(self, loaded_session, tmp_path): + """Verify that ifc_edit mutates the in-memory model but does not write to disk.""" + path = str(tmp_path / "test.ifc") + loaded_session.model.write(path) + loaded_session.model_path = path + + before_count = sum(1 for _ in loaded_session.model) + loaded_session.ifc_edit("root.create_entity", json.dumps({"ifc_class": "IfcWall", "name": "Unsaved"})) + after_count = sum(1 for _ in loaded_session.model) + assert after_count == before_count + 1 + + on_disk = ifcopenshell.open(path) + disk_count = sum(1 for _ in on_disk) + assert disk_count == before_count + + def test_assign_container(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + storey = loaded_session.model.by_type("IfcBuildingStorey")[0] + result = loaded_session.ifc_edit( + "spatial.assign_container", + json.dumps({"products": str(wall.id()), "relating_structure": str(storey.id())}), + ) + assert result["ok"] is True diff --git a/src/ifcmcp/tests/test_query.py b/src/ifcmcp/tests/test_query.py new file mode 100644 index 0000000000..e0a28ae1c0 --- /dev/null +++ b/src/ifcmcp/tests/test_query.py @@ -0,0 +1,113 @@ +# This file was generated with the assistance of an AI coding tool. +import pytest + +from ifcmcp.core import IfcSessionError + + +class TestNoModel: + """All query tools should fail when no model is loaded.""" + + def test_summary_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_summary() + + def test_tree_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_tree() + + def test_info_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_info(1) + + def test_select_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_select("IfcWall") + + def test_relations_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_relations(1) + + +class TestSummary: + def test_schema(self, loaded_session): + result = loaded_session.ifc_summary() + assert result["schema"] == "IFC4" + + def test_total_entities(self, loaded_session): + result = loaded_session.ifc_summary() + assert result["total_entities"] > 0 + + def test_project_name(self, loaded_session): + result = loaded_session.ifc_summary() + assert result["project"]["name"] == "TestProject" + + def test_type_counts(self, loaded_session): + result = loaded_session.ifc_summary() + assert result["types"]["IfcWall"] == 1 + assert result["types"]["IfcSlab"] == 1 + + +class TestTree: + def test_root_is_project(self, loaded_session): + result = loaded_session.ifc_tree() + assert result["type"] == "IfcProject" + assert result["name"] == "TestProject" + + def test_hierarchy_depth(self, loaded_session): + result = loaded_session.ifc_tree() + site = result["children"][0] + assert site["type"] == "IfcSite" + building = site["children"][0] + assert building["type"] == "IfcBuilding" + storey = building["children"][0] + assert storey["type"] == "IfcBuildingStorey" + + +class TestInfo: + def test_wall_info(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_info(wall.id()) + assert result["id"] == wall.id() + assert result["type"] == "IfcWall" + + def test_invalid_id(self, loaded_session): + with pytest.raises(Exception): + loaded_session.ifc_info(999999) + + +class TestSelect: + def test_select_walls(self, loaded_session): + result = loaded_session.ifc_select("IfcWall") + assert len(result) == 1 + assert result[0]["type"] == "IfcWall" + assert result[0]["name"] == "Wall001" + + def test_select_slabs(self, loaded_session): + result = loaded_session.ifc_select("IfcSlab") + assert len(result) == 1 + assert result[0]["name"] == "Slab001" + + def test_select_no_match(self, loaded_session): + result = loaded_session.ifc_select("IfcWindow") + assert result == [] + + +class TestRelations: + def test_wall_relations(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id()) + assert result["id"] == wall.id() + assert result["type"] == "IfcWall" + assert "hierarchy" in result + + def test_traverse_up(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id(), traverse="up") + assert isinstance(result, list) + assert result[0]["type"] == "IfcWall" + assert result[-1]["type"] == "IfcProject" + + def test_traverse_empty_string_means_no_traverse(self, loaded_session): + wall = loaded_session.model.by_type("IfcWall")[0] + result = loaded_session.ifc_relations(wall.id(), traverse="") + assert isinstance(result, dict) diff --git a/src/ifcmcp/tests/test_server.py b/src/ifcmcp/tests/test_server.py new file mode 100644 index 0000000000..ed41434df7 --- /dev/null +++ b/src/ifcmcp/tests/test_server.py @@ -0,0 +1,106 @@ +# This file was generated with the assistance of an AI coding tool. +from unittest.mock import patch + +import pytest + +from ifcmcp.server import build_server + + +class TestServerRegistration: + def test_server_name(self): + server = build_server() + assert server.name == "ifc-mcp" + + def test_all_tools_registered(self): + server = build_server() + tools = [t.name for t in server._tool_manager.list_tools()] + expected = [ + "ifc_load", + "ifc_save", + "ifc_summary", + "ifc_tree", + "ifc_info", + "ifc_select", + "ifc_relations", + "ifc_clash", + "ifc_list", + "ifc_docs", + "ifc_edit", + ] + for name in expected: + assert name in tools, f"Tool {name} not registered" + + +@pytest.fixture +def tool_fns(): + """Return a dict of tool name → raw function from a freshly built server.""" + server = build_server() + return {t.name: t.fn for t in server._tool_manager.list_tools()} + + +PNG_FAKE = b"\x89PNG\r\n\x1a\nFAKE" +SVG_FAKE = b"FAKE" + + +class TestRenderOutputPath: + def test_no_output_path_no_file_written(self, tool_fns, tmp_path): + with patch("ifcmcp.core.IfcSession.ifc_render", return_value=PNG_FAKE): + tool_fns["ifc_render"](selector="", element_ids=None, view="iso", output_path="") + assert list(tmp_path.iterdir()) == [] + + def test_png_output_path_writes_file(self, tool_fns, tmp_path): + out = str(tmp_path / "render.png") + with patch("ifcmcp.core.IfcSession.ifc_render", return_value=PNG_FAKE): + tool_fns["ifc_render"](selector="", element_ids=None, view="iso", output_path=out) + assert open(out, "rb").read() == PNG_FAKE + + +class TestPlotOutputPath: + def test_no_output_path_no_file_written(self, tool_fns, tmp_path): + with patch("ifcmcp.core.IfcSession.ifc_plot", return_value=PNG_FAKE): + tool_fns["ifc_plot"]( + selector="", + element_ids=None, + view="floorplan", + width_mm=297.0, + height_mm=420.0, + scale=0.01, + png_width=1024, + png_height=1024, + output_path="", + ) + assert list(tmp_path.iterdir()) == [] + + def test_png_output_path_writes_png(self, tool_fns, tmp_path): + out = str(tmp_path / "plot.png") + with patch("ifcmcp.core.IfcSession.ifc_plot", return_value=PNG_FAKE): + tool_fns["ifc_plot"]( + selector="", + element_ids=None, + view="floorplan", + width_mm=297.0, + height_mm=420.0, + scale=0.01, + png_width=1024, + png_height=1024, + output_path=out, + ) + assert open(out, "rb").read() == PNG_FAKE + + def test_svg_output_path_writes_svg(self, tool_fns, tmp_path): + out = str(tmp_path / "plot.svg") + # ifc_plot is called twice: once with "png" for the inline image, + # once with "svg" for the file. + with patch("ifcmcp.core.IfcSession.ifc_plot", side_effect=[PNG_FAKE, SVG_FAKE]): + tool_fns["ifc_plot"]( + selector="", + element_ids=None, + view="floorplan", + width_mm=297.0, + height_mm=420.0, + scale=0.01, + png_width=1024, + png_height=1024, + output_path=out, + ) + assert open(out, "rb").read() == SVG_FAKE diff --git a/src/ifcmcp/tests/test_session.py b/src/ifcmcp/tests/test_session.py new file mode 100644 index 0000000000..4fa59120c0 --- /dev/null +++ b/src/ifcmcp/tests/test_session.py @@ -0,0 +1,62 @@ +# This file was generated with the assistance of an AI coding tool. +from unittest.mock import patch + +import ifcopenshell +import pytest + +from ifcmcp.core import IfcSession, IfcSessionError + + +class TestLoad: + def test_load_file(self, session, model_file): + result = session.ifc_load(model_file) + assert "IFC4" in result + assert session.model is not None + assert session.model_path == model_file + + def test_load_sets_entity_count(self, session, model_file): + result = session.ifc_load(model_file) + assert "entities" in result + + def test_load_nonexistent_file(self, session): + with pytest.raises(Exception): + session.ifc_load("/nonexistent/path/model.ifc") + + +class TestSave: + def test_save_no_model(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_save() + + def test_save_overwrites_original(self, session, model_file): + session.ifc_load(model_file) + result = session.ifc_save() + assert model_file in result + + def test_save_to_new_path(self, session, model_file, tmp_path): + session.ifc_load(model_file) + new_path = str(tmp_path / "output.ifc") + result = session.ifc_save(new_path) + assert new_path in result + reloaded = ifcopenshell.open(new_path) + assert reloaded.schema == "IFC4" + + def test_save_no_path_no_original(self, loaded_session): + with pytest.raises(IfcSessionError, match="No path specified"): + loaded_session.ifc_save() + + +class TestIfcPlotOutputFormat: + """ifc_plot should pass output_format through to the underlying plot function.""" + + def test_default_output_format_is_png(self, loaded_session): + with patch("ifcmcp.core.plot_mod.plot", return_value=b"PNG_FAKE") as mock_plot: + loaded_session.ifc_plot() + mock_plot.assert_called_once() + assert mock_plot.call_args.kwargs["output_format"] == "png" + + def test_svg_output_format(self, loaded_session): + with patch("ifcmcp.core.plot_mod.plot", return_value=b"SVG_FAKE") as mock_plot: + result = loaded_session.ifc_plot(output_format="svg") + assert result == b"SVG_FAKE" + assert mock_plot.call_args.kwargs["output_format"] == "svg" diff --git a/src/ifcmcp/tests/test_shape.py b/src/ifcmcp/tests/test_shape.py new file mode 100644 index 0000000000..1b657d5730 --- /dev/null +++ b/src/ifcmcp/tests/test_shape.py @@ -0,0 +1,141 @@ +# This file was generated with the assistance of an AI coding tool. +import json + +import pytest + +from ifcmcp.core import IfcSessionError + + +class TestShapeList: + def test_returns_list(self, loaded_session): + result = loaded_session.ifc_shape_list() + assert isinstance(result, list) + assert len(result) > 0 + + def test_has_expected_methods(self, loaded_session): + result = loaded_session.ifc_shape_list() + names = [m["method"] for m in result] + assert "polyline" in names + assert "rectangle" in names + assert "extrude" in names + assert "profile" in names + assert "get_representation" in names + + def test_well_documented_methods_have_descriptions(self, loaded_session): + result = loaded_session.ifc_shape_list() + by_name = {m["method"]: m for m in result} + # These methods have detailed docstrings + for name in ("polyline", "extrude", "rectangle", "profile", "get_representation"): + assert by_name[name]["description"], f"'{name}' has no description" + + def test_no_private_methods(self, loaded_session): + result = loaded_session.ifc_shape_list() + assert not any(m["method"].startswith("_") for m in result) + + def test_does_not_require_model(self, session): + # ifc_shape_list is pure introspection — no model needed + result = session.ifc_shape_list() + assert isinstance(result, list) + + +class TestShapeDocs: + def test_extrude_docs(self, loaded_session): + result = loaded_session.ifc_shape_docs("extrude") + assert result["method"] == "extrude" + assert result["description"] + assert "params" in result + param_names = [p["name"] for p in result["params"]] + assert "profile_or_curve" in param_names + assert "magnitude" in param_names + + def test_has_return_type(self, loaded_session): + result = loaded_session.ifc_shape_docs("rectangle") + assert "return_type" in result + + def test_has_param_descriptions(self, loaded_session): + result = loaded_session.ifc_shape_docs("polyline") + params_with_desc = [p for p in result["params"] if "description" in p] + assert len(params_with_desc) > 0 + + def test_unknown_method(self, loaded_session): + with pytest.raises(ValueError, match="no method"): + loaded_session.ifc_shape_docs("nonexistent_method") + + def test_private_method_rejected(self, loaded_session): + with pytest.raises(ValueError): + loaded_session.ifc_shape_docs("__init__") + + def test_does_not_require_model(self, session): + result = session.ifc_shape_docs("circle") + assert result["method"] == "circle" + + +class TestShapeExecute: + def test_rectangle(self, loaded_session): + result = loaded_session.ifc_shape("rectangle", json.dumps({"size": [4.0, 0.2]})) + assert result["ok"] is True + assert result["result"]["type"] == "IfcIndexedPolyCurve" + + def test_circle(self, loaded_session): + result = loaded_session.ifc_shape("circle", json.dumps({"center": [0.0, 0.0], "radius": 0.5})) + assert result["ok"] is True + assert result["result"]["type"] == "IfcCircle" + + def test_extrude_chained_from_rectangle(self, loaded_session): + rect = loaded_session.ifc_shape("rectangle", json.dumps({"size": [4.0, 0.2]})) + rect_id = rect["result"]["id"] + result = loaded_session.ifc_shape("extrude", json.dumps({"profile_or_curve": rect_id, "magnitude": 3.0})) + assert result["ok"] is True + assert result["result"]["type"] == "IfcExtrudedAreaSolid" + + def test_entity_id_as_integer(self, loaded_session): + """Entity IDs should be accepted as plain integers (from JSON).""" + rect = loaded_session.ifc_shape("rectangle", json.dumps({"size": [1.0, 1.0]})) + rect_id = rect["result"]["id"] + # Pass as int, not string + result = loaded_session.ifc_shape("extrude", json.dumps({"profile_or_curve": rect_id, "magnitude": 1.0})) + assert result["ok"] is True + + def test_rotate_2d_point_returns_list(self, loaded_session): + """Methods returning numpy arrays should give back plain lists.""" + result = loaded_session.ifc_shape( + "rotate_2d_point", json.dumps({"point_2d": [1.0, 0.0], "angle": 90.0, "counter_clockwise": True}) + ) + assert result["ok"] is True + assert isinstance(result["result"], list) + assert len(result["result"]) == 2 + + def test_set_polyline_coords_returns_none(self, loaded_session): + """In-place methods that return None should give ok=True, result=None.""" + rect = loaded_session.ifc_shape("rectangle", json.dumps({"size": [2.0, 2.0]})) + rect_id = rect["result"]["id"] + result = loaded_session.ifc_shape( + "set_polyline_coords", + json.dumps({"polyline": rect_id, "coords": [[0.0, 0.0], [3.0, 0.0], [3.0, 3.0], [0.0, 3.0]]}), + ) + assert result["ok"] is True + assert result["result"] is None + + def test_unknown_method(self, loaded_session): + result = loaded_session.ifc_shape("nonexistent_method", "{}") + assert result["ok"] is False + assert "error" in result + + def test_private_method_rejected(self, loaded_session): + with pytest.raises(IfcSessionError): + loaded_session.ifc_shape("__init__", "{}") + + def test_no_model_raises(self, session): + with pytest.raises(IfcSessionError, match="No model loaded"): + session.ifc_shape("rectangle", "{}") + + def test_params_as_dict(self, loaded_session): + """params can be passed as a dict (not just a JSON string).""" + result = loaded_session.ifc_shape("rectangle", {"size": [2.0, 1.0]}) + assert result["ok"] is True + + def test_error_on_bad_params(self, loaded_session): + """Bad parameters should give ok=False with an error message.""" + result = loaded_session.ifc_shape("extrude", json.dumps({"profile_or_curve": 999999, "magnitude": 1.0})) + assert result["ok"] is False + assert "error" in result diff --git a/src/ifcopenshell-python/Makefile b/src/ifcopenshell-python/Makefile index 90c503d145..643735ba37 100644 --- a/src/ifcopenshell-python/Makefile +++ b/src/ifcopenshell-python/Makefile @@ -27,29 +27,15 @@ SED:=sed -i '' -e endif endif -# TODO: we should simplify this at some point... -ifeq ($(PYVERSION), py39) -PYNUMBER:=39 -endif -ifeq ($(PYVERSION), py310) -PYNUMBER:=310 -endif -ifeq ($(PYVERSION), py311) -PYNUMBER:=311 -endif -ifeq ($(PYVERSION), py312) -PYNUMBER:=312 -endif -ifeq ($(PYVERSION), py313) -PYNUMBER:=313 -endif -ifeq ($(PYVERSION), py314) -PYNUMBER:=314 -endif -ifndef PYNUMBER -$(error Unsupported PYVERSION '$(PYVERSION)') +SUPPORTED_PYVERSIONS := py310 py311 py312 py313 py314 + +ifeq ($(filter $(PYVERSION),$(SUPPORTED_PYVERSIONS)),) +$(error Unsupported PYVERSION=$(PYVERSION). Must be one of $(SUPPORTED_PYVERSIONS)) endif +PYMINOR:=$(subst py3,,$(PYVERSION)) +PYNUMBER:=3$(PYMINOR) + # We actually do support glibc 2.28-2.30 (see #5636) # but those are old and there's no demand for it. ifeq ($(PLATFORM), linux64) @@ -93,10 +79,6 @@ test-parallel: test-mathutils: pytest -p no:pytest-blender test/util/test_shape_builder.py -.PHONY: build-ids-docs -build-ids-docs: - mkdir -p test/build - cd test && python ids_doc_generator.py .PHONY: qa qa: diff --git a/src/ifcopenshell-python/docs/ifcedit.rst b/src/ifcopenshell-python/docs/ifcedit.rst new file mode 100644 index 0000000000..d5db9b7c82 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcedit.rst @@ -0,0 +1,119 @@ +.. This file was generated with the assistance of an AI coding tool. + +IfcEdit +======= + +IfcEdit is a CLI wrapper for the full ``ifcopenshell.api`` mutation API. It +exposes all editor functions — over 350 across 30+ modules — without requiring +you to write a Python script. It supports four subcommands: + +- **list** — list all API modules, or all functions within a module +- **docs** — show full documentation for a function (parameters, types, descriptions) +- **run** — execute a mutation against an IFC file +- **foreach** — apply an API function to each element in a JSON array read from stdin +- **quantify** — run quantity take-off using ifc5d rules; requires the IfcOpenShell C++ geometry bindings + +Installation +------------ + +:: + + pip install ifcedit + +Or install from source: + +1. :doc:`Install IfcOpenShell ` +2. `Clone the IfcOpenShell repository `_. +3. ``cd /path/to/IfcOpenShell/src/ifcedit`` +4. ``pip install .`` + +Usage +----- + +Discover available API functions:: + + $ ifcedit list + $ ifcedit list root + $ ifcedit list geometry + +Read documentation for a function:: + + $ ifcedit docs root.remove_product + $ ifcedit docs type.assign_type + +Execute a mutation (overwrites the input file by default):: + + $ ifcedit run model.ifc root.remove_product --product 42 + $ ifcedit run model.ifc type.assign_type --related_objects 10 --relating_type 20 + +Write to a separate output file:: + + $ ifcedit run model.ifc root.create_entity -o output.ifc --ifc_class IfcWall + +Dry-run to validate without modifying the file:: + + $ ifcedit run model.ifc root.remove_product --dry-run --product 42 + +Apply an API function to each element in a JSON array from stdin (``{field}`` +placeholders are substituted from each item; model is opened and saved once):: + + $ ifcquery model.ifc select 'IfcWindow' | ifcedit foreach model.ifc root.remove_product --product {id} + $ ifcquery model.ifc select 'IfcDoor' | ifcedit foreach model.ifc attribute.edit_attributes \ + --product {id} --attributes '{"Name": "Door"}' + +Write to a separate output file instead of overwriting:: + + $ ifcquery model.ifc select 'IfcWall' | ifcedit foreach model.ifc root.remove_product -o output.ifc --product {id} + +Quantity take-off (writes ``IfcElementQuantity`` psets back to the file; requires C++ geometry bindings):: + + $ ifcedit quantify list + $ ifcedit quantify run model.ifc IFC4QtoBaseQuantities + $ ifcedit quantify run model.ifc IFC4QtoBaseQuantities --selector IfcWall + $ ifcedit quantify run model.ifc IFC4QtoBaseQuantities -o model_qto.ifc + +Parameter types +--------------- + +IfcEdit automatically coerces CLI string arguments to the correct Python types +using the type hints on each API function: + +.. list-table:: + :header-rows: 1 + + * - Type + - CLI input + - Python value + * - ``str`` + - ``"hello"`` + - ``"hello"`` + * - ``int`` + - ``"42"`` or ``"#42"`` + - ``42`` + * - ``float`` + - ``"3.14"`` + - ``3.14`` + * - ``bool`` + - ``"true"``, ``"1"``, ``"yes"`` + - ``True`` + * - ``Optional[X]`` + - ``"none"`` + - ``None`` + * - ``entity_instance`` + - ``"42"`` or ``"#42"`` + - resolved from model by step ID + * - ``list[entity_instance]`` + - ``"5,6,7"`` + - list of resolved entities + * - ``dict`` + - ``'{"key": "val"}'`` + - parsed JSON object + * - ``Literal["A", "B"]`` + - ``"A"`` + - validated against allowed values + +.. seealso:: + + Use :doc:`IfcQuery ` for read-only inspection of IFC files, and + :doc:`IfcMCP ` for interactive AI-assisted editing with an in-memory + session. diff --git a/src/ifcopenshell-python/docs/ifcmcp.rst b/src/ifcopenshell-python/docs/ifcmcp.rst new file mode 100644 index 0000000000..2f7f0c39f0 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcmcp.rst @@ -0,0 +1,110 @@ +.. This file was generated with the assistance of an AI coding tool. + +IfcMCP +====== + +IfcMCP is an MCP (Model Context Protocol) server that exposes IfcOpenShell +query and edit tools to AI coding assistants such as Claude. It wraps +:doc:`IfcQuery ` and :doc:`IfcEdit `, holding the IFC model +in memory across tool calls so no file I/O is required between operations. + +The ``ifcmcp`` package can also be used directly as a Python library without +the MCP server layer. + +Installation +------------ + +To use IfcMCP as an MCP server, install it together with the ``mcp`` package:: + + pip install 'ifcmcp[mcp]' + +If you only want to use the library directly (without an MCP client):: + + pip install ifcmcp + +Or install from source: + +1. :doc:`Install IfcOpenShell ` +2. `Clone the IfcOpenShell repository `_. +3. ``cd /path/to/IfcOpenShell/src/ifcmcp`` +4. ``pip install '.[mcp]'`` + +Setup +----- + +Add the server to your MCP client. For Claude Code:: + + claude mcp add --transport stdio ifc -- ifcmcp + +Or add to ``.mcp.json``: + +.. code-block:: json + + { + "mcpServers": { + "ifc": { + "type": "stdio", + "command": "ifcmcp" + } + } + } + +Available tools +--------------- + +**Session tools** + +- ``ifc_new(schema="IFC4")`` — create a new empty model in memory +- ``ifc_load(path)`` — open an IFC file into memory +- ``ifc_reset()`` — unload the current model, freeing all session state +- ``ifc_save(path="")`` — write model to disk; empty path overwrites the original + +**Query tools** + +- ``ifc_summary()`` — schema version, entity counts, project metadata +- ``ifc_tree()`` — full spatial hierarchy +- ``ifc_info(element_id)`` — deep inspection by step ID +- ``ifc_select(query)`` — filter elements by IFC class +- ``ifc_relations(element_id, traverse="")`` — relationships for an element +- ``ifc_clash(element_id, ...)`` — geometric intersection and clearance checks +- ``ifc_validate(express_rules=False)`` — schema and constraint validation +- ``ifc_schedule(max_depth=None)`` — work schedules with nested task trees +- ``ifc_cost(max_depth=None)`` — cost schedules with nested cost item trees +- ``ifc_schema(entity_type)`` — IFC class documentation +- ``ifc_contexts()`` — geometric representation contexts +- ``ifc_materials()`` — material definitions + +**Drawing and rendering tools** + +- ``ifc_plot(...)`` — generate a 2D drawing via ``ifcopenshell.draw`` and return it as an inline image the AI assistant can inspect; SVG always available, PNG requires ``cairosvg`` +- ``ifc_render(...)`` — off-screen 3D render returned as an inline PNG image the AI assistant can inspect; requires ``pyvista`` and the IfcOpenShell C++ geometry bindings + +**ShapeBuilder tools** + +- ``ifc_shape_list()`` — list all available ``ShapeBuilder`` methods +- ``ifc_shape_docs(method)`` — documentation for a specific ``ShapeBuilder`` method +- ``ifc_shape(method, params="{}")`` — execute a ``ShapeBuilder`` method; entity references resolved by step ID + +**Edit tools** + +- ``ifc_list(module="")`` — list API modules or functions +- ``ifc_docs(function_path)`` — documentation for an API function +- ``ifc_edit(function_path, params="{}")`` — execute an ``ifcopenshell.api`` mutation +- ``ifc_quantify(rule, selector="")`` — run quantity take-off; writes ``IfcElementQuantity`` psets in-place + +Typical workflow +---------------- + +.. code-block:: text + + ifc_load("/path/to/model.ifc") + ifc_summary() + ifc_tree() + ifc_info(42) + ifc_edit("root.remove_product", '{"product": "42"}') + ifc_save() + +.. seealso:: + + :doc:`IfcQuery ` and :doc:`IfcEdit ` provide the same + functionality as standalone CLI tools for scripting and automation. diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst index 817630ad7d..3aa34a3aa8 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/geometry_creation.rst @@ -775,3 +775,52 @@ responsibility to make sure the geometry is correct. # Assign our new body geometry back to our beam ifcopenshell.api.geometry.assign_representation(model, product=beam, representation=representation) + +Moving assemblies +----------------- + +When moving an assembly and you want all children to follow, pass +``should_transform_children=True``. The default (``False``) rewrites each +child's local placement to preserve its world position, so the parent moves +but the children stay where they are. + +.. code-block:: python + + matrix = numpy.eye(4) + matrix[:,3][0:3] = (0, 0, 6) + + # Move the assembly; children travel with it. + ifcopenshell.api.geometry.edit_object_placement(model, + product=assembly, matrix=matrix, is_si=True, + should_transform_children=True) + +Clipping normals convention +--------------------------- + +The ``normal`` passed to :func:`geometry.clip_solid`, +:func:`geometry.clip_solid_bounded`, and the ``clippings`` parameter of +:func:`geometry.add_wall_representation` points toward the **removed** +material (the discarded side), not toward the kept material. + +.. code-block:: python + + # Clip the top of a wall to a lean-to slope. + # normal points upward into the wedge that will be removed. + bcr = ifcopenshell.api.geometry.clip_solid(model, + item=extrusion, + location=[0.0, 0.0, 3.26], + normal=[0.419, 0.0, 0.908]) + shape_representation.RepresentationType = "Clipping" + +Opening lifecycle +----------------- + +``feature.remove_feature`` permanently deletes the feature entity from the +model. Any fillings (windows, doors) that occupied the opening become +orphaned and must be separately removed via ``root.remove_product``. + +.. code-block:: python + + # Remove a window and its opening from a wall. + ifcopenshell.api.root.remove_product(model, product=window) + ifcopenshell.api.feature.remove_feature(model, feature=opening) diff --git a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst index 4e28e83ed4..5ae16c5ec0 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell-python/selector_syntax.rst @@ -257,7 +257,8 @@ nest formulas, for example ``concat(title("foo"), lower("Bar"))`` will produce "``join({{separator}}, {{values}})``", "``join(""-"", {{mats.Name}})``", "``Name1-Name2``", "Joins a list of items with a custom separator. By default, all lists a rendered as comma separated." "``{{value1}}[+-*/]{{value2}}``", "``{{z}}+3``", "``5``", "Does arithmetic. Typical operators such as +, -, \*, and / are allowed and can be mixed with other variables and formatting functions." -When using queries in an IfcAnnotation tag surround with backticks. -Examples: -````number({{Qto_WallBaseQuantities.Width}}, ",",".")```` or -````round({{Qto_BuildingElementProxyQuantities.NetVolume}},.1)```` +When using queries in an IfcAnnotation tag surround with backticks. Examples: + +- ````number({{Qto_WallBaseQuantities.Width}}, ",",".")```` +- ````round({{Qto_BuildingElementProxyQuantities.NetVolume}},.1)```` +- ````join(", OVER ", reverse({{material.item.Material.Name}}))```` diff --git a/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst b/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst index 351dec6a61..92f89def7f 100644 --- a/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst +++ b/src/ifcopenshell-python/docs/ifcopenshell/getting_started.rst @@ -30,7 +30,7 @@ would be with the use of templates as shown below. #include "ifcparse/Ifc4.h" #include "ifcparse/Ifc4x3_add2.h" - #define IFC_SCHEMA_SEQ (4x3_rc2)(4)(2x3) // TODO: Enumerate through all IFC schemas you want to be able to process + #define IFC_SCHEMA_SEQ (4x3_add2)(4)(2x3) // TODO: Enumerate through all IFC schemas you want to be able to process #define EXPAND_AND_CONCATENATE(elem) Ifc##elem #define PROCESS_FOR_SCHEMA(r, data, elem) if (schema_version == BOOST_PP_STRINGIZE(elem)) { parseIfc(file); } else diff --git a/src/ifcopenshell-python/docs/ifcquery.rst b/src/ifcopenshell-python/docs/ifcquery.rst new file mode 100644 index 0000000000..8735b63da2 --- /dev/null +++ b/src/ifcopenshell-python/docs/ifcquery.rst @@ -0,0 +1,102 @@ +.. This file was generated with the assistance of an AI coding tool. + +IfcQuery +======== + +IfcQuery is a CLI tool for querying and inspecting IFC building models. It +provides read-only subcommands for common inspection tasks, all outputting JSON +so results can be piped into other tools. + +Subcommands: + +- **summary** — schema version, entity counts, project metadata +- **tree** — full spatial hierarchy (IfcProject → Site → Building → Storeys → Spaces → Elements) +- **info** — deep inspection of any entity by step ID (attributes, property sets, placement matrix, type, material) +- **select** — filter elements by IFC class using the IfcOpenShell selector syntax +- **relations** — relationships for an element; use ``--traverse up`` to walk the hierarchy to IfcProject +- **clash** — geometric intersection and clearance checks; requires the IfcOpenShell C++ geometry bindings +- **validate** — schema and constraint validation; add ``--rules`` for a full EXPRESS check +- **schedule** — work schedules with nested task trees +- **cost** — cost schedules with nested cost item trees +- **schema** — IFC class documentation using the loaded model's schema version +- **contexts** — geometric representation contexts +- **materials** — material definitions (IfcMaterial, layer sets, constituent sets, profile sets) +- **plot** — generate a drawing (SVG or PNG) using ``ifcopenshell.draw``; PNG output requires ``cairosvg`` +- **render** — off-screen 3D render to a PNG image; requires ``pyvista`` and the IfcOpenShell C++ geometry bindings + +All subcommands accept ``--format json|text|ids`` to control output (default: ``json``): + +- ``json`` — structured JSON, suitable for piping to ``jq`` or ``ifcedit foreach`` +- ``text`` — indented human-readable output +- ``ids`` — comma-separated step IDs extracted from list results, suitable for piping directly into ``ifcedit run`` parameters + +Installation +------------ + +:: + + pip install ifcquery + +For PNG output from ``plot``, also install ``cairosvg``:: + + pip install cairosvg + +For 3D rendering with ``render``, also install ``pyvista``:: + + pip install pyvista + +Or install from source: + +1. :doc:`Install IfcOpenShell ` +2. `Clone the IfcOpenShell repository `_. +3. ``cd /path/to/IfcOpenShell/src/ifcquery`` +4. ``pip install .`` + +Usage +----- + +:: + + $ ifcquery model.ifc summary + $ ifcquery model.ifc tree + $ ifcquery model.ifc info 42 + $ ifcquery model.ifc select 'IfcWall' + $ ifcquery model.ifc relations 42 + $ ifcquery model.ifc relations 42 --traverse up + $ ifcquery model.ifc validate + $ ifcquery model.ifc validate --rules + $ ifcquery model.ifc schedule + $ ifcquery model.ifc cost + $ ifcquery model.ifc schema IfcWall + $ ifcquery model.ifc materials + $ ifcquery model.ifc plot -o floorplan.svg --out-format svg --view floorplan + $ ifcquery model.ifc plot -o floorplan.png --view floorplan + $ ifcquery model.ifc render -o model.png + $ ifcquery model.ifc --format ids select 'IfcWall' + +Scripting with ifcedit +---------------------- + +``ifcquery`` and ``ifcedit`` are designed to compose. Use ``--format ids`` to +pass query results directly into ``ifcedit run`` parameters, or pipe JSON into +``ifcedit foreach`` to apply an operation to every matching element:: + + # Aggregate — pass all IDs as a list parameter + $ ifcedit run model.ifc spatial.unassign_container \ + --products "$(ifcquery model.ifc --format ids select 'IfcWall')" + + # Fan-out — one operation per element, model opened and saved once + $ ifcquery model.ifc select 'IfcWindow' | ifcedit foreach model.ifc root.remove_product --product {id} + + # Render an element highlighted against everything related to it + $ ifcquery model.ifc render -o relations.png \ + --element "$(ifcquery model.ifc --format ids relations 42)" + + # Render a clash — subject and clashing elements highlighted together + $ ifcquery model.ifc render -o clash.png \ + --element "$(ifcquery model.ifc --format ids clash 42)" + +.. seealso:: + + Use :doc:`IfcEdit ` to make mutations to IFC files from the command + line, and :doc:`IfcMCP ` for interactive AI-assisted editing. diff --git a/src/ifcopenshell-python/docs/index.rst b/src/ifcopenshell-python/docs/index.rst index 8d241bbf74..895dbece88 100644 --- a/src/ifcopenshell-python/docs/index.rst +++ b/src/ifcopenshell-python/docs/index.rst @@ -30,9 +30,12 @@ Let's learn IfcOpenShell! ifcclash ifccsv ifcdiff + ifcedit ifcfm ifcmax + ifcmcp ifcpatch + ifcquery ifcsverchok ifctester other diff --git a/src/ifcopenshell-python/docs/introduction.rst b/src/ifcopenshell-python/docs/introduction.rst index 58430b6da8..39b963e8f7 100644 --- a/src/ifcopenshell-python/docs/introduction.rst +++ b/src/ifcopenshell-python/docs/introduction.rst @@ -69,9 +69,12 @@ IfcOpenShell is a modular ecosystem of tools that work together, where each tool "`IfcClash `_", "A CLI utility and library that lets you perform clash detection on one or more IFC models. Clashes are defined in terms of clash sets with filters using the IFC query syntax." "`IfcCSV `_", "View and edit IFC data using spreadsheets or tabular datasets, such as CSV, ODS, XLSX, Pandas DataFrames, and regular Python lists." "`IfcDiff `_", "A CLI utility and library that lets you compare the changes between two IFC models." + "`IfcEdit `_", "A CLI wrapper for all ifcopenshell.api mutation functions. Browse available API modules, read per-function documentation, and run any API function against an IFC file from the command line." "`IfcFM `_", "A highly standards-compliant tool (e.g. COBie 2.4, COBie 3.0, AOH-BSEM) to convert FM data in IFC databases to spreadsheets and other machine readable formats, such as ODS, XLSX, CSV, Pandas, XML, and JSON." "`IfcMax `_", "A 3ds Max importer plugin able to import the IFC file format." + "`IfcMCP `_", "An MCP (Model Context Protocol) server that exposes IfcOpenShell query and edit tools to AI coding assistants. Loads a model into memory and keeps it there across tool calls, so no file I/O is needed between operations." "`IfcPatch `_", "A CLI utility and library that lets you run and distribute predetermined modifications on an IFC file, known as a patch recipe. Useful in deploying a data pipeline or batch-fixing external models." + "`IfcQuery `_", "A CLI tool for querying and inspecting IFC building models. Subcommands cover spatial hierarchy, element inspection, relationship traversal, clash detection, schema documentation, work schedules, and cost schedules." "`IfcSverchok `_", "A node based visual programming add-on for Blender to interact with IFC and Sverchok." "`IfcTester `_", "Author and read Information Delivery Specification (IDS) files. You can validate IFC models against IDS and generate reports in multiple formats. It works from the command line, as a web app, or as a library." "`VoxelisationToolkit `_", "Converts .ifc geometry into voxels, and lets you perform voxel based geometric analysis." diff --git a/src/ifcopenshell-python/ifcopenshell/__init__.py b/src/ifcopenshell-python/ifcopenshell/__init__.py index 998eb6e5de..7d247c6fab 100644 --- a/src/ifcopenshell-python/ifcopenshell/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/__init__.py @@ -111,8 +111,8 @@ __all__ = [ ] try: - from .stream import stream, stream_entity - from .stream import stream as _stream + from .stream import stream, stream_entity # ty: ignore[possibly-missing-import] + from .stream import stream as _stream # ty: ignore[possibly-missing-import] except: pass @@ -131,17 +131,21 @@ class SchemaError(Error): @overload def open( - path: Union[os.PathLike, str], format: Optional[str] = None, *, should_stream: Literal[False] = False + path: Union[os.PathLike, str], format: SupportedFormat = None, *, should_stream: Literal[False] = False ) -> Union[_file, sqlite]: ... @overload -def open(path: Union[os.PathLike, str], format: Optional[str] = None, *, should_stream: Literal[True]) -> _stream: ... +def open(path: Union[os.PathLike, str], format: SupportedFormat = None, *, should_stream: Literal[True]) -> _stream: ... @overload def open( - path: Union[os.PathLike, str], format: Optional[str] = None, *, should_stream: bool = False, readonly: bool = False + path: Union[os.PathLike, str], + format: SupportedFormat = None, + *, + should_stream: bool = False, + readonly: bool = False, ) -> Union[_file, sqlite, _stream]: ... def open( path: Union[os.PathLike, str], - format: Optional[str] = None, + format: SupportedFormat = None, should_stream: bool = False, readonly: bool = False, mmap: bool = False, @@ -153,8 +157,7 @@ def open( for reading large files. You can specify a file format. If no format is given, it is guessed from - its extension. Currently supported specified format: .ifc | .ifcZIP | - .ifcXML. + its extension. You can then filter by element ID, class, etc, and subscript by id or guid. @@ -199,11 +202,13 @@ def open( for ty in bypass_types: f.bypass_type(ty) if mmap: - f.initialize(str(path.absolute()), mmap=mmap) + # mmap parameter is only available for builds with USE_MMAP, not used in our main builds + f.initialize(str(path.absolute()), mmap=mmap) # ty: ignore[unknown-argument] else: f.initialize(str(path.absolute())) elif mmap: - f = ifcopenshell_wrapper.open(str(path.absolute()), mmap=mmap) + # mmap parameter is only available for builds with USE_MMAP, not used in our main builds + f = ifcopenshell_wrapper.open(str(path.absolute()), mmap=mmap) # ty: ignore[unknown-argument] else: f = ifcopenshell_wrapper.open(str(path.absolute())) return file(f) @@ -286,7 +291,10 @@ def schema_by_name( return ifcopenshell_wrapper.schema_by_name(schema) -def guess_format(path: Path) -> Literal[".ifc", ".ifcZIP", ".ifcXML", ".ifcJSON", ".ifcSQLite", None]: +SupportedFormat = Literal[".ifc", ".ifcZIP", ".ifcXML", ".ifcJSON", ".ifcSQLite", "rocksdb", None] + + +def guess_format(path: Path) -> SupportedFormat: """Guesses the IFC format using file extension IFCs may be serialised as different formats. The most common is a ``.ifc`` diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index 49c3b471dd..f485e865fb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -42,7 +42,6 @@ import importlib import inspect import json from collections.abc import Callable -from functools import partial from typing import TYPE_CHECKING, Any, Optional import numpy @@ -90,11 +89,7 @@ def renamed_arguments_deprecation( # "group.add_group": partial( # renamed_arguments_deprecation, arguments_remapped={"Name": "name", "Description": "description"} # ), -ARGUMENTS_DEPRECATION: dict[str, Callable[[str, dict[str, Any]], tuple[str, dict[str, Any]]]] = { - "control.assign_control": partial( - batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects" - ), -} +ARGUMENTS_DEPRECATION: dict[str, Callable[[str, dict[str, Any]], tuple[str, dict[str, Any]]]] = {} CACHED_USECASE_CLASSES: dict[str, Callable] = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py b/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py index f974ea2cd2..9d988a4e82 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py +++ b/src/ifcopenshell-python/ifcopenshell/api/boundary/assign_connection_geometry.py @@ -108,7 +108,7 @@ class Usecase: self.rel_space_boundary.ConnectionGeometry = connection_geometry def create_point(self, point: npt.NDArray) -> ifcopenshell.entity_instance: - return self.file.create_enitty("IfcCartesianPoint", ifc_safe_vector_type(point / self.unit_scale)) + return self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(point / self.unit_scale)) def close_polyline( self, points: tuple[ifcopenshell.entity_instance, ...] @@ -127,7 +127,7 @@ class Usecase: return self.file.createIfcPlane( self.file.createIfcAxis2Placement3D( self.create_point(location), - self.file.createIfcDirection(axis), - self.file.createIfcDirection(ref_direction), + self.file.createIfcDirection(ifc_safe_vector_type(axis)), + self.file.createIfcDirection(ifc_safe_vector_type(ref_direction)), ) ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/boundary/edit_attributes.py b/src/ifcopenshell-python/ifcopenshell/api/boundary/edit_attributes.py index ee11094d13..4fa5516bc6 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/boundary/edit_attributes.py +++ b/src/ifcopenshell-python/ifcopenshell/api/boundary/edit_attributes.py @@ -27,12 +27,11 @@ def edit_attributes( related_building_element: ifcopenshell.entity_instance, parent_boundary: Optional[ifcopenshell.entity_instance] = None, corresponding_boundary: Optional[ifcopenshell.entity_instance] = None, + physical_or_virtual: str = "NOTDEFINED", + internal_or_external: str = "NOTDEFINED", ) -> None: """Modify the relationships of a space boundary relationship - Currently this function is quite minimal and offers no advantage to - manual assignment of the space boundary attributes. - :param entity: The IfcRelSpaceBoundary to modify :param relating_space: The IfcSpace or IfcExternalSpatialElement that the space boundary is related to. @@ -44,17 +43,18 @@ def edit_attributes( :param corresponding_boundary: The other IfcRelSpaceBoundary on the other side of the related element. The pair together represents a thermal boundary. This only applies to 2nd level boundaries. + :param physical_or_virtual: IfcPhysicalOrVirtualEnum value: "PHYSICAL", + "VIRTUAL", or "NOTDEFINED". + :param internal_or_external: IfcInternalOrExternalEnum value: + "INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER", + "EXTERNAL_FIRE", or "NOTDEFINED". :return: None """ - entity = entity - relating_space = relating_space - related_building_element = related_building_element - parent_boundary = parent_boundary - corresponding_boundary = corresponding_boundary - entity.RelatingSpace = relating_space entity.RelatedBuildingElement = related_building_element if hasattr(entity, "ParentBoundary"): entity.ParentBoundary = parent_boundary if hasattr(entity, "CorrespondingBoundary"): entity.CorrespondingBoundary = corresponding_boundary + entity.PhysicalOrVirtualBoundary = physical_or_virtual + entity.InternalOrExternalBoundary = internal_or_external diff --git a/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py b/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py index bb4dcff79a..806782b1e4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py +++ b/src/ifcopenshell-python/ifcopenshell/api/context/remove_context.py @@ -51,8 +51,10 @@ def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instanc new = context.ParentContext for inverse in file.get_inverse(context): if inverse.is_a("IfcCoordinateOperation"): + # Trick to make sure the coordinate operation is not referenced + # by a context so we can delete it safely inverse.SourceCRS = inverse.TargetCRS - ifcopenshell.util.element.remove_deep(file, inverse) + ifcopenshell.util.element.remove_deep2(file, inverse) else: ifcopenshell.util.element.replace_attribute(inverse, context, new) file.remove(context) diff --git a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py index e1be553922..3a9bbe0cde 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py +++ b/src/ifcopenshell-python/ifcopenshell/api/cost/edit_cost_value.py @@ -59,6 +59,6 @@ def edit_cost_value( value["ValueComponent"], ) value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"]) - if old_unit_basis and file.get_total_inverses(old_unit_basis) == 0: - ifcopenshell.util.element.remove_deep(file, old_unit_basis) + if old_unit_basis: + ifcopenshell.util.element.remove_deep2(file, old_unit_basis) setattr(cost_value, name, value) diff --git a/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py index ff847b39eb..100e886214 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py +++ b/src/ifcopenshell-python/ifcopenshell/api/feature/remove_feature.py @@ -22,11 +22,13 @@ import ifcopenshell.util.element def remove_feature(file: ifcopenshell.file, feature: ifcopenshell.entity_instance) -> None: - """Remove a feature + """Permanently delete a feature element and its void or projection relationship. - Fillings are retained as orphans. Featured elements remain. Features - cannot exist by themselves, so not only is the relationship removed, the - feature is also removed. + The feature entity (e.g. IfcOpeningElement) is removed from the model + along with its IfcRelVoidsElement or IfcRelProjectsElement relationship. + The host element (wall, slab, etc.) is unaffected. Any fillings (windows, + doors) that occupied the opening become orphaned and must be separately + deleted via root.remove_product. :param feature: The IfcFeatureElement to remove. diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py index 44728c2902..d845f4dc83 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/__init__.py @@ -25,7 +25,10 @@ geometry extrusions). from .. import wrap_usecases from .add_axis_representation import add_axis_representation +from .add_topology_representation import add_topology_representation from .add_boolean import add_boolean +from .clip_solid import clip_solid +from .clip_solid_bounded import clip_solid_bounded from .add_door_representation import add_door_representation from .add_footprint_representation import add_footprint_representation from .add_mesh_representation import add_mesh_representation @@ -50,6 +53,7 @@ from .disconnect_element import disconnect_element from .disconnect_path import disconnect_path from .edit_object_placement import edit_object_placement from .map_representation import map_representation +from .copy_representation import copy_representation from .regenerate_wall_representation import regenerate_wall_representation from .remove_boolean import remove_boolean from .remove_representation import remove_representation @@ -60,7 +64,11 @@ wrap_usecases(__path__, __name__) __all__ = [ "add_axis_representation", + "add_topology_representation", "add_boolean", + "clip_solid", + "clip_solid_bounded", + "copy_representation", "add_door_representation", "add_footprint_representation", "add_mesh_representation", diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py index 910df42d8c..80ebf5bb89 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_boolean.py @@ -31,17 +31,6 @@ def add_boolean( ) -> list[ifcopenshell.entity_instance]: """Adds a boolean operation to two or more representation items - If an IfcBooleanOperand is part of the top level items in an - IfcShapeRepresentation, it will be removed from that level whilst being - added to the IfcBooleanResult. This is because it is generally intuitive - that an item is either participating in a boolean operation, or being an - item in its own right, but not both. - - However, if an IfcBooleanOperand is part of another boolean operation - already, it will not be removed from the existing operation. A new - operation will be created, and therefore it will participate in two - operations. - This function protects against recursive booleans. After a boolean operation is made, since the items of @@ -101,9 +90,6 @@ def add_boolean( booleans = [] for second_item in second_items: - for inverse in file.get_inverse(second_item): - if inverse.is_a("IfcShapeRepresentation"): - inverse.Items = list(set(inverse.Items) - {second_item}) if first.is_a("IfcTesselatedFaceSet"): first.Closed = True # For now, trust the user to do the right thing. if second_item.is_a("IfcTesselatedFaceSet"): diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 0a1adb1ec1..e756cb07cf 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -20,11 +20,11 @@ from __future__ import annotations import math from typing import TYPE_CHECKING, Any, Literal, Optional, Union -import bmesh -import bpy.types +import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] +import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import numpy as np import numpy.typing as npt -from mathutils import Matrix, Vector +from mathutils import Matrix, Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import ifcopenshell.util.shape_builder import ifcopenshell.util.unit diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_topology_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_topology_representation.py new file mode 100644 index 0000000000..e01284fd4f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_topology_representation.py @@ -0,0 +1,97 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +from typing import Optional + +import ifcopenshell + +_ITEM_TYPE_TO_REP_TYPE = { + "IfcVertex": "Vertex", + "IfcVertexPoint": "Vertex", + "IfcEdge": "Edge", + "IfcOrientedEdge": "Edge", + "IfcEdgeCurve": "Edge", + "IfcEdgeLoop": "Edge", + "IfcPath": "Edge", + "IfcFace": "Face", + "IfcFaceSurface": "Face", + "IfcAdvancedFace": "Face", + "IfcClosedShell": "Face", + "IfcOpenShell": "Face", + "IfcConnectedFaceSet": "Face", +} + + +def add_topology_representation( + file: ifcopenshell.file, + context: ifcopenshell.entity_instance, + item: ifcopenshell.entity_instance, + representation_identifier: Optional[str] = None, + representation_type: Optional[str] = None, +) -> ifcopenshell.entity_instance: + """Adds an IfcTopologyRepresentation for a structural element + + Structural analysis elements (IfcStructuralSurfaceMember, + IfcStructuralCurveMember) use topology representations rather than solid + geometry. This is analogous to :func:`add_axis_representation` and + :func:`add_profile_representation` but produces an + IfcTopologyRepresentation instead of an IfcShapeRepresentation. + + The representation type ("Face", "Edge", "Vertex") is inferred from the + item's IFC class if not provided explicitly. + + :param context: The IfcGeometricRepresentationContext for the + representation, typically a Reference context. + :param item: The IfcTopologicalRepresentationItem (e.g. IfcFaceSurface, + IfcEdge) to include in the representation. + :param representation_identifier: The RepresentationIdentifier string. + Defaults to the context's ContextIdentifier. + :param representation_type: The RepresentationType string ("Face", + "Edge", "Vertex"). Inferred from item class if not given. + :return: The newly created IfcTopologyRepresentation entity. + + Example: + + .. code:: python + + context = ifcopenshell.util.representation.get_context( + model, "Model", "Reference", "GRAPH_VIEW") + face = model.createIfcFaceSurface(bounds, surface, True) + rep = ifcopenshell.api.geometry.add_topology_representation( + model, context=context, item=face) + ifcopenshell.api.geometry.assign_representation( + model, product=member, representation=rep) + """ + if representation_identifier is None: + representation_identifier = context.ContextIdentifier + + if representation_type is None: + for ifc_class, rep_type in _ITEM_TYPE_TO_REP_TYPE.items(): + if item.is_a(ifc_class): + representation_type = rep_type + break + else: + representation_type = "Undefined" + + return file.createIfcTopologyRepresentation( + context, + representation_identifier, + representation_type, + [item], + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py index ff62bae474..c542471896 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_wall_representation.py @@ -47,7 +47,9 @@ def add_wall_representation( :param thickness: The thickness of the wall in meters. :param x_angle: The slope angle along the wall's X-axis, in radians. :param clippings: List of clipping definitions. Clippings can be `Clipping` objects - or dictionaries of arguments for `Clipping.parse`. + or dictionaries of arguments for `Clipping.parse`. Each clipping has a + ``normal`` that points toward the removed material (the discarded side), + not toward the kept material; see :func:`clip_solid` for details. :param booleans: List of any existing IfcBooleanResults. :return: IfcShapeRepresentation. """ diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid.py new file mode 100644 index 0000000000..a83fd4385f --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid.py @@ -0,0 +1,86 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +from __future__ import annotations + +import json +from typing import Optional, Sequence + +import ifcopenshell.api.pset +import ifcopenshell.util.element +import ifcopenshell.util.unit +from ifcopenshell.util.data import Clipping + + +def clip_solid( + file: ifcopenshell.file, + item: ifcopenshell.entity_instance, + location: Sequence[float], + normal: Sequence[float], + element: Optional[ifcopenshell.entity_instance] = None, +) -> ifcopenshell.entity_instance: + """Clip a solid with a half-space plane, returning an IfcBooleanClippingResult. + + Convenience wrapper around :class:`ifcopenshell.util.data.Clipping` for + use with any solid. This is the same convention used by the ``clippings`` + parameter of :func:`add_wall_representation`. + + .. warning:: + + The ``normal`` points toward the **removed** material (the discarded + side), not toward the kept material. For a slope clip the normal + points upward into the removed wedge above the slope line. For a + side mitre the normal points outward away from the wall body. + + After clipping, set the parent ``IfcShapeRepresentation`` + ``RepresentationType`` to ``"Clipping"``. + + Example — trim an extruded solid to a lean-to slope (removed material is + above the slope):: + + bcr = ifcopenshell.api.run( + "geometry.clip_solid", model, + item=extrusion, + location=[0.0, 0.0, 3.26], + normal=[0.419, 0.0, 0.908], # points UP toward removed material + ) + + :param item: The solid to clip (``IfcSweptAreaSolid``, ``IfcSweptDiskSolid``, + or ``IfcBooleanClippingResult``). + :param location: A point on the clipping plane in the representation's + local coordinate system. + :param normal: Plane normal pointing toward the material to be removed + (see warning above). + :param element: If provided, the resulting ``IfcBooleanClippingResult`` is + registered in the element's ``BBIM_Boolean`` property set so that + :func:`regenerate_wall_representation` preserves it during regeneration. + :return: The resulting ``IfcBooleanClippingResult``. + """ + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) + clipping = Clipping(location=tuple(location), normal=tuple(normal)) + result = clipping.apply(file, item, unit_scale) + if element is not None: + pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean") + if pset_data: + pset = file.by_id(pset_data["id"]) + data = list(set(json.loads(pset_data["Data"]) + [result.id()])) + else: + pset = ifcopenshell.api.pset.add_pset(file, product=element, name="BBIM_Boolean") + data = [result.id()] + ifcopenshell.api.pset.edit_pset(file, pset=pset, properties={"Data": json.dumps(data)}) + return result diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid_bounded.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid_bounded.py new file mode 100644 index 0000000000..ac2e39c741 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/clip_solid_bounded.py @@ -0,0 +1,116 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +from __future__ import annotations + +import json +from typing import Optional, Sequence + +import numpy as np + +import ifcopenshell.api.pset +import ifcopenshell.util.element +import ifcopenshell.util.unit +from ifcopenshell.util.shape_builder import ShapeBuilder + + +def clip_solid_bounded( + file: ifcopenshell.file, + item: ifcopenshell.entity_instance, + location: Sequence[float], + normal: Sequence[float], + boundary_points: Sequence[Sequence[float]], + boundary_position: Sequence[float] = (0.0, 0.0, 0.0), + element: Optional[ifcopenshell.entity_instance] = None, +) -> ifcopenshell.entity_instance: + """Clip a solid with a polygonally bounded half-space, returning an IfcBooleanClippingResult. + + Like :func:`clip_solid`, but the boolean subtraction is restricted to the + region enclosed by ``boundary_points`` rather than extending across the + entire half-space. The clipping plane is still infinite, but material is + only removed within the extruded footprint of the polygon. + + The ``normal`` convention is the same as :func:`clip_solid`: it points + toward the **removed** material. + + After clipping, set the parent ``IfcShapeRepresentation`` + ``RepresentationType`` to ``"Clipping"``. + + Example:: + + bcr = ifcopenshell.api.run( + "geometry.clip_solid_bounded", model, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + + :param item: The solid to clip (``IfcSweptAreaSolid``, ``IfcSweptDiskSolid``, + or ``IfcBooleanClippingResult``). + :param location: A point on the clipping plane in the representation's + local coordinate system. + :param normal: Plane normal pointing toward the material to be removed. + :param boundary_points: 2D ``[x, y]`` points defining the closed polygonal + boundary in the coordinate system of ``boundary_position``. The polygon + is automatically closed — do not repeat the first point. + :param boundary_position: 3D origin of the boundary coordinate system + (axes default to the global X/Y/Z directions). Defaults to the origin. + :param element: If provided, the resulting ``IfcBooleanClippingResult`` is + registered in the element's ``BBIM_Boolean`` property set so that + :func:`regenerate_wall_representation` preserves it during regeneration. + :return: The resulting ``IfcBooleanClippingResult``. + """ + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) + builder = ShapeBuilder(file) + + normal_arr = np.array(normal) + if np.allclose(normal_arr, [0.0, 0.0, 1.0], atol=1e-2) or np.allclose(normal_arr, [0.0, 0.0, -1.0], atol=1e-2): + arbitrary_vector = np.array([0.0, 1.0, 0.0]) + else: + arbitrary_vector = np.array([0.0, 0.0, 1.0]) + x_axis = np.cross(normal_arr, arbitrary_vector) + x_axis /= np.linalg.norm(x_axis) + + scaled_location = [i / unit_scale for i in location] + plane_placement = builder.create_axis2_placement_3d(scaled_location, normal, x_axis) + plane = file.create_entity("IfcPlane", plane_placement) + + scaled_boundary_position = [i / unit_scale for i in boundary_position] + boundary_pos_entity = file.create_entity( + "IfcAxis2Placement3D", + file.create_entity("IfcCartesianPoint", scaled_boundary_position), + ) + + scaled_pts = [[p[0] / unit_scale, p[1] / unit_scale] for p in boundary_points] + scaled_pts.append(scaled_pts[0]) # close the polygon + ifc_pts = [file.create_entity("IfcCartesianPoint", p) for p in scaled_pts] + boundary = file.createIfcPolyline(ifc_pts) + + half_space = file.create_entity("IfcPolygonalBoundedHalfSpace", plane, False, boundary_pos_entity, boundary) + result = file.create_entity("IfcBooleanClippingResult", "DIFFERENCE", item, half_space) + if element is not None: + pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean") + if pset_data: + pset = file.by_id(pset_data["id"]) + data = list(set(json.loads(pset_data["Data"]) + [result.id()])) + else: + pset = ifcopenshell.api.pset.add_pset(file, product=element, name="BBIM_Boolean") + data = [result.id()] + ifcopenshell.api.pset.edit_pset(file, pset=pset, properties={"Data": json.dumps(data)}) + return result diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py index 3135bef116..64c9fddb62 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/connect_path.py @@ -31,6 +31,7 @@ def connect_path( relating_connection: str = "NOTDEFINED", related_connection: str = "NOTDEFINED", description: Optional[str] = None, + connection_geometry: Optional[ifcopenshell.entity_instance] = None, ) -> ifcopenshell.entity_instance: incompatible_connections: list[ifcopenshell.entity_instance] = [] for rel in relating_element.ConnectedTo: @@ -73,6 +74,7 @@ def connect_path( ifcopenshell.guid.new(), OwnerHistory=ifcopenshell.api.owner.create_owner_history(file), Description=description, + ConnectionGeometry=connection_geometry, RelatingElement=relating_element, RelatedElement=related_element, RelatingConnectionType=relating_connection, diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/copy_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/copy_representation.py new file mode 100644 index 0000000000..c2b4c485a9 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/copy_representation.py @@ -0,0 +1,76 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +from __future__ import annotations + +from typing import Optional + +import ifcopenshell.api.geometry +import ifcopenshell.util.element +import ifcopenshell.util.representation + + +def copy_representation( + file: ifcopenshell.file, + source: ifcopenshell.entity_instance, + target: ifcopenshell.entity_instance, + context_identifier: str = "Body", +) -> Optional[ifcopenshell.entity_instance]: + """Copy a geometric representation from one element to another. + + Finds the named representation on ``source``, deep-copies its entity + graph (geometry items, profiles, placements, etc.), and assigns the copy + to ``target``. Representation contexts are shared rather than copied. + If ``target`` already has a matching representation it is removed and + replaced. + + If no matching representation is found on ``source``, returns ``None`` + and leaves ``target`` unchanged. + + :param source: The element to copy the representation from. + :param target: The element to assign the copied representation to. + :param context_identifier: The RepresentationIdentifier to look up on + ``source`` (e.g. ``"Body"``, ``"Axis"``, ``"Box"``). + Defaults to ``"Body"``. + :return: The newly created IfcShapeRepresentation, or None if no + matching representation was found on ``source``. + + Example: + + .. code:: python + + wall_a = model.by_id(1) + wall_b = model.by_id(2) + + # Give wall_b the same body geometry as wall_a. + ifcopenshell.api.geometry.copy_representation(model, + source=wall_a, target=wall_b) + """ + source_rep = ifcopenshell.util.representation.get_representation(source, "Model", context_identifier) + if source_rep is None: + return None + + new_rep = ifcopenshell.util.element.copy_deep(file, source_rep, exclude=["IfcGeometricRepresentationContext"]) + + existing_rep = ifcopenshell.util.representation.get_representation(target, "Model", context_identifier) + if existing_rep: + ifcopenshell.api.geometry.unassign_representation(file, product=target, representation=existing_rep) + ifcopenshell.api.geometry.remove_representation(file, representation=existing_rep) + + ifcopenshell.api.geometry.assign_representation(file, product=target, representation=new_rep) + return new_rep diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py index 7fbe44ce12..0d86b997f5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py @@ -52,10 +52,11 @@ def edit_object_placement( :param is_si: If True, the matrix is given in SI units. If false, in project units. :param should_transform_children: A child element is a nested element, - opening, filling, etc. If true, child elements will move along with the - parent. If false, child elements will stay where they are. Because most - placements in IFC are relative, this means that if a child moves, we - actually don't change their placement. + opening, filling, etc. If True, child elements move along with the + parent; pass True when moving an assembly (roof, furniture group, etc.) + and you want all children to follow. If False (default), child elements + keep their current world positions; their local placements are rewritten + to compensate for the parent move. :return: The new or updated IfcLocalPlacement entity """ usecase = Usecase() diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py index 9143577438..e59c6e1efa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/regenerate_wall_representation.py @@ -69,6 +69,12 @@ def regenerate_wall_representation( additional extrusions are generated for each connection that boolean difference the base extrusion. + Clippings applied via :func:`geometry.clip_solid` or + :func:`geometry.clip_solid_bounded` are preserved only if the ``element`` + parameter was passed when creating them, which registers the result in the + ``BBIM_Boolean`` property set. Clippings created without that parameter + are silently discarded during regeneration. + This will also update the axis line representation (e.g. trim the axis line to any connections). diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py index 46bec8757b..3731b2fffc 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/validate_type.py @@ -83,6 +83,7 @@ def validate_type( if remaining_items: ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION") + representation.Items = [i for i in representation.Items if i not in remaining_items] representation.RepresentationType = ifcopenshell.util.representation.guess_type(representation.Items) if representation.RepresentationType == "CSG": diff --git a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py index 43ccaea2bc..c7e6dfba80 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py +++ b/src/ifcopenshell-python/ifcopenshell/api/georeference/add_georeferencing.py @@ -17,6 +17,7 @@ # along with IfcOpenShell. If not, see . import ifcopenshell +import ifcopenshell.api.georeference import ifcopenshell.api.pset import ifcopenshell.util.element @@ -63,8 +64,13 @@ def add_georeferencing(file: ifcopenshell.file, ifc_class: str = "IfcMapConversi }, ) return - if file.by_type("IfcProjectedCRS"): + has_crs = bool(file.by_type("IfcProjectedCRS")) + has_conversion = bool(file.by_type("IfcCoordinateOperation")) + if has_crs and has_conversion: return + if has_crs or has_conversion: + # This is technically invalid, but we shall forgive the industry here if they are wrong ... + ifcopenshell.api.georeference.remove_georeferencing(file) source_crs = None for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): if context.ContextType == "Model": diff --git a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py index 652fc89f56..f9bcacba8d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py +++ b/src/ifcopenshell-python/ifcopenshell/api/grid/remove_grid_axis.py @@ -42,7 +42,5 @@ def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance ifcopenshell.api.grid.remove_grid_axis(model, axis=axis_2) """ axis_curve = axis.AxisCurve - if file.get_total_inverses(axis_curve) == 1: - ifcopenshell.util.element.remove_deep(file, axis_curve) - file.remove(axis_curve) file.remove(axis) + ifcopenshell.util.element.remove_deep2(file, axis_curve) diff --git a/src/ifcopenshell-python/ifcopenshell/api/nest/assign_object.py b/src/ifcopenshell-python/ifcopenshell/api/nest/assign_object.py index d3432b617f..579d0e7a11 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/nest/assign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/nest/assign_object.py @@ -19,7 +19,9 @@ from typing import Union import ifcopenshell +import ifcopenshell.api.aggregate import ifcopenshell.api.owner +import ifcopenshell.api.spatial import ifcopenshell.guid import ifcopenshell.util.element @@ -137,7 +139,10 @@ def assign_object( if not objects_to_change: return is_nested_by - # NOTE: An object can both be nested and assigned to a container or an aggregate. + # Can be either only nested, aggregated, or contained at the same time. + possibly_contained = [o for o in objects_without_nests if hasattr(o, "ContainedInStructure")] + ifcopenshell.api.spatial.unassign_container(file, products=possibly_contained) + ifcopenshell.api.aggregate.unassign_object(file, products=objects_without_nests) # unassign elements from previous nests for nests in previous_nests_rels: diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py index 0f09bd4991..8a8f2307a4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/append_asset.py @@ -431,7 +431,7 @@ class Usecase: ) ifcopenshell.api.type.assign_type( self.file, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] related_objects=[element], relating_type=new_type, should_map_representations=False, diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py index e122ba766b..a28f6dfa46 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py @@ -53,9 +53,7 @@ def create_file(version: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> ifcope """ file = ifcopenshell.file(schema=version) file.header.file_name.name = "/dev/null" # Hehehe - file.header.file_name.time_stamp = ( - datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat() - ) + file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version) file.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version) file.header.file_name.authorization = "Nobody" diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py index ca8d5ba55e..2f81494bc7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_prop_template.py @@ -22,9 +22,9 @@ import ifcopenshell.util.element def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.entity_instance) -> None: """Removes a property template - Note that a property set template should always have at least one - property template to be valid, so take care when removing property - templates. + Note that a property set template should always have at least one property + template to be valid. So a property set template will not be removed if it + is the only template ina a property ste template. :param prop_template: The IfcSimplePropertyTemplate to remove. :return: None @@ -43,10 +43,8 @@ def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.en ifcopenshell.api.pset_template.remove_prop_template(model, prop_template=prop2) """ for inverse in file.get_inverse(prop_template): - if len(inverse.HasPropertyTemplates) == 1: - inverse.HasPropertyTemplates = [] - else: + if len(inverse.HasPropertyTemplates) > 1: has_property_templates = list(inverse.HasPropertyTemplates) has_property_templates.remove(prop_template) inverse.HasPropertyTemplates = has_property_templates - ifcopenshell.util.element.remove_deep(file, prop_template) + ifcopenshell.util.element.remove_deep2(file, prop_template) diff --git a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py index 07d5cf6daf..5ff3cf6c38 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py +++ b/src/ifcopenshell-python/ifcopenshell/api/pset_template/remove_pset_template.py @@ -38,4 +38,4 @@ def remove_pset_template(file: ifcopenshell.file, pset_template: ifcopenshell.en # Let's remove the template. ifcopenshell.api.pset_template.remove_pset_template(model, pset_template=template) """ - ifcopenshell.util.element.remove_deep(file, pset_template) + ifcopenshell.util.element.remove_deep2(file, pset_template) diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py index fc67a8d3c7..ceb49681b5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py @@ -79,5 +79,5 @@ def add_resource_quantity( old_quantity = resource.BaseQuantity resource.BaseQuantity = quantity if old_quantity: - ifcopenshell.util.element.remove_deep(file, old_quantity) + ifcopenshell.util.element.remove_deep2(file, old_quantity) return quantity diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py index a6b014e1db..afb470565d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/remove_resource_quantity.py @@ -47,4 +47,4 @@ def remove_resource_quantity(file: ifcopenshell.file, resource: ifcopenshell.ent old_quantity = resource.BaseQuantity resource.BaseQuantity = None if old_quantity: - ifcopenshell.util.element.remove_deep(file, old_quantity) + ifcopenshell.util.element.remove_deep2(file, old_quantity) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py index 302445483b..e7cca1dafa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py @@ -26,7 +26,7 @@ def assign_process( relating_process: ifcopenshell.entity_instance, related_object: ifcopenshell.entity_instance, ) -> ifcopenshell.entity_instance: - """Assigns an object to be related to a process, typically a construction task + """Assigns an object as an input, control, or resource of a process Processes work using the ICOM (Input, Controls, Outputs, Mechanisms) paradigm in IFC. This process model is commonly used in modeling @@ -63,6 +63,17 @@ def assign_process( For resources, any construction resource may be assigned to a task. + .. warning:: + + This function creates an **Input** relationship + (``IfcRelAssignsToProcess``), meaning the product is *consumed* or + *operated on* by the task — the typical case is demolition or + maintenance. + + If the task *constructs or installs* a product (e.g. erecting a wall + or fitting a window), use :func:`assign_product` instead, which + creates an **Output** relationship (``IfcRelAssignsToProduct``). + :param relating_process: The IfcProcess (typically IfcTask) that the input, control, or resource is related to. :param related_object: The IfcProduct (for input), IfcCostItem (for @@ -77,7 +88,7 @@ def assign_process( # need to be part of a work schedule. schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A") - # Let's create a construction task. Note that the predefined type is + # Let's create a demolition task. Note that the predefined type is # important to distinguish types of tasks. task = ifcopenshell.api.sequence.add_task(model, work_schedule=schedule, name="Demolish existing", identification="A", predefined_type="DEMOLITION") @@ -85,8 +96,12 @@ def assign_process( # Let's say we have a wall somewhere. wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall") - # Let's demolish that wall! + # The wall is an INPUT to the demolition task (it will be consumed). ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=wall) + + # For a construction task that BUILDS a wall, use assign_product instead: + # build_task = ifcopenshell.api.sequence.add_task(model, ..., predefined_type="CONSTRUCTION") + # ifcopenshell.api.sequence.assign_product(model, relating_product=wall, related_object=build_task) """ if related_object.HasAssignments: for assignment in related_object.HasAssignments: diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/structural/__init__.py index 2baa262432..8a21e73602 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/structural/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/__init__.py @@ -30,7 +30,9 @@ from .add_structural_load import add_structural_load from .add_structural_load_case import add_structural_load_case from .add_structural_load_group import add_structural_load_group from .add_structural_member_connection import add_structural_member_connection +from .assign_product import assign_product from .assign_structural_analysis_model import assign_structural_analysis_model +from .assign_to_building import assign_to_building from .edit_structural_analysis_model import edit_structural_analysis_model from .edit_structural_boundary_condition import edit_structural_boundary_condition from .edit_structural_connection_cs import edit_structural_connection_cs @@ -57,7 +59,9 @@ __all__ = [ "add_structural_load_case", "add_structural_load_group", "add_structural_member_connection", + "assign_product", "assign_structural_analysis_model", + "assign_to_building", "edit_structural_analysis_model", "edit_structural_boundary_condition", "edit_structural_connection_cs", diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/assign_product.py b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_product.py new file mode 100644 index 0000000000..a30672facd --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_product.py @@ -0,0 +1,64 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell +import ifcopenshell.api.root + + +def assign_product( + file: ifcopenshell.file, + relating_product: ifcopenshell.entity_instance, + related_object: ifcopenshell.entity_instance, +) -> ifcopenshell.entity_instance: + """Links an object to a product via IfcRelAssignsToProduct + + Typically used to associate a physical building element with a structural + analysis member (IfcStructuralSurfaceMember, IfcStructuralCurveMember) so + that analysis results can be traced back to the physical model. + + :param relating_product: The IfcProduct that the object is assigned to, + typically an IfcStructuralMember. + :param related_object: The IfcObjectDefinition being assigned, typically + a physical building element such as an IfcWall or IfcSlab. + :return: The IfcRelAssignsToProduct relationship. + + Example: + + .. code:: python + + wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall") + member = ifcopenshell.api.root.create_entity( + model, ifc_class="IfcStructuralSurfaceMember") + ifcopenshell.api.structural.assign_product(model, + relating_product=member, related_object=wall) + """ + for rel in relating_product.ReferencedBy or []: + if not rel.is_a("IfcRelAssignsToProduct"): + continue + if related_object in rel.RelatedObjects: + return rel + related_objects = list(rel.RelatedObjects) + related_objects.append(related_object) + rel.RelatedObjects = related_objects + return rel + + rel = ifcopenshell.api.root.create_entity(file, ifc_class="IfcRelAssignsToProduct") + rel.RelatingProduct = relating_product + rel.RelatedObjects = [related_object] + return rel diff --git a/src/ifcopenshell-python/ifcopenshell/api/structural/assign_to_building.py b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_to_building.py new file mode 100644 index 0000000000..7e70f20272 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/structural/assign_to_building.py @@ -0,0 +1,64 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell +import ifcopenshell.api.owner +import ifcopenshell.guid + + +def assign_to_building( + file: ifcopenshell.file, + structural_analysis_model: ifcopenshell.entity_instance, + building: ifcopenshell.entity_instance, +) -> ifcopenshell.entity_instance: + """Associates a structural analysis model with a building via IfcRelServicesBuildings + + The existing :func:`assign_structural_analysis_model` handles + IfcRelAssignsToGroup (linking structural members to the analysis model). + This function handles the separate model-to-building relationship, which + records which building the structural analysis model serves. + + :param structural_analysis_model: The IfcStructuralAnalysisModel to + associate with the building. + :param building: The IfcBuilding (or other IfcSpatialStructureElement) + that the structural analysis model serves. + :return: The IfcRelServicesBuildings relationship. + + Example: + + .. code:: python + + building = ifcopenshell.util.selector.filter_elements(model, "IfcBuilding")[0] + model_ = ifcopenshell.api.structural.add_structural_analysis_model(model) + ifcopenshell.api.structural.assign_to_building(model, + structural_analysis_model=model_, building=building) + """ + for rel in structural_analysis_model.ServicesBuildings or []: + if building in rel.RelatedBuildings: + return rel + rel.RelatedBuildings = list(rel.RelatedBuildings) + [building] + return rel + + return file.create_entity( + "IfcRelServicesBuildings", + ifcopenshell.guid.new(), + OwnerHistory=ifcopenshell.api.owner.create_owner_history(file), + RelatingSystem=structural_analysis_model, + RelatedBuildings=[building], + ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py index 49e1381da2..7ef4858041 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Any, Optional import ifcopenshell if TYPE_CHECKING: - import bpy + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] def add_surface_textures( diff --git a/src/ifcopenshell-python/ifcopenshell/api/system/disconnect_port.py b/src/ifcopenshell-python/ifcopenshell/api/system/disconnect_port.py index b6b72a192b..7ae790e753 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/system/disconnect_port.py +++ b/src/ifcopenshell-python/ifcopenshell/api/system/disconnect_port.py @@ -65,6 +65,8 @@ def disconnect_port(file: ifcopenshell.file, port: ifcopenshell.entity_instance) rels += port.ConnectedFrom or () for rel in rels: + rel.RelatingPort.FlowDirection = None + rel.RelatedPort.FlowDirection = None history = rel.OwnerHistory file.remove(rel) if history: diff --git a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py index 36ab2f73a2..2611df0b4e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py +++ b/src/ifcopenshell-python/ifcopenshell/api/unit/remove_unit.py @@ -47,4 +47,5 @@ def remove_unit(file: ifcopenshell.file, unit: ifcopenshell.entity_instance) -> unit_assignment.Units = units else: file.remove(unit_assignment) - ifcopenshell.util.element.remove_deep(file, unit) + # TODO handle other possible unit inverses + ifcopenshell.util.element.remove_deep2(file, unit) diff --git a/src/ifcopenshell-python/ifcopenshell/geom/__init__.py b/src/ifcopenshell-python/ifcopenshell/geom/__init__.py index 9ed96fa0c8..2b01d63925 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/__init__.py @@ -33,14 +33,14 @@ def _has_occ(): # Previous versions (pythonocc<=0.17.3) are using just OCC. try: - import OCC.Core.BRepTools + import OCC.Core.BRepTools # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] return True except ImportError: pass try: - import OCC.BRepTools # noqa: F401 + import OCC.BRepTools # noqa: F401 # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] return True except ImportError: diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index db6deecb15..9c36e4b933 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -28,7 +28,7 @@ from ..file import file from . import has_occ if TYPE_CHECKING: - from OCC.Core import TopoDS + from OCC.Core import TopoDS # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] IteratorOutput = Union["ShapeElementType", "utils.shape_tuple"] @@ -47,9 +47,9 @@ if has_occ: from . import occ_utils as utils try: - from OCC.Core import TopoDS + from OCC.Core import TopoDS # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] except ImportError: - from OCC import TopoDS + from OCC import TopoDS # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] def wrap_shape_creation(settings: settings, shape: ifcopenshell_wrapper.Element): if getattr(settings, "use_python_opencascade", False): @@ -89,6 +89,7 @@ SETTING = Literal[ "keep-bounding-boxes", "layerset-first", "length-unit", + "make-volume", "max-offset-deviation", "max-offset", "mesher-angular-deflection", @@ -124,6 +125,7 @@ SERIALIZER_SETTING = Literal[ "ecef", "digits", "wkt-use-section", + "separate-z-up-node", ] # NOTE: hybrid-cgal-simple-opencascade is added just as an example diff --git a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py index 8964b2392a..15a4dfc838 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py @@ -26,17 +26,33 @@ import warnings from collections.abc import Iterable from typing import NamedTuple, Union -import OCC +import OCC # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] from typing_extensions import assert_never import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper try: - from OCC.Core import AIS, BRepTools, Graphic3d, Quantity, TopoDS, V3d, gp + from OCC.Core import ( # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] + AIS, + BRepTools, + Graphic3d, + Quantity, + TopoDS, + V3d, + gp, + ) USE_OCCT_HANDLE = False except ImportError: - from OCC import AIS, BRepTools, Graphic3d, Quantity, TopoDS, V3d, gp + from OCC import ( # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] + AIS, + BRepTools, + Graphic3d, + Quantity, + TopoDS, + V3d, + gp, + ) USE_OCCT_HANDLE = True @@ -68,7 +84,7 @@ DEFAULT_STYLES = { def initialize_display(): - import OCC.Display.SimpleGui + import OCC.Display.SimpleGui # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] global handle, main_loop, add_menu, add_function_to_menu handle, main_loop, add_menu, add_function_to_menu = OCC.Display.SimpleGui.init_display() diff --git a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi index c906f57055..28caafc262 100644 --- a/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi +++ b/src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.pyi @@ -104,6 +104,7 @@ class IfcSpfHeader: https://standards.buildingsmart.org/documents/Implementation/ImplementationGuide_IFCHeaderData_Version_1.0.2.pdf """ + def __init__(self, *args): ... def file(self, *args): ... def file_description_py(self): ... def file_name_py(self): ... @@ -113,7 +114,8 @@ class IfcSpfHeader: def write(self, out): ... class BRep(Representation): - def as_compound(self, force_meters): ... + def __init__(self, settings, entity, id, shapes): ... + def as_compound(self, force_meters=False): ... def begin(self): ... def calculate_projected_surface_area(self, ax, along_x, along_y, along_z): ... def calculate_surface_area(self, arg2): ... @@ -125,6 +127,7 @@ class BRep(Representation): def size(self): ... class BRepElement(Element): + def __init__(self, id, parent_id, name, type, guid, context, trsf, geometry, product): ... def calculate_projected_surface_area(self, along_x, along_y, along_z): ... @property def geometry(self) -> BRep: ... @@ -135,6 +138,7 @@ class BRepElement(Element): def volume(self): ... class ColladaSerializer(WriteOnlyGeometrySerializer): + def __init__(self, dae_filename, geometry_settings, settings): ... def finalize(self): ... def isTesselated(self): ... def object_id(self, o): ... @@ -150,8 +154,9 @@ class ConversionResult: def Shape(self): ... def Style(self): ... def StylePtr(self): ... + def __init__(self, *args): ... def append(self, trsf): ... - def apply_transform(self, unit_scale): ... + def apply_transform(self, unit_scale=1.0): ... def hasStyle(self): ... def prepend(self, trsf): ... def setStyle(self, newStyle): ... @@ -159,6 +164,7 @@ class ConversionResult: class ConversionResultShape: def Serialize(self, place, arg3): ... def Triangulate(self, *args): ... + def __init__(self, *args, **kwargs): ... def add(self, arg2): ... def area(self): ... def axis(self): ... @@ -193,6 +199,7 @@ class ConversionResultShape: def wrap_in_compound(self): ... class DoubleArray3: + def __init__(self, *args): ... def back(self): ... def begin(self): ... def empty(self): ... @@ -206,6 +213,7 @@ class DoubleArray3: def swap(self, v): ... class Element: + def __init__(self, settings, id, parent_id, name, type, guid, context, trsf, product): ... # TODO: Remove from the wrapper? def SetParents(self, newparents): ... # TODO: could it be None? @@ -266,6 +274,7 @@ class Element: class GeometrySerializer: READ_BREP: Any READ_TRIANGULATION: Any + def __init__(self, *args, **kwargs): ... def geometry_settings(self, *args): ... def isTesselated(self): ... def object_id(self, o): ... @@ -275,6 +284,7 @@ class GeometrySerializer: def write(self, *args): ... class GltfSerializer(WriteOnlyGeometrySerializer): + def __init__(self, filename, geometry_settings, settings): ... def finalize(self): ... def isTesselated(self): ... def ready(self): ... @@ -284,6 +294,7 @@ class GltfSerializer(WriteOnlyGeometrySerializer): def writeHeader(self): ... class HdfSerializer(GeometrySerializer): + def __init__(self, hdf_filename, geometry_settings, settings, read_only=False): ... def finalize(self): ... def isTesselated(self): ... def read(self, *args): ... @@ -295,6 +306,7 @@ class HdfSerializer(GeometrySerializer): def writeHeader(self): ... class IfcBaseEntity(entity_instance): + def __init__(self, *args, **kwargs): ... def declaration(self): ... def get(self, name): ... def get_inverse(self, name): ... @@ -302,26 +314,31 @@ class IfcBaseEntity(entity_instance): def set_id(self, i): ... class IfcBaseType(entity_instance): + def __init__(self, *args, **kwargs): ... def declaration(self): ... -class IfcEntityInstanceData: ... +class IfcEntityInstanceData: + def __init__(self, *args, **kwargs): ... class IfcLateBoundEntity(IfcBaseEntity): + def __init__(self, decl, data): ... def declaration(self): ... class InstanceStreamer: + def __init__(self, *args): ... def bypassTypes(self, type_names): ... def bypassed_instances(self): ... coerce_attribute_count: bool def hasSemicolon(self): ... def inverses(self, *args): ... def pushPage(self, page): ... - def readInstancePy(self, type_as_declaration_instance): ... + def readInstancePy(self, type_as_declaration_instance=False): ... def references(self, *args): ... def semicolonCount(self): ... def status(self): ... class Iterator: + def __init__(self, *args): ... initialization_outcome_: Any processed_: Any def bounds_max(self): ... @@ -367,25 +384,36 @@ class Iterator: class JsonSerializer: JSON_DIALECT_CREOOX: Any + def __init__(self, *args): ... def finalize(self): ... def ready(self): ... def setFile(self, arg2): ... def writeHeader(self): ... +# TODO: MakeVolume is ignored in SWIG, remove from stub once build is bumped. +class MakeVolume: + defaultvalue: Any + description: Any + name: Any + class OpaqueCoordinate_3: + def __init__(self, *args): ... def get(self, i): ... def set(self, i, n): ... class OpaqueCoordinate_4: + def __init__(self, *args): ... def get(self, i): ... def set(self, i, n): ... class OpaqueNumber: + def __init__(self, *args, **kwargs): ... def clone(self): ... def to_double(self): ... def to_string(self): ... class Representation: + def __init__(self, settings, entity, id): ... def entity(self): ... @property def id(self) -> str: @@ -398,18 +426,21 @@ class Representation: def settings(self): ... class RocksDBPrefixIterator: + def __init__(self, storage, prefix): ... def key(self): ... def next(self): ... def valid(self): ... def value(self): ... class RocksDbSerializer: - def finalize(self): ... - def ready(self): ... - def setFile(self, arg2): ... - def writeHeader(self): ... + def __init__(self, *args): ... + def finalize(self) -> None: ... + def ready(self) -> bool: ... + def setFile(self, arg2) -> None: ... + def writeHeader(self) -> None: ... class Serialization(Representation): + def __init__(self, brep): ... @property def brep_data(self): ... @property @@ -418,6 +449,7 @@ class Serialization(Representation): def surface_styles(self): ... class SerializedElement(Element): + def __init__(self, shape_model): ... @property def geometry(self) -> Serialization: ... @@ -434,6 +466,7 @@ class Settings: def setting_names(self): ... class SvgSerializer(WriteOnlyGeometrySerializer): + def __init__(self, out_filename, geometry_settings, settings): ... SH_NONE: Any SH_FULL: Any SH_LEFT: Any @@ -479,8 +512,8 @@ class SvgSerializer(WriteOnlyGeometrySerializer): def setPrintSpaceNames(self, b): ... def setProfileThreshold(self, i): ... def setScale(self, s): ... - def setSectionHeight(self, h, storey): ... - def setSectionHeightsFromStoreys(self, offset): ... + def setSectionHeight(self, h, storey=None): ... + def setSectionHeightsFromStoreys(self, offset=1.2): ... def setSectionRef(self, s): ... def setSegmentProjection(self, b): ... def setSpaceNameTransform(self, v): ... @@ -497,22 +530,25 @@ class SvgSerializer(WriteOnlyGeometrySerializer): def writeHeader(self): ... class SwigPyIterator: + def __init__(self, *args, **kwargs): ... def advance(self, n): ... def copy(self): ... - def decr(self, n): ... + def decr(self, n=1): ... def distance(self, x): ... def equal(self, x): ... - def incr(self, n): ... + def incr(self, n=1): ... def next(self): ... def previous(self): ... def value(self): ... class Transformation: + def __init__(self, settings, matrix): ... def data(self): ... @property def matrix(self): ... class Triangulation(Representation): + def __init__(self, *args): ... def addEdge(self, item_id, style, i0, i1): ... def addFace(self, *args): ... def addNormal(self, X, Y, Z): ... @@ -567,21 +603,24 @@ class Triangulation(Representation): def verts_buffer(self) -> bytes: ... class TriangulationElement(Element): + def __init__(self, *args): ... @property def geometry(self) -> Triangulation: ... def geometry_pointer(self): ... class TtlWktSerializer(WriteOnlyGeometrySerializer): + def __init__(self, filename, geometry_settings, settings): ... def finalize(self): ... def isTesselated(self): ... def ready(self): ... def setFile(self, arg2): ... def setUnitNameAndMagnitude(self, arg2, arg3): ... - def ttl_object_id(self, o, postfix): ... + def ttl_object_id(self, o, postfix=None): ... def write(self, *args): ... def writeHeader(self): ... class WaveFrontOBJSerializer(WriteOnlyGeometrySerializer): + def __init__(self, obj_filename, mtl_filename, geometry_settings, settings): ... def finalize(self): ... def isTesselated(self): ... def ready(self): ... @@ -592,9 +631,11 @@ class WaveFrontOBJSerializer(WriteOnlyGeometrySerializer): def writeMaterial(self, style): ... class WriteOnlyGeometrySerializer(GeometrySerializer): + def __init__(self, *args, **kwargs): ... def read(self, *args): ... class XmlSerializer: + def __init__(self, file, xml_filename): ... def finalize(self): ... def ready(self): ... def setFile(self, arg2): ... @@ -603,6 +644,7 @@ class XmlSerializer: class _SwigNonDynamicMeta(type): ... class abstract_arrangement: + def __init__(self, *args, **kwargs): ... def get_face_pairs(self): ... def merge(self, edge_indices): ... def num_edges(self): ... @@ -610,6 +652,7 @@ class abstract_arrangement: def write(self, polygons, progress): ... class aggregation_type(parameter_type): + def __init__(self, type_of_aggregation, bound1, bound2, type_of_element): ... array_type: Any bag_type: Any list_type: Any @@ -622,6 +665,7 @@ class aggregation_type(parameter_type): def type_of_element(self) -> parameter_type: ... class attribute: + def __init__(self, name, type_of_attribute, optional): ... def name(self) -> str: ... def optional(self) -> bool: ... def type_of_attribute(self) -> parameter_type: ... @@ -662,11 +706,13 @@ class bspline_surface(surface): def kind(self): ... class buffer: + def __init__(self, *args): ... def filename(self): ... def get_value(self): ... def is_ready(self): ... class cant_function(function_item): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def end(self): ... @@ -694,6 +740,7 @@ class clash: p2: Any class clashes: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -727,6 +774,7 @@ class collection: def matrix(self): ... class colour(item): + def __init__(self, *args): ... def r(self) -> float: ... def g(self) -> float: ... def b(self) -> float: ... @@ -740,6 +788,7 @@ class colour(item): def kind(self) -> int: ... class context: + def __init__(self, *args): ... def add(self, segments): ... def build(self): ... def get_face_pairs(self): ... @@ -749,7 +798,8 @@ class context: def write(self, arg2): ... class curve(geom_item): - def print_impl(self, o, classname, indent): ... + def __init__(self, *args, **kwargs): ... + def print_impl(self, o, classname, indent=0): ... class cylinder(surface): radius: Any @@ -760,6 +810,7 @@ class cylinder(surface): def matrix(self): ... class declaration: + def __init__(self, name, index_in_schema): ... def _is(self, *args: Union[str, declaration]) -> bool: ... def as_entity(self) -> Union[entity, None]: ... def as_enumeration_type(self) -> Union[enumeration_type, None]: ... @@ -775,6 +826,7 @@ class declaration: def type(self): ... class direction3: + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... @property @@ -786,6 +838,7 @@ class drawing_meta: pln_3d: Any class edge(trimmed_curve): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def kind(self): ... @@ -800,6 +853,7 @@ class ellipse(curve): def matrix(self): ... class entity(declaration): + def __init__(self, name, is_abstract, index_in_schema, supertype): ... def all_attributes(self) -> tuple[attribute, ...]: ... def all_inverse_attributes(self) -> tuple[inverse_attribute, ...]: ... def argument_types(self) -> tuple[str, ...]: @@ -828,6 +882,7 @@ class entity(declaration): def supertype(self) -> Union[entity, None]: ... class entity_instance: + def __init__(self, *args, **kwargs): ... file: ifcopenshell.file """Reference to IFC file to prevent it's garbage collection, if entity is still used.""" @@ -881,11 +936,12 @@ class entity_instance: def setArgumentAsNull(self, i): ... def setArgumentAsString(self, i, a): ... def set_attribute_value(self, *args): ... - def toString(self, arg2, upper): ... - def to_string(self, valid_spf): ... + def toString(self, arg2, upper=False): ... + def to_string(self, valid_spf=True): ... def unset_attribute_value(self, i): ... class enumeration_type(declaration): + def __init__(self, name, index_in_schema, enumeration_items): ... def argument_types(self) -> tuple[str, ...]: ... def as_enumeration_type(self) -> enumeration_type: ... def enumeration_items(self) -> tuple[str, ...]: ... @@ -898,6 +954,7 @@ class enumeration_type(declaration): ... class extrusion(sweep): + def __init__(self, m, basis, dir, d): ... depth: Any direction: Any def calc_hash(self): ... @@ -919,7 +976,8 @@ class face: class file: def FreshId(self): ... - def add(self, entity: entity_instance, id: int) -> entity_instance: ... + def __init__(self, *args): ... + def add(self, entity: entity_instance, id: int = -1) -> entity_instance: ... def addEntities(self, entities): ... def add_type_ref(self, new_entity): ... def batch(self) -> None: @@ -950,7 +1008,7 @@ class file: def by_id(self, id: int) -> entity_instance: ... def by_type(self, *args): ... def by_type_excl_subtypes(self, *args): ... - def bypass_type(self, type_name): ... + def bypass_type(self, type_name: str) -> None: ... calculate_unit_factors: bool check_existance_before_adding: bool def create(self, decl): ... @@ -1010,9 +1068,9 @@ class file: def storage_mode(self): ... def to_string(self): ... @staticmethod - def traverse(instance: entity_instance, max_level: int) -> tuple[entity_instance, ...]: ... + def traverse(instance: entity_instance, max_level: int = -1) -> tuple[entity_instance, ...]: ... @staticmethod - def traverse_breadth_first(instance: entity_instance, max_level: int) -> tuple[entity_instance, ...]: ... + def traverse_breadth_first(instance: entity_instance, max_level: int = -1) -> tuple[entity_instance, ...]: ... def types(self) -> tuple[str, ...]: """Return a tuple of classes present in the file. @@ -1029,9 +1087,11 @@ class file_open_status: UNSUPPORTED_SCHEMA: int INVALID_SYNTAX: int UNKNOWN: int + def __init__(self, *args): ... def value(self): ... class fn_evaluator: + def __init__(self, *args, **kwargs): ... settings_: Any def clone(self): ... def end(self): ... @@ -1040,6 +1100,7 @@ class fn_evaluator: def start(self): ... class function_item(implicit_item): + def __init__(self, *args, **kwargs): ... def calc_hash(self): ... def end(self): ... def kind(self): ... @@ -1047,10 +1108,12 @@ class function_item(implicit_item): def start(self): ... class function_item_evaluator: + def __init__(self, *args): ... def evaluate(self, *args): ... def evaluation_points(self, *args): ... class functor_item(function_item): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def end(self): ... @@ -1058,6 +1121,7 @@ class functor_item(function_item): def start(self): ... class geom_item(item): + def __init__(self, *args, **kwargs): ... matrix: Any surface_style: Any @@ -1071,6 +1135,7 @@ class geometry_conversion_result: representation: Any class gradient_function(function_item): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def end(self): ... @@ -1082,9 +1147,12 @@ class gradient_function(function_item): class equal_functor: ... class hash_functor: ... class horizontal_plan_at_element: ... -class implicit_item(geom_item): ... + +class implicit_item(geom_item): + def __init__(self, *args, **kwargs): ... class inverse_attribute: + def __init__(self, name, type_of_aggregation, bound1, bound2, entity_reference, attribute_reference): ... bag_type: Any set_type: Any unspecified_type: Any @@ -1097,6 +1165,7 @@ class inverse_attribute: def type_of_aggregation_string(self): ... class item: + def __init__(self, *args, **kwargs): ... instance: Any orientation: Any def calc_hash(self): ... @@ -1119,6 +1188,7 @@ class line(curve): def matrix(self): ... class line_segment: + def __init__(self, *args): ... def back(self): ... def begin(self): ... def empty(self): ... @@ -1143,7 +1213,8 @@ class loft: class loop: closed: Any external: Any - fi: Any + function_item: Any + tags: Any def calc_hash(self): ... def calculate_linear_edge_curves(self): ... def centroid(self): ... @@ -1160,6 +1231,7 @@ class matrix4(item): AFFINE_W_UNIFORM_SCALE: Any AFFINE_W_NONUNIFORM_SCALE: Any OTHER: Any + def __init__(self, *args): ... tag: Any def calc_hash(self): ... def clone_(self): ... @@ -1172,6 +1244,7 @@ class matrix4(item): def pre_multiply_scale(self, s): ... class named_type(parameter_type): + def __init__(self, declared_type): ... def _is(self, *args): ... def as_named_type(self) -> named_type: ... def declared_type(self) -> declaration: ... @@ -1190,6 +1263,7 @@ class offset_curve(curve): def kind(self): ... class offset_function(function_item): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def end(self): ... @@ -1211,6 +1285,7 @@ class parameter_type: def as_simple_type(self) -> Union[simple_type, None]: ... class piecewise_function(function_item): + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... def end(self): ... @@ -1230,6 +1305,7 @@ class plane(surface): def matrix(self): ... class point3: + def __init__(self, *args): ... def calc_hash(self): ... def clone_(self): ... @property @@ -1251,6 +1327,7 @@ class ray_intersection_result: style_index: Any class ray_intersection_results: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1275,6 +1352,7 @@ class ray_intersection_results: def swap(self, v): ... class revolve(sweep): + def __init__(self, m, basis, pnt, dir, a): ... angle: Any axis_origin: Any direction: Any @@ -1285,6 +1363,7 @@ class revolve(sweep): def matrix(self): ... class schema_definition: + def __init__(self, name, declarations, factory): ... def declaration_by_name(self, *args: str) -> declaration: """ :return: ``declaration`` but upcasted to the most advanced available type @@ -1306,6 +1385,7 @@ class schema_definition: def name(self) -> str: ... class select_type(declaration): + def __init__(self, name, index_in_schema, select_list): ... def as_select_type(self) -> select_type: ... def select_list(self) -> tuple[declaration, ...]: ... @@ -1320,6 +1400,7 @@ class shell: def print_impl(self, o, indent): ... class simple_type(parameter_type): + def __init__(self, declared_type): ... binary_type: Any boolean_type: Any integer_type: Any @@ -1349,6 +1430,7 @@ class sphere(surface): def matrix(self): ... class style(item): + def __init__(self, *args): ... diffuse: colour name: str """E.g. 'IfcSurfaceStyleShading-218', where 218 is style's STEP id.""" @@ -1383,9 +1465,11 @@ class style(item): def kind(self) -> int: ... -class surface(geom_item): ... +class surface(geom_item): + def __init__(self, *args, **kwargs): ... class svg_groups_of_line_segments: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1410,6 +1494,7 @@ class svg_groups_of_line_segments: def swap(self, v): ... class svg_groups_of_polygons: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1434,6 +1519,7 @@ class svg_groups_of_polygons: def swap(self, v): ... class svg_line_segments: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1458,6 +1544,7 @@ class svg_line_segments: def swap(self, v): ... class svg_loop: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1482,6 +1569,7 @@ class svg_loop: def swap(self, v): ... class svg_loops: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1506,6 +1594,7 @@ class svg_loops: def swap(self, v): ... class svg_point: + def __init__(self, *args): ... def back(self): ... def begin(self): ... def empty(self): ... @@ -1519,6 +1608,7 @@ class svg_point: def swap(self, v): ... class svg_polygons: + def __init__(self, *args): ... def append(self, x): ... def assign(self, n, x): ... def back(self): ... @@ -1543,9 +1633,11 @@ class svg_polygons: def swap(self, v): ... class sweep(geom_item): + def __init__(self, *args, **kwargs): ... basis: Any class sweep_along_curve(sweep): + def __init__(self, *args): ... curve: Any surface: Any direction: Any @@ -1563,6 +1655,7 @@ class torus(surface): def matrix(self): ... class tree: + def __init__(self, *args): ... def add_element(self, *args): ... def add_file(self, *args): ... def clash_clearance_many(self, set_a, set_b, clearance, check_all): ... @@ -1575,7 +1668,7 @@ class tree: def protrusion_distances(self): ... def select(self, *args): ... def select_box(self, *args): ... - def select_ray(self, p0, d, length): ... + def select_ray(self, p0, d, length=1000.0): ... def styles(self): ... def uint8_to_b64(self, uuids_array): ... @staticmethod @@ -1583,6 +1676,7 @@ class tree: def write_h5(self): ... class trimmed_curve(geom_item): + def __init__(self, *args, **kwargs): ... basis: Any curve_sense: Any start: Any @@ -1594,6 +1688,7 @@ class type_by_kind: max: Any class type_declaration(declaration): + def __init__(self, name, index_in_schema, declared_type): ... def argument_types(self): ... def as_type_declaration(self) -> type_declaration: ... def declared_type(self): ... @@ -1612,7 +1707,7 @@ def create_epeck(*args): ... def create_shape(*args): ... def flatten(deep): ... def get_feature(x): ... -def get_info_cpp(v, include_identifier): ... +def get_info_cpp(v, include_identifier=True): ... def get_log(): ... def guess_file_type(fn): ... def helmert_curve_point(A0, A1, A2, s): ... @@ -1622,14 +1717,14 @@ def line_segments_to_polygons(s, eps, segments): ... def map_shape(settings, instance): ... def nary_union(sequence): ... def new_IfcBaseClass(schema_identifier: str, name: str) -> entity_instance: ... -def open(fn, readonly): ... +def open(fn: str, readonly: bool = False) -> file: ... def parse_ifcxml(filename): ... def polygons_to_svg(*args): ... def read(data): ... def register_schema(arg1): ... def schema_by_name(arg1: str) -> schema_definition: ... def schema_names() -> tuple[str, ...]: ... -def serialise(schema_name, shape_str, advanced): ... +def serialise(schema_name, shape_str, advanced=True): ... def set_feature(x, v): ... def set_log_format_json(): ... def set_log_format_text(): ... diff --git a/src/ifcopenshell-python/ifcopenshell/simple_spf b/src/ifcopenshell-python/ifcopenshell/simple_spf index 30317826f8..ed50b756c4 160000 --- a/src/ifcopenshell-python/ifcopenshell/simple_spf +++ b/src/ifcopenshell-python/ifcopenshell/simple_spf @@ -1 +1 @@ -Subproject commit 30317826f8f743860ecff9e183699296593e42cb +Subproject commit ed50b756c4035290d50eb2d928f89423f3aa0947 diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 149606cb14..1c52ebc49d 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -1234,7 +1234,9 @@ def get_controls(element: ifcopenshell.entity_instance) -> Generator[ifcopenshel yield rel.RelatingControl -def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: +def get_parent( + element: ifcopenshell.entity_instance, ifc_class: Optional[str] = None +) -> Union[ifcopenshell.entity_instance, None]: """Get the parent in the spatial heirarchy IFC features a spatial hierarchy tree of all objects. Each spatial element @@ -1251,6 +1253,8 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti - Voiding: the opening voids another physical element, such as a hole in a wall :param element: Any physical or spatial element in the tree + :param ifc_class: Optionally filter the type of parent you're after. For + example, you may be after the storey, not a space. :return: Its parent. This must exist for any valid file, or None if we've reached the IfcProject. Example: @@ -1260,7 +1264,7 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti element = file.by_type("IfcWall")[0] parent = ifcopenshell.util.element.get_parent(element) """ - return ( + parent = ( get_container(element, should_get_direct=True) or get_aggregate(element) or get_nest(element) @@ -1268,6 +1272,16 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti or get_voided_element(element) ) + if not ifc_class: + return parent + + while parent: + if parent.is_a(ifc_class): + return parent + parent = get_parent(parent) + + return None + def get_filled_void(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: """If the element is filling a void, get the void diff --git a/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py b/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py index 5ba2466f29..1c3b6cb001 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py +++ b/src/ifcopenshell-python/ifcopenshell/util/scripts/validate_stub.py @@ -57,11 +57,28 @@ def get_function_node_name(node: ast.FunctionDef) -> Union[SubnameType, None]: :return: Function node name as ``SubnameType`` or ``None``, if function wasn't processed and can be skipped. """ node_name = node.name - if node_name.startswith("_") and node_name not in ("_is",): + is_init = node_name == "__init__" + + if node_name.startswith("_") and node_name not in ("_is",) and not is_init: + return None + arg_nodes = node.args.args + defaults = [None] * (len(arg_nodes) - len(node.args.defaults)) + node.args.defaults + args: list[str] = [] + for arg, default in zip(arg_nodes, defaults): + if default is None: + args.append(arg.arg) + else: + args.append(f"{arg.arg}={ast.unparse(default)}") + + if arg := node.args.vararg: + args.append(f"*{arg.arg}") + + if arg := node.args.kwarg: + args.append(f"**{arg.arg}") + + # Skip non-informative constructors. + if is_init and args == ["self"]: return None - args = [a.arg for a in node.args.args] - if node.args.vararg: - args.append("*args") node_name = f"def {node.name}" node_name = f"{node_name}({', '.join(args)}): ..." diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index c6ee820ab8..bbe8125927 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -440,13 +440,13 @@ def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) - elif key == "container": value = ifcopenshell.util.element.get_container(value) elif key == "space": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace") + value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSpace") elif key == "storey": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey") + value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuildingStorey") elif key == "building": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding") + value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuilding") elif key == "site": - value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite") + value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSite") elif key == "parent": value = ifcopenshell.util.element.get_parent(value) elif key in ("types", "occurrences"): diff --git a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py index 31a067bc80..e53d069a76 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py +++ b/src/ifcopenshell-python/ifcopenshell/util/shape_builder.py @@ -40,7 +40,7 @@ if TYPE_CHECKING: # NOTE: mathutils is never used at runtime in ifcopenshell, # only for type checking to ensure methods are compatible with # Blender vectors. - from mathutils import Vector + from mathutils import Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] # Support both numpy arrays and python sequences as inputs. VectorType = Union[Sequence[float], Vector, np.ndarray] @@ -517,11 +517,18 @@ class ShapeBuilder: trim_points_mask: Sequence[int], position_offset: Optional[VectorType] = None, ) -> np.ndarray: - """Handy way to get edge points of the ellipse like shape of a given radiuses. + """Get cardinal-point coordinates of an ellipse by index mask. - Mask points are numerated from 0 to 3 ccw starting from (x_axis_radius/2; 0). + The four cardinal points are numbered 0–3 counter-clockwise starting from the + positive X axis: 0 → ``(x, 0)``, 1 → ``(0, y)``, 2 → ``(-x, 0)``, 3 → ``(0, -y)``. - Example: mask (0, 1, 2, 3) will return points (x, 0), (0, y), (-x, 0), (0, -y) + Example: mask ``(0, 1, 2, 3)`` returns all four points in order. + + :param x_axis_radius: Radius (semi-axis length) along the X axis. + :param y_axis_radius: Radius (semi-axis length) along the Y axis. + :param trim_points_mask: Sequence of cardinal-point indices (0–3) to select. + :param position_offset: Optional 2D offset added to all returned points. + :return: Numpy array of the selected 2D points. """ points = np.array( ( @@ -546,15 +553,23 @@ class ShapeBuilder: ref_x_direction: VectorType = (1.0, 0.0), trim_points_mask: Sequence[int] = (), ) -> ifcopenshell.entity_instance: - """ - Ellipse trimming points should be specified in counter clockwise order. + """Create an IfcEllipse, optionally trimmed to an arc. - For example, if you need to get the part of the ellipse ABOVE y-axis, you need to use mask (0,2). Below y-axis - (2,0) + If neither ``trim_points`` nor ``trim_points_mask`` is provided, a full IfcEllipse is returned. + Trimming points must be given in counter-clockwise order. For example, to get the arc + above the Y-axis use mask ``(0, 2)``; below the Y-axis use ``(2, 0)``. - For more information about trim_points_mask check builder.get_trim_points_from_mask + A trimmed result (IfcTrimmedCurve) includes a closing segment between the trim points, + making it suitable for use as a profile in :meth:`extrude`. - Notion: trimmed ellipse also contains polyline between trim points, meaning IfcTrimmedCurve could be used - for further extrusion. + :param x_axis_radius: Semi-axis length along the local X axis. + :param y_axis_radius: Semi-axis length along the local Y axis. + :param position: 2D centre of the ellipse. + :param trim_points: Explicit pair of 2D trim points. Takes precedence over ``trim_points_mask``. + :param ref_x_direction: Direction of the local X axis. + :param trim_points_mask: Pair of cardinal-point indices (0–3) used when ``trim_points`` is empty. + See :meth:`get_trim_points_from_mask` for index definitions. + :return: IfcEllipse (untrimmed) or IfcTrimmedCurve (trimmed). """ ifc_position = self.create_axis2_placement_2d(position, ref_x_direction) ifc_ellipse = self.file.createIfcEllipse( @@ -685,6 +700,14 @@ class ShapeBuilder: pivot_point: VectorType = (0.0, 0.0), counter_clockwise: bool = False, ) -> np.ndarray: + """Rotate a single 2D point around a pivot. + + :param point_2d: The 2D point to rotate. + :param angle: Rotation angle, in degrees. Defaults to 90. + :param pivot_point: The point to rotate around. + :param counter_clockwise: If True, rotate counter-clockwise. Defaults to clockwise. + :return: Rotated 2D point as a numpy array. + """ angle_rad = radians(angle) * (1 if counter_clockwise else -1) relative_point = np.array(point_2d) - pivot_point relative_point = np_rotation_matrix(angle_rad, 2) @ relative_point @@ -752,7 +775,16 @@ class ShapeBuilder: mirror_axes: VectorType = (1.0, 1.0), mirror_point: VectorType = (0.0, 0.0), ) -> np.ndarray: - """mirror_axes - along which axes mirror will be applied""" + """Mirror a single 2D point across the specified axes. + + :param point_2d: The 2D point to mirror. + :param mirror_axes: Indicates which axes to mirror across. A positive value in a + component means that axis is mirrored (negated relative to ``mirror_point``). + Example: ``(1, 0)`` mirrors across the Y-axis (negates X only), + ``(1, 1)`` mirrors across both axes. + :param mirror_point: Origin of the mirror operation. + :return: Mirrored 2D point as a numpy array. + """ mirror_axes: np.ndarray = np.where(np.array(mirror_axes) > 0, -1, 1) mirror_point: np.ndarray = np.array(mirror_point) relative_point = point_2d - mirror_point @@ -798,7 +830,13 @@ class ShapeBuilder: def create_axis2_placement_2d( self, position: VectorType = (0.0, 0.0), x_direction: Optional[VectorType] = None ) -> ifcopenshell.entity_instance: - """Create IfcAxis2Placement2D.""" + """Create IfcAxis2Placement2D. + + :param position: 2D origin of the placement. + :param x_direction: Direction of the local X axis. If not provided, defaults to + the global X axis ``(1, 0)``. + :return: IfcAxis2Placement2D + """ ref_direction = ( self.file.create_entity("IfcDirection", ifc_safe_vector_type(x_direction)) if x_direction else None ) @@ -1000,7 +1038,7 @@ class ShapeBuilder: ) -> ifcopenshell.entity_instance: """ :param plane: The IfcPlane representing the half space. - :param agreement_flag: False if +Z represents the void + :param agreement_flag: If False (default), the plane normal points toward the **removed** material (the void). The kept region is on the opposite side from the normal. :return: IfcHalfSpaceSolid """ return self.file.createIfcHalfSpaceSolid(plane, AgreementFlag=agreement_flag) @@ -1053,7 +1091,14 @@ class ShapeBuilder: def create_swept_disk_solid( self, path_curve: ifcopenshell.entity_instance, radius: float ) -> ifcopenshell.entity_instance: - """Create IfcSweptDiskSolid from `path_curve` (must be 3D) and `radius`""" + """Create an IfcSweptDiskSolid — a circular cross-section swept along a 3D path. + + Useful for modelling round pipes, conduits, and cables. + + :param path_curve: A 3D curve entity defining the centreline path. Must have ``Dim == 3``. + :param radius: Radius of the circular disk cross-section. + :return: IfcSweptDiskSolid + """ if path_curve.Dim != 3: raise Exception( f"Path curve for IfcSweptDiskSolid should be 3D to be valid, currently it has {path_curve.Dim} dimensions.\n" @@ -1071,10 +1116,22 @@ class ShapeBuilder: ) -> ifcopenshell.entity_instance: """Create IFC representation for the specified context and items. + **All items must belong to the same geometry category.** IFC prohibits + mixing incompatible item types in one representation (e.g. + ``IfcExtrudedAreaSolid`` with ``IfcBlock``, or solids with curves). + When ``representation_type`` is omitted the type is inferred via + :func:`ifcopenshell.util.representation.guess_type`; if the items are + heterogeneous ``guess_type`` returns ``None`` and the representation is + written with no ``RepresentationType``, which fails IFC validation. + Avoid mixing swept-solid primitives (``IfcExtrudedAreaSolid``, + ``IfcRevolvedAreaSolid``) with CSG primitives (``IfcBlock``, + ``IfcSphere``, etc.) or any other category in a single call. + :param context: IfcGeometricRepresentationSubContext - :param items: could be a list or single curve/IfcExtrudedAreaSolid + :param items: A single item or list of items, all of the same geometry + category (e.g. all ``IfcExtrudedAreaSolid``, all ``IfcIndexedPolyCurve``) :param representation_type: Explicitly specified RepresentationType. - If not provided it will be guessed from the items types + If not provided it will be guessed from the items types. :return: IfcShapeRepresentation """ if not isinstance(items, collections.abc.Iterable): @@ -1096,18 +1153,26 @@ class ShapeBuilder: ) def deep_copy(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: + """Create a deep copy of an IFC element and all its referenced entities. + + :param element: The IFC entity to copy. + :return: A new independent copy of the element. + """ return ifcopenshell.util.element.copy_deep(self.file, element) # UTILITIES def extrude_kwargs(self, axis: Literal["Y", "X", "Z"]) -> dict[str, tuple[float, float, float]]: - """Shortcut to get kwargs for `ShapeBuilder.extrude` to extrude by some axis. + """Shortcut to get kwargs for :meth:`extrude` to extrude along a principal axis. - It assumes you have 2D profile in: - XZ plane for Y axis extrusion, \n - YZ plane for X axis extrusion, \n - XY plane for Z axis extrusion, \n + Assumes the 2D profile lies in the plane perpendicular to the extrusion axis: + XZ plane for Y-axis extrusion, YZ plane for X-axis extrusion, XY plane for Z-axis extrusion. - Extruding by X/Y using other kwargs might break ValidExtrusionDirection.""" + Extruding along X or Y with other kwargs may violate the IFC ValidExtrusionDirection constraint. + + :param axis: The extrusion axis: ``'X'``, ``'Y'``, or ``'Z'``. + :return: A dict with keys ``position_x_axis``, ``position_z_axis``, and ``extrusion_vector`` + suitable for passing as ``**kwargs`` to :meth:`extrude`. + """ if axis == "Y": return { @@ -1131,13 +1196,16 @@ class ShapeBuilder: def rotate_extrusion_kwargs_by_z( self, kwargs: dict[str, Any], angle: float, counter_clockwise: bool = False ) -> dict[str, VectorType]: - """shortcut to rotate extrusion kwargs by z axis + """Rotate extrusion kwargs around the Z axis. - `kwargs` expected to have `position_x_axis` and `position_z_axis` keys + A shortcut to rotate the ``position_x_axis`` and ``position_z_axis`` values returned by + :meth:`extrude_kwargs` around the Z axis before passing them to :meth:`extrude`. - `angle` is a rotation value in radians - - by default rotation is clockwise, to make it counter clockwise use `counter_clockwise` flag + :param kwargs: A dict with ``position_x_axis`` and ``position_z_axis`` keys, + as returned by :meth:`extrude_kwargs`. The original dict is not mutated. + :param angle: Rotation angle, in radians. + :param counter_clockwise: If True, rotate counter-clockwise. Defaults to clockwise. + :return: A new dict with ``position_x_axis`` and ``position_z_axis`` rotated around Z. """ rot = np_rotation_matrix(-angle, 3, "Z") kwargs = kwargs.copy() # prevent mutation of original kwargs @@ -1146,7 +1214,11 @@ class ShapeBuilder: return kwargs def get_polyline_coords(self, polyline: ifcopenshell.entity_instance) -> np.ndarray: - """polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`""" + """Extract the coordinate array from a polyline entity. + + :param polyline: An ``IfcIndexedPolyCurve`` or ``IfcPolyline`` entity. + :return: Numpy array of the polyline's point coordinates. + """ coords = None if polyline.is_a("IfcIndexedPolyCurve"): coords = np.array(polyline.Points.CoordList) @@ -1157,7 +1229,12 @@ class ShapeBuilder: return coords def set_polyline_coords(self, polyline: ifcopenshell.entity_instance, coords: SequenceOfVectors) -> None: - """polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`""" + """Update the coordinates of a polyline entity in-place. + + :param polyline: An ``IfcIndexedPolyCurve`` or ``IfcPolyline`` entity. + :param coords: New sequence of point coordinates. Must contain the same number of + points as the original polyline. + """ if polyline.is_a("IfcIndexedPolyCurve"): polyline.Points.CoordList = ifc_safe_vector_type(coords) elif polyline.is_a("IfcPolyline"): @@ -1296,6 +1373,18 @@ class ShapeBuilder: WallThickness: float, FilletRadius: float, ) -> ifcopenshell.entity_instance: + """Create a Z-profile (cold-formed steel section) outline curve with lips and fillets. + + All dimensions are in the IFC project's length units. + + :param FirstFlangeWidth: Width of the first (top) flange, measured from the web centreline. + :param SecondFlangeWidth: Width of the second (bottom) flange, measured from the web centreline. + :param Depth: Total depth of the section (web height). + :param Girth: Length of the return lips on each flange. + :param WallThickness: Uniform material thickness. + :param FilletRadius: Inner bend radius at each corner. + :return: IfcIndexedPolyCurve representing the closed Z-profile outline. + """ x1 = FirstFlangeWidth x2 = SecondFlangeWidth y = Depth / 2 @@ -1337,10 +1426,17 @@ class ShapeBuilder: def create_transition_arc_ifc( self, width: float, height: float, create_ifc_curve: bool = False ) -> tuple[SequenceOfVectors, list[list[int]], Union[ifcopenshell.entity_instance, None]]: - """Create an arc in the rectangle with specified width and height. + """Create an arc fitting inside a rectangle of the given width and height. - If it's not possible to make a complete arc, create an arc with longest radius possible - and straight segment in the middle. + If a single arc cannot span the full width, the longest possible radius is used and + a straight segment is inserted in the middle. + + :param width: Width of the bounding rectangle. + :param height: Height of the bounding rectangle (also the maximum arc radius). + :param create_ifc_curve: If True, also create and return an ``IfcIndexedPolyCurve``. + If False, only return the raw point and segment data. + :return: A tuple ``(points, segments, ifc_curve)`` where ``ifc_curve`` is an + ``IfcIndexedPolyCurve`` when ``create_ifc_curve=True``, otherwise ``None``. """ fillet_size = (width / 2) / height if fillet_size <= 1: @@ -1370,6 +1466,14 @@ class ShapeBuilder: return points, segments, transition_arc def mesh(self, points: SequenceOfVectors, faces: Sequence[Sequence[int]]) -> ifcopenshell.entity_instance: + """Create a tessellated mesh from points and face indices. + + Delegates to :meth:`faceted_brep` for IFC2X3, or :meth:`polygonal_face_set` for IFC4 and later. + + :param points: List of 3D coordinates. + :param faces: List of faces, each face a sequence of zero-based point indices. + :return: IfcFacetedBrep (IFC2X3) or IfcPolygonalFaceSet (IFC4+). + """ if self.file.schema == "IFC2X3": return self.faceted_brep(points, faces) return self.polygonal_face_set(points, faces) @@ -1723,11 +1827,20 @@ class ShapeBuilder: angle: float, profile_offset: VectorType = (0.0, 0.0), verbose: bool = True, - ): - """get the final transition length for two profiles dimensions, angle and XY offset between them, + ) -> Optional[float]: + """Get the transition length for two profile half-dimensions, an angle, and an XY offset. - the difference from `calculate_transition` - `get_transition_length` is making sure - that length will fit both sides of the transition + Unlike :meth:`mep_transition_calculate`, this method checks that the resulting length + satisfies the angle constraint from both the start and end profile perspectives. + + :param start_half_dim: Half-dimensions of the start profile as a 3-element array + ``[half_x, half_y, depth]``. For circular profiles ``half_x == half_y == radius``. + :param end_half_dim: Half-dimensions of the end profile in the same format. + :param angle: Maximum allowed transition angle, in degrees. + :param profile_offset: 2D XY offset between the centrelines of the start and end profiles. + :param verbose: If True, print diagnostic values during calculation. + :return: Transition length in project length units, or ``None`` if no valid length exists + for the given angle and offset. """ print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None np_X, np_Y = 0, 1 @@ -1788,9 +1901,23 @@ class ShapeBuilder: angle: Optional[float] = None, verbose: bool = True, ) -> Union[float, None]: - """will return transition length based on the profile dimension differences and offset. + """Calculate MEP transition length from angle, or transition angle from length. - If `length` is provided will return transition angle""" + Low-level calculation kernel used by :meth:`mep_transition_length`. Provide either + ``angle`` or ``length`` (not both); the other value is computed and returned. + + :param start_half_dim: Half-dimensions of the start profile ``[half_x, half_y, depth]``. + :param end_half_dim: Half-dimensions of the end profile ``[half_x, half_y, depth]``. + :param offset: 2D XY offset between profile centrelines. + :param diff: Pre-computed absolute difference of start and end half-dimensions (XY only). + Computed from ``start_half_dim`` and ``end_half_dim`` if not provided. + :param end_profile: If True, swap X and Y axes to compute from the end-profile perspective. + :param length: Known transition length. If provided, the corresponding angle is returned. + :param angle: Known transition angle, in degrees. If provided, the corresponding length is returned. + :param verbose: If True, print diagnostic values during calculation. + :return: Transition length (if ``angle`` was given) or transition angle in degrees + (if ``length`` was given), or ``None`` if the geometry is not feasible. + """ print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py index e1270afb57..524dc78d35 100644 --- a/src/ifcopenshell-python/ifcopenshell/validate.py +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -47,6 +47,7 @@ from __future__ import annotations import argparse import functools +import itertools import json import os import sys @@ -60,7 +61,6 @@ from typing import TYPE_CHECKING, Any, Optional, Union import ifcopenshell import ifcopenshell.express.rule_executor import ifcopenshell.ifcopenshell_wrapper -import ifcopenshell.ifcopenshell_wrapper as W if TYPE_CHECKING: import ifcopenshell.simple_spf @@ -332,7 +332,7 @@ def log_internal_cpp_errors( lines = list(open(filename, "rb")) lengths = list(map(len, lines)) cumsum = 0 - cs = [cumsum := cumsum + x for x in lengths] + cs = list(itertools.accumulate(lengths)) for offsets, msg in zip(chr_offsets, msgs): if offsets: @@ -705,10 +705,10 @@ def validate_ifc_header( log_error(header_entity, name, index, STRING_TYPE, type(value).__name__) # Ignore header.file_schema as file won't load to IfcOpenShell with invalid file_schema. - file_description: W.FileDescription = header.file_description + file_description = header.file_description validate_attribute(file_description, "description", 0, aggregate=True) validate_attribute(file_description, "implementation_level", 1) - file_name: W.FileName = header.file_name + file_name = header.file_name validate_attribute(file_name, "name", 0) validate_attribute(file_name, "time_stamp", 1) validate_attribute(file_name, "author", 2, aggregate=True) diff --git a/src/ifcopenshell-python/scripts/dev_environment.py b/src/ifcopenshell-python/scripts/dev_environment.py index 8058a58b92..b958369ba1 100644 --- a/src/ifcopenshell-python/scripts/dev_environment.py +++ b/src/ifcopenshell-python/scripts/dev_environment.py @@ -17,8 +17,18 @@ REPO_PATH_SRC = REPO_PATH / "src" assert REPO_PATH_SRC.exists(), f"'{REPO_PATH_SRC}' doesn't exist." packages = { + "bcf": REPO_PATH_SRC / "bcf" / "bcf", + "bsdd.py": REPO_PATH_SRC / "bsdd" / "bsdd.py", + "ifc4d": REPO_PATH_SRC / "ifc4d" / "ifc4d", + "ifc5d": REPO_PATH_SRC / "ifc5d" / "ifc5d", + "ifccityjson": REPO_PATH_SRC / "ifccityjson" / "ifccityjson", + "ifcclash": REPO_PATH_SRC / "ifcclash" / "ifcclash", + "ifccsv.py": REPO_PATH_SRC / "ifccsv" / "ifccsv.py", + "ifcdiff.py": REPO_PATH_SRC / "ifcdiff" / "ifcdiff.py", + "ifcfm": REPO_PATH_SRC / "ifcfm" / "ifcfm", "ifcopenshell": REPO_PATH_SRC / "ifcopenshell-python" / "ifcopenshell", "ifcpatch": REPO_PATH_SRC / "ifcpatch" / "ifcpatch", + "ifctester": REPO_PATH_SRC / "ifctester" / "ifctester", } @@ -35,10 +45,12 @@ for package, repo_package_path in packages.items(): continue package_path.unlink() if package_path.exists(): - # I guess it's a directory. - shutil.rmtree(package_path) + if package_path.is_dir(): + shutil.rmtree(package_path) + else: + package_path.unlink() print(f"Symlinking {package_path} -> {repo_package_path}") - package_path.symlink_to(repo_package_path, True) + package_path.symlink_to(repo_package_path, target_is_directory=repo_package_path.is_dir()) PACKAGE_PATH = SITE / "ifcopenshell" diff --git a/src/ifcopenshell-python/test/api/boundary/test_edit_attributes.py b/src/ifcopenshell-python/test/api/boundary/test_edit_attributes.py new file mode 100644 index 0000000000..ed79d7e8bf --- /dev/null +++ b/src/ifcopenshell-python/test/api/boundary/test_edit_attributes.py @@ -0,0 +1,115 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell.api.boundary +import ifcopenshell.api.root +import test.bootstrap + + +class TestEditAttributes(test.bootstrap.IFC4): + def setup_boundary(self): + space = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSpace") + wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + boundary = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcRelSpaceBoundary") + return boundary, space, wall + + def test_sets_relating_space_and_building_element(self): + boundary, space, wall = self.setup_boundary() + ifcopenshell.api.boundary.edit_attributes( + self.file, entity=boundary, relating_space=space, related_building_element=wall + ) + assert boundary.RelatingSpace == space + assert boundary.RelatedBuildingElement == wall + + def test_defaults_enums_to_notdefined(self): + boundary, space, wall = self.setup_boundary() + ifcopenshell.api.boundary.edit_attributes( + self.file, entity=boundary, relating_space=space, related_building_element=wall + ) + assert boundary.PhysicalOrVirtualBoundary == "NOTDEFINED" + assert boundary.InternalOrExternalBoundary == "NOTDEFINED" + + def test_sets_physical_or_virtual(self): + boundary, space, wall = self.setup_boundary() + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + physical_or_virtual="PHYSICAL", + ) + assert boundary.PhysicalOrVirtualBoundary == "PHYSICAL" + + def test_sets_internal_or_external(self): + boundary, space, wall = self.setup_boundary() + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + internal_or_external="EXTERNAL", + ) + assert boundary.InternalOrExternalBoundary == "EXTERNAL" + + def test_sets_all_enum_variants(self): + boundary, space, wall = self.setup_boundary() + for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"): + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + physical_or_virtual=value, + ) + assert boundary.PhysicalOrVirtualBoundary == value + + for value in ("INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER", "EXTERNAL_FIRE", "NOTDEFINED"): + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + internal_or_external=value, + ) + assert boundary.InternalOrExternalBoundary == value + + +class TestEditAttributesIFC2X3(test.bootstrap.IFC2X3, TestEditAttributes): + def test_sets_all_enum_variants(self): + boundary, space, wall = self.setup_boundary() + for value in ("PHYSICAL", "VIRTUAL", "NOTDEFINED"): + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + physical_or_virtual=value, + ) + assert boundary.PhysicalOrVirtualBoundary == value + + # IFC2X3 only has INTERNAL, EXTERNAL, NOTDEFINED + for value in ("INTERNAL", "EXTERNAL", "NOTDEFINED"): + ifcopenshell.api.boundary.edit_attributes( + self.file, + entity=boundary, + relating_space=space, + related_building_element=wall, + internal_or_external=value, + ) + assert boundary.InternalOrExternalBoundary == value diff --git a/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py b/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py new file mode 100644 index 0000000000..38b6775c08 --- /dev/null +++ b/src/ifcopenshell-python/test/api/cost/test_edit_cost_value.py @@ -0,0 +1,70 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.cost +import ifcopenshell.api.unit +import test.bootstrap + + +class TestEditCostValue(test.bootstrap.IFC4): + def test_editing_applied_value(self): + schedule = ifcopenshell.api.cost.add_cost_schedule(self.file) + item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule) + value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item) + ifcopenshell.api.cost.edit_cost_value(self.file, cost_value=value, attributes={"AppliedValue": 42.0}) + assert value.AppliedValue.wrappedValue == 42.0 + + def test_editing_unit_basis_removes_old_deeply(self): + schedule = ifcopenshell.api.cost.add_cost_schedule(self.file) + item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule) + value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item) + unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT") + ifcopenshell.api.cost.edit_cost_value( + self.file, + cost_value=value, + attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}}, + ) + old_basis = value.UnitBasis + assert old_basis is not None + old_basis_id = old_basis.id() + # Now change to a new unit basis — the old one should be deeply removed. + ifcopenshell.api.cost.edit_cost_value( + self.file, + cost_value=value, + attributes={"UnitBasis": {"ValueComponent": 2.0, "UnitComponent": unit}}, + ) + assert value.UnitBasis is not None + assert value.UnitBasis.id() != old_basis_id + + def test_clearing_unit_basis(self): + schedule = ifcopenshell.api.cost.add_cost_schedule(self.file) + item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule) + value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item) + unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT") + ifcopenshell.api.cost.edit_cost_value( + self.file, + cost_value=value, + attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}}, + ) + assert value.UnitBasis is not None + ifcopenshell.api.cost.edit_cost_value(self.file, cost_value=value, attributes={"UnitBasis": None}) + assert value.UnitBasis is None + + +class TestEditCostValueIFC4X3(test.bootstrap.IFC4X3, TestEditCostValue): + pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_add_boolean.py b/src/ifcopenshell-python/test/api/geometry/test_add_boolean.py index d507f89308..f750795ee7 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_add_boolean.py +++ b/src/ifcopenshell-python/test/api/geometry/test_add_boolean.py @@ -42,7 +42,7 @@ class TestAddBoolean(test.bootstrap.IFC4): assert boolean.FirstOperand == first assert boolean.SecondOperand == second assert boolean.Operator == "DIFFERENCE" - assert set(rep.Items) == {boolean} + assert set(rep.Items) == {boolean, second} def test_adding_multiple_booleans_from_three_top_level_items(self): ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") @@ -58,13 +58,14 @@ class TestAddBoolean(test.bootstrap.IFC4): booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1, second2]) assert len(booleans) == 2 - assert len(rep.Items) == 1 - assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult") - assert rep.Items[0].SecondOperand == second2 - assert rep.Items[0].Operator == "DIFFERENCE" - assert rep.Items[0].FirstOperand.FirstOperand == first - assert rep.Items[0].FirstOperand.SecondOperand == second1 - assert rep.Items[0].FirstOperand.Operator == "DIFFERENCE" + final_boolean = booleans[-1] + assert final_boolean.FirstOperand.is_a("IfcBooleanResult") + assert final_boolean.SecondOperand == second2 + assert final_boolean.Operator == "DIFFERENCE" + assert final_boolean.FirstOperand.FirstOperand == first + assert final_boolean.FirstOperand.SecondOperand == second1 + assert final_boolean.FirstOperand.Operator == "DIFFERENCE" + assert set(rep.Items) == {final_boolean, second1, second2} def test_adding_a_boolean_to_an_existing_operand_from_a_top_level_item(self): ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") @@ -78,14 +79,16 @@ class TestAddBoolean(test.bootstrap.IFC4): second2 = builder.block() rep = builder.get_representation(body, [first, second1]) booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1]) + # second1 stays in Items, add second2 as well rep.Items = list(rep.Items) + [second2] booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second2]) assert len(booleans) == 1 - assert len(rep.Items) == 1 - assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult") - assert rep.Items[0].SecondOperand == second2 - assert rep.Items[0].FirstOperand.FirstOperand == first - assert rep.Items[0].FirstOperand.SecondOperand == second1 + final_boolean = booleans[0] + assert final_boolean.FirstOperand.is_a("IfcBooleanResult") + assert final_boolean.SecondOperand == second2 + assert final_boolean.FirstOperand.FirstOperand == first + assert final_boolean.FirstOperand.SecondOperand == second1 + assert set(rep.Items) == {final_boolean, second1, second2} def test_adding_a_boolean_to_an_existing_operand_from_another_operand(self): ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") @@ -104,7 +107,7 @@ class TestAddBoolean(test.bootstrap.IFC4): booleans = ifcopenshell.api.geometry.add_boolean(self.file, first1, [second2]) assert len(booleans) == 1 - assert len(rep.Items) == 2 + assert len(rep.Items) == 4 assert self.file.get_total_inverses(first1) == 1 result = next(iter(self.file.get_inverse(first1))) @@ -132,14 +135,15 @@ class TestAddBoolean(test.bootstrap.IFC4): rep = builder.get_representation(body, [first, second]) ifcopenshell.api.geometry.add_boolean(self.file, first, [second]) ifcopenshell.api.geometry.add_boolean(self.file, first, [second]) - assert len(rep.Items) == 1 - assert rep.Items[0].FirstOperand == first - assert rep.Items[0].SecondOperand == second + assert set(rep.Items) == {self.file.by_type("IfcBooleanResult")[0], second} + boolean = self.file.by_type("IfcBooleanResult")[0] + assert boolean.FirstOperand == first + assert boolean.SecondOperand == second ifcopenshell.api.geometry.add_boolean(self.file, second, [second]) ifcopenshell.api.geometry.add_boolean(self.file, second, [first]) - assert len(rep.Items) == 1 - assert rep.Items[0].FirstOperand == first - assert rep.Items[0].SecondOperand == second + assert set(rep.Items) == {boolean, second} + assert boolean.FirstOperand == first + assert boolean.SecondOperand == second assert len(self.file.by_type("IfcBooleanResult")) == 1 diff --git a/src/ifcopenshell-python/test/api/geometry/test_add_topology_representation.py b/src/ifcopenshell-python/test/api/geometry/test_add_topology_representation.py new file mode 100644 index 0000000000..5b89f50338 --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_add_topology_representation.py @@ -0,0 +1,82 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell.api.context +import ifcopenshell.api.geometry +import ifcopenshell.api.root +import test.bootstrap + + +class TestAddTopologyRepresentation(test.bootstrap.IFC4): + def setup_context(self): + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + model = ifcopenshell.api.context.add_context(self.file, context_type="Model") + return ifcopenshell.api.context.add_context( + self.file, + context_type="Model", + context_identifier="Reference", + target_view="GRAPH_VIEW", + parent=model, + ) + + def test_creates_topology_representation(self): + context = self.setup_context() + face = self.file.create_entity("IfcFaceSurface") + rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face) + assert rep.is_a("IfcTopologyRepresentation") + assert rep.ContextOfItems == context + assert face in rep.Items + + def test_infers_face_representation_type(self): + context = self.setup_context() + face = self.file.create_entity("IfcFaceSurface") + rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face) + assert rep.RepresentationType == "Face" + + def test_infers_edge_representation_type(self): + context = self.setup_context() + edge = self.file.create_entity("IfcEdge") + rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=edge) + assert rep.RepresentationType == "Edge" + + def test_defaults_representation_identifier_to_context_identifier(self): + context = self.setup_context() + face = self.file.create_entity("IfcFaceSurface") + rep = ifcopenshell.api.geometry.add_topology_representation(self.file, context=context, item=face) + assert rep.RepresentationIdentifier == context.ContextIdentifier + + def test_custom_representation_identifier(self): + context = self.setup_context() + face = self.file.create_entity("IfcFaceSurface") + rep = ifcopenshell.api.geometry.add_topology_representation( + self.file, context=context, item=face, representation_identifier="Body" + ) + assert rep.RepresentationIdentifier == "Body" + + def test_custom_representation_type_overrides_inferred(self): + context = self.setup_context() + face = self.file.create_entity("IfcFaceSurface") + rep = ifcopenshell.api.geometry.add_topology_representation( + self.file, context=context, item=face, representation_type="Undefined" + ) + assert rep.RepresentationType == "Undefined" + + +class TestAddTopologyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestAddTopologyRepresentation): + pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_clip_solid.py b/src/ifcopenshell-python/test/api/geometry/test_clip_solid.py new file mode 100644 index 0000000000..512bfcc92e --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_clip_solid.py @@ -0,0 +1,154 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import json + +import ifcopenshell.api.geometry +import ifcopenshell.util.element +import ifcopenshell.util.shape_builder +import test.bootstrap + + +class TestClipSolid(test.bootstrap.IFC4): + def make_extrusion(self): + builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file) + rect = builder.rectangle(size=(1.0, 1.0)) + return builder.extrude(rect, magnitude=4.0) + + def test_returns_boolean_clipping_result(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + assert result.is_a("IfcBooleanClippingResult") + assert result.Operator == "DIFFERENCE" + + def test_first_operand_is_the_item(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + assert result.FirstOperand == extrusion + + def test_second_operand_is_half_space_solid(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + assert result.SecondOperand.is_a("IfcHalfSpaceSolid") + + def test_clip_plane_location_matches(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + plane = result.SecondOperand.BaseSurface + coords = plane.Position.Location.Coordinates + assert list(coords) == [0.0, 0.0, 3.0] + + def test_chaining_two_clips(self): + extrusion = self.make_extrusion() + first_clip = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + second_clip = ifcopenshell.api.geometry.clip_solid( + self.file, + item=first_clip, + location=[0.0, 0.0, 1.0], + normal=[0.0, 0.0, -1.0], + ) + assert second_clip.is_a("IfcBooleanClippingResult") + assert second_clip.FirstOperand == first_clip + assert first_clip.FirstOperand == extrusion + + def test_angled_clip_plane(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.26], + normal=[0.419, 0.0, 0.908], + ) + assert result.is_a("IfcBooleanClippingResult") + assert result.SecondOperand.is_a("IfcHalfSpaceSolid") + + def test_element_registers_result_in_bbim_boolean(self): + extrusion = self.make_extrusion() + wall = self.file.createIfcWall() + result = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + element=wall, + ) + pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") + assert pset is not None + assert result.id() in json.loads(pset["Data"]) + + def test_element_appends_to_existing_bbim_boolean(self): + extrusion = self.make_extrusion() + wall = self.file.createIfcWall() + first = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + element=wall, + ) + second = ifcopenshell.api.geometry.clip_solid( + self.file, + item=first, + location=[0.0, 0.0, 1.0], + normal=[0.0, 0.0, -1.0], + element=wall, + ) + pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") + ids = json.loads(pset["Data"]) + assert first.id() in ids + assert second.id() in ids + + def test_no_element_does_not_create_pset(self): + extrusion = self.make_extrusion() + wall = self.file.createIfcWall() + ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + assert ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") is None + + +class TestClipSolidIFC2X3(test.bootstrap.IFC2X3, TestClipSolid): + pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_clip_solid_bounded.py b/src/ifcopenshell-python/test/api/geometry/test_clip_solid_bounded.py new file mode 100644 index 0000000000..3dae335cad --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_clip_solid_bounded.py @@ -0,0 +1,179 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import json + +import ifcopenshell.api.geometry +import ifcopenshell.util.element +import ifcopenshell.util.shape_builder +import test.bootstrap + + +class TestClipSolidBounded(test.bootstrap.IFC4): + def make_extrusion(self): + builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file) + rect = builder.rectangle(size=(4.0, 1.0)) + return builder.extrude(rect, magnitude=3.0) + + def test_returns_boolean_clipping_result(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert result.is_a("IfcBooleanClippingResult") + assert result.Operator == "DIFFERENCE" + + def test_first_operand_is_the_item(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert result.FirstOperand == extrusion + + def test_second_operand_is_polygonal_bounded_half_space(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert result.SecondOperand.is_a("IfcPolygonalBoundedHalfSpace") + + def test_agreement_flag_is_false(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert result.SecondOperand.AgreementFlag is False + + def test_clip_plane_location_matches(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + plane = result.SecondOperand.BaseSurface + coords = plane.Position.Location.Coordinates + assert list(coords) == [2.5, 0.0, 2.0] + + def test_boundary_is_closed_polyline(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + boundary = result.SecondOperand.PolygonalBoundary + assert boundary.is_a("IfcPolyline") + pts = [list(p.Coordinates) for p in boundary.Points] + assert pts[0] == pts[-1], "polygon should be closed" + assert len(pts) == 5 # 4 unique + closing repeat + + def test_boundary_position_defaults_to_origin(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + pos = result.SecondOperand.Position + assert list(pos.Location.Coordinates) == [0.0, 0.0, 0.0] + + def test_custom_boundary_position(self): + extrusion = self.make_extrusion() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + boundary_position=[1.0, 2.0, 3.0], + ) + pos = result.SecondOperand.Position + assert list(pos.Location.Coordinates) == [1.0, 2.0, 3.0] + + def test_chaining_with_clip_solid(self): + extrusion = self.make_extrusion() + first_clip = ifcopenshell.api.geometry.clip_solid( + self.file, + item=extrusion, + location=[0.0, 0.0, 3.0], + normal=[0.0, 0.0, 1.0], + ) + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=first_clip, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert result.is_a("IfcBooleanClippingResult") + assert result.FirstOperand == first_clip + assert first_clip.FirstOperand == extrusion + + def test_element_registers_result_in_bbim_boolean(self): + extrusion = self.make_extrusion() + wall = self.file.createIfcWall() + result = ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + element=wall, + ) + pset = ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") + assert pset is not None + assert result.id() in json.loads(pset["Data"]) + + def test_no_element_does_not_create_pset(self): + extrusion = self.make_extrusion() + wall = self.file.createIfcWall() + ifcopenshell.api.geometry.clip_solid_bounded( + self.file, + item=extrusion, + location=[2.5, 0.0, 2.0], + normal=[0.6, 0.0, 0.8], + boundary_points=[[2.0, 0.0], [3.0, 0.0], [3.0, 2.0], [2.0, 2.0]], + ) + assert ifcopenshell.util.element.get_pset(wall, "BBIM_Boolean") is None + + +class TestClipSolidBoundedIFC2X3(test.bootstrap.IFC2X3, TestClipSolidBounded): + pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_connect_path.py b/src/ifcopenshell-python/test/api/geometry/test_connect_path.py index bda341d2c6..0927e474ae 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_connect_path.py +++ b/src/ifcopenshell-python/test/api/geometry/test_connect_path.py @@ -33,6 +33,18 @@ class TestConnectPath(test.bootstrap.IFC4): assert rel.RelatedConnectionType == "ATEND" assert rel.Description == "MITRE" + def test_storing_connection_geometry(self): + wall1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + geometry = self.file.create_entity("IfcConnectionPointGeometry") + rel = ifcopenshell.api.geometry.connect_path( + self.file, + relating_element=wall1, + related_element=wall2, + connection_geometry=geometry, + ) + assert rel.ConnectionGeometry == geometry + def test_doing_nothing_if_the_element_is_already_connected(self): wall1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") diff --git a/src/ifcopenshell-python/test/api/geometry/test_copy_representation.py b/src/ifcopenshell-python/test/api/geometry/test_copy_representation.py new file mode 100644 index 0000000000..b997a91bcf --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_copy_representation.py @@ -0,0 +1,133 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.geometry +import ifcopenshell.api.root +import ifcopenshell.util.representation +import test.bootstrap + + +class TestCopyRepresentation(test.bootstrap.IFC4): + def _body_context(self): + body = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") + if body is None: + model = self.file.createIfcGeometricRepresentationContext( + ContextType="Model", + CoordinateSpaceDimension=3, + Precision=1e-5, + WorldCoordinateSystem=self.file.createIfcAxis2Placement3D( + self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)) + ), + ) + body = self.file.createIfcGeometricRepresentationSubContext( + ContextIdentifier="Body", + ContextType="Model", + TargetView="MODEL_VIEW", + ParentContext=model, + ) + return body + + def _add_body_rep(self, element): + body = self._body_context() + rep = ifcopenshell.api.geometry.add_wall_representation( + self.file, context=body, length=5.0, height=3.0, thickness=0.2 + ) + ifcopenshell.api.geometry.assign_representation(self.file, product=element, representation=rep) + return rep + + def test_copy_to_empty_target(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + self._add_body_rep(wall_a) + + result = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + assert result is not None + assert result.is_a("IfcShapeRepresentation") + target_rep = ifcopenshell.util.representation.get_representation(wall_b, "Model", "Body") + assert target_rep is not None + assert target_rep == result + + def test_source_rep_entities_are_distinct(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + source_rep = self._add_body_rep(wall_a) + + new_rep = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + assert new_rep.id() != source_rep.id() + assert new_rep.Items[0].id() != source_rep.Items[0].id() + + def test_context_is_shared_not_copied(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + source_rep = self._add_body_rep(wall_a) + + new_rep = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + assert new_rep.ContextOfItems.id() == source_rep.ContextOfItems.id() + + def test_replaces_existing_target_rep(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + self._add_body_rep(wall_a) + old_rep = self._add_body_rep(wall_b) + old_rep_id = old_rep.id() + + ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + try: + self.file.by_id(old_rep_id) + assert False, "old representation still exists" + except RuntimeError: + pass # entity was removed, as expected + + def test_source_unchanged_after_copy(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + source_rep = self._add_body_rep(wall_a) + source_rep_id = source_rep.id() + + ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + assert self.file.by_id(source_rep_id) is not None # source must still exist + assert ifcopenshell.util.representation.get_representation(wall_a, "Model", "Body") is not None + + def test_returns_none_when_no_matching_rep(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + + result = ifcopenshell.api.geometry.copy_representation(self.file, source=wall_a, target=wall_b) + + assert result is None + + def test_custom_context_identifier(self): + wall_a = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall_b = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + self._add_body_rep(wall_a) + + # "Axis" doesn't exist on wall_a, so should return None + result = ifcopenshell.api.geometry.copy_representation( + self.file, source=wall_a, target=wall_b, context_identifier="Axis" + ) + + assert result is None + + +class TestCopyRepresentationIFC2X3(test.bootstrap.IFC2X3, TestCopyRepresentation): + pass diff --git a/src/ifcopenshell-python/test/api/geometry/test_validate_type.py b/src/ifcopenshell-python/test/api/geometry/test_validate_type.py index 7f4a57e995..01fa17bfed 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_validate_type.py +++ b/src/ifcopenshell-python/test/api/geometry/test_validate_type.py @@ -76,7 +76,7 @@ class TestValidateType(test.bootstrap.IFC4): booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1]) assert len(booleans) == 1 - assert len(rep.Items) == 3 + assert len(rep.Items) == 4 assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True assert len(rep.Items) == 1 assert rep.RepresentationType == "CSG" @@ -96,9 +96,9 @@ class TestValidateType(test.bootstrap.IFC4): booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1]) assert len(booleans) == 1 - assert len(rep.Items) == 2 + assert len(rep.Items) == 3 # boolean replaced first, but second1 stays in Items assert ifcopenshell.api.geometry.validate_type(self.file, rep) is False - assert len(rep.Items) == 2 + assert len(rep.Items) == 2 # validate_type unioned second1 into the boolean assert rep.RepresentationType is None diff --git a/src/ifcopenshell-python/test/api/georeference/test_add_georeferencing.py b/src/ifcopenshell-python/test/api/georeference/test_add_georeferencing.py index 68f1ac2597..4eb0e4e609 100644 --- a/src/ifcopenshell-python/test/api/georeference/test_add_georeferencing.py +++ b/src/ifcopenshell-python/test/api/georeference/test_add_georeferencing.py @@ -51,6 +51,38 @@ class TestAddGeoreferencing(test.bootstrap.IFC4): assert len(self.file.by_type("IfcMapConversion")) == 1 assert len(self.file.by_type("IfcProjectedCRS")) == 1 + def test_recovering_from_orphan_projected_crs(self): + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + ifcopenshell.api.context.add_context(self.file, "Model") + self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234") + assert len(self.file.by_type("IfcProjectedCRS")) == 1 + assert len(self.file.by_type("IfcCoordinateOperation")) == 0 + ifcopenshell.api.georeference.add_georeferencing(self.file) + assert len(self.file.by_type("IfcMapConversion")) == 1 + assert len(self.file.by_type("IfcProjectedCRS")) == 1 + + def test_recovering_from_orphan_coordinate_operation(self): + ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject") + context = ifcopenshell.api.context.add_context(self.file, "Model") + self.file.create_entity( + "IfcMapConversion", + SourceCRS=context, + TargetCRS=self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234"), + ) + ifcopenshell.api.georeference.remove_georeferencing(self.file) + # Simulate orphan by re-adding just a conversion without CRS + self.file.create_entity( + "IfcMapConversion", + SourceCRS=context, + TargetCRS=self.file.create_entity("IfcProjectedCRS", Name="EPSG:1234"), + ) + self.file.remove(self.file.by_type("IfcProjectedCRS")[0]) + assert len(self.file.by_type("IfcProjectedCRS")) == 0 + assert len(self.file.by_type("IfcCoordinateOperation")) == 1 + ifcopenshell.api.georeference.add_georeferencing(self.file) + assert len(self.file.by_type("IfcMapConversion")) == 1 + assert len(self.file.by_type("IfcProjectedCRS")) == 1 + class TestAddGeoreferencingIFC2X3(test.bootstrap.IFC2X3): def test_adding_georeferencing(self): diff --git a/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py b/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py new file mode 100644 index 0000000000..f59930e0e5 --- /dev/null +++ b/src/ifcopenshell-python/test/api/grid/test_remove_grid_axis.py @@ -0,0 +1,59 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.grid +import test.bootstrap + + +class TestRemoveGridAxis(test.bootstrap.IFC4): + def test_removing_an_axis_removes_its_curve(self): + grid = self.file.createIfcGrid() + axis = ifcopenshell.api.grid.create_grid_axis( + self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid + ) + axis.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))]) + axis2 = ifcopenshell.api.grid.create_grid_axis( + self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid + ) + axis2.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((1.0, 0.0, 0.0))]) + ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2) + assert grid.UAxes == (axis,) + assert len(self.file.by_type("IfcGridAxis")) == 1 + # The curve should be removed since it was only used by the removed axis. + assert len(self.file.by_type("IfcPolyline")) == 1 + + def test_removing_an_axis_preserves_shared_curve(self): + grid = self.file.createIfcGrid() + shared_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))]) + axis = ifcopenshell.api.grid.create_grid_axis( + self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid + ) + axis.AxisCurve = shared_curve + axis2 = ifcopenshell.api.grid.create_grid_axis( + self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid + ) + axis2.AxisCurve = shared_curve + ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2) + assert grid.UAxes == (axis,) + # The shared curve should be preserved since it's still used by axis. + assert shared_curve in self.file + assert axis.AxisCurve == shared_curve + + +class TestRemoveGridAxisIFC2X3(test.bootstrap.IFC2X3, TestRemoveGridAxis): + pass diff --git a/src/ifcopenshell-python/test/api/nest/test_assign_object.py b/src/ifcopenshell-python/test/api/nest/test_assign_object.py index 88f8d0bb90..c83f796ac0 100644 --- a/src/ifcopenshell-python/test/api/nest/test_assign_object.py +++ b/src/ifcopenshell-python/test/api/nest/test_assign_object.py @@ -18,8 +18,10 @@ import pytest +import ifcopenshell.api.aggregate import ifcopenshell.api.nest import ifcopenshell.api.root +import ifcopenshell.api.spatial import ifcopenshell.util.element import test.bootstrap @@ -82,6 +84,24 @@ class TestAssignObject(test.bootstrap.IFC4): ifcopenshell.api.nest.assign_object(self.file, related_objects=subelements[2:3], relating_object=element2) assert rel.RelatedObjects == tuple(subelements[:2] + subelements[3:]) + def test_nesting_removes_spatial_containment(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + storey = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuildingStorey") + ifcopenshell.api.spatial.assign_container(self.file, products=[subelement], relating_structure=storey) + assert ifcopenshell.util.element.get_container(subelement) == storey + ifcopenshell.api.nest.assign_object(self.file, related_objects=[subelement], relating_object=element) + assert ifcopenshell.util.element.get_container(subelement) is None + + def test_nesting_removes_aggregate(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + assembly = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcElementAssembly") + ifcopenshell.api.aggregate.assign_object(self.file, products=[subelement], relating_object=assembly) + assert ifcopenshell.util.element.get_aggregate(subelement) == assembly + ifcopenshell.api.nest.assign_object(self.file, related_objects=[subelement], relating_object=element) + assert ifcopenshell.util.element.get_aggregate(subelement) is None + class TestAssignObjectIFC2X3(test.bootstrap.IFC2X3, TestAssignObject): pass diff --git a/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py b/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py new file mode 100644 index 0000000000..d967a0017a --- /dev/null +++ b/src/ifcopenshell-python/test/api/pset_template/test_remove_prop_template.py @@ -0,0 +1,38 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.pset_template +import test.bootstrap + + +class TestRemovePropTemplate(test.bootstrap.IFC4): + def test_removing_a_prop_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop2) + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1 + assert template.HasPropertyTemplates == (prop1,) + + def test_not_removing_the_last_prop_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop) + # The last prop template should not be removed to keep the pset template valid. + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1 + assert template.HasPropertyTemplates == (prop,) diff --git a/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py b/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py new file mode 100644 index 0000000000..67c700a74f --- /dev/null +++ b/src/ifcopenshell-python/test/api/pset_template/test_remove_pset_template.py @@ -0,0 +1,35 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.pset_template +import test.bootstrap + + +class TestRemovePsetTemplate(test.bootstrap.IFC4): + def test_removing_a_pset_template(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template) + assert len(self.file.by_type("IfcPropertySetTemplate")) == 0 + + def test_removing_a_pset_template_with_property_templates(self): + template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors") + prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template) + ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template) + assert len(self.file.by_type("IfcPropertySetTemplate")) == 0 + assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 0 diff --git a/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py b/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py new file mode 100644 index 0000000000..74182d1c69 --- /dev/null +++ b/src/ifcopenshell-python/test/api/resource/test_remove_resource_quantity.py @@ -0,0 +1,42 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2021 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.resource +import test.bootstrap + + +class TestRemoveResourceQuantity(test.bootstrap.IFC4): + def test_removing_a_resource_quantity(self): + self.file.create_entity("IfcProject") + resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource") + ifcopenshell.api.resource.add_resource_quantity(self.file, resource=resource, ifc_class="IfcQuantityTime") + assert resource.BaseQuantity is not None + ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource) + assert resource.BaseQuantity is None + assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 0 + + def test_removing_a_resource_quantity_when_none_exists(self): + self.file.create_entity("IfcProject") + resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource") + # Should not raise. + ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource) + assert resource.BaseQuantity is None + + +class TestRemoveResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestRemoveResourceQuantity): + pass diff --git a/src/ifcopenshell-python/test/api/structural/test_assign_product.py b/src/ifcopenshell-python/test/api/structural/test_assign_product.py new file mode 100644 index 0000000000..3e2f1c2b4a --- /dev/null +++ b/src/ifcopenshell-python/test/api/structural/test_assign_product.py @@ -0,0 +1,56 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell.api.root +import ifcopenshell.api.structural +import test.bootstrap + + +class TestAssignProduct(test.bootstrap.IFC4): + def test_creating_a_new_relationship(self): + member = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcStructuralSurfaceMember") + wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + rel = ifcopenshell.api.structural.assign_product(self.file, relating_product=member, related_object=wall) + assert rel.is_a("IfcRelAssignsToProduct") + assert rel.RelatingProduct == member + assert wall in rel.RelatedObjects + + def test_adding_a_second_object_to_an_existing_relationship(self): + member = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcStructuralSurfaceMember") + wall1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + wall2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + rel1 = ifcopenshell.api.structural.assign_product(self.file, relating_product=member, related_object=wall1) + rel2 = ifcopenshell.api.structural.assign_product(self.file, relating_product=member, related_object=wall2) + assert rel1 == rel2 + assert len(self.file.by_type("IfcRelAssignsToProduct")) == 1 + assert wall1 in rel1.RelatedObjects + assert wall2 in rel1.RelatedObjects + + def test_does_not_duplicate_an_existing_assignment(self): + member = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcStructuralSurfaceMember") + wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + ifcopenshell.api.structural.assign_product(self.file, relating_product=member, related_object=wall) + ifcopenshell.api.structural.assign_product(self.file, relating_product=member, related_object=wall) + assert len(self.file.by_type("IfcRelAssignsToProduct")) == 1 + rels = self.file.by_type("IfcRelAssignsToProduct") + assert len(rels[0].RelatedObjects) == 1 + + +class TestAssignProductIFC2X3(test.bootstrap.IFC2X3, TestAssignProduct): + pass diff --git a/src/ifcopenshell-python/test/api/structural/test_assign_to_building.py b/src/ifcopenshell-python/test/api/structural/test_assign_to_building.py new file mode 100644 index 0000000000..671ca1b476 --- /dev/null +++ b/src/ifcopenshell-python/test/api/structural/test_assign_to_building.py @@ -0,0 +1,62 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2026 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . +# This file was generated with the assistance of an AI coding tool. + +import ifcopenshell.api.root +import ifcopenshell.api.structural +import test.bootstrap + + +class TestAssignToBuilding(test.bootstrap.IFC4): + def test_creating_a_new_relationship(self): + model = ifcopenshell.api.structural.add_structural_analysis_model(self.file) + building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + rel = ifcopenshell.api.structural.assign_to_building( + self.file, structural_analysis_model=model, building=building + ) + assert rel.is_a("IfcRelServicesBuildings") + assert rel.RelatingSystem == model + assert building in rel.RelatedBuildings + + def test_adding_a_second_building_to_an_existing_relationship(self): + model = ifcopenshell.api.structural.add_structural_analysis_model(self.file) + building1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + building2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + rel1 = ifcopenshell.api.structural.assign_to_building( + self.file, structural_analysis_model=model, building=building1 + ) + rel2 = ifcopenshell.api.structural.assign_to_building( + self.file, structural_analysis_model=model, building=building2 + ) + assert rel1 == rel2 + assert len(self.file.by_type("IfcRelServicesBuildings")) == 1 + assert building1 in rel1.RelatedBuildings + assert building2 in rel1.RelatedBuildings + + def test_does_not_duplicate_an_existing_assignment(self): + model = ifcopenshell.api.structural.add_structural_analysis_model(self.file) + building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + ifcopenshell.api.structural.assign_to_building(self.file, structural_analysis_model=model, building=building) + ifcopenshell.api.structural.assign_to_building(self.file, structural_analysis_model=model, building=building) + assert len(self.file.by_type("IfcRelServicesBuildings")) == 1 + rels = self.file.by_type("IfcRelServicesBuildings") + assert len(rels[0].RelatedBuildings) == 1 + + +class TestAssignToBuildingIFC2X3(test.bootstrap.IFC2X3, TestAssignToBuilding): + pass diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 7036f93528..8bed51a56f 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -15,38 +15,3 @@ # # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . - -from datetime import datetime - -import ifcopenshell.api.control -import ifcopenshell.api.cost -import ifcopenshell.api.root -import ifcopenshell.util.element -import test.bootstrap - - -def deprecation_check(test): - def new_test(self): - assert datetime.now().date() < datetime(2026, 1, 9).date(), "API arguments are completely deprecated" - test(self) - - return new_test - - -class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): - @deprecation_check - def test_assigning_control(self): - model = self.file - element = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall") - control = ifcopenshell.api.cost.add_cost_schedule(model) - ifcopenshell.api.control.assign_control(model, relating_control=control, related_objects=[element]) - assert list(ifcopenshell.util.element.get_controls(element)) == [control] - - @deprecation_check - def test_unassigning_control(self): - TestTemporarySupportForDeprecatedAPIArguments.test_assigning_control(self) - model = self.file - element = model.by_type("IfcWall")[0] - control = model.by_type("IfcCostSchedule")[0] - ifcopenshell.api.control.unassign_control(model, relating_control=control, related_objects=[element]) - assert list(ifcopenshell.util.element.get_controls(element)) == [] diff --git a/src/ifcopenshell-python/test/file_gc.py b/src/ifcopenshell-python/test/test_file_gc.py similarity index 99% rename from src/ifcopenshell-python/test/file_gc.py rename to src/ifcopenshell-python/test/test_file_gc.py index 483c268e17..532fa664ec 100644 --- a/src/ifcopenshell-python/test/file_gc.py +++ b/src/ifcopenshell-python/test/test_file_gc.py @@ -95,7 +95,7 @@ def test_bug_2486_a(): file = ifcopenshell.api.project.create_file() mymaterial = ifcopenshell.api.material.add_material(file) - pset = ifcopenshell.api.pset.add_pset(file, product=mymaterial) + pset = ifcopenshell.api.pset.add_pset(file, product=mymaterial, name="Foo") ifcopenshell.api.pset.edit_pset( file, pset=pset, diff --git a/src/ifcopenshell-python/test/global_id_updates.py b/src/ifcopenshell-python/test/test_global_id_updates.py similarity index 100% rename from src/ifcopenshell-python/test/global_id_updates.py rename to src/ifcopenshell-python/test/test_global_id_updates.py diff --git a/src/ifcopenshell-python/test/instance_string_formatting.py b/src/ifcopenshell-python/test/test_instance_string_formatting.py similarity index 100% rename from src/ifcopenshell-python/test/instance_string_formatting.py rename to src/ifcopenshell-python/test/test_instance_string_formatting.py diff --git a/src/ifcopenshell-python/test/test_package.py b/src/ifcopenshell-python/test/test_package.py index b251e081c7..c7ed42a26a 100644 --- a/src/ifcopenshell-python/test/test_package.py +++ b/src/ifcopenshell-python/test/test_package.py @@ -32,7 +32,7 @@ except: # - .github/workflows/ci-ifcopenshell-python.yml # - .github/workflows/ci-ifcopenshell-python-pypi.yml # - src/ifcopenshell-python/Makefile (PYVERSION check) -SUPPORTED_PY_VERSIONS = ("39", "310", "311", "312", "313", "314") +SUPPORTED_PY_VERSIONS = ("310", "311", "312", "313", "314") SUPPORTED_PLATFORMS = ("win64", "linux64", "macos64", "macosm164") WASM_SUPPORTED_PY_VERSIONS = ("313",) diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 1308e957c1..5eb6034402 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -38,6 +38,7 @@ import ifcopenshell.api.sequence import ifcopenshell.api.spatial import ifcopenshell.api.style import ifcopenshell.api.type +import ifcopenshell.api.feature import ifcopenshell.guid import ifcopenshell.util.element as subject import test.bootstrap @@ -891,6 +892,35 @@ class TestGetlayers(test.bootstrap.IFC4, TestGetlayersIFC2X3): assert subject.get_layers(self.file, element) == [layer] +class TestGetParentIFC4(test.bootstrap.IFC4): + def test_getting_the_parent_of_an_element(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + ifcopenshell.api.spatial.assign_container(self.file, products=[element], relating_structure=building) + assert subject.get_parent(element) == building + + def test_getting_the_specific_parent_of_an_element(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + storey = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuildingStorey") + ifcopenshell.api.aggregate.assign_object(self.file, products=[storey], relating_object=building) + ifcopenshell.api.spatial.assign_container(self.file, products=[element], relating_structure=storey) + assert subject.get_parent(element, ifc_class="IfcBuilding") == building + assert subject.get_parent(element, ifc_class="IfcSite") == None + + def test_getting_the_specific_parent_of_an_element_via_voiding(self): + wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding") + ifcopenshell.api.spatial.assign_container(self.file, products=[wall], relating_structure=building) + opening = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcOpeningElement") + ifcopenshell.api.feature.add_feature(self.file, feature=opening, element=wall) + window = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWindow") + ifcopenshell.api.feature.add_filling(self.file, opening=opening, element=window) + assert subject.get_parent(window, ifc_class="IfcWall") == wall + assert subject.get_parent(window, ifc_class="IfcBuilding") == building + assert subject.get_parent(window, ifc_class="IfcSite") == None + + class TestGetContainerIFC4(test.bootstrap.IFC4): def test_getting_the_spatial_container_of_an_element(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") diff --git a/src/ifcopenshell-python/test/util/test_shape_builder.py b/src/ifcopenshell-python/test/util/test_shape_builder.py index 5ff8adc03f..6a01a74201 100644 --- a/src/ifcopenshell-python/test/util/test_shape_builder.py +++ b/src/ifcopenshell-python/test/util/test_shape_builder.py @@ -41,7 +41,7 @@ from ifcopenshell.util.shape_builder import ( class TestMathutilsCompatibleMethods(test.bootstrap.IFC4): def test_np_rotation_matrix(self): - from mathutils import Matrix, Vector # pyright: ignore[reportMissingImports] + from mathutils import Matrix, Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] # 2D. assert np.allclose(Matrix.Rotation(radians(45), 2), np_rotation_matrix(radians(45), 2)) @@ -62,7 +62,7 @@ class TestMathutilsCompatibleMethods(test.bootstrap.IFC4): assert np.allclose(Matrix.Rotation(*rotation_vector_args), np_rotation_matrix(*rotation_vector_args)) def test_np_matrix_to_euler(self): - from mathutils import Euler # pyright: ignore[reportMissingImports] + from mathutils import Euler # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] # Test 3x3. rot = Euler((0.5, 0.5, 0.5)).to_matrix() @@ -77,7 +77,7 @@ class TestMathutilsCompatibleMethods(test.bootstrap.IFC4): assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot))) def test_np_angle(self): - from mathutils import Vector # pyright: ignore[reportMissingImports] + from mathutils import Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] v1, v2 = (1, 0, 0), (0, 1, 0) angle = np_angle(v1, v2) @@ -100,7 +100,7 @@ class TestMathutilsCompatibleMethods(test.bootstrap.IFC4): assert is_x(angle, radians(90)) def test_np_normal(self): - import mathutils.geometry # pyright: ignore[reportMissingImports] + import mathutils.geometry # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] vectors = (0, 0, 0), (1, 0, 0), (0, 1, 0) n = mathutils.geometry.normal(vectors) @@ -113,7 +113,7 @@ class TestMathutilsCompatibleMethods(test.bootstrap.IFC4): assert np.allclose(n, (0, 0, -1)) def test_np_intersect_line_line(self): - import mathutils.geometry # pyright: ignore[reportMissingImports] + import mathutils.geometry # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] p1, p2 = [0, 0, 0], [1, 1, 1] q1, q2 = [0, 1, 0], [1, 0, 1] diff --git a/src/ifcopenshell-python/type-check-requirements.txt b/src/ifcopenshell-python/type-check-requirements.txt new file mode 100644 index 0000000000..c797c28034 --- /dev/null +++ b/src/ifcopenshell-python/type-check-requirements.txt @@ -0,0 +1,31 @@ +beautifulsoup4 +cjio >=0.8, <0.10 +deepdiff +docutils +flask +isodate +jinja2 +lark +meshio +mysql-connector-python +networkx +numpy +odfpy +openpyxl +pandas +psutil +pydantic +PyP6Xer +pystache +pytest +python-dateutil +requests +scikit-learn +shapely +tabulate +toposort +typing-extensions +typst +xlsxwriter +xmlschema +xsdata diff --git a/src/ifcpatch/ifcpatch/__init__.py b/src/ifcpatch/ifcpatch/__init__.py index c279bfea1c..3b6b8fdf40 100644 --- a/src/ifcpatch/ifcpatch/__init__.py +++ b/src/ifcpatch/ifcpatch/__init__.py @@ -215,11 +215,11 @@ def _extract_docs(cls: type, method_name: str, boilerplate_args: Union[Sequence[ input_data = inputs[input_name] # E.g. list[str]. - if isinstance(type_hint, typing.GenericAlias): + if isinstance(type_hint, typing.GenericAlias): # pyright: ignore[reportAttributeAccessIssue] input_data["generic_type"] = type_hint.__name__ type_hint = typing.get_args(type_hint)[0] - if isinstance(type_hint, typing._UnionGenericAlias): + if isinstance(type_hint, typing._UnionGenericAlias): # pyright: ignore[reportAttributeAccessIssue] inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] elif type_hint.__name__ == "Literal": inputs[input_name]["type"] = "Literal" diff --git a/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py index 38a7a2950e..fac4573a23 100644 --- a/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py +++ b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py @@ -67,9 +67,9 @@ class Patcher: def patch(self) -> None: import bonsai.tool as tool - import bpy + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import ifcopenshell.util.element - from mathutils import Matrix, Vector + from mathutils import Matrix, Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] if len(bpy.data.objects) > 0: bpy.data.batch_remove(bpy.data.objects) diff --git a/src/ifcpatch/ifcpatch/recipes/FixRevit2025TINs.py b/src/ifcpatch/ifcpatch/recipes/FixRevit2025TINs.py index 642afb6e8c..8cb68c3dfc 100644 --- a/src/ifcpatch/ifcpatch/recipes/FixRevit2025TINs.py +++ b/src/ifcpatch/ifcpatch/recipes/FixRevit2025TINs.py @@ -16,13 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcPatch. If not, see . +from __future__ import annotations import logging -from typing import Optional +from typing import Optional, TYPE_CHECKING + import ifcopenshell import ifcopenshell.util.shape_builder +if TYPE_CHECKING: + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] + class Patcher: def __init__( @@ -109,12 +114,14 @@ class Patcher: self.should_create_edges = should_create_edges def patch(self) -> None: - import bmesh + import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import bonsai.tool as tool - import bpy - import ifcopenshell.util.shape_builder + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] + import ifcopenshell.util.schema + import ifcopenshell.util.unit - bpy.context.scene.BIMProjectProperties.should_use_native_meshes = True + props = tool.Project.get_project_props() + props.should_use_native_meshes = True bpy.ops.bim.load_project(filepath=self.filepath) old_history_size = tool.Ifc.get().history_size @@ -125,7 +132,7 @@ class Patcher: self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) for obj in bpy.data.objects: - if not obj.BIMObjectProperties.ifc_definition_id or not obj.data: + if not tool.Blender.get_ifc_definition_id(obj) or not obj.data: continue if not obj.data.polygons: continue @@ -159,14 +166,14 @@ class Patcher: self.file = tool.Ifc.get() - def create_edges(self, obj): - import bmesh + def create_edges(self, obj: bpy.types.Object) -> None: + import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import bonsai.tool as tool - import bpy + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import ifcopenshell.api.geometry import ifcopenshell.api.root - import ifcopenshell.util.element import ifcopenshell.util.representation + import ifcopenshell.util.schema element = tool.Ifc.get_entity(obj) data = obj.data @@ -224,16 +231,16 @@ class Patcher: ifcopenshell.util.schema.reassign_class(tool.Ifc.get(), element2, "IfcVirtualElement") bm.free() - def create_face_sampleable_object(self, obj): + def create_face_sampleable_object(self, obj: bpy.types.Object) -> None: # No sharp faces from math import degrees - import bmesh + import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import bonsai.tool as tool import ifcopenshell.api.geometry import ifcopenshell.api.root - import ifcopenshell.util.element import ifcopenshell.util.representation + import ifcopenshell.util.shape_builder print("working on ", obj.name) element = tool.Ifc.get_entity(obj) @@ -271,17 +278,17 @@ class Patcher: bm.free() - def create_edge_sampleable_object(self, obj): + def create_edge_sampleable_object(self, obj: bpy.types.Object) -> None: # This is crazy but we need a sharp face per island from math import degrees, radians, sin - import bmesh + import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import bonsai.tool as tool import ifcopenshell.api.geometry import ifcopenshell.api.root - import ifcopenshell.util.element import ifcopenshell.util.representation - from mathutils import Matrix + import ifcopenshell.util.shape_builder + from mathutils import Matrix # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] # Get the active object (assumed to have a mesh) mesh = obj.data @@ -430,7 +437,6 @@ class Patcher: bm.free() print("Added a triangle to", islands_count, "mesh island(s).") - return def create_curves_from_curve_ifc2x3( self, is_2d: bool = False, curve_object_data=None diff --git a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py index 6c083456e9..cd801d9fe3 100644 --- a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py +++ b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py @@ -80,9 +80,9 @@ class Patcher: def patch(self) -> None: from math import degrees - import bmesh + import bmesh # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] import bonsai.tool as tool - import bpy + import bpy # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import] props = tool.Project.get_project_props() props.should_use_native_meshes = True diff --git a/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py b/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py index 38b01135b3..8da51a4ac8 100644 --- a/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py +++ b/src/ifcpatch/ifcpatch/recipes/MergeDuplicateTypes.py @@ -97,5 +97,5 @@ class Patcher: relating_type=relating_type, related_objects=related_objects, should_map_representations=False, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] ) diff --git a/src/ifcpatch/ifcpatch/recipes/PurgeData.py b/src/ifcpatch/ifcpatch/recipes/PurgeData.py index 3c34927a63..0ea968f48a 100644 --- a/src/ifcpatch/ifcpatch/recipes/PurgeData.py +++ b/src/ifcpatch/ifcpatch/recipes/PurgeData.py @@ -57,13 +57,7 @@ class Patcher: def patch(self): self.file.header.file_name.name = "Rabbit" - self.file.header.file_name.time_stamp = ( - datetime.datetime.utcnow() - .replace(tzinfo=datetime.timezone.utc) - .astimezone() - .replace(microsecond=0) - .isoformat() - ) + self.file.header.file_name.time_stamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat() self.file.header.file_name.preprocessor_version = "Rabbit" self.file.header.file_name.originating_system = "Rabbit" diff --git a/src/ifcpatch/test/test_ifcpatch.py b/src/ifcpatch/test/test_ifcpatch.py index 4d7e98e55a..d0976965b6 100644 --- a/src/ifcpatch/test/test_ifcpatch.py +++ b/src/ifcpatch/test/test_ifcpatch.py @@ -52,10 +52,10 @@ class Test: assert output.by_type("IfcProject")[0].GlobalId == project.GlobalId assert output.by_type("IfcWall")[0].GlobalId == wall.GlobalId - output_path = Path(tempfile.mktemp()) + output_path = Path(tempfile.mkstemp()[1]) try: - assert not output_path.exists() + assert output_path.stat().st_size == 0 ifcpatch.write(patcher.get_output(), output_path) - assert output_path.exists() + assert output_path.stat().st_size != 0 finally: output_path.unlink() diff --git a/src/ifcquery/Makefile b/src/ifcquery/Makefile new file mode 100644 index 0000000000..f77132db5e --- /dev/null +++ b/src/ifcquery/Makefile @@ -0,0 +1,10 @@ +PACKAGE_NAME:=ifcquery +include ../common.mk + +.PHONY: test +test: + pytest tests + +.PHONY: qa +qa: + black . diff --git a/src/ifcquery/README.md b/src/ifcquery/README.md new file mode 100644 index 0000000000..5cb895dc44 --- /dev/null +++ b/src/ifcquery/README.md @@ -0,0 +1,500 @@ + +# ifcquery + +A CLI tool for querying and inspecting IFC building models. All output is +structured JSON (or human-readable text), making it easy to pipe into other +tools or scripts. + +## Installation + +```bash +pip install ifcquery +``` + +Requires `ifcopenshell`. The `clash` subcommand additionally requires the +IfcOpenShell C++ geometry bindings (`ifcopenshell.geom`). + +## Usage + +``` +ifcquery [options] [--format json|text|ids] +``` + +The `--format` flag controls output: + +- `json` (default) -- structured JSON, suitable for piping to `jq` or `ifcedit foreach` +- `text` -- indented human-readable output +- `ids` -- comma-separated step IDs extracted from list results, suitable for piping directly into `ifcedit run` parameters + +## Subcommands + +### summary + +Get a model overview: schema version, entity counts, and project info. + +```bash +ifcquery model.ifc summary +``` + +```json +{ + "schema": "IFC4", + "total_entities": 1847, + "project": { + "id": 1, + "name": "Office Building", + "description": null + }, + "types": { + "IfcWall": 42, + "IfcSlab": 12, + "IfcWindow": 36 + } +} +``` + +### tree + +Display the spatial hierarchy from IfcProject down through sites, buildings, +storeys, and their contained elements. + +```bash +ifcquery model.ifc tree +``` + +```json +{ + "id": 1, + "type": "IfcProject", + "name": "Office Building", + "children": [ + { + "id": 2, + "type": "IfcSite", + "name": "Default Site", + "children": [ + { + "id": 3, + "type": "IfcBuilding", + "name": "Main Building", + "children": [ + { + "id": 4, + "type": "IfcBuildingStorey", + "name": "Ground Floor", + "elements": [ + {"id": 10, "type": "IfcWall", "name": "Wall001"}, + {"id": 11, "type": "IfcSlab", "name": "Floor001"} + ] + } + ] + } + ] + } + ] +} +``` + +### info + +Get detailed information about a specific element by step ID. + +```bash +ifcquery model.ifc info 10 +ifcquery model.ifc info '#10' +``` + +Returns attributes, property sets, type relationship, material assignment, +spatial container, and placement matrix. + +```json +{ + "id": 10, + "type": "IfcWall", + "attributes": { + "Name": "Wall001", + "Description": null, + "ObjectType": "LOADBEARING" + }, + "property_sets": { + "Pset_WallCommon": { + "IsExternal": true, + "FireRating": "2HR" + } + }, + "element_type": {"id": 50, "type": "IfcWallType", "name": "Standard"}, + "material": {"id": 60, "type": "IfcMaterial", "name": "Concrete"}, + "container": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"}, + "placement": [ + [1.0, 0.0, 0.0, 5.0], + [0.0, 1.0, 0.0, 0.0], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0] + ] +} +``` + +### select + +Filter elements using the ifcopenshell selector syntax. + +```bash +ifcquery model.ifc select 'IfcWall' +ifcquery model.ifc select 'IfcWall, IfcSlab' +``` + +```json +[ + {"id": 10, "type": "IfcWall", "name": "Wall001"}, + {"id": 11, "type": "IfcWall", "name": "Wall002"}, + {"id": 20, "type": "IfcSlab", "name": "Floor001"} +] +``` + +Results are sorted by ID. + +Use `--format ids` to get a comma-separated list of step IDs for direct use +in `ifcedit run` parameters: + +```bash +ifcedit run model.ifc type.assign_type \ + --related_objects "$(ifcquery model.ifc --format ids select 'IfcWall')" \ + --relating_type 456 +``` + +### relations + +Show all relationships for an element, organized by category: hierarchy, +children, type relationships, groups, systems, material, and connections. + +```bash +ifcquery model.ifc relations 10 +``` + +```json +{ + "id": 10, + "type": "IfcWall", + "name": "Wall001", + "hierarchy": { + "parent": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"}, + "container": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"} + }, + "children": { + "openings": [{"id": 30, "type": "IfcOpeningElement", "name": "Opening01"}] + }, + "type_relationship": { + "type_of": {"id": 50, "type": "IfcWallType", "name": "Standard"} + }, + "material": {"id": 60, "type": "IfcMaterial", "name": "Concrete"} +} +``` + +Empty categories are omitted from output. + +Use `--traverse up` to walk the spatial hierarchy from the element up to +IfcProject: + +```bash +ifcquery model.ifc relations 10 --traverse up +``` + +```json +[ + {"id": 10, "type": "IfcWall", "name": "Wall001"}, + {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"}, + {"id": 3, "type": "IfcBuilding", "name": "Main Building"}, + {"id": 2, "type": "IfcSite", "name": "Default Site"}, + {"id": 1, "type": "IfcProject", "name": "Office Building"} +] +``` + +### validate + +Check the model for schema and constraint violations. + +```bash +ifcquery model.ifc validate +ifcquery model.ifc validate --rules +``` + +Options: + +- `--rules` -- also run the slower EXPRESS rules check (default: off) + +```json +{ + "valid": true, + "issues": [] +} +``` + +On an invalid model: + +```json +{ + "valid": false, + "issues": [ + {"level": "ERROR", "message": "Entity #42 IfcWall.GlobalId is not a valid IfcGloballyUniqueId"} + ] +} +``` + +### schedule + +List all work schedules and their task trees from the model. + +```bash +ifcquery model.ifc schedule +ifcquery model.ifc schedule --depth 1 +``` + +Options: + +- `--depth N` -- expand at most N levels of subtasks (default: unlimited). At the + cutoff, `subtasks` is replaced with `{"truncated": true, "count": N}`. + +```json +[ + { + "id": 42, + "name": "Construction Schedule", + "predefined_type": "BASELINE", + "tasks": [ + { + "id": 55, + "name": "Phase 1", + "start": "2024-01-01T09:00:00", + "finish": "2024-06-30T17:00:00", + "is_milestone": false, + "outputs": [{"id": 10, "type": "IfcWall", "name": "Wall A"}], + "subtasks": [ + {"id": 56, "name": "Foundations", "start": null, "finish": null, + "is_milestone": false, "outputs": [], "subtasks": []} + ] + } + ] + } +] +``` + +### cost + +List all cost schedules and their cost item trees from the model. + +```bash +ifcquery model.ifc cost +ifcquery model.ifc cost --depth 2 +``` + +Options: + +- `--depth N` -- expand at most N levels of subitems (default: unlimited). At the + cutoff, `subitems` is replaced with `{"truncated": true, "count": N}`. + +```json +[ + { + "id": 100, + "name": "Bill of Quantities", + "predefined_type": "COSTPLAN", + "items": [ + { + "id": 110, + "name": "Concrete Works", + "values": [{"formula": "1200.00 = material(1200.0)", "category": "material"}], + "subitems": [ + {"id": 111, "name": "Formwork", "values": [], "subitems": []} + ] + } + ] + } +] +``` + +### schema + +Show IFC class documentation for any entity type, using the schema version of +the loaded model. + +```bash +ifcquery model.ifc schema IfcWall +ifcquery model.ifc schema IfcBuildingStorey +``` + +```json +{ + "description": "The wall represents a vertical construction ...", + "predefined_types": {"STANDARD": "A standard wall, extruded vertically ..."}, + "spec_url": "https://standards.buildingsmart.org/...", + "attributes": { + "Name": "Optional name for use by the participating software systems", + "ObjectPlacement": "Placement of the product in space ..." + } +} +``` + +Returns `{"error": "Unknown entity: Foo"}` for unrecognised types. + +### contexts + +List all geometric representation contexts and subcontexts in the model. + +```bash +ifcquery model.ifc contexts +``` + +```json +[ + { + "id": 5, + "type": "IfcGeometricRepresentationContext", + "context_type": "Model", + "subcontexts": [ + {"id": 6, "type": "IfcGeometricRepresentationSubContext", "context_identifier": "Body", "target_view": "MODEL_VIEW"}, + {"id": 7, "type": "IfcGeometricRepresentationSubContext", "context_identifier": "Axis", "target_view": "GRAPH_VIEW"} + ] + } +] +``` + +### materials + +List all materials and material sets used in the model, with their assigned elements. + +```bash +ifcquery model.ifc materials +``` + +```json +[ + { + "id": 60, + "type": "IfcMaterial", + "name": "Concrete", + "elements": [{"id": 10, "type": "IfcWall", "name": "Wall001"}] + } +] +``` + +### plot + +Generate a 2D technical drawing (floor plan, elevation, or section) of the model and write it to a file. + +```bash +ifcquery model.ifc plot -o output.svg --out-format svg +ifcquery model.ifc plot -o output.png --view floorplan --scale 0.01 +``` + +Options: + +- `-o, --output ` -- output file path (default: `.svg` or `.png`) +- `--out-format {svg,png,base64}` -- output format (default: `png`) +- `--view {floorplan,elevation,section,auto}` -- drawing view (default: `floorplan`) +- `--scale ` -- model-to-paper scale ratio (default: 0.01 = 1:100) +- `--width-mm ` -- paper width in mm (default: 297) +- `--height-mm ` -- paper height in mm (default: 420) +- `--png-width ` -- raster output width in pixels (default: 1024) +- `--png-height ` -- raster output height in pixels (default: 1024) + +Requires the IfcOpenShell drawing module (`ifcopenshell.draw`). PNG output additionally requires `cairosvg`. + +### render + +Render a 3D view of the model geometry to a PNG file. + +```bash +ifcquery model.ifc render -o output.png +ifcquery model.ifc render -o output.png --view iso --selector IfcWall +``` + +Options: + +- `--view {iso,top,south,north,east,west}` -- camera angle (default: `iso`) +- `--selector ` -- ifcopenshell selector to restrict rendered elements + +Requires `pyvista` and the IfcOpenShell C++ geometry bindings. + +### clash + +Check a single element for geometric intersections and clearance violations +against other elements. + +```bash +ifcquery model.ifc clash 10 +ifcquery model.ifc clash 10 --clearance 0.5 +ifcquery model.ifc clash 10 --scope all --tolerance 0.001 +``` + +Options: + +- `--clearance ` -- minimum clearance distance to check +- `--tolerance ` -- intersection tolerance (default: 0.002) +- `--scope {storey,all}` -- check against same-storey elements or all elements (default: storey) + +```json +{ + "element": {"id": 10, "type": "IfcWall", "name": "Wall001"}, + "scope": "storey", + "pass": false, + "checks": { + "intersection": { + "pass": false, + "tolerance": 0.002, + "clashes": [ + { + "element": {"id": 11, "type": "IfcWall", "name": "Wall002"}, + "type": "intersection", + "distance": 0.0, + "p1": [2.5, 2.5, 1.5], + "p2": [2.5, 2.5, 1.5] + } + ] + }, + "clearance": { + "pass": true, + "clearance": 0.5, + "clashes": [] + } + } +} +``` + +Requires the IfcOpenShell C++ geometry bindings. + +## Scripting with ifcedit + +`ifcquery` and `ifcedit` are designed to compose. Use `--format ids` to pass +query results directly into `ifcedit run` parameters, or pipe JSON into +`ifcedit foreach` to apply an operation to every matching element. + +```bash +# Remove all walls from their spatial container +ifcedit run model.ifc spatial.unassign_container \ + --products "$(ifcquery model.ifc --format ids select 'IfcWall')" + +# Delete every window (model opened and saved once) +ifcquery model.ifc select 'IfcWindow' | ifcedit foreach model.ifc root.remove_product --product {id} + +# Bulk rename all doors +ifcquery model.ifc select 'IfcDoor' | ifcedit foreach model.ifc attribute.edit_attributes \ + --product {id} --attributes '{"Name": "Door"}' + +# Render an element highlighted against everything related to it +ifcquery model.ifc render relations.png \ + --element "$(ifcquery model.ifc --format ids relations 42)" + +# Render a clash — subject and clashing elements highlighted together +ifcquery model.ifc render clash.png \ + --element "$(ifcquery model.ifc --format ids clash 42)" +``` + +See the `ifcedit` documentation for the full `foreach` reference. + +## Error handling + +Errors are written to stderr. Exit code is 0 on success, 1 on error. + +## License + +LGPLv3+ -- see the IfcOpenShell project license. diff --git a/src/ifcquery/ifcquery/__init__.py b/src/ifcquery/ifcquery/__init__.py new file mode 100644 index 0000000000..be5327b20d --- /dev/null +++ b/src/ifcquery/ifcquery/__init__.py @@ -0,0 +1,20 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +__version__ = version = "0.0.0" diff --git a/src/ifcquery/ifcquery/__main__.py b/src/ifcquery/ifcquery/__main__.py new file mode 100644 index 0000000000..d4b6141fd9 --- /dev/null +++ b/src/ifcquery/ifcquery/__main__.py @@ -0,0 +1,397 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import argparse +import json +import os +import sys + +import ifcopenshell + +from ifcquery import clash as clash_mod +from ifcquery import contexts as contexts_mod +from ifcquery import cost as cost_mod +from ifcquery import ( + info, + plot, + relations, + schedule, + schema, + select, + summary, + tree, +) +from ifcquery import ( + materials as materials_mod, +) +from ifcquery import ( + render as render_mod, +) +from ifcquery import validate as validate_mod + + +def parse_element_id(raw: str) -> int: + """Parse an element ID from '#123' or '123' format.""" + raw = raw.strip().lstrip("#") + return int(raw) + + +def format_output(data, fmt: str) -> str: + if fmt == "json": + return json.dumps(data, indent=2, ensure_ascii=False) + elif fmt == "text": + return _format_text(data) + elif fmt == "ids": + return _format_ids(data) + return json.dumps(data, indent=2, ensure_ascii=False) + + +def _format_ids(data) -> str: + """Extract 'id' fields from a list of dicts and return as comma-separated string. + + For dicts with a top-level 'elements' key (e.g. clash, relations output), + extracts from that flat summary list rather than the nested structure. + """ + if isinstance(data, list): + ids = [str(item["id"]) for item in data if isinstance(item, dict) and "id" in item] + return ",".join(ids) + if isinstance(data, dict): + if "elements" in data and isinstance(data["elements"], list): + return _format_ids(data["elements"]) + if "id" in data: + return str(data["id"]) + return "" + + +def _format_text(data, indent: int = 0) -> str: + prefix = " " * indent + lines = [] + if isinstance(data, dict): + for key, value in data.items(): + if isinstance(value, (dict, list)): + lines.append(f"{prefix}{key}:") + lines.append(_format_text(value, indent + 1)) + else: + lines.append(f"{prefix}{key}: {value}") + elif isinstance(data, list): + for item in data: + if isinstance(item, dict): + lines.append(_format_text(item, indent)) + lines.append("") + else: + lines.append(f"{prefix}- {item}") + else: + lines.append(f"{prefix}{data}") + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser( + prog="ifcquery", + description="Query and inspect IFC building models", + ) + parser.add_argument("ifc_file", help="Path to the IFC file") + parser.add_argument( + "--format", + choices=["json", "text", "ids"], + default="json", + dest="output_format", + help="Output format: json (default), text (human-readable), ids (comma-separated step IDs)", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + subparsers.add_parser("summary", help="Model overview: schema, element counts, project info") + + subparsers.add_parser("tree", help="Spatial hierarchy tree") + + info_parser = subparsers.add_parser("info", help="Deep inspection of a specific element") + info_parser.add_argument("element_id", help="Element step ID (e.g. 123 or #123)") + + select_parser = subparsers.add_parser("select", help="Filter elements using selector syntax") + select_parser.add_argument("query", help="Selector query string") + + relations_parser = subparsers.add_parser("relations", help="Show relationships for an element") + relations_parser.add_argument("element_id", help="Element step ID (e.g. 123 or #123)") + relations_parser.add_argument("--traverse", choices=["up"], help="Traverse hierarchy (up: walk to IfcProject)") + + clash_parser = subparsers.add_parser("clash", help="Check element placement for clashes") + clash_parser.add_argument("element_id", help="Element step ID (e.g. 123 or #123)") + clash_parser.add_argument("--clearance", type=float, help="Minimum clearance distance") + clash_parser.add_argument("--tolerance", type=float, default=0.002, help="Intersection tolerance (default: 0.002)") + clash_parser.add_argument( + "--scope", choices=["storey", "all"], default="storey", help="Scope of elements to check (default: storey)" + ) + + validate_parser = subparsers.add_parser("validate", help="Schema/constraint validation") + validate_parser.add_argument( + "--rules", action="store_true", help="Also check EXPRESS rules (slower, default: false)" + ) + + schedule_parser = subparsers.add_parser("schedule", help="List work plans and tasks from the model") + schedule_parser.add_argument( + "--depth", type=int, default=None, metavar="N", help="Limit subtask expansion to N levels (default: unlimited)" + ) + + cost_parser = subparsers.add_parser("cost", help="List cost schedules and cost items from the model") + cost_parser.add_argument( + "--depth", + type=int, + default=None, + metavar="N", + help="Limit cost item expansion to N levels (default: unlimited)", + ) + + subparsers.add_parser("contexts", help="List geometric representation contexts and subcontexts") + + subparsers.add_parser("materials", help="List materials and material sets") + + schema_parser = subparsers.add_parser("schema", help="IFC class documentation") + schema_parser.add_argument("entity_type", help="IFC entity type (e.g. IfcWall)") + + render_parser = subparsers.add_parser("render", help="Render model geometry to a PNG image") + render_parser.add_argument( + "-o", "--output", default="", metavar="FILE", help="Output PNG path (default: .png)" + ) + render_parser.add_argument( + "--selector", default="", metavar="QUERY", help="ifcopenshell selector to restrict rendered elements" + ) + render_parser.add_argument( + "--element", + default="", + metavar="ID[,ID...]", + help="Comma-separated step IDs of elements to highlight (rest rendered in grey)", + ) + render_parser.add_argument( + "--view", + choices=render_mod.VIEWS, + default="iso", + help="Camera angle (default: iso)", + ) + + plot_parser = subparsers.add_parser( + "plot", help="Plot model drawing (SVG via ifcopenshell.draw; optional PNG via CairoSVG)" + ) + plot_parser.add_argument( + "-o", + "--output", + default="", + metavar="FILE", + help="Output file path. Default depends on --out-format: .svg/.png", + ) + plot_parser.add_argument( + "--out-format", + choices=["svg", "png", "base64"], + default="png", + help="Output format: svg (write SVG), png (write PNG), base64 (print base64 in JSON/text). Default: png", + ) + plot_parser.add_argument( + "--selector", default="", metavar="QUERY", help="ifcopenshell selector to restrict plotted elements" + ) + plot_parser.add_argument( + "--element", default="", metavar="ID[,ID...]", help="Comma-separated step IDs of elements to highlight" + ) + plot_parser.add_argument( + "--view", + choices=getattr(plot, "VIEWS", ("floorplan", "elevation", "section", "auto")), + default="floorplan", + help="Drawing view (default: floorplan)", + ) + plot_parser.add_argument( + "--width-mm", + type=float, + default=297.0, + metavar="MM", + help="Paper width in mm (default: 297)", + ) + plot_parser.add_argument( + "--height-mm", + type=float, + default=420.0, + metavar="MM", + help="Paper height in mm (default: 420)", + ) + plot_parser.add_argument( + "--scale", + type=float, + default=1.0 / 100.0, + metavar="S", + help="Model-to-paper scale (default: 0.01 = 1:100)", + ) + plot_parser.add_argument( + "--png-width", + type=int, + default=1024, + metavar="PX", + help="PNG width in pixels (default: 1024)", + ) + plot_parser.add_argument( + "--png-height", + type=int, + default=1024, + metavar="PX", + help="PNG height in pixels (default: 1024)", + ) + + args = parser.parse_args() + + try: + model = ifcopenshell.open(args.ifc_file) + except Exception as e: + print(f"Error: Could not open IFC file: {e}", file=sys.stderr) + sys.exit(1) + + if args.command == "summary": + result = summary.summary(model) + elif args.command == "tree": + result = tree.tree(model) + elif args.command == "info": + try: + element_id = parse_element_id(args.element_id) + except ValueError: + print(f"Error: Invalid element ID: {args.element_id}", file=sys.stderr) + sys.exit(1) + try: + element = model.by_id(element_id) + except RuntimeError: + print(f"Error: Element #{element_id} not found", file=sys.stderr) + sys.exit(1) + result = info.info(model, element) + elif args.command == "select": + result = select.select(model, args.query) + elif args.command == "relations": + try: + element_id = parse_element_id(args.element_id) + except ValueError: + print(f"Error: Invalid element ID: {args.element_id}", file=sys.stderr) + sys.exit(1) + try: + element = model.by_id(element_id) + except RuntimeError: + print(f"Error: Element #{element_id} not found", file=sys.stderr) + sys.exit(1) + result = relations.relations(model, element, traverse=args.traverse) + elif args.command == "clash": + try: + element_id = parse_element_id(args.element_id) + except ValueError: + print(f"Error: Invalid element ID: {args.element_id}", file=sys.stderr) + sys.exit(1) + try: + element = model.by_id(element_id) + except RuntimeError: + print(f"Error: Element #{element_id} not found", file=sys.stderr) + sys.exit(1) + try: + result = clash_mod.clash( + model, element, clearance=args.clearance, tolerance=args.tolerance, scope=args.scope + ) + except ImportError: + print("Error: ifcopenshell geometry engine not available (C++ bindings required)", file=sys.stderr) + sys.exit(1) + elif args.command == "validate": + result = validate_mod.validate(model, express_rules=args.rules) + elif args.command == "schedule": + result = schedule.schedule(model, max_depth=args.depth) + elif args.command == "cost": + result = cost_mod.cost(model, max_depth=args.depth) + elif args.command == "contexts": + result = contexts_mod.contexts(model) + elif args.command == "materials": + result = materials_mod.materials(model) + elif args.command == "schema": + result = schema.schema(model, args.entity_type) + elif args.command == "render": + element_ids = None + if args.element: + try: + element_ids = [parse_element_id(part) for part in args.element.split(",")] + except ValueError: + print(f"Error: Invalid element ID(s): {args.element}", file=sys.stderr) + sys.exit(1) + out_path = args.output or (os.path.splitext(args.ifc_file)[0] + ".png") + try: + png_bytes = render_mod.render( + model, + selector=args.selector or None, + element_ids=element_ids, + view=args.view, + ) + except ImportError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + with open(out_path, "wb") as f: + f.write(png_bytes) + print(f"Saved render to {out_path}", file=sys.stderr) + return + elif args.command == "plot": + element_ids = None + if args.element: + try: + element_ids = [parse_element_id(part) for part in args.element.split(",")] + except ValueError: + print(f"Error: Invalid element ID(s): {args.element}", file=sys.stderr) + sys.exit(1) + + try: + result = plot.plot( + model, + selector=args.selector or None, + element_ids=element_ids, + view=args.view, + width_mm=args.width_mm, + height_mm=args.height_mm, + scale=args.scale, + output_format=args.out_format, + ) + except ImportError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + if args.out_format == "base64": + # result is a dict; serialise to stdout so callers can consume it + print(format_output(result, args.output_format)) + return + + # svg or png: write to a file + base = os.path.splitext(args.ifc_file)[0] + if args.out_format == "svg": + out_path = args.output or (base + ".svg") + else: + out_path = args.output or (base + ".png") + + with open(out_path, "wb") as f: + f.write(result) + + print(f"Saved drawing to {out_path}", file=sys.stderr) + return + + print(format_output(result, args.output_format)) + + +if __name__ == "__main__": + main() diff --git a/src/ifcquery/ifcquery/clash.py b/src/ifcquery/ifcquery/clash.py new file mode 100644 index 0000000000..52c8d53e3b --- /dev/null +++ b/src/ifcquery/ifcquery/clash.py @@ -0,0 +1,180 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import multiprocessing +import sys +from typing import Any + +import ifcopenshell +import ifcopenshell.geom +import ifcopenshell.util.element + + +def _ref(element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Serialize an element to a compact reference dict.""" + result: dict[str, Any] = {"id": element.id(), "type": element.is_a()} + if hasattr(element, "Name") and element.Name: + result["name"] = element.Name + return result + + +def _get_scope_elements( + model: ifcopenshell.file, element: ifcopenshell.entity_instance, scope: str +) -> tuple[set[ifcopenshell.entity_instance], str]: + """Return set of elements to check against and the effective scope used. + + Returns (elements, effective_scope) where effective_scope may differ from + the requested scope if fallback was needed. + """ + if scope == "storey": + container = ifcopenshell.util.element.get_container(element) + if container is not None: + siblings = set(ifcopenshell.util.element.get_contained(container)) + siblings.discard(element) + return siblings, "storey" + else: + print( + f"Warning: Element #{element.id()} has no spatial container, falling back to --scope all", + file=sys.stderr, + ) + + # scope == "all" or fallback + elements = set(model.by_type("IfcElement")) + elements -= set(model.by_type("IfcFeatureElement")) + elements.discard(element) + return elements, "all" + + +def _build_tree(model: ifcopenshell.file, elements: set[ifcopenshell.entity_instance]) -> ifcopenshell.geom.tree | None: + """Build geometry tree for given elements using iterator. + + Returns None if iterator fails to initialize (no geometry available). + """ + geom_settings = ifcopenshell.geom.settings() + geom_settings.set("use-world-coords", True) + geom_tree = ifcopenshell.geom.tree() + iterator = ifcopenshell.geom.iterator(geom_settings, model, multiprocessing.cpu_count(), include=list(elements)) + if not iterator.initialize(): + return None + while True: + geom_tree.add_element(iterator.get()) + if not iterator.next(): + break + return geom_tree + + +def _format_clash(clash_result, geom_tree: ifcopenshell.geom.tree, model: ifcopenshell.file) -> dict[str, Any]: + """Format a single clash result to dict.""" + # clash result .a/.b are C++ wrapper entity_instances without .Name; + # look up the Python entity from the model by id for proper serialization + other = model.by_id(clash_result.b.id()) + return { + "element": _ref(other), + "type": geom_tree.get_clash_type(clash_result.clash_type), + "distance": clash_result.distance, + "p1": list(clash_result.p1), + "p2": list(clash_result.p2), + } + + +def clash( + model: ifcopenshell.file, + element: ifcopenshell.entity_instance, + clearance: float | None = None, + tolerance: float = 0.002, + scope: str = "storey", +) -> dict[str, Any]: + """Check element for geometric clashes against other elements. + + :param model: The IFC model. + :param element: The element to check. + :param clearance: Minimum clearance distance; if provided, runs clearance check. + :param tolerance: Intersection tolerance in meters (default 0.002). + :param scope: Which elements to check against: "storey" or "all". + :return: Dict with clash results suitable for JSON serialization. + """ + result: dict[str, Any] = {"element": _ref(element)} + + # Get scope elements + scope_elements, effective_scope = _get_scope_elements(model, element, scope) + result["scope"] = effective_scope + + if not scope_elements: + result["pass"] = True + result["checks"] = {"intersection": {"pass": True, "tolerance": tolerance, "clashes": []}} + if clearance is not None: + result["checks"]["clearance"] = {"pass": True, "clearance": clearance, "clashes": []} + return result + + # Build geometry tree for target element + scope elements + all_elements = scope_elements | {element} + geom_tree = _build_tree(model, all_elements) + + if geom_tree is None: + result["pass"] = None + result["error"] = f"No geometry for element #{element.id()}" + return result + + # Run intersection check + intersection_clashes = geom_tree.clash_intersection_many( + [element], list(scope_elements), tolerance=tolerance, check_all=True + ) + intersection_results = [_format_clash(c, geom_tree, model) for c in intersection_clashes] + checks: dict[str, Any] = { + "intersection": { + "pass": len(intersection_results) == 0, + "tolerance": tolerance, + "clashes": intersection_results, + } + } + + all_pass = len(intersection_results) == 0 + + # Run clearance check if requested + if clearance is not None: + clearance_clashes = geom_tree.clash_clearance_many( + [element], list(scope_elements), clearance=clearance, check_all=True + ) + clearance_results = [_format_clash(c, geom_tree, model) for c in clearance_clashes] + checks["clearance"] = { + "pass": len(clearance_results) == 0, + "clearance": clearance, + "clashes": clearance_results, + } + if clearance_results: + all_pass = False + + result["pass"] = all_pass + result["checks"] = checks + + # Flat list of subject + all clashing elements across all checks, deduplicated. + # Allows --format ids to extract all involved IDs without jq. + seen: set[int] = {element.id()} + involved = [_ref(element)] + for check in checks.values(): + for clash_item in check.get("clashes", []): + eid = clash_item["element"]["id"] + if eid not in seen: + seen.add(eid) + involved.append(clash_item["element"]) + result["elements"] = involved + + return result diff --git a/src/ifcquery/ifcquery/contexts.py b/src/ifcquery/ifcquery/contexts.py new file mode 100644 index 0000000000..912df173d7 --- /dev/null +++ b/src/ifcquery/ifcquery/contexts.py @@ -0,0 +1,44 @@ +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import ifcopenshell + + +def contexts(model: ifcopenshell.file) -> list[dict]: + """Return all geometric representation contexts and subcontexts. + + :param model: The in-memory IFC model. + :return: List of dicts with id, type, context_type, context_identifier, + and (for subcontexts) target_view and parent_context_id. + """ + results = [] + for ctx in model.by_type("IfcGeometricRepresentationContext"): + entry = { + "id": ctx.id(), + "type": ctx.is_a(), + "context_type": getattr(ctx, "ContextType", None), + "context_identifier": getattr(ctx, "ContextIdentifier", None), + } + if ctx.is_a("IfcGeometricRepresentationSubContext"): + entry["target_view"] = ctx.TargetView + parent = ctx.ParentContext + entry["parent_context_id"] = parent.id() if parent else None + results.append(entry) + return results diff --git a/src/ifcquery/ifcquery/cost.py b/src/ifcquery/ifcquery/cost.py new file mode 100644 index 0000000000..ef9559d597 --- /dev/null +++ b/src/ifcquery/ifcquery/cost.py @@ -0,0 +1,45 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.cost as cost_util + + +def _cost_item_to_dict(item: ifcopenshell.entity_instance, max_depth: int | None, depth: int) -> dict[str, Any]: + raw_values = cost_util.get_cost_values(item) + values = [{"formula": v.get("label", ""), "category": v.get("category")} for v in raw_values] + + if max_depth is not None and depth >= max_depth: + child_count = len(cost_util.get_nested_cost_items(item)) + subitems = {"truncated": True, "count": child_count} if child_count else [] + else: + subitems = [_cost_item_to_dict(sub, max_depth, depth + 1) for sub in cost_util.get_nested_cost_items(item)] + + return { + "id": item.id(), + "name": getattr(item, "Name", None), + "values": values, + "subitems": subitems, + } + + +def cost(model: ifcopenshell.file, max_depth: int | None = None) -> list[dict[str, Any]]: + """Return a list of IfcCostSchedule entries with nested cost item trees. + + max_depth limits how many levels of subitems are expanded (None = unlimited). + At the cutoff level, subitems is replaced with {"truncated": True, "count": N}. + """ + result = [] + for cost_schedule in model.by_type("IfcCostSchedule"): + items = [_cost_item_to_dict(i, max_depth, depth=1) for i in cost_util.get_root_cost_items(cost_schedule)] + result.append( + { + "id": cost_schedule.id(), + "name": getattr(cost_schedule, "Name", None), + "predefined_type": getattr(cost_schedule, "PredefinedType", None), + "items": items, + } + ) + return result diff --git a/src/ifcquery/ifcquery/info.py b/src/ifcquery/ifcquery/info.py new file mode 100644 index 0000000000..ad9a9daa4f --- /dev/null +++ b/src/ifcquery/ifcquery/info.py @@ -0,0 +1,262 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.element +import ifcopenshell.util.placement + +# --------------------------------------------------------------------------- +# Geometry summary helpers +# --------------------------------------------------------------------------- + +_MAX_PROFILE_POINTS = 20 + + +def _rc(coords) -> list[float]: + """Round a coordinate sequence to 6 decimal places.""" + return [round(float(c), 6) for c in coords] + + +def _curve_points(curve) -> list | None: + if curve.is_a("IfcPolyline"): + return [_rc(p.Coordinates) for p in curve.Points] + if curve.is_a("IfcIndexedPolyCurve"): + return [_rc(c) for c in curve.Points.CoordList] + return None + + +def _profile_summary(profile) -> dict: + t = profile.is_a() + result: dict[str, Any] = {"type": t} + if t == "IfcRectangleProfileDef": + result["x_dim"] = profile.XDim + result["y_dim"] = profile.YDim + elif t in ("IfcCircleProfileDef", "IfcCircleHollowProfileDef"): + result["radius"] = profile.Radius + if t == "IfcCircleHollowProfileDef": + result["wall_thickness"] = profile.WallThickness + elif t in ("IfcArbitraryClosedProfileDef", "IfcArbitraryProfileDefWithVoids"): + pts = _curve_points(profile.OuterCurve) + if pts is not None: + if len(pts) <= _MAX_PROFILE_POINTS: + result["points"] = pts + else: + result["point_count"] = len(pts) + elif t == "IfcCompositeProfileDef": + result["profiles"] = [_profile_summary(p) for p in profile.Profiles] + return result + + +def _half_space_plane(half_space) -> dict | None: + if not half_space.is_a("IfcHalfSpaceSolid"): + return None + surface = half_space.BaseSurface + if not surface or not surface.is_a("IfcPlane"): + return None + pos = surface.Position + loc = _rc(pos.Location.Coordinates) + normal = _rc(pos.Axis.DirectionRatios) if pos.Axis else [0.0, 0.0, 1.0] + return {"location": loc, "normal": normal} + + +def _walk_clipping(item) -> tuple: + """Return (base_solid, [clipping_plane_dicts]) from a BooleanClippingResult chain.""" + planes = [] + current = item + while current.is_a("IfcBooleanClippingResult"): + plane = _half_space_plane(current.SecondOperand) + if plane: + planes.append(plane) + current = current.FirstOperand + return current, planes + + +def _swept_solid_dict(item) -> dict: + result: dict[str, Any] = {"solid_type": item.is_a()} + if item.is_a("IfcExtrudedAreaSolid"): + result["depth"] = item.Depth + if item.ExtrudedDirection: + result["direction"] = _rc(item.ExtrudedDirection.DirectionRatios) + if item.SweptArea: + result["profile"] = _profile_summary(item.SweptArea) + return result + + +def _summarize_rep(rep) -> dict: + rep_type = rep.RepresentationType or "" + result: dict[str, Any] = {"representation_type": rep_type} + items = list(rep.Items) + + if rep_type == "MappedRepresentation": + for item in items: + if item.is_a("IfcMappedItem"): + return _summarize_rep(item.MappingSource.MappedRepresentation) + + elif rep_type == "SweptSolid": + result["solids"] = [_swept_solid_dict(item) for item in items] + + elif rep_type == "Clipping": + solids = [] + for item in items: + base, planes = _walk_clipping(item) + solid = _swept_solid_dict(base) + if planes: + solid["clipping_planes"] = planes + solids.append(solid) + result["solids"] = solids + + elif rep_type == "CSG": + ops = [] + for item in items: + if hasattr(item, "Operator"): + ops.append({"operator": str(item.Operator), "type": item.is_a()}) + if ops: + result["operations"] = ops + + elif rep_type in ("Brep", "Tessellation", "SolidModel"): + face_count = 0 + vertex_count = 0 + for item in items: + if item.is_a("IfcPolygonalFaceSet"): + face_count += len(item.Faces) + vertex_count += len(item.Coordinates.CoordList) + elif item.is_a("IfcFacetedBrep"): + face_count += len(item.Outer.CfsFaces) + if face_count: + result["face_count"] = face_count + if vertex_count: + result["vertex_count"] = vertex_count + + return result + + +def _geometry_summary(element) -> dict | None: + if not hasattr(element, "Representation") or not element.Representation: + return None + body_rep = next( + (r for r in element.Representation.Representations if r.RepresentationIdentifier == "Body"), + None, + ) + if body_rep is None: + return None + try: + return _summarize_rep(body_rep) + except Exception: + return None + + +def _serialize_attribute(value: Any) -> Any: + """Convert an IFC attribute value to a JSON-serializable form.""" + if isinstance(value, ifcopenshell.entity_instance): + return {"id": value.id(), "type": value.is_a()} + if isinstance(value, tuple): + return [_serialize_attribute(v) for v in value] + return value + + +def _material_to_dict(material: ifcopenshell.entity_instance | None) -> dict[str, Any] | None: + """Convert a material entity to a summary dict.""" + if material is None: + return None + result: dict[str, Any] = { + "id": material.id(), + "type": material.is_a(), + } + if hasattr(material, "Name"): + result["name"] = material.Name + return result + + +def info(model: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Return deep inspection data for an element.""" + result: dict[str, Any] = { + "id": element.id(), + "type": element.is_a(), + } + + # Direct attributes via get_info() which returns a dict of all attributes + element_info = element.get_info() + attrs = {} + for key, value in element_info.items(): + if key in ("id", "type"): + continue + attrs[key] = _serialize_attribute(value) + result["attributes"] = attrs + + # Property sets and quantity sets + try: + psets = ifcopenshell.util.element.get_psets(element) + if psets: + result["property_sets"] = psets + except Exception: + pass + + # Element type + try: + element_type = ifcopenshell.util.element.get_type(element) + if element_type: + type_info: dict[str, Any] = { + "id": element_type.id(), + "type": element_type.is_a(), + } + if hasattr(element_type, "Name"): + type_info["name"] = element_type.Name + result["element_type"] = type_info + except Exception: + pass + + # Material + try: + material = ifcopenshell.util.element.get_material(element) + mat_dict = _material_to_dict(material) + if mat_dict: + result["material"] = mat_dict + except Exception: + pass + + # Spatial container + try: + container = ifcopenshell.util.element.get_container(element) + if container: + result["container"] = { + "id": container.id(), + "type": container.is_a(), + "name": container.Name if hasattr(container, "Name") else None, + } + except Exception: + pass + + # Placement (as 4x4 matrix) + try: + if hasattr(element, "ObjectPlacement") and element.ObjectPlacement: + matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement) + result["placement"] = matrix.tolist() + except Exception: + pass + + # Geometry summary + geom = _geometry_summary(element) + if geom: + result["geometry_summary"] = geom + + return result diff --git a/src/ifcquery/ifcquery/materials.py b/src/ifcquery/ifcquery/materials.py new file mode 100644 index 0000000000..c7402d51e5 --- /dev/null +++ b/src/ifcquery/ifcquery/materials.py @@ -0,0 +1,100 @@ +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import ifcopenshell + + +def materials(model: ifcopenshell.file) -> list[dict]: + """Return all materials and material sets from the model. + + :param model: The in-memory IFC model. + :return: List of dicts covering IfcMaterial, IfcMaterialLayerSet, + IfcMaterialConstituentSet, and IfcMaterialProfileSet entities. + """ + results = [] + + for m in model.by_type("IfcMaterial"): + results.append( + { + "id": m.id(), + "type": "IfcMaterial", + "name": m.Name, + "category": getattr(m, "Category", None), + } + ) + + for ls in model.by_type("IfcMaterialLayerSet"): + layers = [] + for layer in ls.MaterialLayers or []: + layers.append( + { + "name": layer.Name, + "thickness": layer.LayerThickness, + "material": layer.Material.Name if layer.Material else None, + "is_ventilated": layer.IsVentilated, + } + ) + results.append( + { + "id": ls.id(), + "type": "IfcMaterialLayerSet", + "name": ls.LayerSetName, + "layers": layers, + } + ) + + for cs in model.by_type("IfcMaterialConstituentSet"): + constituents = [] + for c in cs.MaterialConstituents or []: + constituents.append( + { + "name": c.Name, + "material": c.Material.Name if c.Material else None, + "fraction": c.Fraction, + } + ) + results.append( + { + "id": cs.id(), + "type": "IfcMaterialConstituentSet", + "name": cs.Name, + "constituents": constituents, + } + ) + + for ps in model.by_type("IfcMaterialProfileSet"): + profiles = [] + for p in ps.MaterialProfiles or []: + profiles.append( + { + "name": p.Name, + "material": p.Material.Name if p.Material else None, + } + ) + results.append( + { + "id": ps.id(), + "type": "IfcMaterialProfileSet", + "name": ps.Name, + "profiles": profiles, + } + ) + + return results diff --git a/src/ifcquery/ifcquery/plot.py b/src/ifcquery/ifcquery/plot.py new file mode 100644 index 0000000000..3393a87fc8 --- /dev/null +++ b/src/ifcquery/ifcquery/plot.py @@ -0,0 +1,284 @@ +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import base64 +import os +from io import BytesIO +from typing import Any + +import ifcopenshell +import ifcopenshell.geom +import ifcopenshell.util.selector + +try: + import ifcopenshell.draw + + _HAS_DRAW = True +except ImportError: + _HAS_DRAW = False + +from xml.etree.ElementTree import Element, ElementTree, SubElement, register_namespace + +try: + import cairosvg # type: ignore + + _HAS_CAIROSVG = True +except Exception: + _HAS_CAIROSVG = False + + +try: + from PIL import Image # type: ignore + + _HAS_PIL = True +except Exception: + _HAS_PIL = False + +VIEWS = ("floorplan", "elevation", "section", "auto") +OUTPUT_FORMATS = ("svg", "png", "base64") + + +def _escape_css_attr(name: str) -> str: + # CSS attribute selectors must escape ':' (e.g. ifc:guid -> ifc\:guid) + return name.replace(":", "\\:") + + +def _highlight_css_from_ids(model: ifcopenshell.file, element_ids: list[int]) -> str: + guids: list[str] = [] + for sid in element_ids: + try: + e = model.by_id(int(sid)) + except RuntimeError: + continue + if e is None: + continue + gid = getattr(e, "GlobalId", None) + if isinstance(gid, str) and gid: + guids.append(gid) + + if not guids: + return "" + + attr = _escape_css_attr("ifc:guid") + + css = [ + "/* Auto-highlight injected by ifcquery.plot */", + f"[{attr}] path {{ opacity: 0.10; }}", + f"[{attr}] text {{ opacity: 0.25; }}", + ] + for gid in guids: + css.append(f'[{attr}="{gid}"] path {{ opacity: 1.0; stroke: #d00; stroke-width: 0.25; }}') + css.append(f'[{attr}="{gid}"] text {{ opacity: 1.0; fill: #d00; }}') + return "\n".join(css) + "\n" + + +def _make_filtered_iterator(model: ifcopenshell.file, include_elements: list[Any]) -> ifcopenshell.geom.iterator: + # Avoid multiprocessing in WASM; os.cpu_count is good enough. + n_threads = os.cpu_count() or 1 + + # These flags mirror the defaults used by ifcopenshell.draw in v0.8.x. + geom_settings = ifcopenshell.geom.settings( + REORIENT_SHELLS=False, + ELEMENT_HIERARCHY=True, + ) + + # IfcOpenShell wrapper constants may live in different places across builds. + wrapper = getattr(ifcopenshell, "ifcopenshell_wrapper", None) + if wrapper is not None: + try: + geom_settings.set("iterator-output", wrapper.NATIVE) + except Exception: + pass + try: + geom_settings.set("apply-default-materials", True) + except Exception: + pass + try: + geom_settings.set("dimensionality", wrapper.SURFACES_AND_SOLIDS) + except Exception: + pass + + return ifcopenshell.geom.iterator(geom_settings, model, n_threads, include=include_elements) + + +def _diagnose_empty_drawing(model: ifcopenshell.file, view: str) -> str: + """Return a helpful error message when ifcopenshell.draw produces no geometry groups.""" + hints = [] + + if view in ("floorplan", "auto"): + storeys = model.by_type("IfcBuildingStorey") + if not storeys: + hints.append("the model has no IfcBuildingStorey entities (required for auto_floorplan)") + else: + null_elevation = [s for s in storeys if getattr(s, "Elevation", None) is None] + if null_elevation: + names = ", ".join(f'"{s.Name or s.GlobalId}"' for s in null_elevation) + hints.append( + f"storey Elevation is None for: {names} — " + "set IfcBuildingStorey.Elevation (e.g. 0.0) so the section cut height can be determined" + ) + + has_geom = any(getattr(e, "Representation", None) is not None for e in model.by_type("IfcProduct")) + if not has_geom: + hints.append("no IfcProduct entities have geometric representations") + + base = f"No plan geometry found for view={view!r}." + if hints: + return base + " Possible causes: " + "; ".join(hints) + "." + return base + " The model may lack geometry visible in this view." + + +def plot( + model: ifcopenshell.file, + *, + output_format: str = "png", + selector: str | None = None, + element_ids: list[int] | None = None, + view: str = "floorplan", + # SVG / page sizing (draw works in mm coordinates) + width_mm: float = 297.0, + height_mm: float = 420.0, + scale: float = 1.0 / 100.0, + merge_projection: bool = True, + # PNG sizing (only for output_format png/base64) + png_width: int = 1024, + png_height: int = 1024, +) -> bytes | dict[str, Any]: + """ + Plot IFC model as SVG (via ifcopenshell.draw) or PNG/base64 (via CairoSVG). + + Args: + model: In-memory IFC model. + output_format: 'svg' | 'png' | 'base64' + - 'svg' -> returns SVG bytes + - 'png' -> returns PNG bytes + - 'base64'-> returns dict: {mime, png_b64, width, height, view} + selector: ifcopenshell selector query to restrict plotted elements. + element_ids: STEP ids to highlight; non-highlighted geometry is faded. + view: One of VIEWS ('floorplan', 'elevation', 'section', 'auto'). + width_mm, height_mm: Page size in mm. + scale: Model-to-paper scale (0.01 means 1:100). + merge_projection: Passed through to ifcopenshell.draw.main. + png_width, png_height: Raster size in pixels for png/base64 outputs. + + Raises: + ImportError: if ifcopenshell.draw or CairoSVG is not available (as required). + ValueError: invalid args or selector matches nothing. + """ + if output_format not in OUTPUT_FORMATS: + raise ValueError(f"output_format must be one of {OUTPUT_FORMATS}, got {output_format!r}") + if view not in VIEWS: + raise ValueError(f"view must be one of {VIEWS}, got {view!r}") + if not _HAS_DRAW: + raise ImportError("ifcopenshell.draw is not available in this environment.") + + # Configure draw settings + settings = ifcopenshell.draw.draw_settings( + auto_floorplan=(view in ("floorplan", "auto")), + auto_elevation=(view in ("elevation", "auto")), + auto_section=(view in ("section", "auto")), + width=width_mm, + height=height_mm, + scale=scale, + css="", + ) + + # Optional highlight CSS overlay + if element_ids: + settings.css = _highlight_css_from_ids(model, element_ids) + + # Optional element restriction via selector -> custom iterator + iterators: tuple[Any, ...] = () + if selector: + include_elements = list(ifcopenshell.util.selector.filter_elements(model, selector)) + if not include_elements: + raise ValueError(f"Selector {selector!r} matched no elements") + it = _make_filtered_iterator(model, include_elements) + iterators = (it,) + # If we explicitly include elements, don't rely on exclude_entities (best-effort). + settings.exclude_entities = "" + + # Generate SVG + svg_bytes = ifcopenshell.draw.main( + settings, + files=[model], + iterators=iterators, + merge_projection=merge_projection, + ) + + register_namespace("", "http://www.w3.org/2000/svg") + + def svg_split(f): + x = ElementTree(file=f) + svg = x.getroot() + resources = [] + for child in svg: + if child.tag == "{http://www.w3.org/2000/svg}g": + root = Element(svg.tag, svg.attrib) + n = ElementTree(root) + for r in resources + [child]: + root.append(r) + b = BytesIO() + n.write(b, xml_declaration=True, encoding="utf-8", method="xml") + yield b.getvalue() + else: + resources.append(child) + + if output_format == "svg": + return svg_bytes + + # Need CairoSVG for png/base64 + if not _HAS_CAIROSVG: + raise ImportError("CairoSVG is not installed. Install with: pip install cairosvg") + + svgs = list(svg_split(BytesIO(svg_bytes))) + if not svgs: + raise ValueError(_diagnose_empty_drawing(model, view)) + + composite = None + png_bytes = None + for i, svgb in enumerate(svgs): + png_bytes = cairosvg.svg2png(bytestring=svgb, output_width=png_width, output_height=png_height) + if len(svgs) == 1: + break + + # Need Pillow for concatenating images + if not _HAS_PIL: + raise ImportError("Pillow is not installed. Install with: pip install Pillow") + + if composite is None: + composite = Image.new("RGBA", (png_width, png_height * len(svgs))) + img = Image.open(BytesIO(png_bytes)) + composite.paste(img, (0, png_height * i)) + if composite is not None: + b = BytesIO() + composite.save(b, "png") + png_bytes = b.getvalue() + + if output_format == "base64": + return { + "mime": "image/png", + "png_b64": base64.b64encode(png_bytes).decode(), + "width": png_width, + "height": png_height, + "view": view, + } + + return png_bytes diff --git a/src/ifcquery/ifcquery/relations.py b/src/ifcquery/ifcquery/relations.py new file mode 100644 index 0000000000..556ee0324d --- /dev/null +++ b/src/ifcquery/ifcquery/relations.py @@ -0,0 +1,197 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.element +import ifcopenshell.util.system + + +def _ref(element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Serialize an element to a compact reference dict.""" + result: dict[str, Any] = {"id": element.id(), "type": element.is_a()} + if hasattr(element, "Name") and element.Name: + result["name"] = element.Name + return result + + +def _ref_or_none(element: ifcopenshell.entity_instance | None) -> dict[str, Any] | None: + return _ref(element) if element is not None else None + + +def _ref_list(elements) -> list[dict[str, Any]]: + return [_ref(e) for e in elements] + + +def _traverse_up(element: ifcopenshell.entity_instance) -> list[dict[str, Any]]: + """Walk the hierarchy from element up to IfcProject.""" + chain = [_ref(element)] + current = element + while True: + parent = ifcopenshell.util.element.get_parent(current) + if parent is None: + break + chain.append(_ref(parent)) + current = parent + return chain + + +def _all_relations(model: ifcopenshell.file, element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Collect all relationships for an element.""" + result: dict[str, Any] = { + "id": element.id(), + "type": element.is_a(), + } + if hasattr(element, "Name") and element.Name: + result["name"] = element.Name + + # Hierarchy (upward) + hierarchy: dict[str, Any] = {} + parent = ifcopenshell.util.element.get_parent(element) + if parent is not None: + hierarchy["parent"] = _ref(parent) + container = ifcopenshell.util.element.get_container(element) + if container is not None: + hierarchy["container"] = _ref(container) + aggregate = ifcopenshell.util.element.get_aggregate(element) + if aggregate is not None: + hierarchy["aggregate"] = _ref(aggregate) + nest = ifcopenshell.util.element.get_nest(element) + if nest is not None: + hierarchy["nest"] = _ref(nest) + filled_void = ifcopenshell.util.element.get_filled_void(element) + if filled_void is not None: + hierarchy["filled_void"] = _ref(filled_void) + voided_element = ifcopenshell.util.element.get_voided_element(element) + if voided_element is not None: + hierarchy["voided_element"] = _ref(voided_element) + if hierarchy: + result["hierarchy"] = hierarchy + + # Children (downward) + children: dict[str, Any] = {} + contained = ifcopenshell.util.element.get_contained(element) + if contained: + children["contained"] = _ref_list(contained) + parts = ifcopenshell.util.element.get_parts(element) + if parts: + children["parts"] = _ref_list(parts) + components = ifcopenshell.util.element.get_components(element) + if components: + children["components"] = _ref_list(components) + openings = list(ifcopenshell.util.element.get_openings(element)) + if openings: + children["openings"] = _ref_list(openings) + if children: + result["children"] = children + + # Type relationship + type_relationship: dict[str, Any] = {} + element_type = ifcopenshell.util.element.get_type(element) + if element_type is not None: + type_relationship["type_of"] = _ref(element_type) + try: + occurrences = ifcopenshell.util.element.get_types(element) + if occurrences: + type_relationship["occurrences"] = _ref_list(occurrences) + except Exception: + pass + if type_relationship: + result["type_relationship"] = type_relationship + + # Groups + groups = ifcopenshell.util.element.get_groups(element) + if groups: + result["groups"] = _ref_list(groups) + + # Systems + systems = ifcopenshell.util.system.get_element_systems(element) + if systems: + result["systems"] = _ref_list(systems) + + # Zones + zones = ifcopenshell.util.system.get_element_zones(element) + if zones: + result["zones"] = _ref_list(zones) + + # Material + material = ifcopenshell.util.element.get_material(element) + if material is not None: + result["material"] = _ref(material) + + # Referenced structures + referenced = ifcopenshell.util.element.get_referenced_structures(element) + if referenced: + result["referenced_structures"] = _ref_list(referenced) + + # Connections + connections: dict[str, Any] = {} + connected_to = ifcopenshell.util.system.get_connected_to(element) + if connected_to: + connections["connected_to"] = _ref_list(connected_to) + connected_from = ifcopenshell.util.system.get_connected_from(element) + if connected_from: + connections["connected_from"] = _ref_list(connected_from) + ports = ifcopenshell.util.system.get_ports(element) + if ports: + connections["ports"] = _ref_list(ports) + if connections: + result["connections"] = connections + + return result + + +def _collect_elements(data: Any, seen: set[int], result: list[dict[str, Any]]) -> None: + """Recursively collect all element refs (dicts with 'id') from a nested structure.""" + if isinstance(data, dict): + if "id" in data and isinstance(data["id"], int): + eid = data["id"] + if eid not in seen: + seen.add(eid) + result.append( + {"id": data["id"], "type": data.get("type"), "name": data.get("name")} + if "name" in data + else {"id": data["id"], "type": data.get("type")} + ) + for v in data.values(): + _collect_elements(v, seen, result) + elif isinstance(data, list): + for item in data: + _collect_elements(item, seen, result) + + +def relations( + model: ifcopenshell.file, element: ifcopenshell.entity_instance, traverse: str | None = None +) -> dict[str, Any] | list[dict[str, Any]]: + """Return relationships for an element, or hierarchy chain if traverse='up'.""" + if traverse == "up": + return _traverse_up(element) + result = _all_relations(model, element) + + # Flat list of subject + all referenced elements, deduplicated. + # Allows --format ids to extract all involved IDs without jq. + seen: set[int] = set() + elements: list[dict[str, Any]] = [] + _collect_elements(result, seen, elements) + result["elements"] = elements + + return result diff --git a/src/ifcquery/ifcquery/render.py b/src/ifcquery/ifcquery/render.py new file mode 100644 index 0000000000..44105e0ee2 --- /dev/null +++ b/src/ifcquery/ifcquery/render.py @@ -0,0 +1,465 @@ +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +import multiprocessing +import os +import tempfile + +import ifcopenshell +import ifcopenshell.geom +import ifcopenshell.guid +import ifcopenshell.util.selector + +try: + import numpy as np + import pyvista as pv + + _HAS_PYVISTA = True +except ImportError: + _HAS_PYVISTA = False + +VIEWS = ("iso", "top", "south", "north", "east", "west") + + +def _apply_view(plotter: pv.Plotter, view: str) -> None: + """Set the camera to the requested named view. Z is up (IFC convention).""" + if view == "top": + plotter.view_xy() + elif view == "south": + # Camera at -Y looking toward +Y (south face of building) + plotter.view_xz(negative=True) + elif view == "north": + plotter.view_xz(negative=False) + elif view == "east": + plotter.view_yz(negative=False) + elif view == "west": + plotter.view_yz(negative=True) + else: + plotter.view_isometric() + # Ensure Z is world up for elevation views + if view not in ("top",): + plotter.camera.up = (0, 0, 1) + + +def _add_shape( + shape: object, + plotter: pv.Plotter, + highlight_ids: frozenset[int] | None, +) -> None: + """Triangulate and add a geometry shape to the plotter.""" + geom = shape.geometry + verts = np.array(geom.verts, dtype=float).reshape(-1, 3) + if verts.size == 0: + return + + raw_faces = np.array(geom.faces, dtype=int) + if raw_faces.size == 0 or raw_faces.size % 3 != 0: + return # degenerate geometry from kernel — skip silently + faces = raw_faces.reshape(-1, 3) + material_ids = np.array(geom.material_ids, dtype=int) + + is_subject = highlight_ids is not None and shape.product.id() in highlight_ids + + for midx, mat in enumerate(geom.materials): + tri_mask = material_ids == midx + if not np.any(tri_mask): + continue + + sub_faces = faces[tri_mask] + faces_pv = np.hstack([np.full((sub_faces.shape[0], 1), 3, dtype=int), sub_faces]).ravel() + mesh = pv.PolyData(verts, faces_pv) + + if highlight_ids is not None and not is_subject: + color = (180, 180, 180) + opacity = 0.10 + else: + diffuse = np.clip(np.array(mat.diffuse.components), 0.0, 1.0) + color = tuple((diffuse * 255).astype(np.uint8)) + transparency = mat.transparency if mat.transparency == mat.transparency else 0.0 + opacity = float(np.clip(1.0 - transparency, 0.0, 1.0)) + + plotter.add_mesh(mesh, color=color, opacity=opacity, show_edges=False) + + +def _render_iterator( + iterator: object, + highlight_ids: list[int] | None, + view: str, +) -> bytes: + """Drive a geometry iterator into a pyvista plotter and return PNG bytes.""" + plotter = pv.Plotter(off_screen=True, window_size=(1280, 960)) + plotter.background_color = "white" + + while True: + try: + _add_shape(iterator.get(), plotter, highlight_ids=frozenset(highlight_ids) if highlight_ids else None) + except Exception: + pass # skip broken shapes, keep rendering the rest + if not iterator.next(): + break + + plotter.reset_camera() + _apply_view(plotter, view) + + tmp_fd, tmp_path = tempfile.mkstemp(suffix=".png") + os.close(tmp_fd) + try: + plotter.show(screenshot=tmp_path, auto_close=True) + with open(tmp_path, "rb") as f: + return f.read() + finally: + try: + os.unlink(tmp_path) + except OSError: + pass + + +def _build_geom_settings(model: ifcopenshell.file) -> ifcopenshell.geom.settings: + """Build geometry settings, excluding Clearance subcontexts.""" + settings = ifcopenshell.geom.settings() + settings.set("use-world-coords", True) + + clearance_ids = { + c.id() for c in model.by_type("IfcGeometricRepresentationSubContext") if c.ContextIdentifier == "Clearance" + } + if clearance_ids: + ctx_ids = [c.id() for c in model.by_type("IfcGeometricRepresentationContext") if c.id() not in clearance_ids] + if ctx_ids: + settings.set("context-ids", ctx_ids) + + return settings + + +def _get_occurrence_class(type_entity) -> str: + """Derive the occurrence IFC class from a type entity class name.""" + type_class = type_entity.is_a() + if type_class.endswith("Type"): + return type_class[:-4] + return "IfcBuildingElementProxy" + + +def _make_type_occurrence(model: ifcopenshell.file, type_entity) -> object | None: + """Create a temporary occurrence for *type_entity* using its RepresentationMaps. + + The occurrence is added to *model* and references the type's existing + RepresentationMap entities via IfcMappedItem. Returns the occurrence entity, + or ``None`` when the type has no usable RepresentationMaps. + + .. note:: + This function is intended for use on a temporary model copy. The + caller is responsible for discarding that copy after rendering. + """ + rep_maps = getattr(type_entity, "RepresentationMaps", None) or [] + if not rep_maps: + return None + + # One IfcMappedItem per RepresentationMap. + mapped_items = [] + for rep_map in rep_maps: + origin = model.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)) + transform = model.create_entity( + "IfcCartesianTransformationOperator3D", + LocalOrigin=origin, + ) + mapped_item = model.create_entity( + "IfcMappedItem", + MappingSource=rep_map, + MappingTarget=transform, + ) + mapped_items.append(mapped_item) + + context = rep_maps[0].MappedRepresentation.ContextOfItems + shape_rep = model.create_entity( + "IfcShapeRepresentation", + ContextOfItems=context, + RepresentationIdentifier="Body", + RepresentationType="MappedRepresentation", + Items=mapped_items, + ) + prod_def_shape = model.create_entity( + "IfcProductDefinitionShape", + Representations=[shape_rep], + ) + + # Identity placement. + pt = model.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)) + z_dir = model.create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)) + x_dir = model.create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)) + axis2 = model.create_entity("IfcAxis2Placement3D", Location=pt, Axis=z_dir, RefDirection=x_dir) + placement = model.create_entity("IfcLocalPlacement", RelativePlacement=axis2) + + occ_class = _get_occurrence_class(type_entity) + try: + occurrence = model.create_entity( + occ_class, + GlobalId=ifcopenshell.guid.new(), + Name=f"_type_preview_{type_entity.id()}", + ObjectPlacement=placement, + Representation=prod_def_shape, + ) + except Exception: + occurrence = model.create_entity( + "IfcBuildingElementProxy", + GlobalId=ifcopenshell.guid.new(), + Name=f"_type_preview_{type_entity.id()}", + ObjectPlacement=placement, + Representation=prod_def_shape, + ) + return occurrence + + +def _make_profile_occurrence(model: ifcopenshell.file, type_entity) -> object | None: + """Create a temporary occurrence for a type that has a material profile set. + + Finds the first profile in the type's IfcMaterialProfileSet and creates a + 1-metre IfcExtrudedAreaSolid body representation from it. Returns the + occurrence, or ``None`` when no usable profile is found. + + .. note:: + Intended for use on a temporary model copy; caller discards it after + rendering. + """ + # Locate the first profile from the type's material profile set. + profile = None + for rel in getattr(type_entity, "HasAssociations", []): + if not rel.is_a("IfcRelAssociatesMaterial"): + continue + mat = rel.RelatingMaterial + if mat.is_a("IfcMaterialProfileSetUsage"): + mat = mat.ForProfileSet + if mat.is_a("IfcMaterialProfileSet"): + mat_profiles = list(getattr(mat, "MaterialProfiles", None) or []) + if mat_profiles: + profile = getattr(mat_profiles[0], "Profile", None) + if profile is not None: + break + if profile is None: + return None + + # Find a Body subcontext, or fall back to any Model context. + body_ctx = None + for ctx in model.by_type("IfcGeometricRepresentationSubContext"): + if ctx.ContextIdentifier == "Body": + body_ctx = ctx + break + if body_ctx is None: + for ctx in model.by_type("IfcGeometricRepresentationContext"): + if ctx.ContextType == "Model": + body_ctx = ctx + break + if body_ctx is None: + return None + + # Extrude 1 metre along Z (profile lies in XY plane). + origin = model.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)) + z_axis = model.create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)) + x_axis = model.create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)) + position = model.create_entity("IfcAxis2Placement3D", Location=origin, Axis=z_axis, RefDirection=x_axis) + extrude_dir = model.create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)) + extrusion = model.create_entity( + "IfcExtrudedAreaSolid", + SweptArea=profile, + Position=position, + ExtrudedDirection=extrude_dir, + Depth=1.0, + ) + shape_rep = model.create_entity( + "IfcShapeRepresentation", + ContextOfItems=body_ctx, + RepresentationIdentifier="Body", + RepresentationType="SweptSolid", + Items=[extrusion], + ) + prod_def_shape = model.create_entity( + "IfcProductDefinitionShape", + Representations=[shape_rep], + ) + + # Identity placement. + pt = model.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)) + z_dir = model.create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)) + x_dir = model.create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)) + axis2 = model.create_entity("IfcAxis2Placement3D", Location=pt, Axis=z_dir, RefDirection=x_dir) + placement = model.create_entity("IfcLocalPlacement", RelativePlacement=axis2) + + occ_class = _get_occurrence_class(type_entity) + try: + occurrence = model.create_entity( + occ_class, + GlobalId=ifcopenshell.guid.new(), + Name=f"_profile_preview_{type_entity.id()}", + ObjectPlacement=placement, + Representation=prod_def_shape, + ) + except Exception: + occurrence = model.create_entity( + "IfcBuildingElementProxy", + GlobalId=ifcopenshell.guid.new(), + Name=f"_profile_preview_{type_entity.id()}", + ObjectPlacement=placement, + Representation=prod_def_shape, + ) + return occurrence + + +def _render_with_types( + model: ifcopenshell.file, + types: list, + selector_elements: list | None, + element_ids: list[int] | None, + type_highlight_ids: set[int], + view: str, +) -> bytes: + """Render type entities by creating occurrences in a temporary model copy. + + *types* — list of IfcTypeProduct entities to render. + *selector_elements* — non-type elements from the selector (or ``None``). + *element_ids* — original highlight IDs (may contain type IDs). + *type_highlight_ids* — subset of *element_ids* that are type IDs. + """ + tmp_fd, tmp_path = tempfile.mkstemp(suffix=".ifc") + os.close(tmp_fd) + try: + model.write(tmp_path) + tmp = ifcopenshell.open(tmp_path) + + # Map original type step-ID → new occurrence step-ID in the tmp model. + type_id_to_occ_id: dict[int, int] = {} + for t in types: + tmp_type = tmp.by_id(t.id()) + occ = _make_type_occurrence(tmp, tmp_type) or _make_profile_occurrence(tmp, tmp_type) + if occ: + type_id_to_occ_id[t.id()] = occ.id() + + if not type_id_to_occ_id: + raise ValueError("Type entities have no RepresentationMaps or material profile sets to render") + + include = [tmp.by_id(occ_id) for occ_id in type_id_to_occ_id.values()] + if selector_elements: + include.extend(tmp.by_id(e.id()) for e in selector_elements) + + settings = _build_geom_settings(tmp) + iterator = ifcopenshell.geom.iterator(settings, tmp, multiprocessing.cpu_count(), include=include) + if not iterator.initialize(): + raise ValueError("Type entities have no renderable geometry") + + # Remap type IDs → occurrence IDs in the highlight list. + new_highlight = None + if element_ids: + new_highlight = [] + for hid in element_ids: + if hid in type_highlight_ids: + mapped = type_id_to_occ_id.get(hid) + if mapped: + new_highlight.append(mapped) + else: + new_highlight.append(hid) + + return _render_iterator(iterator, new_highlight, view) + finally: + try: + os.unlink(tmp_path) + except OSError: + pass + + +def render( + model: ifcopenshell.file, + selector: str | None = None, + element_ids: list[int] | None = None, + view: str = "iso", +) -> bytes: + """Render IFC model geometry to a PNG image. + + Supports both element instances and element types (e.g. ``IfcWallType``). + When type entities are targeted — via *selector* or *element_ids* — a + temporary copy of the model is used to create proxy occurrences that + reference the type's RepresentationMaps; the original model is not + modified. + + :param model: The in-memory IFC model. + :param selector: ifcopenshell selector to restrict rendered elements + (e.g. ``'IfcWall'``, ``'IfcWallType'``, or + ``'IfcBuildingStorey[Name="Ground Floor"]'``). + When omitted the whole model is rendered. + :param element_ids: Step IDs of elements (or types) to highlight. The + rest of the model is rendered in translucent grey so the highlighted + items stand out. + :param view: Camera angle: ``iso``, ``top``, ``south``, ``north``, + ``east``, or ``west``. Defaults to ``iso``. + :return: PNG image as raw bytes. + :raises ImportError: If pyvista is not installed. + :raises ValueError: If the selector matches nothing or the model has no + renderable geometry. + """ + if not _HAS_PYVISTA: + raise ImportError("pyvista is not installed. Install with: pip install pyvista") + + # --- Partition selector results into types and elements --- + if selector: + matched = list(ifcopenshell.util.selector.filter_elements(model, selector)) + if not matched: + raise ValueError(f"Selector {selector!r} matched no elements") + types = [e for e in matched if e.is_a("IfcTypeProduct")] + selector_elements: list | None = [e for e in matched if not e.is_a("IfcTypeProduct")] + else: + types = [] + selector_elements = None # no restriction — render all elements + + # --- Collect any type entities from element_ids --- + type_highlight_ids: set[int] = set() + if element_ids: + for eid in element_ids: + entity = model.by_id(eid) + if entity.is_a("IfcTypeProduct"): + type_highlight_ids.add(eid) + seen = {t.id() for t in types} + if eid not in seen: + types.append(entity) + + # --- Delegate to temp-copy path when any type entities are involved --- + if types: + return _render_with_types(model, types, selector_elements, element_ids, type_highlight_ids, view) + + # --- Regular element rendering --- + settings = _build_geom_settings(model) + + if selector_elements is not None: + if not selector_elements: + raise ValueError(f"Selector {selector!r} matched only type entities (use a type selector or IfcElement)") + iterator = ifcopenshell.geom.iterator( + settings, + model, + multiprocessing.cpu_count(), + include=selector_elements, + ) + else: + exclude = list(model.by_type("IfcOpeningElement")) + iterator = ifcopenshell.geom.iterator( + settings, + model, + multiprocessing.cpu_count(), + exclude=exclude if exclude else None, + ) + + if not iterator.initialize(): + raise ValueError("No renderable geometry found in model (or selector matched nothing)") + + return _render_iterator(iterator, element_ids, view) diff --git a/src/ifcquery/ifcquery/schedule.py b/src/ifcquery/ifcquery/schedule.py new file mode 100644 index 0000000000..b3e3ae45d6 --- /dev/null +++ b/src/ifcquery/ifcquery/schedule.py @@ -0,0 +1,56 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.sequence as seq + + +def _task_to_dict(task: ifcopenshell.entity_instance, max_depth: int | None, depth: int) -> dict[str, Any]: + task_time = task.TaskTime + start = None + finish = None + if task_time: + start = task_time.ScheduleStart + finish = task_time.ScheduleFinish + + outputs = [] + for product in seq.get_task_outputs(task): + outputs.append({"id": product.id(), "type": product.is_a(), "name": getattr(product, "Name", None)}) + + if max_depth is not None and depth >= max_depth: + child_count = len(seq.get_nested_tasks(task)) + subtasks = {"truncated": True, "count": child_count} if child_count else [] + else: + subtasks = [_task_to_dict(sub, max_depth, depth + 1) for sub in seq.get_nested_tasks(task)] + + return { + "id": task.id(), + "name": getattr(task, "Name", None), + "start": start, + "finish": finish, + "is_milestone": bool(task.IsMilestone) if hasattr(task, "IsMilestone") else False, + "outputs": outputs, + "subtasks": subtasks, + } + + +def schedule(model: ifcopenshell.file, max_depth: int | None = None) -> list[dict[str, Any]]: + """Return a list of IfcWorkSchedule entries with nested task trees. + + max_depth limits how many levels of subtasks are expanded (None = unlimited). + At the cutoff level, subtasks is replaced with {"truncated": True, "count": N}. + """ + result = [] + for work_schedule in model.by_type("IfcWorkSchedule"): + tasks = [_task_to_dict(t, max_depth, depth=1) for t in seq.get_root_tasks(work_schedule)] + result.append( + { + "id": work_schedule.id(), + "name": getattr(work_schedule, "Name", None), + "predefined_type": getattr(work_schedule, "PredefinedType", None), + "tasks": tasks, + } + ) + return result diff --git a/src/ifcquery/ifcquery/schema.py b/src/ifcquery/ifcquery/schema.py new file mode 100644 index 0000000000..ad47486db2 --- /dev/null +++ b/src/ifcquery/ifcquery/schema.py @@ -0,0 +1,19 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.doc + + +def schema(model: ifcopenshell.file, entity_type: str) -> dict[str, Any]: + """Return IFC class documentation for entity_type from model's schema version.""" + schema_name = model.schema + try: + doc = ifcopenshell.util.doc.get_entity_doc(schema_name, entity_type) + except Exception: + return {"error": f"Unknown entity: {entity_type}"} + if not doc: + return {"error": f"Unknown entity: {entity_type}"} + return dict(doc) diff --git a/src/ifcquery/ifcquery/select.py b/src/ifcquery/ifcquery/select.py new file mode 100644 index 0000000000..c28a159b90 --- /dev/null +++ b/src/ifcquery/ifcquery/select.py @@ -0,0 +1,41 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.selector + + +def select(model: ifcopenshell.file, query: str) -> list[dict[str, Any]]: + """Filter elements using selector syntax and return matching element summaries.""" + elements = ifcopenshell.util.selector.filter_elements(model, query) + results = [] + for element in sorted(elements, key=lambda e: e.id()): + entry: dict[str, Any] = { + "id": element.id(), + "type": element.is_a(), + "repr": str(element), + } + if hasattr(element, "Name"): + entry["name"] = element.Name + results.append(entry) + return results diff --git a/src/ifcquery/ifcquery/summary.py b/src/ifcquery/ifcquery/summary.py new file mode 100644 index 0000000000..d13f070472 --- /dev/null +++ b/src/ifcquery/ifcquery/summary.py @@ -0,0 +1,52 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +from collections import Counter +from typing import Any + +import ifcopenshell + + +def summary(model: ifcopenshell.file) -> dict[str, Any]: + """Return a model overview with schema, element counts, and project info.""" + # Count elements by IFC type, sorted by count descending + type_counter: Counter[str] = Counter() + total = 0 + for entity in model: + type_counter[entity.is_a()] += 1 + total += 1 + + result: dict[str, Any] = { + "schema": model.schema, + "total_entities": total, + } + + projects = model.by_type("IfcProject") + if projects: + project = projects[0] + result["project"] = { + "id": project.id(), + "name": project.Name, + "description": project.Description, + } + + result["types"] = dict(type_counter.most_common()) + return result diff --git a/src/ifcquery/ifcquery/tree.py b/src/ifcquery/ifcquery/tree.py new file mode 100644 index 0000000000..1cbbaeb986 --- /dev/null +++ b/src/ifcquery/ifcquery/tree.py @@ -0,0 +1,68 @@ +# This file was generated with the assistance of an AI coding tool. +# IfcQuery - IFC model interrogation CLI +# Copyright (C) 2026 Bruno Postle +# +# This file is part of IfcQuery. +# +# IfcQuery is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcQuery is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcQuery. If not, see . + +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.util.element + + +def _element_summary(element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Return a minimal summary dict for an element.""" + return { + "id": element.id(), + "type": element.is_a(), + "name": element.Name if hasattr(element, "Name") else None, + } + + +def _build_spatial_node(element: ifcopenshell.entity_instance) -> dict[str, Any]: + """Recursively build a spatial tree node.""" + node = _element_summary(element) + + # Get aggregated children (Site in Project, Building in Site, Storey in Building, etc.) + aggregates = [] + for rel in getattr(element, "IsDecomposedBy", []): + for child in rel.RelatedObjects: + aggregates.append(_build_spatial_node(child)) + + # Get contained elements (walls, slabs, etc. in a storey/space) + contained = [] + for rel in getattr(element, "ContainsElements", []): + for child in rel.RelatedElements: + contained.append(_element_summary(child)) + + if aggregates: + node["children"] = aggregates + if contained: + node["elements"] = contained + + return node + + +def tree(model: ifcopenshell.file) -> dict[str, Any] | list[dict[str, Any]]: + """Return the spatial hierarchy tree starting from IfcProject.""" + projects = model.by_type("IfcProject") + if not projects: + return {"error": "No IfcProject found in model"} + if len(projects) == 1: + return _build_spatial_node(projects[0]) + return [_build_spatial_node(p) for p in projects] diff --git a/src/ifcquery/ifcquery/validate.py b/src/ifcquery/ifcquery/validate.py new file mode 100644 index 0000000000..d35d9150af --- /dev/null +++ b/src/ifcquery/ifcquery/validate.py @@ -0,0 +1,15 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +from typing import Any + +import ifcopenshell +import ifcopenshell.validate + + +def validate(model: ifcopenshell.file, express_rules: bool = False) -> dict[str, Any]: + """Validate the model and return a dict with 'valid' bool and 'issues' list.""" + logger = ifcopenshell.validate.json_logger() + ifcopenshell.validate.validate(model, logger, express_rules=express_rules) + issues = [{"level": s["level"], "message": s["message"]} for s in logger.statements] + return {"valid": len(issues) == 0, "issues": issues} diff --git a/src/ifcquery/pyproject.toml b/src/ifcquery/pyproject.toml new file mode 100644 index 0000000000..3c8e734474 --- /dev/null +++ b/src/ifcquery/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ifcquery" +version = "0.0.0" +authors = [ + { name="Bruno Postle", email="bruno@postle.net" }, +] +description = "CLI tool for querying and inspecting IFC building models" +readme = "README.md" +keywords = ["IFC", "BIM", "Query"] +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", +] +dependencies = ["ifcopenshell"] + +[project.scripts] +ifcquery = "ifcquery.__main__:main" + +[project.urls] +Homepage = "http://ifcopenshell.org" +Documentation = "https://docs.ifcopenshell.org" +Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues" + +[tool.setuptools.packages.find] +include = ["ifcquery*"] +exclude = ["test*"] + +[tool.ruff] +extend = "../../pyproject.toml" diff --git a/src/ifcquery/tests/__init__.py b/src/ifcquery/tests/__init__.py new file mode 100644 index 0000000000..0a3bc271a0 --- /dev/null +++ b/src/ifcquery/tests/__init__.py @@ -0,0 +1 @@ +# This file was generated with the assistance of an AI coding tool. diff --git a/src/ifcquery/tests/conftest.py b/src/ifcquery/tests/conftest.py new file mode 100644 index 0000000000..496e74cd6e --- /dev/null +++ b/src/ifcquery/tests/conftest.py @@ -0,0 +1,36 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + + +@pytest.fixture +def model(): + """Create an IFC4 model with a spatial hierarchy and a wall.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + slab = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSlab", name="Slab001") + ifcopenshell.api.spatial.assign_container(f, products=[slab], relating_structure=storey) + + return f diff --git a/src/ifcquery/tests/test_clash.py b/src/ifcquery/tests/test_clash.py new file mode 100644 index 0000000000..150200f95c --- /dev/null +++ b/src/ifcquery/tests/test_clash.py @@ -0,0 +1,330 @@ +# This file was generated with the assistance of an AI coding tool. +import json +import os +import subprocess +import sys +import tempfile + +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.context +import ifcopenshell.api.geometry +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import numpy as np +import pytest + +from ifcquery.clash import clash + +try: + import ifcopenshell.geom + + HAS_GEOM = True +except ImportError: + HAS_GEOM = False + +pytestmark = pytest.mark.skipif(not HAS_GEOM, reason="ifcopenshell geometry engine not available") + + +@pytest.fixture +def model_with_geometry(): + """Create an IFC4 model with walls that have geometric representations.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + # Create geometry context + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + body = ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + + # Wall 1 at origin + wall1 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + rep1 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall1, representation=rep1) + ifcopenshell.api.spatial.assign_container(f, products=[wall1], relating_structure=storey) + + # Wall 2 perpendicular, crossing through wall 1 + wall2 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall002") + rep2 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall2, representation=rep2) + ifcopenshell.api.spatial.assign_container(f, products=[wall2], relating_structure=storey) + matrix2 = np.array([[0, -1, 0, 2.5], [1, 0, 0, -2.0], [0, 0, 1, 0], [0, 0, 0, 1]], dtype=float) + ifcopenshell.api.geometry.edit_object_placement(f, product=wall2, matrix=matrix2) + + # Wall 3 far away (10m offset in Y) + wall3 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall003") + rep3 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall3, representation=rep3) + ifcopenshell.api.spatial.assign_container(f, products=[wall3], relating_structure=storey) + matrix3 = np.eye(4) + matrix3[1, 3] = 10.0 # 10m in Y direction + ifcopenshell.api.geometry.edit_object_placement(f, product=wall3, matrix=matrix3) + + # Wall 4 close but not overlapping (0.3m offset in Y, wall thickness is 0.2m) + wall4 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall004") + rep4 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall4, representation=rep4) + ifcopenshell.api.spatial.assign_container(f, products=[wall4], relating_structure=storey) + matrix4 = np.eye(4) + matrix4[1, 3] = 0.3 # 0.3m in Y (gap of 0.1m from wall1) + ifcopenshell.api.geometry.edit_object_placement(f, product=wall4, matrix=matrix4) + + return f + + +@pytest.fixture +def model_two_storeys(): + """Create a model with walls in different storeys.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey1 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + storey2 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="First Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey1, storey2], relating_object=building) + + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + body = ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + + # Wall in storey 1 + wall1 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="GroundWall") + rep1 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall1, representation=rep1) + ifcopenshell.api.spatial.assign_container(f, products=[wall1], relating_structure=storey1) + + # Wall in storey 2, perpendicular and crossing wall1 + wall2 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="FirstFloorWall") + rep2 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall2, representation=rep2) + ifcopenshell.api.spatial.assign_container(f, products=[wall2], relating_structure=storey2) + matrix2 = np.array([[0, -1, 0, 2.5], [1, 0, 0, -2.0], [0, 0, 1, 0], [0, 0, 0, 1]], dtype=float) + ifcopenshell.api.geometry.edit_object_placement(f, product=wall2, matrix=matrix2) + + return f + + +class TestNoClashes: + def test_no_clashes_far_apart(self, model_with_geometry): + wall3 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall003") + result = clash(model_with_geometry, wall3) + assert result["pass"] is True + assert result["checks"]["intersection"]["pass"] is True + assert result["checks"]["intersection"]["clashes"] == [] + + def test_no_clashes_empty_scope(self, model_with_geometry): + """A model where the element is the only one in scope should pass.""" + # Create a model with a single wall + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P") + ifcopenshell.api.unit.assign_unit(f) + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="S") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="B") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="GF") + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + body = ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="OnlyWall") + rep = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall, representation=rep) + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + result = clash(f, wall) + assert result["pass"] is True + + +class TestIntersectionDetected: + def test_overlapping_walls(self, model_with_geometry): + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1) + assert result["pass"] is False + assert result["checks"]["intersection"]["pass"] is False + clashes = result["checks"]["intersection"]["clashes"] + assert len(clashes) > 0 + # Wall002 should be in the clashes (it overlaps wall1) + clash_ids = {c["element"]["id"] for c in clashes} + wall2 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall002") + assert wall2.id() in clash_ids + + def test_clash_has_points(self, model_with_geometry): + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1) + clashes = result["checks"]["intersection"]["clashes"] + for c in clashes: + assert "p1" in c + assert "p2" in c + assert len(c["p1"]) == 3 + assert len(c["p2"]) == 3 + assert "type" in c + assert "distance" in c + + +class TestClearance: + def test_clearance_violation(self, model_with_geometry): + """Wall004 is 0.1m from wall1; clearance of 0.5m should fail.""" + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1, clearance=0.5) + assert "clearance" in result["checks"] + # Wall004 should violate clearance + clearance_clashes = result["checks"]["clearance"]["clashes"] + clash_ids = {c["element"]["id"] for c in clearance_clashes} + wall4 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall004") + assert wall4.id() in clash_ids + assert result["checks"]["clearance"]["pass"] is False + + def test_clearance_pass(self, model_with_geometry): + """Wall003 is 10m away; clearance of 0.5m should pass for wall003.""" + wall3 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall003") + result = clash(model_with_geometry, wall3, clearance=0.5) + assert result["checks"]["clearance"]["pass"] is True + assert result["checks"]["clearance"]["clashes"] == [] + + def test_clearance_not_included_by_default(self, model_with_geometry): + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1) + assert "clearance" not in result["checks"] + + +class TestScope: + def test_scope_storey_excludes_other_storeys(self, model_two_storeys): + wall1 = next(w for w in model_two_storeys.by_type("IfcWall") if w.Name == "GroundWall") + result = clash(model_two_storeys, wall1, scope="storey") + assert result["scope"] == "storey" + # No clashes because the overlapping wall is in a different storey + assert result["pass"] is True + + def test_scope_all_includes_other_storeys(self, model_two_storeys): + wall1 = next(w for w in model_two_storeys.by_type("IfcWall") if w.Name == "GroundWall") + result = clash(model_two_storeys, wall1, scope="all") + assert result["scope"] == "all" + # Should detect clash with the other-storey wall + assert result["pass"] is False + clash_ids = {c["element"]["id"] for c in result["checks"]["intersection"]["clashes"]} + wall2 = next(w for w in model_two_storeys.by_type("IfcWall") if w.Name == "FirstFloorWall") + assert wall2.id() in clash_ids + + +class TestNoGeometry: + def test_no_geometry_error(self, model): + """Element without geometry reports error.""" + wall = model.by_type("IfcWall")[0] + result = clash(model, wall) + assert result["pass"] is None + assert "error" in result + assert "No geometry" in result["error"] + + +class TestJsonSerializable: + def test_result_serializable(self, model_with_geometry): + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1) + serialized = json.dumps(result) + parsed = json.loads(serialized) + assert parsed["element"]["type"] == "IfcWall" + + def test_clearance_result_serializable(self, model_with_geometry): + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = clash(model_with_geometry, wall1, clearance=0.5) + serialized = json.dumps(result) + parsed = json.loads(serialized) + assert "clearance" in parsed["checks"] + + +class TestCLI: + @staticmethod + def _ifc_path(model): + f = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) + model.write(f.name) + f.close() + return f.name + + def test_clash_json(self, model_with_geometry): + path = self._ifc_path(model_with_geometry) + try: + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "clash", str(wall1.id())], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["element"]["type"] == "IfcWall" + assert "checks" in data + assert "intersection" in data["checks"] + finally: + os.unlink(path) + + def test_clash_with_clearance(self, model_with_geometry): + path = self._ifc_path(model_with_geometry) + try: + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "clash", str(wall1.id()), "--clearance", "0.5"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert "clearance" in data["checks"] + finally: + os.unlink(path) + + def test_clash_scope_all(self, model_with_geometry): + path = self._ifc_path(model_with_geometry) + try: + wall1 = next(w for w in model_with_geometry.by_type("IfcWall") if w.Name == "Wall001") + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "clash", str(wall1.id()), "--scope", "all"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["scope"] == "all" + finally: + os.unlink(path) + + def test_clash_bad_id(self, model_with_geometry): + path = self._ifc_path(model_with_geometry) + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "clash", "999999"], + capture_output=True, + text=True, + ) + assert result.returncode != 0 + assert "Error" in result.stderr + finally: + os.unlink(path) diff --git a/src/ifcquery/tests/test_contexts.py b/src/ifcquery/tests/test_contexts.py new file mode 100644 index 0000000000..c7a21acb96 --- /dev/null +++ b/src/ifcquery/tests/test_contexts.py @@ -0,0 +1,52 @@ +import ifcopenshell.api.context +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.unit + +from ifcquery.contexts import contexts + + +class TestContexts: + def test_empty_model(self): + f = ifcopenshell.api.project.create_file() + result = contexts(f) + assert isinstance(result, list) + assert len(result) == 0 + + def test_model_context(self, model): + import ifcopenshell.api.context + + ifcopenshell.api.context.add_context(model, context_type="Model") + result = contexts(model) + assert len(result) == 1 + entry = result[0] + assert entry["type"] == "IfcGeometricRepresentationContext" + assert entry["context_type"] == "Model" + assert "id" in entry + assert "context_identifier" in entry + + def test_subcontext(self, model): + import ifcopenshell.api.context + + model_ctx = ifcopenshell.api.context.add_context(model, context_type="Model") + ifcopenshell.api.context.add_context( + model, + context_type="Model", + context_identifier="Body", + target_view="MODEL_VIEW", + parent=model_ctx, + ) + result = contexts(model) + assert len(result) == 2 + subctx = next(e for e in result if e["type"] == "IfcGeometricRepresentationSubContext") + assert subctx["context_identifier"] == "Body" + assert subctx["target_view"] == "MODEL_VIEW" + assert subctx["parent_context_id"] == model_ctx.id() + + def test_ids_are_integers(self, model): + import ifcopenshell.api.context + + ifcopenshell.api.context.add_context(model, context_type="Model") + result = contexts(model) + for entry in result: + assert isinstance(entry["id"], int) diff --git a/src/ifcquery/tests/test_cost.py b/src/ifcquery/tests/test_cost.py new file mode 100644 index 0000000000..1011beca0a --- /dev/null +++ b/src/ifcquery/tests/test_cost.py @@ -0,0 +1,108 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import ifcopenshell +import ifcopenshell.api.cost +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.unit +import pytest + +from ifcquery.cost import cost + + +@pytest.fixture +def cost_model(): + """Create an IFC4 model with a cost schedule, a top-level item, and one nested subitem.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + cs = ifcopenshell.api.cost.add_cost_schedule(f, name="Bill of Quantities") + item = ifcopenshell.api.cost.add_cost_item(f, cost_schedule=cs) + ifcopenshell.api.cost.edit_cost_item(f, cost_item=item, attributes={"Name": "Concrete Works"}) + cv = ifcopenshell.api.cost.add_cost_value(f, parent=item) + ifcopenshell.api.cost.edit_cost_value(f, cost_value=cv, attributes={"AppliedValue": 1200.0, "Category": "material"}) + + # Add a nested subitem + subitem = ifcopenshell.api.cost.add_cost_item(f, cost_item=item) + ifcopenshell.api.cost.edit_cost_item(f, cost_item=subitem, attributes={"Name": "Formwork"}) + + return f + + +class TestCost: + def test_returns_list(self, cost_model): + result = cost(cost_model) + assert isinstance(result, list) + + def test_finds_cost_schedule(self, cost_model): + result = cost(cost_model) + assert len(result) == 1 + + def test_schedule_has_name(self, cost_model): + result = cost(cost_model) + assert result[0]["name"] == "Bill of Quantities" + + def test_schedule_has_id(self, cost_model): + result = cost(cost_model) + assert isinstance(result[0]["id"], int) + assert result[0]["id"] > 0 + + def test_schedule_has_items(self, cost_model): + result = cost(cost_model) + assert len(result[0]["items"]) == 1 + + def test_item_has_required_fields(self, cost_model): + result = cost(cost_model) + item = result[0]["items"][0] + assert "id" in item + assert "name" in item + assert "values" in item + assert "subitems" in item + + def test_item_name(self, cost_model): + result = cost(cost_model) + assert result[0]["items"][0]["name"] == "Concrete Works" + + def test_item_has_values(self, cost_model): + result = cost(cost_model) + values = result[0]["items"][0]["values"] + assert len(values) == 1 + assert "formula" in values[0] + assert "category" in values[0] + + def test_item_value_category(self, cost_model): + result = cost(cost_model) + values = result[0]["items"][0]["values"] + assert values[0]["category"] == "material" + + def test_empty_model_returns_empty_list(self, model): + result = cost(model) + assert result == [] + + def test_max_depth_none_returns_full_tree(self, cost_model): + result = cost(cost_model, max_depth=None) + item = result[0]["items"][0] + assert isinstance(item["subitems"], list) + assert len(item["subitems"]) == 1 + assert item["subitems"][0]["name"] == "Formwork" + + def test_max_depth_1_truncates_subitems(self, cost_model): + result = cost(cost_model, max_depth=1) + item = result[0]["items"][0] + assert isinstance(item["subitems"], dict) + assert item["subitems"]["truncated"] is True + assert item["subitems"]["count"] == 1 + + def test_max_depth_2_expands_to_depth_2(self, cost_model): + result = cost(cost_model, max_depth=2) + item = result[0]["items"][0] + assert isinstance(item["subitems"], list) + assert item["subitems"][0]["name"] == "Formwork" + # subitem has no children, so subitems should be empty list + assert item["subitems"][0]["subitems"] == [] diff --git a/src/ifcquery/tests/test_info.py b/src/ifcquery/tests/test_info.py new file mode 100644 index 0000000000..c17331a24d --- /dev/null +++ b/src/ifcquery/tests/test_info.py @@ -0,0 +1,112 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.context +import ifcopenshell.api.geometry +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.unit +import ifcopenshell.util.representation +import ifcopenshell.util.shape_builder + +from ifcquery.info import info + + +class TestInfo: + def test_basic_attributes(self, model): + wall = model.by_type("IfcWall")[0] + result = info(model, wall) + assert result["id"] == wall.id() + assert result["type"] == "IfcWall" + assert result["attributes"]["Name"] == "Wall001" + + def test_container(self, model): + wall = model.by_type("IfcWall")[0] + result = info(model, wall) + assert result["container"]["type"] == "IfcBuildingStorey" + assert result["container"]["name"] == "Ground Floor" + + def test_project_info(self, model): + project = model.by_type("IfcProject")[0] + result = info(model, project) + assert result["type"] == "IfcProject" + assert result["attributes"]["Name"] == "TestProject" + + def test_all_attributes_serializable(self, model): + """All attribute values should be JSON-serializable (no entity instances).""" + import json + + wall = model.by_type("IfcWall")[0] + result = info(model, wall) + # Should not raise + json.dumps(result) + + def test_no_geometry_summary_without_representation(self, model): + wall = model.by_type("IfcWall")[0] + result = info(model, wall) + assert "geometry_summary" not in result + + +class TestGeometrySummary: + def _make_model_with_wall(self): + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject") + ifcopenshell.api.unit.assign_unit(f) + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + ifcopenshell.api.context.add_context( + f, + context_type="Model", + context_identifier="Body", + target_view="MODEL_VIEW", + parent=model_ctx, + ) + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="W1") + ifcopenshell.api.geometry.edit_object_placement(f, product=wall) + return f, wall + + def _body_context(self, f): + return ifcopenshell.util.representation.get_context(f, "Model", "Body", "MODEL_VIEW") + + def test_swept_solid_summary(self): + f, wall = self._make_model_with_wall() + body = self._body_context(f) + rep = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5.0, height=3.0, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall, representation=rep) + result = info(f, wall) + gs = result["geometry_summary"] + assert gs["representation_type"] == "SweptSolid" + assert len(gs["solids"]) == 1 + solid = gs["solids"][0] + assert solid["depth"] == 3000.0 # stored in project units (mm) + assert solid["profile"]["type"] == "IfcArbitraryClosedProfileDef" + assert len(solid["profile"]["points"]) == 5 # closed polyline + + def test_clipping_summary(self): + f, wall = self._make_model_with_wall() + body = self._body_context(f) + rep = ifcopenshell.api.geometry.add_wall_representation( + f, + context=body, + length=5.0, + height=4.0, + thickness=0.2, + clippings=[{"location": (0.0, 0.0, 3.0), "normal": (0.0, 0.0, 1.0)}], + ) + ifcopenshell.api.geometry.assign_representation(f, product=wall, representation=rep) + result = info(f, wall) + gs = result["geometry_summary"] + assert gs["representation_type"] == "Clipping" + solid = gs["solids"][0] + assert len(solid["clipping_planes"]) == 1 + plane = solid["clipping_planes"][0] + assert plane["location"][2] == 3000.0 # stored in project units (mm) + assert plane["normal"] == [0.0, 0.0, 1.0] + + def test_geometry_summary_json_serializable(self): + import json + + f, wall = self._make_model_with_wall() + body = self._body_context(f) + rep = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5.0, height=3.0, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall, representation=rep) + result = info(f, wall) + json.dumps(result) diff --git a/src/ifcquery/tests/test_main.py b/src/ifcquery/tests/test_main.py new file mode 100644 index 0000000000..164b93152d --- /dev/null +++ b/src/ifcquery/tests/test_main.py @@ -0,0 +1,121 @@ +# This file was generated with the assistance of an AI coding tool. +import json +import os +import subprocess +import sys +import tempfile + +import ifcopenshell +import ifcopenshell.api.project +import pytest + + +@pytest.fixture +def ifc_path(model): + """Write the model fixture to a temp file and return its path.""" + with tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) as f: + model.write(f.name) + yield f.name + os.unlink(f.name) + + +def run_ifcquery(*args): + """Run ifcquery as a subprocess and return (returncode, stdout, stderr).""" + result = subprocess.run( + [sys.executable, "-m", "ifcquery", *args], + capture_output=True, + text=True, + ) + return result.returncode, result.stdout, result.stderr + + +class TestCLI: + def test_summary_json(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "summary") + assert rc == 0 + data = json.loads(stdout) + assert data["schema"] == "IFC4" + assert "types" in data + + def test_tree_json(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "tree") + assert rc == 0 + data = json.loads(stdout) + assert data["type"] == "IfcProject" + + def test_info_json(self, ifc_path, model): + wall = model.by_type("IfcWall")[0] + rc, stdout, stderr = run_ifcquery(ifc_path, "info", str(wall.id())) + assert rc == 0 + data = json.loads(stdout) + assert data["type"] == "IfcWall" + + def test_info_hash_id(self, ifc_path, model): + wall = model.by_type("IfcWall")[0] + rc, stdout, stderr = run_ifcquery(ifc_path, "info", f"#{wall.id()}") + assert rc == 0 + data = json.loads(stdout) + assert data["type"] == "IfcWall" + + def test_select_json(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "select", "IfcWall") + assert rc == 0 + data = json.loads(stdout) + assert len(data) == 1 + assert data[0]["type"] == "IfcWall" + + def test_text_format(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "text", "summary") + assert rc == 0 + assert "schema:" in stdout + + def test_bad_file(self): + rc, stdout, stderr = run_ifcquery("/nonexistent.ifc", "summary") + assert rc != 0 + assert "Error" in stderr + + def test_bad_element_id(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "info", "999999") + assert rc != 0 + assert "Error" in stderr + + def test_no_command(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path) + assert rc != 0 + + def test_select_ids_format(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "ids", "select", "IfcWall") + assert rc == 0 + # Should be a comma-separated string of integers with no surrounding whitespace + ids = stdout.strip() + assert ids != "" + for part in ids.split(","): + assert part.isdigit() + + def test_select_ids_format_multiple(self, ifc_path, model): + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "ids", "select", "IfcElement") + assert rc == 0 + ids = stdout.strip().split(",") + assert len(ids) >= 2 + + def test_ids_format_empty_result(self, ifc_path): + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "ids", "select", "IfcDoor") + assert rc == 0 + assert stdout.strip() == "" + + def test_relations_ids_format(self, ifc_path, model): + storey = model.by_type("IfcBuildingStorey")[0] + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "ids", "relations", str(storey.id())) + assert rc == 0 + ids = stdout.strip().split(",") + assert all(i.isdigit() for i in ids) + # should include the storey itself and its contained elements + assert str(storey.id()) in ids + wall_id = str(model.by_type("IfcWall")[0].id()) + assert wall_id in ids + + def test_info_ids_format(self, ifc_path, model): + wall = model.by_type("IfcWall")[0] + rc, stdout, stderr = run_ifcquery(ifc_path, "--format", "ids", "info", str(wall.id())) + assert rc == 0 + assert stdout.strip() == str(wall.id()) diff --git a/src/ifcquery/tests/test_materials.py b/src/ifcquery/tests/test_materials.py new file mode 100644 index 0000000000..aa6886156f --- /dev/null +++ b/src/ifcquery/tests/test_materials.py @@ -0,0 +1,52 @@ +import ifcopenshell.api.material +import ifcopenshell.api.project + +from ifcquery.materials import materials + + +class TestMaterials: + def test_empty_model(self, model): + result = materials(model) + assert isinstance(result, list) + assert len(result) == 0 + + def test_single_material(self, model): + ifcopenshell.api.material.add_material(model, name="Concrete", category="concrete") + result = materials(model) + assert len(result) == 1 + m = result[0] + assert m["type"] == "IfcMaterial" + assert m["name"] == "Concrete" + assert m["category"] == "concrete" + assert isinstance(m["id"], int) + + def test_material_layer_set(self, model): + mat = ifcopenshell.api.material.add_material(model, name="Brick") + layer_set = ifcopenshell.api.material.add_material_set(model, name="BrickSet", set_type="IfcMaterialLayerSet") + ifcopenshell.api.material.add_layer(model, layer_set=layer_set, material=mat) + result = materials(model) + layer_sets = [e for e in result if e["type"] == "IfcMaterialLayerSet"] + assert len(layer_sets) == 1 + ls = layer_sets[0] + assert ls["name"] == "BrickSet" + assert isinstance(ls["layers"], list) + assert len(ls["layers"]) == 1 + layer = ls["layers"][0] + assert layer["material"] == "Brick" + + def test_material_constituent_set(self, model): + mat = ifcopenshell.api.material.add_material(model, name="Steel") + cs = ifcopenshell.api.material.add_material_set(model, name="CompSet", set_type="IfcMaterialConstituentSet") + ifcopenshell.api.material.add_constituent(model, constituent_set=cs, material=mat) + result = materials(model) + constituent_sets = [e for e in result if e["type"] == "IfcMaterialConstituentSet"] + assert len(constituent_sets) == 1 + entry = constituent_sets[0] + assert entry["name"] == "CompSet" + assert isinstance(entry["constituents"], list) + + def test_ids_are_integers(self, model): + ifcopenshell.api.material.add_material(model, name="Wood") + result = materials(model) + for entry in result: + assert isinstance(entry["id"], int) diff --git a/src/ifcquery/tests/test_plot.py b/src/ifcquery/tests/test_plot.py new file mode 100644 index 0000000000..4c003ab946 --- /dev/null +++ b/src/ifcquery/tests/test_plot.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import base64 +import os +import subprocess +import sys +import tempfile + +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.context +import ifcopenshell.api.geometry +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import pytest + +from ifcquery.plot import _highlight_css_from_ids, plot + +try: + import ifcopenshell.draw # noqa: F401 + + HAS_DRAW = True +except ImportError: + HAS_DRAW = False + +try: + import cairosvg # noqa: F401 + + HAS_CAIROSVG = True +except ImportError: + HAS_CAIROSVG = False + +pytestmark = pytest.mark.skipif(not HAS_DRAW, reason="ifcopenshell.draw not available") + +SVG_MAGIC = b" elements, PNG/base64 should raise a clear error.""" + + def test_empty_drawing_png_raises(self, model_no_plan): + """PNG format raises ValueError (not silently returns None) for empty drawings.""" + model, _ = model_no_plan + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if not has_groups: + pytest.raises(ValueError, plot, model, output_format="png") + else: + pytest.skip("Model produced non-empty SVG — empty path not triggered") + + def test_empty_drawing_base64_raises(self, model_no_plan): + """base64 format raises ValueError (not silently returns None) for empty drawings.""" + model, _ = model_no_plan + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if not has_groups: + pytest.raises(ValueError, plot, model, output_format="base64") + else: + pytest.skip("Model produced non-empty SVG — empty path not triggered") + + +@pytest.mark.skipif(not HAS_CAIROSVG, reason="cairosvg not installed") +class TestPlotPNG: + """PNG and base64 require cairosvg.""" + + def test_png_returns_bytes_or_raises_on_empty(self, model_with_annotations): + model, _ = model_with_annotations + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if has_groups: + result = plot(model, output_format="png") + assert isinstance(result, bytes) + assert result[:4] == PNG_MAGIC + else: + with pytest.raises(ValueError, match="No plan geometry"): + plot(model, output_format="png") + + def test_base64_returns_dict(self, model_with_annotations): + model, _ = model_with_annotations + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if has_groups: + result = plot(model, output_format="base64") + assert isinstance(result, dict) + assert result["mime"] == "image/png" + assert "png_b64" in result + assert "width" in result + assert "height" in result + assert "view" in result + # Verify the base64 is valid PNG + decoded = base64.b64decode(result["png_b64"]) + assert decoded[:4] == PNG_MAGIC + else: + with pytest.raises(ValueError, match="No plan geometry"): + plot(model, output_format="base64") + + def test_base64_view_field_matches_requested(self, model_with_annotations): + model, _ = model_with_annotations + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if not has_groups: + pytest.skip("Model produces empty SVG") + result = plot(model, output_format="base64", view="floorplan") + assert result["view"] == "floorplan" + + def test_png_custom_size(self, model_with_annotations): + model, _ = model_with_annotations + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if not has_groups: + pytest.skip("Model produces empty SVG") + result = plot(model, output_format="png", png_width=512, png_height=512) + assert isinstance(result, bytes) + assert result[:4] == PNG_MAGIC + + +class TestCLI: + @staticmethod + def _ifc_path(model): + f = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) + model.write(f.name) + f.close() + return f.name + + def test_plot_svg_writes_file(self, model_with_annotations): + model, _ = model_with_annotations + ifc_path = self._ifc_path(model) + out_path = ifc_path.replace(".ifc", "_out.svg") + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "plot", "--out-format", "svg", "-o", out_path], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert os.path.exists(out_path) + with open(out_path, "rb") as f: + assert f.read(5) == SVG_MAGIC + finally: + for path in (ifc_path, out_path): + try: + os.unlink(path) + except OSError: + pass + + @pytest.mark.skipif(not HAS_CAIROSVG, reason="cairosvg not installed") + def test_plot_base64_prints_json(self, model_with_annotations): + """base64 format prints JSON to stdout instead of writing a file.""" + model, _ = model_with_annotations + ifc_path = self._ifc_path(model) + try: + # First check if the model would produce geometry + svg = plot(model, output_format="svg") + has_groups = b"" in svg + if not has_groups: + pytest.skip("Model produces empty SVG — base64 would raise ValueError") + + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "plot", "--out-format", "base64"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + # Output should be JSON (not an error) and contain base64 key + assert "png_b64" in result.stdout + finally: + try: + os.unlink(ifc_path) + except OSError: + pass diff --git a/src/ifcquery/tests/test_relations.py b/src/ifcquery/tests/test_relations.py new file mode 100644 index 0000000000..32bc8bf3cb --- /dev/null +++ b/src/ifcquery/tests/test_relations.py @@ -0,0 +1,199 @@ +# This file was generated with the assistance of an AI coding tool. +import json +import os +import subprocess +import sys +import tempfile + +from ifcquery.relations import relations + + +class TestWallRelations: + def test_wall_has_container(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + assert result["id"] == wall.id() + assert result["type"] == "IfcWall" + assert result["hierarchy"]["container"]["type"] == "IfcBuildingStorey" + assert result["hierarchy"]["container"]["name"] == "Ground Floor" + + def test_wall_has_parent(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + assert result["hierarchy"]["parent"]["type"] == "IfcBuildingStorey" + + def test_wall_no_children(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + assert "children" not in result + + def test_wall_empty_categories_omitted(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + assert "groups" not in result + assert "systems" not in result + assert "zones" not in result + assert "connections" not in result + assert "referenced_structures" not in result + + +class TestStoreyRelations: + def test_storey_has_contained(self, model): + storey = model.by_type("IfcBuildingStorey")[0] + result = relations(model, storey) + contained_types = {e["type"] for e in result["children"]["contained"]} + assert "IfcWall" in contained_types + assert "IfcSlab" in contained_types + + def test_storey_has_aggregate_parent(self, model): + storey = model.by_type("IfcBuildingStorey")[0] + result = relations(model, storey) + assert result["hierarchy"]["aggregate"]["type"] == "IfcBuilding" + assert result["hierarchy"]["aggregate"]["name"] == "TestBuilding" + + +class TestProjectRelations: + def test_project_has_parts(self, model): + project = model.by_type("IfcProject")[0] + result = relations(model, project) + parts = result["children"]["parts"] + assert any(p["type"] == "IfcSite" for p in parts) + + def test_project_no_hierarchy(self, model): + project = model.by_type("IfcProject")[0] + result = relations(model, project) + assert "hierarchy" not in result + + +class TestTraverseUp: + def test_wall_to_project(self, model): + wall = model.by_type("IfcWall")[0] + chain = relations(model, wall, traverse="up") + assert isinstance(chain, list) + assert chain[0]["type"] == "IfcWall" + assert chain[-1]["type"] == "IfcProject" + types = [e["type"] for e in chain] + assert "IfcBuildingStorey" in types + assert "IfcBuilding" in types + assert "IfcSite" in types + + def test_project_traverse(self, model): + project = model.by_type("IfcProject")[0] + chain = relations(model, project, traverse="up") + assert len(chain) == 1 + assert chain[0]["type"] == "IfcProject" + + def test_storey_to_project(self, model): + storey = model.by_type("IfcBuildingStorey")[0] + chain = relations(model, storey, traverse="up") + assert chain[0]["type"] == "IfcBuildingStorey" + assert chain[-1]["type"] == "IfcProject" + assert len(chain) == 4 # storey -> building -> site -> project + + +class TestElementsSummary: + def test_wall_elements_includes_self(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + ids = [e["id"] for e in result["elements"]] + assert wall.id() in ids + + def test_wall_elements_includes_container(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + ids = [e["id"] for e in result["elements"]] + storey = model.by_type("IfcBuildingStorey")[0] + assert storey.id() in ids + + def test_storey_elements_includes_contained(self, model): + storey = model.by_type("IfcBuildingStorey")[0] + result = relations(model, storey) + ids = [e["id"] for e in result["elements"]] + wall = model.by_type("IfcWall")[0] + assert wall.id() in ids + + def test_elements_no_duplicates(self, model): + storey = model.by_type("IfcBuildingStorey")[0] + result = relations(model, storey) + ids = [e["id"] for e in result["elements"]] + assert len(ids) == len(set(ids)) + + def test_elements_all_have_id_and_type(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + for e in result["elements"]: + assert "id" in e + assert "type" in e + + def test_traverse_up_has_no_elements_field(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall, traverse="up") + assert isinstance(result, list) + assert not any("elements" in item for item in result) + + +class TestJsonSerializable: + def test_relations_serializable(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall) + json.dumps(result) + + def test_traverse_serializable(self, model): + wall = model.by_type("IfcWall")[0] + result = relations(model, wall, traverse="up") + json.dumps(result) + + +class TestCLI: + @staticmethod + def _ifc_path(model): + f = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) + model.write(f.name) + f.close() + return f.name + + def test_relations_json(self, model): + path = self._ifc_path(model) + try: + wall = model.by_type("IfcWall")[0] + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "relations", str(wall.id())], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert data["type"] == "IfcWall" + assert "hierarchy" in data + finally: + os.unlink(path) + + def test_relations_traverse_up(self, model): + path = self._ifc_path(model) + try: + wall = model.by_type("IfcWall")[0] + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "relations", str(wall.id()), "--traverse", "up"], + capture_output=True, + text=True, + ) + assert result.returncode == 0 + data = json.loads(result.stdout) + assert isinstance(data, list) + assert data[0]["type"] == "IfcWall" + assert data[-1]["type"] == "IfcProject" + finally: + os.unlink(path) + + def test_relations_bad_id(self, model): + path = self._ifc_path(model) + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", path, "relations", "999999"], + capture_output=True, + text=True, + ) + assert result.returncode != 0 + assert "Error" in result.stderr + finally: + os.unlink(path) diff --git a/src/ifcquery/tests/test_render.py b/src/ifcquery/tests/test_render.py new file mode 100644 index 0000000000..28d83a6b68 --- /dev/null +++ b/src/ifcquery/tests/test_render.py @@ -0,0 +1,355 @@ +# This file was generated with the assistance of an AI coding tool. +import os +import subprocess +import sys +import tempfile + +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.context +import ifcopenshell.api.geometry +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.spatial +import ifcopenshell.api.unit +import ifcopenshell.guid +import numpy as np +import pytest + +from ifcquery.render import _make_profile_occurrence, _make_type_occurrence, render + +try: + import pyvista # noqa: F401 + + HAS_PYVISTA = True +except ImportError: + HAS_PYVISTA = False + +pytestmark = pytest.mark.skipif(not HAS_PYVISTA, reason="pyvista not installed") + +PNG_MAGIC = b"\x89PNG" + + +@pytest.fixture +def model_with_geometry(): + """Create an IFC4 model with walls that have geometric representations.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="TestSite") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="TestBuilding") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="Ground Floor") + + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + body = ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + + wall1 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall001") + rep1 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=5, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall1, representation=rep1) + ifcopenshell.api.spatial.assign_container(f, products=[wall1], relating_structure=storey) + + wall2 = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wall002") + rep2 = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=4, height=3, thickness=0.2) + ifcopenshell.api.geometry.assign_representation(f, product=wall2, representation=rep2) + ifcopenshell.api.spatial.assign_container(f, products=[wall2], relating_structure=storey) + matrix2 = np.eye(4) + matrix2[1, 3] = 3.0 + ifcopenshell.api.geometry.edit_object_placement(f, product=wall2, matrix=matrix2) + + return f + + +@pytest.fixture +def library_with_type(): + """IFC4 library file: a WallType with a RepresentationMap but no instances.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="LibProject") + ifcopenshell.api.unit.assign_unit(f) + + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + body = ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + + # Build the shape representation and wrap it in an IfcRepresentationMap. + shape_rep = ifcopenshell.api.geometry.add_wall_representation(f, context=body, length=3, height=2.5, thickness=0.2) + origin = f.create_entity("IfcCartesianPoint", Coordinates=(0.0, 0.0, 0.0)) + z_dir = f.create_entity("IfcDirection", DirectionRatios=(0.0, 0.0, 1.0)) + x_dir = f.create_entity("IfcDirection", DirectionRatios=(1.0, 0.0, 0.0)) + map_origin = f.create_entity("IfcAxis2Placement3D", Location=origin, Axis=z_dir, RefDirection=x_dir) + rep_map = f.create_entity("IfcRepresentationMap", MappingOrigin=map_origin, MappedRepresentation=shape_rep) + + wall_type = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWallType", name="LibWallType") + wall_type.RepresentationMaps = [rep_map] + + return f, wall_type + + +@pytest.fixture +def library_with_profile_type(): + """IFC4 library: a BeamType with an IfcMaterialProfileSet but no RepresentationMaps.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="ProfileLibProject") + ifcopenshell.api.unit.assign_unit(f) + + model_ctx = ifcopenshell.api.context.add_context(f, context_type="Model") + ifcopenshell.api.context.add_context( + f, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model_ctx + ) + + # Rectangular profile 0.2m x 0.3m + profile = f.create_entity( + "IfcRectangleProfileDef", + ProfileType="AREA", + ProfileName="200x300", + XDim=0.2, + YDim=0.3, + ) + material = f.create_entity("IfcMaterial", Name="Steel") + mat_profile = f.create_entity("IfcMaterialProfile", Material=material, Profile=profile) + profile_set = f.create_entity("IfcMaterialProfileSet", MaterialProfiles=[mat_profile]) + + beam_type = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBeamType", name="200x300 Steel Beam") + rel = f.create_entity( + "IfcRelAssociatesMaterial", + GlobalId=ifcopenshell.guid.new(), + RelatedObjects=[beam_type], + RelatingMaterial=profile_set, + ) + + return f, beam_type + + +class TestRenderBasic: + def test_returns_png_bytes(self, model_with_geometry): + result = render(model_with_geometry) + assert isinstance(result, bytes) + assert result[:4] == PNG_MAGIC + + def test_iso_view(self, model_with_geometry): + result = render(model_with_geometry, view="iso") + assert result[:4] == PNG_MAGIC + + def test_top_view(self, model_with_geometry): + result = render(model_with_geometry, view="top") + assert result[:4] == PNG_MAGIC + + def test_south_view(self, model_with_geometry): + result = render(model_with_geometry, view="south") + assert result[:4] == PNG_MAGIC + + def test_unknown_view_falls_back_to_iso(self, model_with_geometry): + # Unknown view strings fall through to isometric + result = render(model_with_geometry, view="diagonal") + assert result[:4] == PNG_MAGIC + + +class TestRenderSelector: + def test_selector_restricts_elements(self, model_with_geometry): + result = render(model_with_geometry, selector="IfcWall") + assert result[:4] == PNG_MAGIC + + def test_selector_no_match_raises(self, model_with_geometry): + with pytest.raises(ValueError, match="matched no elements"): + render(model_with_geometry, selector="IfcDoor") + + +class TestRenderHighlight: + def test_highlight_single_element(self, model_with_geometry): + wall = model_with_geometry.by_type("IfcWall")[0] + result = render(model_with_geometry, element_ids=[wall.id()]) + assert result[:4] == PNG_MAGIC + + def test_highlight_multiple_elements(self, model_with_geometry): + walls = model_with_geometry.by_type("IfcWall") + result = render(model_with_geometry, element_ids=[w.id() for w in walls]) + assert result[:4] == PNG_MAGIC + + +class TestRenderTypes: + def test_render_type_by_selector(self, library_with_type): + """Selecting a type class renders its RepresentationMap geometry.""" + model, wall_type = library_with_type + result = render(model, selector="IfcWallType") + assert result[:4] == PNG_MAGIC + + def test_render_type_by_element_id(self, library_with_type): + """Passing a type step-ID via element_ids renders it highlighted.""" + model, wall_type = library_with_type + result = render(model, element_ids=[wall_type.id()]) + assert result[:4] == PNG_MAGIC + + def test_original_model_unmodified(self, library_with_type): + """Rendering a type must not add entities to the original model.""" + model, wall_type = library_with_type + entity_count_before = len(list(model)) + render(model, selector="IfcWallType") + assert len(list(model)) == entity_count_before + + def test_make_type_occurrence_no_rep_maps(self, library_with_type): + """_make_type_occurrence returns None for a type with no RepresentationMaps.""" + model, _ = library_with_type + bare_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType", name="Bare") + assert _make_type_occurrence(model, bare_type) is None + + def test_type_without_rep_maps_raises(self): + """Selecting a type that has no RepresentationMaps raises ValueError.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P") + ifcopenshell.api.unit.assign_unit(f) + ifcopenshell.api.root.create_entity(f, ifc_class="IfcWallType", name="Bare") + with pytest.raises(ValueError): + render(f, selector="IfcWallType") + + +class TestRenderProfileTypes: + def test_render_profile_type_by_element_id(self, library_with_profile_type): + """A type with only a material profile set renders via temporary extrusion.""" + model, beam_type = library_with_profile_type + result = render(model, element_ids=[beam_type.id()]) + assert result[:4] == PNG_MAGIC + + def test_make_profile_occurrence_creates_occurrence(self, library_with_profile_type): + """_make_profile_occurrence returns an occurrence entity for a profile-set type.""" + model, beam_type = library_with_profile_type + occ = _make_profile_occurrence(model, beam_type) + assert occ is not None + + def test_make_profile_occurrence_no_profile_returns_none(self, library_with_type): + """_make_profile_occurrence returns None when type has no material profile set.""" + model, wall_type = library_with_type + # wall_type has RepresentationMaps but no material profile set + occ = _make_profile_occurrence(model, wall_type) + assert occ is None + + def test_original_model_unmodified_for_profile_type(self, library_with_profile_type): + """Rendering a profile-based type does not modify the original model.""" + model, beam_type = library_with_profile_type + entity_count_before = len(list(model)) + render(model, element_ids=[beam_type.id()]) + assert len(list(model)) == entity_count_before + + +class TestRenderNoGeometry: + def test_no_geometry_raises(self): + """A model without geometry representations raises ValueError.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P") + ifcopenshell.api.unit.assign_unit(f) + site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="S") + building = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuilding", name="B") + storey = ifcopenshell.api.root.create_entity(f, ifc_class="IfcBuildingStorey", name="GF") + ifcopenshell.api.aggregate.assign_object(f, products=[site], relating_object=project) + ifcopenshell.api.aggregate.assign_object(f, products=[building], relating_object=site) + ifcopenshell.api.aggregate.assign_object(f, products=[storey], relating_object=building) + wall = ifcopenshell.api.root.create_entity(f, ifc_class="IfcWall", name="Wallless") + ifcopenshell.api.spatial.assign_container(f, products=[wall], relating_structure=storey) + + with pytest.raises(ValueError, match="No renderable geometry"): + render(f) + + +class TestCLI: + @staticmethod + def _ifc_path(model): + f = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) + model.write(f.name) + f.close() + return f.name + + def test_render_writes_png(self, model_with_geometry): + ifc_path = self._ifc_path(model_with_geometry) + out_path = ifc_path.replace(".ifc", "_out.png") + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "render", "-o", out_path], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert os.path.exists(out_path) + with open(out_path, "rb") as f: + assert f.read(4) == PNG_MAGIC + finally: + for path in (ifc_path, out_path): + try: + os.unlink(path) + except OSError: + pass + + def test_render_default_output_path(self, model_with_geometry): + ifc_path = self._ifc_path(model_with_geometry) + expected_png = ifc_path.replace(".ifc", ".png") + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "render"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + assert os.path.exists(expected_png) + finally: + for path in (ifc_path, expected_png): + try: + os.unlink(path) + except OSError: + pass + + def test_render_with_selector(self, model_with_geometry): + ifc_path = self._ifc_path(model_with_geometry) + out_path = ifc_path.replace(".ifc", "_sel.png") + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "render", "-o", out_path, "--selector", "IfcWall"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + with open(out_path, "rb") as f: + assert f.read(4) == PNG_MAGIC + finally: + for path in (ifc_path, out_path): + try: + os.unlink(path) + except OSError: + pass + + def test_render_with_view(self, model_with_geometry): + ifc_path = self._ifc_path(model_with_geometry) + out_path = ifc_path.replace(".ifc", "_top.png") + try: + result = subprocess.run( + [sys.executable, "-m", "ifcquery", ifc_path, "render", "-o", out_path, "--view", "top"], + capture_output=True, + text=True, + ) + assert result.returncode == 0, result.stderr + with open(out_path, "rb") as f: + assert f.read(4) == PNG_MAGIC + finally: + for path in (ifc_path, out_path): + try: + os.unlink(path) + except OSError: + pass diff --git a/src/ifcquery/tests/test_schedule.py b/src/ifcquery/tests/test_schedule.py new file mode 100644 index 0000000000..0d3a3e934f --- /dev/null +++ b/src/ifcquery/tests/test_schedule.py @@ -0,0 +1,121 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import ifcopenshell +import ifcopenshell.api.aggregate +import ifcopenshell.api.owner.settings +import ifcopenshell.api.project +import ifcopenshell.api.root +import ifcopenshell.api.sequence +import ifcopenshell.api.unit +import pytest + +from ifcquery.schedule import schedule + + +@pytest.fixture +def schedule_model(): + """Create an IFC4 model with a work schedule and nested tasks.""" + f = ifcopenshell.api.project.create_file() + ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] + ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] + + project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="TestProject") + ifcopenshell.api.unit.assign_unit(f) + + ws = ifcopenshell.api.sequence.add_work_schedule(f, name="Construction Schedule") + + task1 = ifcopenshell.api.sequence.add_task(f, work_schedule=ws, name="Phase 1", identification="P1") + tt1 = ifcopenshell.api.sequence.add_task_time(f, task=task1) + ifcopenshell.api.sequence.edit_task_time( + f, task_time=tt1, attributes={"ScheduleStart": "2024-01-01", "ScheduleFinish": "2024-06-30"} + ) + + task2 = ifcopenshell.api.sequence.add_task(f, work_schedule=ws, name="Phase 2", identification="P2") + subtask = ifcopenshell.api.sequence.add_task(f, parent_task=task1, name="Sub Task", identification="S1") + + return f + + +class TestSchedule: + def test_returns_list(self, schedule_model): + result = schedule(schedule_model) + assert isinstance(result, list) + + def test_finds_work_schedule(self, schedule_model): + result = schedule(schedule_model) + assert len(result) == 1 + + def test_work_schedule_has_name(self, schedule_model): + result = schedule(schedule_model) + assert result[0]["name"] == "Construction Schedule" + + def test_work_schedule_has_id(self, schedule_model): + result = schedule(schedule_model) + assert isinstance(result[0]["id"], int) + assert result[0]["id"] > 0 + + def test_work_schedule_has_tasks(self, schedule_model): + result = schedule(schedule_model) + tasks = result[0]["tasks"] + assert isinstance(tasks, list) + assert len(tasks) >= 1 + + def test_task_has_required_fields(self, schedule_model): + result = schedule(schedule_model) + task = result[0]["tasks"][0] + assert "id" in task + assert "name" in task + assert "start" in task + assert "finish" in task + assert "is_milestone" in task + assert "outputs" in task + assert "subtasks" in task + + def test_task_name(self, schedule_model): + result = schedule(schedule_model) + task_names = [t["name"] for t in result[0]["tasks"]] + assert "Phase 1" in task_names + + def test_task_start_finish(self, schedule_model): + result = schedule(schedule_model) + phase1 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 1") + assert phase1["start"] is not None + assert phase1["finish"] is not None + + def test_subtasks(self, schedule_model): + result = schedule(schedule_model) + phase1 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 1") + assert len(phase1["subtasks"]) == 1 + assert phase1["subtasks"][0]["name"] == "Sub Task" + + def test_empty_model_returns_empty_list(self, model): + result = schedule(model) + assert result == [] + + def test_max_depth_none_returns_full_tree(self, schedule_model): + result = schedule(schedule_model, max_depth=None) + phase1 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 1") + assert isinstance(phase1["subtasks"], list) + assert len(phase1["subtasks"]) == 1 + + def test_max_depth_1_truncates_subtasks(self, schedule_model): + result = schedule(schedule_model, max_depth=1) + phase1 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 1") + assert isinstance(phase1["subtasks"], dict) + assert phase1["subtasks"]["truncated"] is True + assert phase1["subtasks"]["count"] == 1 + + def test_max_depth_truncation_shows_count(self, schedule_model): + result = schedule(schedule_model, max_depth=1) + # Phase 2 has no subtasks — should return empty list, not truncation dict + phase2 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 2") + assert phase2["subtasks"] == [] + + def test_max_depth_2_expands_to_depth_2(self, schedule_model): + result = schedule(schedule_model, max_depth=2) + phase1 = next(t for t in result[0]["tasks"] if t["name"] == "Phase 1") + # subtask at depth 2 should be fully expanded (it has no children) + assert isinstance(phase1["subtasks"], list) + assert phase1["subtasks"][0]["name"] == "Sub Task" + assert phase1["subtasks"][0]["subtasks"] == [] diff --git a/src/ifcquery/tests/test_schema.py b/src/ifcquery/tests/test_schema.py new file mode 100644 index 0000000000..c4f63344ee --- /dev/null +++ b/src/ifcquery/tests/test_schema.py @@ -0,0 +1,32 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import pytest + +from ifcquery.schema import schema + + +class TestSchema: + def test_ifc_wall_has_description(self, model): + result = schema(model, "IfcWall") + assert "description" in result + assert isinstance(result["description"], str) + assert len(result["description"]) > 0 + + def test_ifc_wall_has_attributes(self, model): + result = schema(model, "IfcWall") + assert "attributes" in result + + def test_ifc_wall_has_spec_url(self, model): + result = schema(model, "IfcWall") + assert "spec_url" in result + + def test_unknown_entity_returns_error(self, model): + result = schema(model, "IfcNonExistentFooBar") + assert "error" in result + assert "IfcNonExistentFooBar" in result["error"] + + def test_ifc_window_has_description(self, model): + result = schema(model, "IfcWindow") + assert "description" in result + assert len(result["description"]) > 0 diff --git a/src/ifcquery/tests/test_select.py b/src/ifcquery/tests/test_select.py new file mode 100644 index 0000000000..d5700a38e6 --- /dev/null +++ b/src/ifcquery/tests/test_select.py @@ -0,0 +1,32 @@ +# This file was generated with the assistance of an AI coding tool. +from ifcquery.select import select + + +class TestSelect: + def test_select_by_type(self, model): + result = select(model, "IfcWall") + assert len(result) == 1 + assert result[0]["type"] == "IfcWall" + assert result[0]["name"] == "Wall001" + + def test_select_multiple_types(self, model): + result = select(model, "IfcWall, IfcSlab") + assert len(result) == 2 + types = {r["type"] for r in result} + assert types == {"IfcWall", "IfcSlab"} + + def test_select_no_match(self, model): + result = select(model, "IfcDoor") + assert result == [] + + def test_results_sorted_by_id(self, model): + result = select(model, "IfcWall, IfcSlab") + ids = [r["id"] for r in result] + assert ids == sorted(ids) + + def test_result_has_id_type_name(self, model): + result = select(model, "IfcWall") + entry = result[0] + assert "id" in entry + assert "type" in entry + assert "name" in entry diff --git a/src/ifcquery/tests/test_summary.py b/src/ifcquery/tests/test_summary.py new file mode 100644 index 0000000000..c230b48ab7 --- /dev/null +++ b/src/ifcquery/tests/test_summary.py @@ -0,0 +1,34 @@ +# This file was generated with the assistance of an AI coding tool. +import ifcopenshell +import ifcopenshell.api.project + +from ifcquery.summary import summary + + +class TestSummary: + def test_schema(self, model): + result = summary(model) + assert result["schema"] == "IFC4" + + def test_total_entities(self, model): + result = summary(model) + assert result["total_entities"] == len(list(model)) + assert result["total_entities"] > 0 + + def test_project_info(self, model): + result = summary(model) + assert result["project"]["name"] == "TestProject" + + def test_type_counts(self, model): + result = summary(model) + types = result["types"] + assert "IfcWall" in types + assert types["IfcWall"] == 1 + assert "IfcSlab" in types + assert types["IfcSlab"] == 1 + + def test_empty_model(self): + f = ifcopenshell.api.project.create_file() + result = summary(f) + assert result["schema"] == "IFC4" + assert "project" not in result diff --git a/src/ifcquery/tests/test_tree.py b/src/ifcquery/tests/test_tree.py new file mode 100644 index 0000000000..2110be3188 --- /dev/null +++ b/src/ifcquery/tests/test_tree.py @@ -0,0 +1,37 @@ +# This file was generated with the assistance of an AI coding tool. +from ifcquery.tree import tree + + +class TestTree: + def test_root_is_project(self, model): + result = tree(model) + assert result["type"] == "IfcProject" + assert result["name"] == "TestProject" + + def test_spatial_hierarchy(self, model): + result = tree(model) + # Project > Site > Building > Storey + site = result["children"][0] + assert site["type"] == "IfcSite" + assert site["name"] == "TestSite" + + building = site["children"][0] + assert building["type"] == "IfcBuilding" + assert building["name"] == "TestBuilding" + + storey = building["children"][0] + assert storey["type"] == "IfcBuildingStorey" + assert storey["name"] == "Ground Floor" + + def test_contained_elements(self, model): + result = tree(model) + storey = result["children"][0]["children"][0]["children"][0] + elements = storey["elements"] + element_types = {e["type"] for e in elements} + assert "IfcWall" in element_types + assert "IfcSlab" in element_types + + def test_element_ids_present(self, model): + result = tree(model) + assert "id" in result + assert isinstance(result["id"], int) diff --git a/src/ifcquery/tests/test_validate.py b/src/ifcquery/tests/test_validate.py new file mode 100644 index 0000000000..44734ce4b0 --- /dev/null +++ b/src/ifcquery/tests/test_validate.py @@ -0,0 +1,47 @@ +# This file was generated with the assistance of an AI coding tool. +from __future__ import annotations + +import ifcopenshell +import ifcopenshell.api.project +import pytest + +from ifcquery.validate import validate + + +class TestValidate: + def test_valid_model_returns_valid_true(self, model): + result = validate(model) + assert result["valid"] is True + assert isinstance(result["issues"], list) + + def test_valid_model_has_no_issues(self, model): + result = validate(model) + assert result["issues"] == [] + + def test_empty_model_is_valid(self): + f = ifcopenshell.api.project.create_file() + result = validate(f) + assert result["valid"] is True + assert result["issues"] == [] + + def test_result_has_expected_keys(self, model): + result = validate(model) + assert "valid" in result + assert "issues" in result + + def test_express_rules_flag_accepted(self, model): + # Just verify it runs without error; express rules may add/not add issues + result = validate(model, express_rules=True) + assert "valid" in result + assert isinstance(result["issues"], list) + + def test_issue_has_level_and_message(self, model): + # Force an issue by manually breaking the model (invalid IfcWall attribute) + f = ifcopenshell.file() + # Create a raw IfcWall with deliberately wrong type for GlobalId (use int) + # We just check structure if any issues appear; on well-formed models there are none. + result = validate(model) + # Even if no issues, the structure contract must hold for any issues present + for issue in result["issues"]: + assert "level" in issue + assert "message" in issue diff --git a/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py b/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py index 785d5db718..134ad05d99 100644 --- a/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py +++ b/src/ifcsverchok/nodes/ifc/bmesh_to_ifc.py @@ -193,7 +193,7 @@ class SvIfcBMeshToIfcRepr(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.help blender_object=obj, geometry=obj.data, context=context, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] ) if not representation: raise Exception("Couldn't create representation. Possibly wrong context.") diff --git a/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py b/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py index be06fac1ce..eb0965b8b3 100644 --- a/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py +++ b/src/ifcsverchok/nodes/ifc/sverchok_to_ifc.py @@ -143,7 +143,7 @@ class SvIfcSverchokToIfcRepr(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.h for item in obj: representation = ifcopenshell.api.geometry.add_mesh_representation( self.file, - should_run_listeners=False, + should_run_listeners=False, # ty:ignore[unknown-argument] context=self.context, vertices=[list(map(tuple, item[0]))], edges=[list(map(tuple, item[1]))], diff --git a/src/ifctester/Makefile b/src/ifctester/Makefile index a2df0c5820..71ecbd48d7 100644 --- a/src/ifctester/Makefile +++ b/src/ifctester/Makefile @@ -25,9 +25,19 @@ WEBAPP_BUILD_DIR := $(WEBAPP_DIR)/dist PYODIDE_DIR := $(WEBAPP_DIR)/public/pyodide PYODIDE_VERSION := 0.28.0 PYODIDE_URL := https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/pyodide-$(PYODIDE_VERSION).tar.bz2 +WORKER_BIN_DIR := $(WEBAPP_DIR)/public/worker/bin +IFCOPENSHELL_WASM_WHEEL := ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl +IFCOPENSHELL_WASM_WHEEL_URL := https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-0.8.5%2Ba51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl +IFCOPENSHELL_WASM_WHEEL_PATH := $(WORKER_BIN_DIR)/$(IFCOPENSHELL_WASM_WHEEL) +WEBAPP_GENERATED_DIR := $(WEBAPP_DIR)/public/worker/generated +WEBAPP_IFCTESTER_MANIFEST := $(WEBAPP_GENERATED_DIR)/ifctester.json +WEBAPP_IFCTESTER_BUILD_DIR := build-webapp-wheel +PACKAGE_WEBAPP_DIR := $(PACKAGE_NAME)/webapp +PACKAGE_WEBAPP_WWW_DIR := $(PACKAGE_WEBAPP_DIR)/www .PHONY: webapp-dev -webapp-dev: +webapp-dev: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel + cd $(WEBAPP_DIR) && npm install cd $(WEBAPP_DIR) && npm run dev .PHONY: pyodide-download @@ -62,8 +72,43 @@ pyodide-download: echo "Pyodide $(PYODIDE_VERSION) prepared in $(PYODIDE_DIR)"; \ fi +.PHONY: ifcopenshell-wasm-download +ifcopenshell-wasm-download: + @if [ -f "$(IFCOPENSHELL_WASM_WHEEL_PATH)" ]; then \ + echo "IfcOpenShell wasm wheel already exists at $(IFCOPENSHELL_WASM_WHEEL_PATH), skipping download"; \ + else \ + echo "Downloading IfcOpenShell wasm wheel..."; \ + mkdir -p $(WORKER_BIN_DIR); \ + rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl; \ + curl -fL -o "$(IFCOPENSHELL_WASM_WHEEL_PATH)" "$(IFCOPENSHELL_WASM_WHEEL_URL)"; \ + echo "IfcOpenShell wasm wheel prepared at $(IFCOPENSHELL_WASM_WHEEL_PATH)"; \ + fi + +.PHONY: webapp-stage-ifctester-wheel +webapp-stage-ifctester-wheel: + rm -rf $(WEBAPP_GENERATED_DIR) + rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR) + mkdir -p $(WEBAPP_IFCTESTER_BUILD_DIR) + cp -r $(PACKAGE_NAME) $(WEBAPP_IFCTESTER_BUILD_DIR)/ + rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/webapp + cp pyproject.toml $(WEBAPP_IFCTESTER_BUILD_DIR)/ + cp README.md $(WEBAPP_IFCTESTER_BUILD_DIR)/ +ifeq ($(IS_STABLE), TRUE) + $(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml + $(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py +else + $(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml + $(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py +endif + cd $(WEBAPP_IFCTESTER_BUILD_DIR) && $(PYTHON) -m venv env --system-site-packages && . env/$(VENV_ACTIVATE) && python -m pip install build && python -m build --wheel --no-isolation + mkdir -p $(WEBAPP_GENERATED_DIR) + wheel=$$(basename $(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$(PACKAGE_NAME)-*.whl); \ + cp "$(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$$wheel" "$(WEBAPP_GENERATED_DIR)/$$wheel"; \ + printf '{\n "wheel_url": "/worker/generated/%s"\n}\n' "$$wheel" > "$(WEBAPP_IFCTESTER_MANIFEST)" + rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR) + .PHONY: webapp-build -webapp-build: pyodide-download +webapp-build: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel cd $(WEBAPP_DIR) && npm install cd $(WEBAPP_DIR) && npm run build @@ -76,23 +121,36 @@ clean: rm -rf $(WEBAPP_BUILD_DIR) rm -rf $(WEBAPP_DIR)/node_modules rm -rf $(PYODIDE_DIR) + rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl + rm -rf $(WEBAPP_GENERATED_DIR) + rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR) rm -rf $(PACKAGE_NAME)/webapp rm -rf dist -.PHONY: dist -dist: webapp-prepare +.PHONY: python-dist +python-dist: + rm -rf dist # For some reason OS is not initalized when we call common.mk dist, which matters on Windows. # So we pass it explicitly. - $(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS) + $(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS) IS_STABLE=$(IS_STABLE) + +.PHONY: dist +dist: webapp-prepare + $(MAKE) python-dist OS=$(OS) IS_STABLE=$(IS_STABLE) .PHONY: webapp-prepare webapp-prepare: webapp-build - rm -rf $(PACKAGE_NAME)/webapp/www/* - mkdir -p $(PACKAGE_NAME)/webapp/www - cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_NAME)/webapp/www/ - cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_NAME)/webapp/__init__.py - cp $(WEBAPP_DIR)/serve.py $(PACKAGE_NAME)/webapp/serve.py + rm -rf $(PACKAGE_WEBAPP_WWW_DIR) + mkdir -p $(PACKAGE_WEBAPP_WWW_DIR) + cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_WEBAPP_WWW_DIR)/ + cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_WEBAPP_DIR)/__init__.py + cp $(WEBAPP_DIR)/serve.py $(PACKAGE_WEBAPP_DIR)/serve.py .PHONY: test test: pytest -p no:pytest-blender test + +.PHONY: build-ids-docs +build-ids-docs: + mkdir -p test/build + cd test && python ids_doc_generator.py diff --git a/src/ifctester/test/ids_doc_generator.py b/src/ifctester/test/ids_doc_generator.py index 3788afebbc..f91decc1a7 100644 --- a/src/ifctester/test/ids_doc_generator.py +++ b/src/ifctester/test/ids_doc_generator.py @@ -16,6 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcTester. If not, see . +""" +Documentation generator for IfcTester IDS facets and test cases. + +This is not a test file. It lives in the test/ directory because it reuses +test cases from test_facet.py and test_ids.py to generate example IFC files, +IDS files, and Markdown documentation into test/build/. + +Run via: make build-ids-docs (from src/ifctester/) +""" + import functools import os import re @@ -40,6 +50,7 @@ import test_ids from ifcopenshell import validate import ifctester +import ifctester.facet from ifctester import ids outdir = "build" @@ -117,6 +128,8 @@ class FacetDocGenerator: {"name": name, "ids": xml_text, "ifc": ifc_text, "basename": basename, "result": result, "id": inst.id()} ) + ifctester.facet.get_pset.cache_clear() + ifctester.facet.get_psets.cache_clear() assert bool(facet(inst)) is expected def set_facet(self, facet): @@ -140,7 +153,7 @@ class IdsDocGenerator: all_applicable.update(spec.applicable_entities) for requirement in spec.requirements: if requirement.status is False: - all_failures.update(requirement.failed_entities) + all_failures.update(f["element"] for f in requirement.failures) assert set(all_applicable) == set(applicable_entities) assert set(all_failures) == set(failed_entities) @@ -151,7 +164,11 @@ class IdsDocGenerator: l = validate.json_logger() validate.validate(ifc, l) for issue in l.statements: - raise Exception("About to emit invalid example data:", issue) + # test_parsing_entities_with_no_attributes uses nameless IfcMaterial; fix for doc generation. + if issue["instance"].is_a("IfcMaterial") and issue.get("attribute") == "IfcMaterial.Name": + issue["instance"].Name = "Unnamed" + else: + raise Exception("About to emit invalid example data:", issue) lines = ifc.wrapped_data.to_string().split("\n")[7:-3] ifc_text = "" @@ -293,12 +310,12 @@ spec = ifctester.ids.Specification( ) specs.specifications.append(spec) spec.applicability.append(ifctester.ids.Entity(name="IFCWALLTYPE")) -restriction = ifctester.ids.Restriction(options={"pattern": "(-|[0-9]{2,3})\/(-|[0-9]{2,3})\/(-|[0-9]{2,3})"}) +restriction = ifctester.ids.Restriction(options={"pattern": r"(-|[0-9]{2,3})/(-|[0-9]{2,3})/(-|[0-9]{2,3})"}) spec.requirements.append( ifctester.ids.Property( propertySet="Pset_WallCommon", - name="FireRating", - datatype="IfcLabel", + baseName="FireRating", + dataType="IfcLabel", value=restriction, instructions="Fire rating is specified using the Fire Resistance Level as defined in the Australian National Construction Code (NCC) 2019. Valid examples include -/-/-, -/120/120, and 60/60/60", ) diff --git a/src/ifctester/webapp/.gitignore b/src/ifctester/webapp/.gitignore index 4205867691..f7daf321ef 100644 --- a/src/ifctester/webapp/.gitignore +++ b/src/ifctester/webapp/.gitignore @@ -11,6 +11,8 @@ node_modules dist dist-ssr *.local +public/worker/bin/ifcopenshell-*.whl +public/worker/generated # Editor directories and files .vscode/* @@ -24,4 +26,4 @@ dist-ssr *.sw? .claude -experiment/* \ No newline at end of file +experiment/* diff --git a/src/ifctester/webapp/biome.json b/src/ifctester/webapp/biome.json new file mode 100644 index 0000000000..ce01c80c3f --- /dev/null +++ b/src/ifctester/webapp/biome.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "files": { + "ignore": [ + "dist", + "node_modules", + "public/pyodide", + "**/*.svelte" + ] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "overrides": [ + { + "include": [ + "src/modules/wasm/worker/**" + ], + "linter": { + "rules": { + "suspicious": { + "noExplicitAny": "off" + } + } + } + } + ] +} diff --git a/src/ifctester/webapp/index.html b/src/ifctester/webapp/index.html index 27c32440dc..48ac4cc1c8 100644 --- a/src/ifctester/webapp/index.html +++ b/src/ifctester/webapp/index.html @@ -9,6 +9,6 @@
- + diff --git a/src/ifctester/webapp/jsconfig.json b/src/ifctester/webapp/jsconfig.json deleted file mode 100644 index 2aef2313e4..0000000000 --- a/src/ifctester/webapp/jsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "compilerOptions": { - "moduleResolution": "bundler", - "target": "ESNext", - "module": "ESNext", - /** - * svelte-preprocess cannot figure out whether you have - * a value or a type, so tell TypeScript to enforce using - * `import type` instead of `import` for Types. - */ - "verbatimModuleSyntax": true, - "isolatedModules": true, - "resolveJsonModule": true, - /** - * To have warnings / errors of the Svelte compiler at the - * correct position, enable source maps by default. - */ - "sourceMap": true, - "esModuleInterop": true, - "skipLibCheck": true, - /** - * Typecheck JS in `.svelte` and `.js` files by default. - * Disable this if you'd like to use dynamic types. - */ - "checkJs": false, - "baseUrl": ".", - "paths": { - "$lib": ["./src/lib"], - "$lib/*": ["./src/lib/*"], - "$src": ["./src"], - "$src/*": ["./src/*"] - } - }, - /** - * Use global.d.ts instead of compilerOptions.types - * to avoid limiting type declarations. - */ - "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] -} diff --git a/src/ifctester/webapp/package-lock.json b/src/ifctester/webapp/package-lock.json index d94807f270..1244317fd2 100644 --- a/src/ifctester/webapp/package-lock.json +++ b/src/ifctester/webapp/package-lock.json @@ -16,6 +16,7 @@ "svelte-spa-router": "^4.0.1" }, "devDependencies": { + "@biomejs/biome": "^1.9.4", "@internationalized/date": "^3.8.1", "@lucide/svelte": "^0.515.0", "@sveltejs/vite-plugin-svelte": "^5.0.3", @@ -25,11 +26,13 @@ "mode-watcher": "^1.1.0", "sass-embedded": "^1.89.0", "svelte": "^5.53.6", + "svelte-check": "^4.0.0", "svelte-sonner": "^1.0.5", "tailwind-merge": "^3.3.0", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", "tw-animate-css": "^1.3.2", + "typescript": "^5.8.3", "vite": "^6.4.1" } }, @@ -47,6 +50,170 @@ "node": ">=6.0.0" } }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz", @@ -1468,6 +1635,22 @@ "dev": true, "license": "MIT/X11" }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -1498,7 +1681,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1533,9 +1715,9 @@ } }, "node_modules/devalue": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.3.tgz", - "integrity": "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", + "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", "license": "MIT" }, "node_modules/engine.io-client": { @@ -1732,9 +1914,9 @@ "license": "BSD-3-Clause" }, "node_modules/immutable": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", - "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true, "license": "MIT" }, @@ -2127,6 +2309,16 @@ "svelte": "^5.7.0" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2160,9 +2352,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2201,6 +2393,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/regexparam": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz", @@ -2282,6 +2488,19 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/sass-embedded": { "version": "1.89.0", "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz", @@ -2700,35 +2919,18 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2801,6 +3003,30 @@ "node": ">=18" } }, + "node_modules/svelte-check": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz", + "integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, "node_modules/svelte-sonner": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz", @@ -2951,9 +3177,9 @@ } }, "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3001,6 +3227,20 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/src/ifctester/webapp/package.json b/src/ifctester/webapp/package.json index bb7d58567e..14b54ad8ed 100644 --- a/src/ifctester/webapp/package.json +++ b/src/ifctester/webapp/package.json @@ -6,11 +6,15 @@ "scripts": { "dev": "vite", "build": "vite build", + "check": "tsc -p tsconfig.json --noEmit && svelte-check && biome lint .", + "lint": "biome lint .", + "lint:fix": "biome lint --write .", "preview": "vite preview", "deploy": "npm run build && npx wrangler pages deploy dist" }, "devDependencies": { "@internationalized/date": "^3.8.1", + "@biomejs/biome": "^1.9.4", "@lucide/svelte": "^0.515.0", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tailwindcss/vite": "^4.0.0", @@ -19,10 +23,12 @@ "mode-watcher": "^1.1.0", "sass-embedded": "^1.89.0", "svelte": "^5.53.6", + "svelte-check": "^4.0.0", "svelte-sonner": "^1.0.5", "tailwind-merge": "^3.3.0", "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", + "typescript": "^5.8.3", "tw-animate-css": "^1.3.2", "vite": "^6.4.1" }, diff --git a/src/ifctester/webapp/public/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl b/src/ifctester/webapp/public/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl deleted file mode 100644 index 703ae3b877..0000000000 Binary files a/src/ifctester/webapp/public/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl and /dev/null differ diff --git a/src/ifctester/webapp/serve.py b/src/ifctester/webapp/serve.py index 5b18f15545..c114b47542 100644 --- a/src/ifctester/webapp/serve.py +++ b/src/ifctester/webapp/serve.py @@ -22,9 +22,6 @@ import os import sys bonsai_lib_path = os.environ.get("BONSAI_LIB_PATH") -print(os.environ) -print(bonsai_lib_path) -bonsai_version = os.environ.get("BONSAI_VERSION") if bonsai_lib_path: sys.path.insert(0, bonsai_lib_path) diff --git a/src/ifctester/webapp/src/App.svelte b/src/ifctester/webapp/src/App.svelte index 19222ad8d1..27b5c8001b 100644 --- a/src/ifctester/webapp/src/App.svelte +++ b/src/ifctester/webapp/src/App.svelte @@ -1,4 +1,4 @@ - diff --git a/src/ifctester/webapp/src/app.d.ts b/src/ifctester/webapp/src/app.d.ts new file mode 100644 index 0000000000..ff86489626 --- /dev/null +++ b/src/ifctester/webapp/src/app.d.ts @@ -0,0 +1,4 @@ +declare module "*.svelte" { + import type { SvelteComponent } from "svelte"; + export default class Component extends SvelteComponent {} +} diff --git a/src/ifctester/webapp/src/components/AppHeader.svelte b/src/ifctester/webapp/src/components/AppHeader.svelte index 61f8a5b3b2..8fb71b6180 100644 --- a/src/ifctester/webapp/src/components/AppHeader.svelte +++ b/src/ifctester/webapp/src/components/AppHeader.svelte @@ -1,11 +1,11 @@ - @@ -142,4 +143,4 @@ - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/components/AppRibbon.svelte b/src/ifctester/webapp/src/components/AppRibbon.svelte index 4f9643637e..662a93b500 100644 --- a/src/ifctester/webapp/src/components/AppRibbon.svelte +++ b/src/ifctester/webapp/src/components/AppRibbon.svelte @@ -1,6 +1,5 @@ -
@@ -20,4 +19,4 @@ Error
{/if} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/components/AppToolbar.svelte b/src/ifctester/webapp/src/components/AppToolbar.svelte index 3552b13969..c9b3dd2968 100644 --- a/src/ifctester/webapp/src/components/AppToolbar.svelte +++ b/src/ifctester/webapp/src/components/AppToolbar.svelte @@ -1,14 +1,16 @@ - @@ -63,4 +65,4 @@ Part Of - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/components/IdsTabs.svelte b/src/ifctester/webapp/src/components/IdsTabs.svelte index 0f6f38a569..25d6891ab7 100644 --- a/src/ifctester/webapp/src/components/IdsTabs.svelte +++ b/src/ifctester/webapp/src/components/IdsTabs.svelte @@ -1,13 +1,22 @@ -
@@ -15,10 +24,13 @@
switchDocument(docId)} - aria-label={doc.info.title || "Untitled"} + onkeydown={(event) => handleActivation(event, () => switchDocument(docId))} + aria-label={(doc as IdsDocument).info.title || "Untitled"} > - {doc.info.title || "Untitled"} + {(doc as IdsDocument).info.title || "Untitled"}
{/each}
-
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/config.json b/src/ifctester/webapp/src/config.json index d5c450f460..f4f469178f 100644 --- a/src/ifctester/webapp/src/config.json +++ b/src/ifctester/webapp/src/config.json @@ -1,8 +1,7 @@ { "wasm": { - "wheel_url": "/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl", + "wheel_url": "/worker/bin/ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl", "odfpy_url": "/worker/bin/odfpy-1.4.2-py2.py3-none-any.whl", - "api_py_url": "/worker/api.py", - "pyodide_url": "https://cdn.jsdelivr.net/pyodide/v0.28.0/full/pyodide.js" + "api_py_url": "/worker/api.py" } -} \ No newline at end of file +} diff --git a/src/ifctester/webapp/src/css/app.css b/src/ifctester/webapp/src/css/app.css index a8210b43e8..09f12f3b9f 100644 --- a/src/ifctester/webapp/src/css/app.css +++ b/src/ifctester/webapp/src/css/app.css @@ -576,7 +576,8 @@ html, body { display: flex; flex-direction: column; - label { + label, + .form-label { margin-bottom: 4px; font-size: 14px; font-weight: 500; diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-close.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-close.svelte index 94266a6315..a6ac3f979a 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-close.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-close.svelte @@ -1,7 +1,11 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-content.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-content.svelte index 9ceaa7faf7..17d98fe392 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-content.svelte @@ -1,8 +1,17 @@ - @@ -35,4 +44,4 @@ {/if} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-description.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-description.svelte index 419a27e449..0f91cf0670 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-description.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-description.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-footer.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-footer.svelte index b2d4035e6e..7071ae2a0c 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-footer.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-footer.svelte @@ -1,11 +1,19 @@ -
{@render children?.()} -
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-header.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-header.svelte index 791dcde5d7..9bb73aa601 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-header.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-header.svelte @@ -1,12 +1,19 @@ -
{@render children?.()} -
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-overlay.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-overlay.svelte index adb8a38e94..90e3104148 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-overlay.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-title.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-title.svelte index 2d36371d01..866d5ef826 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-title.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-title.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-trigger.svelte index 7a38ff9e6a..5636ea2df1 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -1,7 +1,11 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dialog/index.js b/src/ifctester/webapp/src/lib/components/ui/dialog/index.ts similarity index 100% rename from src/ifctester/webapp/src/lib/components/ui/dialog/index.js rename to src/ifctester/webapp/src/lib/components/ui/dialog/index.ts diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte index fc034efd31..242f3088ef 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -1,8 +1,16 @@ - {@render childrenProp?.()} {/snippet} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte index d13c1ad08d..b793c23e85 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -1,14 +1,21 @@ - @@ -22,4 +29,4 @@ )} {...restProps} /> - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte index 89454b26df..83032fee1e 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte index 4f3421fbe8..55220a4783 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -1,7 +1,11 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte index a06f2099dd..20744df288 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -1,14 +1,21 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte index e0aceee7d3..2fc90b933b 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -1,12 +1,20 @@ -
{@render children?.()} -
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte index 009a4dd02d..0be2895631 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -1,11 +1,16 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte index 218db7f78a..629cc37563 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -1,14 +1,22 @@ - {@render childrenProp?.({ checked })} {/snippet} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte index 4d02884d17..533e47c574 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte index 20f2210c5a..93b6175647 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -1,12 +1,19 @@ - {@render children?.()} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte index 0f3c698fea..7a33ac199c 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte index e4e236e111..a2be23a136 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -1,7 +1,15 @@ - {@render children?.()} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte index 720d196b77..1aa23bc512 100644 --- a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -1,7 +1,11 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/index.js b/src/ifctester/webapp/src/lib/components/ui/dropdown-menu/index.ts similarity index 100% rename from src/ifctester/webapp/src/lib/components/ui/dropdown-menu/index.js rename to src/ifctester/webapp/src/lib/components/ui/dropdown-menu/index.ts diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/index.js b/src/ifctester/webapp/src/lib/components/ui/menubar/index.ts similarity index 100% rename from src/ifctester/webapp/src/lib/components/ui/menubar/index.js rename to src/ifctester/webapp/src/lib/components/ui/menubar/index.ts diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-checkbox-item.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-checkbox-item.svelte index 0d6e298b0f..bb020eec7b 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-checkbox-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-checkbox-item.svelte @@ -1,8 +1,16 @@ - {@render childrenProp?.()} {/snippet} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-content.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-content.svelte index 2706fc76e2..4c7696d218 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-content.svelte @@ -1,6 +1,19 @@ - @@ -28,4 +41,4 @@ )} {...restProps} /> - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group-heading.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group-heading.svelte index c64dd91534..13417d54ec 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group-heading.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group-heading.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group.svelte index 8acc8b9549..2c77293f76 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-group.svelte @@ -1,10 +1,14 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-item.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-item.svelte index f4fee4711e..1ff76231a5 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-item.svelte @@ -1,6 +1,13 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-label.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-label.svelte index 129be16dd5..efbbb9b017 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-label.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-label.svelte @@ -1,12 +1,19 @@ -
{@render children?.()} -
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-radio-item.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-radio-item.svelte index c7c122881f..1947458beb 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-radio-item.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-radio-item.svelte @@ -1,14 +1,22 @@ - {@render childrenProp?.({ checked })} {/snippet} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-separator.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-separator.svelte index d32bbab673..7674b560bc 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-separator.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-separator.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-shortcut.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-shortcut.svelte index 7df7a0d118..05cf1bc13b 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-shortcut.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-shortcut.svelte @@ -1,12 +1,19 @@ - {@render children?.()} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-content.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-content.svelte index 5a79de2c58..67b68ca633 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-content.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-trigger.svelte index f9fa0461ee..c14976bd12 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-sub-trigger.svelte @@ -1,7 +1,15 @@ - {@render children?.()} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-trigger.svelte index c39921a2a1..33119d913c 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar-trigger.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar.svelte b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar.svelte index cddff1b908..ebc3534d09 100644 --- a/src/ifctester/webapp/src/lib/components/ui/menubar/menubar.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/menubar/menubar.svelte @@ -1,12 +1,17 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/sonner/index.js b/src/ifctester/webapp/src/lib/components/ui/sonner/index.ts similarity index 100% rename from src/ifctester/webapp/src/lib/components/ui/sonner/index.js rename to src/ifctester/webapp/src/lib/components/ui/sonner/index.ts diff --git a/src/ifctester/webapp/src/lib/components/ui/sonner/sonner.svelte b/src/ifctester/webapp/src/lib/components/ui/sonner/sonner.svelte index 91981de7e6..28a794ca0f 100644 --- a/src/ifctester/webapp/src/lib/components/ui/sonner/sonner.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/sonner/sonner.svelte @@ -1,8 +1,8 @@ - \ No newline at end of file +/> diff --git a/src/ifctester/webapp/src/lib/components/ui/tooltip/index.js b/src/ifctester/webapp/src/lib/components/ui/tooltip/index.ts similarity index 100% rename from src/ifctester/webapp/src/lib/components/ui/tooltip/index.js rename to src/ifctester/webapp/src/lib/components/ui/tooltip/index.ts diff --git a/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-content.svelte b/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-content.svelte index 3c25c65b9a..6db2203857 100644 --- a/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-content.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -1,6 +1,18 @@ - @@ -42,4 +54,4 @@ {/snippet} - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-trigger.svelte index a2885f2cca..b1524c3a3c 100644 --- a/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-trigger.svelte +++ b/src/ifctester/webapp/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -1,7 +1,11 @@ - - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/lib/utils.js b/src/ifctester/webapp/src/lib/utils.js deleted file mode 100644 index f79e2db4c9..0000000000 --- a/src/ifctester/webapp/src/lib/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -import { clsx, } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs) { - return twMerge(clsx(inputs)); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any \ No newline at end of file diff --git a/src/ifctester/webapp/src/lib/utils.ts b/src/ifctester/webapp/src/lib/utils.ts new file mode 100644 index 0000000000..be010d3ced --- /dev/null +++ b/src/ifctester/webapp/src/lib/utils.ts @@ -0,0 +1,7 @@ +import { clsx } from "clsx"; +import type { ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/ifctester/webapp/src/main.js b/src/ifctester/webapp/src/main.js deleted file mode 100644 index 14fb0c3be8..0000000000 --- a/src/ifctester/webapp/src/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import { mount } from 'svelte'; -import './css/app.css'; -import App from './App.svelte'; - -const app = mount(App, { - target: document.getElementById('root'), -}); - -export default app; \ No newline at end of file diff --git a/src/ifctester/webapp/src/main.ts b/src/ifctester/webapp/src/main.ts new file mode 100644 index 0000000000..54ddb22cf9 --- /dev/null +++ b/src/ifctester/webapp/src/main.ts @@ -0,0 +1,14 @@ +import { mount } from 'svelte'; +import './css/app.css'; +import App from './App.svelte'; + +const root = document.getElementById('root'); +if (!root) { + throw new Error('Missing root element'); +} + +const app = mount(App, { + target: root, +}); + +export default app; diff --git a/src/ifctester/webapp/src/modules/api/api.svelte.js b/src/ifctester/webapp/src/modules/api/api.svelte.ts similarity index 72% rename from src/ifctester/webapp/src/modules/api/api.svelte.js rename to src/ifctester/webapp/src/modules/api/api.svelte.ts index ad2a7b8c3f..b4ebe01fdc 100644 --- a/src/ifctester/webapp/src/modules/api/api.svelte.js +++ b/src/ifctester/webapp/src/modules/api/api.svelte.ts @@ -1,8 +1,31 @@ import wasm from "$src/modules/wasm"; -import * as IDS from "$src/modules/api/ids.svelte.js"; +import * as IDS from "$src/modules/api/ids.svelte"; import hyperid from "hyperid"; +import type { AuditReport, AuditReportData } from "$src/types/report"; +import type { IdsDocument } from "$src/types/ids"; -export let Autocompletions = $state({ +type AutocompletionState = { + entityClasses: string[]; + materialCategories: string[]; + classificationSystems: Record; + dataTypes: string[]; + isLoaded: boolean; +}; + +type IfcModel = { + id: string; + fileName: string; + fileSize: number; + loadedAt: Date; +}; + +type IfcModelState = { + models: IfcModel[]; + isLoading: boolean; + audits: AuditReport[]; +}; + +export const Autocompletions: AutocompletionState = $state({ entityClasses: [], materialCategories: [], classificationSystems: {}, @@ -10,13 +33,13 @@ export let Autocompletions = $state({ isLoaded: false }); -export let IFCModels = $state({ +export const IFCModels: IfcModelState = $state({ models: [], isLoading: false, audits: [] }); -const id = hyperid(); +const id: () => string = hyperid(); // Preload autocompletions on initialization wasm.init().then(async () => { @@ -30,26 +53,30 @@ export async function preloadAutocompletions() { // Entity classes const entitySets = await Promise.all( schemas.map(schema => wasm.getAllEntityClasses(schema)) - ); - const allEntities = new Set(); - entitySets.forEach(entities => { - entities.forEach(entity => allEntities.add(entity.toUpperCase())); - }); + ) as string[][]; + const allEntities = new Set(); + for (const entities of entitySets) { + for (const entity of entities) { + allEntities.add(entity.toUpperCase()); + } + } // Data types const dataTypeSets = await Promise.all( schemas.map(schema => wasm.getAllDataTypes(schema)) - ); - const allDataTypes = new Set(); - dataTypeSets.forEach(dataTypes => { - Object.keys(dataTypes).forEach(dataType => allDataTypes.add(dataType)); - }); + ) as Record[]; + const allDataTypes = new Set(); + for (const dataTypes of dataTypeSets) { + for (const dataType of Object.keys(dataTypes as Record)) { + allDataTypes.add(dataType); + } + } // Material categories and Classification systems const [materialCategories, classificationSystems] = await Promise.all([ wasm.getMaterialCategories(), wasm.getStandardClassificationSystems() - ]); + ]) as [string[], AutocompletionState["classificationSystems"]]; // Cache autocompletions Autocompletions.entityClasses = Array.from(allEntities).sort(); @@ -64,15 +91,15 @@ export async function preloadAutocompletions() { } } -export async function getPredefinedTypes(schema, entity) { +export async function getPredefinedTypes(schema: string, entity: string) { return await wasm.getPredefinedTypes(schema, entity); } -export async function getEntityAttributes(schema, entity) { +export async function getEntityAttributes(schema: string, entity: string) { return await wasm.getEntityAttributes(schema, entity); } -export async function getApplicablePsets(schema, entity, predefinedType = '') { +export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') { return await wasm.getApplicablePsets(schema, entity, predefinedType); } @@ -92,7 +119,7 @@ export function getDataTypes() { return Autocompletions.dataTypes; } -export async function loadIfc(file) { +export async function loadIfc(file: File): Promise { try { IFCModels.isLoading = true; @@ -100,10 +127,10 @@ export async function loadIfc(file) { const uint8Array = new Uint8Array(arrayBuffer); // Load IFC model - const ifcId = await wasm.loadIfc(Array.from(uint8Array)); + const ifcId = await wasm.loadIfc(Array.from(uint8Array)) as string; // Add to models list - const model = { + const model: IfcModel = { id: ifcId, fileName: file.name, fileSize: file.size, @@ -121,7 +148,7 @@ export async function loadIfc(file) { } } -export async function unloadIfc(modelId) { +export async function unloadIfc(modelId: string) { try { // Unload model await wasm.unloadIfc(modelId); @@ -136,9 +163,9 @@ export async function unloadIfc(modelId) { } } -export async function auditIfc(modelId, idsData) { +export async function auditIfc(modelId: string, idsData: string | Uint8Array | ArrayBuffer) { try { - let idsBytes; + let idsBytes: Uint8Array; if (typeof idsData === 'string') { idsBytes = new TextEncoder().encode(idsData); } else if (idsData instanceof ArrayBuffer) { @@ -148,7 +175,7 @@ export async function auditIfc(modelId, idsData) { } // Run audit - const auditResult = await wasm.auditIfc(modelId, idsBytes); + const auditResult = await wasm.auditIfc(modelId, idsBytes) as { json: AuditReportData; html: string }; console.log(`Audit completed for model ${modelId}`); return auditResult; @@ -163,13 +190,14 @@ export function getLoadedModels() { } export async function openIfc() { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.ifc'; fileInput.onchange = async (event) => { - const file = event.target.files[0]; + const target = event.target as HTMLInputElement | null; + const file = target?.files?.[0]; if (!file) { reject(new Error('No file selected')); return; @@ -194,15 +222,20 @@ export async function openIfc() { }); } -export function getIfcById(modelId) { +export function getIfcById(modelId: string) { return IFCModels.models.find(model => model.id === modelId); } -export function createAuditReport(modelId, document, auditData, htmlReport = null) { +export function createAuditReport( + modelId: string, + document: string, + auditData: AuditReportData, + htmlReport: string | null = null +): AuditReport | undefined { const model = getIfcById(modelId); if (!model) return; - const auditReport = { + const auditReport: AuditReport = { id: id(), modelId: modelId, modelName: model.fileName, @@ -216,19 +249,19 @@ export function createAuditReport(modelId, document, auditData, htmlReport = nul return auditReport; } -export function getAuditReportsForIfc(modelId) { +export function getAuditReportsForIfc(modelId: string) { return IFCModels.audits.filter(audit => audit.modelId === modelId); } -export function getAuditReportById(auditId) { +export function getAuditReportById(auditId: string) { return IFCModels.audits.find(audit => audit.id === auditId); } -export function clearIdsAuditReports(document) { +export function clearIdsAuditReports(document: string) { IFCModels.audits = IFCModels.audits.filter(audit => audit.document !== document); } -export async function downloadAuditReport(auditId) { +export async function downloadAuditReport(auditId: string) { const audit = getAuditReportById(auditId); if (!audit || !audit.htmlReport) { throw new Error('HTML report not available for this audit'); @@ -237,7 +270,7 @@ export async function downloadAuditReport(auditId) { // Get IDS document title for filename let filename = 'report.html'; if (audit.document && IDS.Module.documents[audit.document]) { - const doc = IDS.Module.documents[audit.document]; + const doc = IDS.Module.documents[audit.document] as IdsDocument; const title = doc.info?.title || 'untitled'; filename = `report_${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.html`; } @@ -270,9 +303,12 @@ export async function runAudit() { // Get the active IDS document XML const idsXml = await IDS.exportActiveDocument(); + if (!idsXml) { + throw new Error('Failed to export IDS document'); + } // Run audit on all loaded models - let firstAuditReport = null; + let firstAuditReport: AuditReport | undefined; for (const model of IFCModels.models) { const result = await auditIfc(model.id, idsXml); @@ -280,7 +316,10 @@ export async function runAudit() { const jsonData = result.json || null; const htmlReport = result.html || null; - const auditReport = createAuditReport(model.id, IDS.Module.activeDocument, jsonData, htmlReport); + if (!jsonData) { + continue; + } + const auditReport = createAuditReport(model.id, IDS.Module.activeDocument as string, jsonData, htmlReport); // Store the first audit report to open in viewer if (!firstAuditReport) { diff --git a/src/ifctester/webapp/src/modules/api/bonsai.svelte.js b/src/ifctester/webapp/src/modules/api/bonsai.svelte.ts similarity index 64% rename from src/ifctester/webapp/src/modules/api/bonsai.svelte.js rename to src/ifctester/webapp/src/modules/api/bonsai.svelte.ts index bcc4b8965d..5ed9b562f4 100644 --- a/src/ifctester/webapp/src/modules/api/bonsai.svelte.js +++ b/src/ifctester/webapp/src/modules/api/bonsai.svelte.ts @@ -1,12 +1,37 @@ import { io } from 'socket.io-client'; -import { IFCModels } from './api.svelte.js'; -import * as IDS from './ids.svelte.js'; -import { error, success } from '../utils/toast.svelte.js'; +import type { Socket } from 'socket.io-client'; +import { IFCModels } from './api.svelte'; +import * as IDS from './ids.svelte'; +import { error, success } from '../utils/toast.svelte'; import hyperid from 'hyperid'; -import { onMount } from 'svelte'; +import type { AuditReport, AuditReportData } from "$src/types/report"; // Bonsai connection state -export let Bonsai = $state({ +type BonsaiState = { + enabled: boolean; + port: string | null; + socket: Socket | null; + connected: boolean; + auditing: boolean; +}; + +type PendingAudit = { + resolve: (value: string | null) => void; + reject: (reason?: unknown) => void; +}; + +type AuditResultPayload = { + id?: string; + json_report?: string; + html_report?: string; +}; + +type AuditErrorPayload = { + id?: string; + error?: string; +}; + +export const Bonsai: BonsaiState = $state({ enabled: false, port: null, socket: null, @@ -14,8 +39,8 @@ export let Bonsai = $state({ auditing: false }); -const id = hyperid(); -const pendingAudits = new Map(); +const id: () => string = hyperid(); +const pendingAudits = new Map(); // Check for Bonsai server port in URL parameters const urlParams = new URLSearchParams(window.location.search); @@ -29,8 +54,11 @@ if (serverPort) { /** * Connect to Bonsai server */ -export const connect = () => new Promise((resolve, reject) => { - if (!Bonsai.port) return; +export const connect = () => new Promise((resolve, reject) => { + if (!Bonsai.port) { + resolve(); + return; + } try { Bonsai.socket = io(`ws://127.0.0.1:${Bonsai.port}/ifctester`, { @@ -49,7 +77,7 @@ export const connect = () => new Promise((resolve, reject) => { Bonsai.connected = false; }); - Bonsai.socket.on('connect_error', (err) => { + Bonsai.socket.on('connect_error', (err: Error) => { Bonsai.connected = false; error(`Failed to connect to Bonsai: ${err.message}`); reject(err); @@ -59,7 +87,8 @@ export const connect = () => new Promise((resolve, reject) => { Bonsai.socket.on('error', handleAuditError); } catch (err) { - error(`Failed to connect to Bonsai: ${err.message}`); + const message = err instanceof Error ? err.message : String(err); + error(`Failed to connect to Bonsai: ${message}`); reject(err); } }); @@ -93,14 +122,21 @@ export const runAudit = async () => { // Convert IDS document to XML string const idsXml = await IDS.exportActiveDocument(); + if (!idsXml) { + throw new Error('Failed to export IDS document'); + } const requestId = id(); + const socket = Bonsai.socket; + if (!socket) { + throw new Error('Bonsai socket not connected'); + } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { // Store request with resolve/reject functions pendingAudits.set(requestId, { resolve, reject }); - Bonsai.socket.emit('audit_ids', { + socket.emit('audit_ids', { id: requestId, ids: idsXml }); @@ -108,7 +144,8 @@ export const runAudit = async () => { } catch (err) { Bonsai.auditing = false; - error(`Failed to run Bonsai audit: ${err.message}`); + const message = err instanceof Error ? err.message : String(err); + error(`Failed to run Bonsai audit: ${message}`); return null; } }; @@ -117,7 +154,7 @@ export const runAudit = async () => { * Handles audit results from Bonsai server * @param {Object} data - Audit result data */ -const handleAuditResult = (data) => { +const handleAuditResult = (data: AuditResultPayload) => { if (!data.id || !data.json_report) return; const pendingAudit = pendingAudits.get(data.id); @@ -130,13 +167,14 @@ const handleAuditResult = (data) => { const { resolve } = pendingAudit; try { - const reportData = JSON.parse(data.json_report); + const reportData = JSON.parse(data.json_report) as AuditReportData; - const auditReport = { + const auditReport: AuditReport = { id: data.id, + modelId: `bonsai:${data.id}`, date: new Date().toISOString(), modelName: 'Bonsai IFC Model', - document: IDS.Module.activeDocument, + document: IDS.Module.activeDocument ?? "", data: reportData, htmlReport: data.html_report }; @@ -152,7 +190,8 @@ const handleAuditResult = (data) => { } catch (err) { Bonsai.auditing = false; - error(`Failed to process audit result: ${err.message}`); + const message = err instanceof Error ? err.message : String(err); + error(`Failed to process audit result: ${message}`); resolve(null); } }; @@ -161,7 +200,7 @@ const handleAuditResult = (data) => { * Handles audit errors from Bonsai server * @param {Object} data - Error data */ -const handleAuditError = (data) => { +const handleAuditError = (data: AuditErrorPayload) => { if (!data.id) return; const pendingAudit = pendingAudits.get(data.id); @@ -174,7 +213,6 @@ const handleAuditError = (data) => { const { resolve } = pendingAudit; Bonsai.auditing = false; - error(`Audit failed (Bonsai): ${data.error}`); + error(`Audit failed (Bonsai): ${data.error ?? "Unknown error"}`); resolve(null); }; - diff --git a/src/ifctester/webapp/src/modules/api/ids.svelte.js b/src/ifctester/webapp/src/modules/api/ids.svelte.ts similarity index 63% rename from src/ifctester/webapp/src/modules/api/ids.svelte.js rename to src/ifctester/webapp/src/modules/api/ids.svelte.ts index 3d30948d3d..3035113654 100644 --- a/src/ifctester/webapp/src/modules/api/ids.svelte.js +++ b/src/ifctester/webapp/src/modules/api/ids.svelte.ts @@ -1,10 +1,18 @@ import wasm from "$src/modules/wasm"; -import { clearIdsAuditReports } from "./api.svelte.js"; +import { clearIdsAuditReports } from "./api.svelte"; import hyperid from "hyperid"; import {tick} from "svelte"; +import type { DocumentState, Facet, FacetValue, IdsDocument, IdsCardinality, Restriction, Specification } from "$src/types/ids"; -export let Module = $state({ - documents: [], +type ModuleState = { + documents: Record; + activeDocument: string | null; + status: "loading" | "ready" | "error"; + states: Record; +}; + +export const Module: ModuleState = $state({ + documents: {}, activeDocument: null, status: "loading", states: {} @@ -17,14 +25,15 @@ wasm.init().then(() => { Module.status = "error"; }); -const id = hyperid() +const id: () => string = hyperid(); -export function setDocumentState(docId, updates) { +export function setDocumentState(docId: string, updates: Partial) { if (!Module.states[docId]) { Module.states[docId] = { activeTab: 'info', viewMode: 'editor', - activeSpecification: null + activeSpecification: null, + auditReport: null }; } Object.assign(Module.states[docId], updates); @@ -32,7 +41,7 @@ export function setDocumentState(docId, updates) { export async function createDocument() { const docId = id(); - const doc = await wasm.createIDS(); + const doc = await wasm.createIDS() as IdsDocument; Module.documents[docId] = doc; @@ -43,14 +52,14 @@ export async function createDocument() { Module.activeDocument = docId; } -export async function deleteDocument(id) { +export async function deleteDocument(id: string) { // Clear any audit reports generated using this IDS document clearIdsAuditReports(id); delete Module.documents[id]; delete Module.states[id]; - if (Module.activeDocument == id) { + if (Module.activeDocument === id) { // If there are other documents, set the first one as active if (Object.keys(Module.documents).length > 0) { Module.activeDocument = Object.keys(Module.documents)[0]; @@ -62,19 +71,19 @@ export async function deleteDocument(id) { // Normalize (remove xs: prefix) from JSON dict returned from Python // We need this because the backend exports with xs: prefix, yet expects a dict without prefixes. -function normalizeIdsDict(obj) { +function normalizeIdsDict(obj: unknown): unknown { if (typeof obj !== 'object' || obj === null) return obj; if (Array.isArray(obj)) { return obj.map(normalizeIdsDict); } - const result = {}; + const result: Record = {}; for (const [key, value] of Object.entries(obj)) { if (key === 'xs:restriction' && Array.isArray(value) && value.length > 0) { // Convert xs:restriction array to restriction object - const restriction = value[0]; - const newRestriction = {}; + const restriction = value[0] as Record; + const newRestriction: Record = {}; for (const [restrictionKey, restrictionValue] of Object.entries(restriction)) { if (restrictionKey.startsWith('xs:')) { @@ -86,7 +95,7 @@ function normalizeIdsDict(obj) { } } - result['restriction'] = newRestriction; + result.restriction = newRestriction; } else { result[key] = normalizeIdsDict(value); } @@ -96,13 +105,16 @@ function normalizeIdsDict(obj) { } export async function openDocument() { - return new Promise((resolve, reject) => { - const fileInput = document.createElement('input'); + return new Promise((resolve, reject) => { + const fileInput = document.createElement('input') as HTMLInputElement & { + oncancel?: ((this: HTMLInputElement, ev: Event) => void) | null; + }; fileInput.type = 'file'; fileInput.accept = '.ids,.xml'; fileInput.onchange = async (event) => { - const file = event.target.files[0]; + const target = event.target as HTMLInputElement | null; + const file = target?.files?.[0]; if (!file) { reject(new Error('No file selected')); return; @@ -112,8 +124,8 @@ export async function openDocument() { const reader = new FileReader(); reader.onload = async (e) => { try { - const fileContent = e.target.result; - const doc = normalizeIdsDict(await wasm.openIDS(fileContent, false)); + const fileContent = (e.target as FileReader).result; + const doc = normalizeIdsDict(await wasm.openIDS(String(fileContent), false)) as IdsDocument; const docId = id(); // Add document to list and set as active @@ -145,16 +157,16 @@ export async function openDocument() { }); } -export async function exportActiveDocument() { +export async function exportActiveDocument(): Promise { if (!Module.activeDocument) return null; const doc = $state.snapshot(Module.documents[Module.activeDocument]); - const xmlString = await wasm.exportIDS(doc); + const xmlString = await wasm.exportIDS(doc as Record) as string; return xmlString; } -export async function exportDocument(docId) { +export async function exportDocument(docId: string) { const doc = $state.snapshot(Module.documents[docId]); // Validate @@ -162,37 +174,38 @@ export async function exportDocument(docId) { throw new Error("Please create at least one specification before exporting the document."); } - const xmlString = await wasm.exportIDS(doc); + const xmlString = await wasm.exportIDS(doc as Record) as string; // Create and download file const blob = new Blob([xmlString], { type: 'application/xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = `${Module.documents[docId].info.title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`; + const title = Module.documents[docId].info.title || "untitled"; + a.download = `${title.replace(/[^a-zA-Z0-9]/g, '_')}.ids`; a.click(); URL.revokeObjectURL(url); } -export async function createSpecification(docId) { - const spec = await wasm.createSpecification(); +export async function createSpecification(docId: string) { + const spec = await wasm.createSpecification() as Specification; // Add specification to document Module.documents[docId].specifications.specification.push(spec); // Set as active specification - if (Module.activeDocument == docId) { + if (Module.activeDocument === docId) { const state = Module.states[docId]; state.activeSpecification = Module.documents[docId].specifications.specification.length - 1; } } -export async function deleteSpecification(docId, specId) { +export async function deleteSpecification(docId: string, specId: number) { Module.documents[docId].specifications.specification.splice(specId, 1); - if (Module.activeDocument == docId) { + if (Module.activeDocument === docId) { const state = Module.states[docId]; - if (state.activeSpecification == specId) { + if (state.activeSpecification === specId) { // We need to wait for the next tick here because of Svelte's internal shenanigans await tick(); setDocumentState(docId, { activeSpecification: null }); @@ -209,124 +222,148 @@ export async function deleteSpecification(docId, specId) { * clause: "applicability", "requirements" * facet: "entity", "attribute", "classification", "partOf", "property", "material" */ -export async function createFacet(docId, specId, clause, facet) { - let facetObj; - if (facet == "entity") { - facetObj = await wasm.createEntityFacet(clause, {}); - } else if (facet == "attribute") { - facetObj = await wasm.createAttributeFacet(clause, {}); - } else if (facet == "classification") { - facetObj = await wasm.createClassificationFacet(clause, {}); - } else if (facet == "partOf") { - facetObj = await wasm.createPartOfFacet(clause, {}); - } else if (facet == "property") { - facetObj = await wasm.createPropertyFacet(clause, {}); - } else if (facet == "material") { - facetObj = await wasm.createMaterialFacet(clause, {}); +export async function createFacet( + docId: string, + specId: number, + clause: "applicability" | "requirements", + facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material" +) { + let facetObj: Facet | undefined; + if (facet === "entity") { + facetObj = await wasm.createEntityFacet(clause, {}) as Facet; + } else if (facet === "attribute") { + facetObj = await wasm.createAttributeFacet(clause, {}) as Facet; + } else if (facet === "classification") { + facetObj = await wasm.createClassificationFacet(clause, {}) as Facet; + } else if (facet === "partOf") { + facetObj = await wasm.createPartOfFacet(clause, {}) as Facet; + } else if (facet === "property") { + facetObj = await wasm.createPropertyFacet(clause, {}) as Facet; + } else if (facet === "material") { + facetObj = await wasm.createMaterialFacet(clause, {}) as Facet; } - if (!(facet in Module.documents[docId].specifications.specification[specId][clause])) { - Module.documents[docId].specifications.specification[specId][clause][facet] = []; + if (!facetObj) return; + + const spec = Module.documents[docId].specifications.specification[specId]; + const clauseKey = clause as "applicability" | "requirements"; + if (!spec[clauseKey]) spec[clauseKey] = {}; + if (!(facet in (spec[clauseKey] as Record))) { + (spec[clauseKey] as Record)[facet] = []; } - Module.documents[docId].specifications.specification[specId][clause][facet].push(facetObj); + ((spec[clauseKey] as Record)[facet] as Facet[]).push(facetObj); } -export async function deleteFacet(docId, specId, clause, facet, facetId) { - delete Module.documents[docId].specifications.specification[specId][clause][facet][facetId]; +export async function deleteFacet( + docId: string, + specId: number, + clause: "applicability" | "requirements", + facet: "entity" | "attribute" | "classification" | "partOf" | "property" | "material", + facetId: number +) { + const spec = Module.documents[docId].specifications.specification[specId]; + const list = (spec[clause] as Record | undefined)?.[facet] as Facet[] | undefined; + if (!list) return; + list.splice(facetId, 1); } -export function getSpecUsage(spec) { +export function getSpecUsage(spec?: Specification | null): IdsCardinality { if (!spec?.applicability) return 'required'; - const minOccurs = spec.applicability["@minOccurs"]; - const maxOccurs = spec.applicability["@maxOccurs"]; - - if (minOccurs === 1 && maxOccurs === "unbounded") return 'required'; - if (minOccurs === 0 && maxOccurs === "unbounded") return 'optional'; - if (minOccurs === 0 && maxOccurs === 0) return 'prohibited'; + const minOccurs = spec.applicability["@minOccurs"] as number | undefined; + const maxOccurs = spec.applicability["@maxOccurs"] as number | "unbounded" | undefined; + + if (minOccurs !== 0) return 'required'; + if (minOccurs === 0 && maxOccurs !== 0) return 'optional'; + if (maxOccurs === 0) return 'prohibited'; return 'required'; }; // Converts facet to human-readable description -export function stringifyFacet(clauseType, facet, facetType, spec) { +export function stringifyFacet( + clauseType: "applicability" | "requirements", + facet: Facet, + facetType: string, + spec?: Specification | null +) { if (!facet) return ""; const usage = getSpecUsage(spec); - const descriptions = []; + const descriptions: string[] = []; // Entity facet if (facetType === "entity") { if (clauseType === "applicability") { - descriptions.push(`All data where IFC class ${stringifyValue(facet.name)}`); + descriptions.push(`All data where IFC class ${stringifyValue(facet.name as FacetValue)}`); } else { - descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name)}`); + descriptions.push(`Shall be data where IFC class ${stringifyValue(facet.name as FacetValue)}`); } if (facet.predefinedType) { - descriptions.push(`and type ${stringifyValue(facet.predefinedType)}`); + descriptions.push(`and type ${stringifyValue(facet.predefinedType as FacetValue)}`); } } // Attribute facet else if (facetType === "attribute") { if (clauseType === "applicability") { - descriptions.push(`All data where attribute ${stringifyValue(facet.name)}`); + descriptions.push(`All data where attribute ${stringifyValue(facet.name as FacetValue)}`); } else { - descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name)}`); + descriptions.push(`Shall be data where attribute ${stringifyValue(facet.name as FacetValue)}`); } - descriptions.push(`and value ${stringifyValue(facet.value)}`); + descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`); } // Property facet else if (facetType === "property") { if (clauseType === "applicability") { - descriptions.push(`Elements where property ${stringifyValue(facet.baseName)}`); + descriptions.push(`Elements where property ${stringifyValue(facet.baseName as FacetValue)}`); } else { - descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName)}`); + descriptions.push(`Shall be elements where property ${stringifyValue(facet.baseName as FacetValue)}`); } if (facet.value) { - descriptions.push(`and value ${stringifyValue(facet.value)}`); + descriptions.push(`and value ${stringifyValue(facet.value as FacetValue)}`); } - descriptions.push(`and dataset ${stringifyValue(facet.propertySet)}`); + descriptions.push(`and dataset ${stringifyValue(facet.propertySet as FacetValue)}`); } // Classification facet else if (facetType === "classification") { if (clauseType === "applicability") { - descriptions.push(`All data where classification system ${stringifyValue(facet.system)}`); + descriptions.push(`All data where classification system ${stringifyValue(facet.system as FacetValue)}`); } else { - descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system)}`); + descriptions.push(`Shall be data where classification system ${stringifyValue(facet.system as FacetValue)}`); } if (facet.value) { - descriptions.push(`and classification ${stringifyValue(facet.value)}`); + descriptions.push(`and classification ${stringifyValue(facet.value as FacetValue)}`); } } // Material facet else if (facetType === "material") { if (clauseType === "applicability") { - descriptions.push(`All data where material ${stringifyValue(facet.value)}`); + descriptions.push(`All data where material ${stringifyValue(facet.value as FacetValue)}`); } else { - descriptions.push(`Shall be data where material ${stringifyValue(facet.value)}`); + descriptions.push(`Shall be data where material ${stringifyValue(facet.value as FacetValue)}`); } } // PartOf facet else if (facetType === "partOf") { if (clauseType === "applicability") { - descriptions.push(`An element with an **${facet['@relation']}** relationship`); + descriptions.push(`An element with an **${String(facet['@relation'] ?? "")}** relationship`); if (facet.name) { - descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`); + descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`); } } else { - descriptions.push(`An element shall have an **${facet['@relation']}** relationship`); + descriptions.push(`An element shall have an **${String(facet['@relation'] ?? "")}** relationship`); if (facet.name) { - descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name)}`); + descriptions.push(`with an entity where IFC class ${stringifyValue(facet.name as FacetValue)}`); } if (facet.predefinedType) { - descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType)}`); + descriptions.push(`and predefined type ${stringifyValue(facet.predefinedType as FacetValue)}`); } } } @@ -336,20 +373,22 @@ export function stringifyFacet(clauseType, facet, facetType, spec) { // Post-process for prohibited and optional requirements let isProhibited = false; - if (usage == "prohibited") isProhibited = !isProhibited; - if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "prohibited") isProhibited = !isProhibited; + if (usage === "prohibited") isProhibited = !isProhibited; + if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "prohibited") { + isProhibited = !isProhibited; + } if (isProhibited) combined = combined.replace("Shall", "Shall not").replace("shall", "shall not"); - if (clauseType == "requirements" && "@cardinality" in facet && facet["@cardinality"] == "optional") + if (clauseType === "requirements" && "@cardinality" in facet && facet["@cardinality"] === "optional") combined = combined.replace("Shall", "May").replace("shall", "may"); return renderFacetString(combined); } // Converts value objects to human-readable strings -function stringifyValue(value) { +function stringifyValue(value?: FacetValue) { if (!value) return "is provided"; if (value.simpleValue) return `is **${value.simpleValue}**`; if (value.restriction) return stringifyRestriction(value.restriction); @@ -357,7 +396,7 @@ function stringifyValue(value) { } // Converts restriction objects to human-readable strings -function stringifyRestriction(restriction) { +function stringifyRestriction(restriction: Restriction) { if (!restriction) return ""; // Handle enumeration @@ -394,7 +433,7 @@ function stringifyRestriction(restriction) { if (restriction.maxExclusive && restriction.maxExclusive.length > 0) { parts.push(`**< ${restriction.maxExclusive[0]['@value'] || ''}**`); } - return parts.length > 0 ? "is in range " + parts.join(", ") : "has range restriction"; + return parts.length > 0 ? `is in range ${parts.join(", ")}` : "has range restriction"; } // Handle length range restrictions @@ -406,18 +445,18 @@ function stringifyRestriction(restriction) { if (restriction.maxLength && restriction.maxLength.length > 0) { parts.push(`**max length ${restriction.maxLength[0]['@value'] || ''}**`); } - return parts.length > 0 ? "has " + parts.join(", ") : "has length range restriction"; + return parts.length > 0 ? `has ${parts.join(", ")}` : "has length range restriction"; } return "has complex restriction"; } -function renderFacetString(text) { +function renderFacetString(text: string): string { // Convert **text** to text - text = text.replace(/\*\*([^*]+)\*\*/g, '$1'); + const withStrong = text.replace(/\*\*([^*]+)\*\*/g, '$1'); // Convert `text` to text - text = text.replace(/`([^`]+)`/g, '$1'); + const withCode = withStrong.replace(/`([^`]+)`/g, '$1'); - return text; -} \ No newline at end of file + return withCode; +} diff --git a/src/ifctester/webapp/src/modules/utils/toast.svelte.js b/src/ifctester/webapp/src/modules/utils/toast.svelte.ts similarity index 73% rename from src/ifctester/webapp/src/modules/utils/toast.svelte.js rename to src/ifctester/webapp/src/modules/utils/toast.svelte.ts index eaf9b373f0..335be48645 100644 --- a/src/ifctester/webapp/src/modules/utils/toast.svelte.js +++ b/src/ifctester/webapp/src/modules/utils/toast.svelte.ts @@ -4,7 +4,7 @@ import { toast } from "svelte-sonner"; * Show an error toast notification * @param {string} message - The error message to display */ -export function error(message) { +export function error(message: string): void { toast.error(message); } @@ -12,7 +12,7 @@ export function error(message) { * Show a success toast notification * @param {string} message - The success message to display */ -export function success(message) { +export function success(message: string): void { toast.success(message); } @@ -20,7 +20,7 @@ export function success(message) { * Show an info toast notification * @param {string} message - The info message to display */ -export function info(message) { +export function info(message: string): void { toast.info(message); } @@ -28,7 +28,7 @@ export function info(message) { * Show a warning toast notification * @param {string} message - The warning message to display */ -export function warning(message) { +export function warning(message: string): void { toast.warning(message); } @@ -37,7 +37,7 @@ export function warning(message) { * @param {string} message - The loading message to display * @returns {string} - Toast ID for dismissing later */ -export function loading(message) { +export function loading(message: string): string | number { return toast.loading(message); } @@ -45,7 +45,7 @@ export function loading(message) { * Dismiss a specific toast * @param {string} toastId - The toast ID to dismiss */ -export function dismiss(toastId) { +export function dismiss(toastId: string | number): void { toast.dismiss(toastId); } @@ -57,10 +57,16 @@ export function dismiss(toastId) { * @param {string} messages.success - Success message * @param {string} messages.error - Error message */ -export function promise(promiseToTrack, messages) { +type PromiseToastMessages = { + loading: string; + success: string; + error: string; +}; + +export function promise(promiseToTrack: Promise, messages: PromiseToastMessages) { return toast.promise(promiseToTrack, { loading: messages.loading, success: messages.success, error: messages.error, }); -} \ No newline at end of file +} diff --git a/src/ifctester/webapp/src/modules/wasm/index.js b/src/ifctester/webapp/src/modules/wasm/index.ts similarity index 69% rename from src/ifctester/webapp/src/modules/wasm/index.js rename to src/ifctester/webapp/src/modules/wasm/index.ts index cecb655f73..d7c4f1e40a 100644 --- a/src/ifctester/webapp/src/modules/wasm/index.js +++ b/src/ifctester/webapp/src/modules/wasm/index.ts @@ -5,6 +5,7 @@ import hyperid from "hyperid"; import EventEmitter from "eventemitter3"; +import type { WorkerResponse } from "$src/types/wasm"; // Message types export const MessageType = { @@ -25,52 +26,57 @@ export const MessageType = { // WASM module disposed DISPOSED: 'disposed' +} as const; + +type PendingMessage = { + resolve: (value?: unknown) => void; + reject: (reason?: unknown) => void; }; +type WasmReadyState = boolean | Promise; + class WASMModule extends EventEmitter { id = hyperid(); - ready = false; - worker = null; - pendingMessages = new Map(); + ready: WasmReadyState = false; + worker: Worker | null = null; + pendingMessages = new Map(); async init() { if (this.ready === true) return; - else if (this.ready instanceof Promise) return this.ready; + if (this.ready instanceof Promise) return this.ready; - this.worker = new Worker(new URL('./worker/worker.js', import.meta.url), {type: 'module'}); + this.worker = new Worker(new URL('./worker/worker.ts', import.meta.url), {type: 'module'}); - this.worker.onmessage = (event) => { + this.worker.onmessage = (event: MessageEvent) => { this._handleWorkerMessage(event.data); }; - this.worker.onerror = (error) => { + this.worker.onerror = (error: ErrorEvent) => { console.error('[WASM] Web worker error:', error); this._rejectPendingMessages(error); }; - this.ready = new Promise(async (resolve, reject) => { - try { - await this._sendMessage(MessageType.INIT); - resolve(true); - } catch (error) { + this.ready = this._sendMessage(MessageType.INIT) + .then(() => true) + .catch((error) => { console.error('[WASM] Failed to initialize:', error); this.ready = false; - reject(error); - } - }); + throw error; + }); return this.ready; } - async _sendMessage(type, payload = {}) { - if (!this.worker) throw new Error('Worker not initialized'); + async _sendMessage(type: string, payload: Record = {}): Promise { + const worker = this.worker; + if (!worker) throw new Error('Worker not initialized'); const id = this.id(); return new Promise((resolve, reject) => { this.pendingMessages.set(id, { resolve, reject }); - this.worker.postMessage({ + worker.postMessage({ type, payload, id @@ -78,7 +84,7 @@ class WASMModule extends EventEmitter { }); } - _handleWorkerMessage({ type, payload, id }) { + _handleWorkerMessage({ type, payload, id }: WorkerResponse) { const pendingMessage = this.pendingMessages.get(id); if (!pendingMessage) { @@ -97,23 +103,28 @@ class WASMModule extends EventEmitter { case MessageType.API_RESPONSE: resolve(payload); break; - case MessageType.ERROR: - reject(new Error(payload.message)); + case MessageType.ERROR: { + const message = + payload && typeof payload === "object" && "message" in payload + ? String(payload.message) + : "Unknown worker error"; + reject(new Error(message)); break; + } default: console.warn('[WASM] Unknown message type:', type); reject(new Error(`Unknown message type: ${type}`)); } } - _rejectPendingMessages(error) { + _rejectPendingMessages(error: unknown) { for (const { reject } of this.pendingMessages.values()) { reject(error); } this.pendingMessages.clear(); } - async _apiCall(method, ...args) { + async _apiCall(method: string, ...args: unknown[]) { if (!this.ready) await this.init(); const result = await this._sendMessage(MessageType.API_CALL, { method, args }); @@ -123,35 +134,35 @@ class WASMModule extends EventEmitter { /** * Get all entity classes in a given IFC schema */ - async getAllEntityClasses(schema) { + async getAllEntityClasses(schema: string) { return this._apiCall('getAllEntityClasses', schema); } /** * Get all data types in a given IFC schema */ - async getAllDataTypes(schema) { + async getAllDataTypes(schema: string) { return this._apiCall('getAllDataTypes', schema); } /** * Get predefined types for a given IFC entity */ - async getPredefinedTypes(schema, entity) { + async getPredefinedTypes(schema: string, entity: string) { return this._apiCall('getPredefinedTypes', schema, entity); } /** * Get all attributes for a given IFC entity */ - async getEntityAttributes(schema, entity) { + async getEntityAttributes(schema: string, entity: string) { return this._apiCall('getEntityAttributes', schema, entity); } /** * Get applicable property sets for a given IFC entity */ - async getApplicablePsets(schema, entity, predefinedType = '') { + async getApplicablePsets(schema: string, entity: string, predefinedType = '') { return this._apiCall('getApplicablePsets', schema, entity, predefinedType); } @@ -172,21 +183,21 @@ class WASMModule extends EventEmitter { /** * Load an IFC file. Returns a unique ID for the loaded file. */ - async loadIfc(ifcData) { + async loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) { return this._apiCall('loadIfc', ifcData); } /** * Unload an IFC file */ - async unloadIfc(ifcId) { + async unloadIfc(ifcId: string) { return this._apiCall('unloadIfc', ifcId); } /** * Audit a loaded IFC file against IDS specifications */ - async auditIfc(ifcId, idsData) { + async auditIfc(ifcId: string, idsData: ArrayBuffer | Uint8Array | number[]) { const idsBytes = idsData instanceof ArrayBuffer ? new Uint8Array(idsData) : idsData; return this._apiCall('auditIfc', ifcId, Array.from(idsBytes)); @@ -204,70 +215,70 @@ class WASMModule extends EventEmitter { /** * Open an existing IDS from XML string */ - async openIDS(idsXml, validate = false) { + async openIDS(idsXml: string, validate = false) { return this._apiCall('openIDS', idsXml, validate); } /** * Create a specification */ - async createSpecification(options = {}) { + async createSpecification(options: Record = {}) { return this._apiCall('createSpecification', options); } /** * Create an entity facet */ - async createEntityFacet(clause, options = {}) { + async createEntityFacet(clause: string, options: Record = {}) { return this._apiCall('createEntityFacet', clause, options); } /** * Create an attribute facet */ - async createAttributeFacet(clause, options = {}) { + async createAttributeFacet(clause: string, options: Record = {}) { return this._apiCall('createAttributeFacet', clause, options); } /** * Create a property facet */ - async createPropertyFacet(clause, options = {}) { + async createPropertyFacet(clause: string, options: Record = {}) { return this._apiCall('createPropertyFacet', clause, options); } /** * Create a material facet */ - async createMaterialFacet(clause, options = {}) { + async createMaterialFacet(clause: string, options: Record = {}) { return this._apiCall('createMaterialFacet', clause, options); } /** * Create a classification facet */ - async createClassificationFacet(clause, options = {}) { + async createClassificationFacet(clause: string, options: Record = {}) { return this._apiCall('createClassificationFacet', clause, options); } /** * Create a part-of facet */ - async createPartOfFacet(clause, options = {}) { + async createPartOfFacet(clause: string, options: Record = {}) { return this._apiCall('createPartOfFacet', clause, options); } /** * Validate an IDS object */ - async validateIDS(idsObj) { + async validateIDS(idsObj: Record) { return await this._apiCall('validateIDS', idsObj); } /** * Export IDS instance to XML string */ - async exportIDS(idsObj) { + async exportIDS(idsObj: Record) { return this._apiCall('exportIDS', idsObj); } @@ -319,4 +330,4 @@ export const { dispose } = wasm; -export default wasm; \ No newline at end of file +export default wasm; diff --git a/src/ifctester/webapp/src/modules/wasm/worker/api.js b/src/ifctester/webapp/src/modules/wasm/worker/api.ts similarity index 80% rename from src/ifctester/webapp/src/modules/wasm/worker/api.js rename to src/ifctester/webapp/src/modules/wasm/worker/api.ts index d08c17efe2..283971bc27 100644 --- a/src/ifctester/webapp/src/modules/wasm/worker/api.js +++ b/src/ifctester/webapp/src/modules/wasm/worker/api.ts @@ -1,12 +1,13 @@ -import config from '../../../config.json'; import hyperid from 'hyperid'; +import config from '../../../config.json'; +import type { AuditReportData } from "$src/types/report"; -let pyodide = null; -let id = hyperid(); +let pyodide: any = null; +const id = hyperid(); -let LoadedIFC = new Map(); +const LoadedIFC = new Map(); -export async function init(pdide) { +export async function init(pdide: any) { pyodide = pdide; // Load Python API bindings @@ -18,7 +19,7 @@ export async function init(pdide) { `); } -export async function getPredefinedTypes(schema, entity) { +export async function getPredefinedTypes(schema: string, entity: string) { const result = await pyodide.runPythonAsync(` from api import get_predefined_types_for_entity predef_types = get_predefined_types_for_entity("${schema}", "${entity}") @@ -27,7 +28,7 @@ export async function getPredefinedTypes(schema, entity) { return result.toJs({ dict_converter: Object.fromEntries }); } -export async function getAllEntityClasses(schema) { +export async function getAllEntityClasses(schema: string) { const result = await pyodide.runPythonAsync(` from api import get_all_entity_classes entities = get_all_entity_classes("${schema}") @@ -36,7 +37,7 @@ export async function getAllEntityClasses(schema) { return result.toJs({ dict_converter: Object.fromEntries }); } -export async function getAllDataTypes(schema) { +export async function getAllDataTypes(schema: string) { const result = await pyodide.runPythonAsync(` from api import get_all_data_types data_types = get_all_data_types("${schema}") @@ -45,7 +46,7 @@ export async function getAllDataTypes(schema) { return result.toJs({ dict_converter: Object.fromEntries }); } -export async function getEntityAttributes(schema, entity) { +export async function getEntityAttributes(schema: string, entity: string) { const result = await pyodide.runPythonAsync(` from api import get_entity_attributes attrs = get_entity_attributes("${schema}", "${entity}") @@ -54,7 +55,7 @@ export async function getEntityAttributes(schema, entity) { return result.toJs({ dict_converter: Object.fromEntries }); } -export async function getApplicablePsets(schema, entity, predefinedType = '') { +export async function getApplicablePsets(schema: string, entity: string, predefinedType = '') { const result = await pyodide.runPythonAsync(` from api import get_applicable_psets psets = get_applicable_psets("${schema}", "${entity}", "${predefinedType}") @@ -81,7 +82,7 @@ export async function getStandardClassificationSystems() { return result.toJs({ dict_converter: Object.fromEntries }); } -export async function loadIfc(ifcData) { +export async function loadIfc(ifcData: number[] | Uint8Array | ArrayBuffer) { const ifc_id = id(); const path = `/tmp/${encodeURIComponent(ifc_id)}.ifc`; @@ -97,14 +98,14 @@ export async function loadIfc(ifcData) { return ifc_id; } -export async function unloadIfc(ifcId) { +export async function unloadIfc(ifcId: string) { const path = `/tmp/${encodeURIComponent(ifcId)}.ifc`; pyodide.FS.unlink(path); LoadedIFC.delete(ifcId); } -export async function auditIfc(ifcId, idsData) { +export async function auditIfc(ifcId: string, idsData: number[] | Uint8Array | ArrayBuffer) { const reporter = pyodide.pyimport("ifctester.reporter"); const api = pyodide.pyimport("api"); @@ -116,16 +117,16 @@ export async function auditIfc(ifcId, idsData) { specs.validate(ifc); // Create report in both HTML and JSON formats - let jsonReporter = reporter.Json(specs); + const jsonReporter = reporter.Json(specs); jsonReporter.report(); const jsonReport = jsonReporter.to_string(); - let htmlReporter = reporter.Html(specs); + const htmlReporter = reporter.Html(specs); htmlReporter.report(); const htmlReport = htmlReporter.to_string(); return { - json: JSON.parse(jsonReport), + json: JSON.parse(jsonReport) as AuditReportData, html: htmlReport }; } @@ -142,4 +143,4 @@ export const API = { "loadIfc": loadIfc, "unloadIfc": unloadIfc, "auditIfc": auditIfc -}; \ No newline at end of file +}; diff --git a/src/ifctester/webapp/src/modules/wasm/worker/ids.js b/src/ifctester/webapp/src/modules/wasm/worker/ids.ts similarity index 57% rename from src/ifctester/webapp/src/modules/wasm/worker/ids.js rename to src/ifctester/webapp/src/modules/wasm/worker/ids.ts index 8afc023cec..ca34b21d75 100644 --- a/src/ifctester/webapp/src/modules/wasm/worker/ids.js +++ b/src/ifctester/webapp/src/modules/wasm/worker/ids.ts @@ -2,13 +2,19 @@ * IDS module */ -let pyodide = null; +let pyodide: any = null; // IDS Python classes -let Ids, Specification; -let Entity, Attribute, Property, Material, Classification, PartOf; +let Ids: any; +let Specification: any; +let Entity: any; +let Attribute: any; +let Property: any; +let Material: any; +let Classification: any; +let PartOf: any; -export async function init(pdide) { +export async function init(pdide: any) { pyodide = pdide; await pyodide.loadPackagesFromImports(` @@ -29,24 +35,24 @@ export async function init(pdide) { PartOf = pyodide.pyimport("ifctester.facet").PartOf; } -function _idsToInstance(idsObj) { +function _idsToInstance(idsObj: Record) { const ids_raw = Ids(); return ids_raw.parse(pyodide.toPy(idsObj)) } -export function createIDS() { +export function createIDS(): Record { const ids_raw = Ids() return ids_raw.asdict().toJs({dict_converter: Object.fromEntries}); } -export function openIDS(ids_xml, validate = false) { +export function openIDS(ids_xml: string, validate = false): Record { const ids_from_xml_string = pyodide.pyimport("api").ids_from_xml_string; const ids_raw = ids_from_xml_string(ids_xml, validate); return ids_raw.asdict().toJs({dict_converter: Object.fromEntries}); } -export function validateIDS(idsObj) { +export function validateIDS(idsObj: Record): boolean { const ids_raw = _idsToInstance(idsObj) const tempFilename = `temp_${Date.now()}.xml`; const isValid = ids_raw.to_xml(tempFilename); // to_xml validates the XML as well, as far as I understand @@ -60,12 +66,26 @@ export function validateIDS(idsObj) { return isValid; } -export function exportIDS(idsObj) { +export function exportIDS(idsObj: Record): string { const ids_raw = _idsToInstance(idsObj) return ids_raw.to_string(); } -export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", "IFC4"], identifier = null, description = null, instructions = null, usage = "required"}) { +export function createSpecification({ + name = "Unnamed", + ifcVersion = ["IFC2X3", "IFC4"], + identifier = null, + description = null, + instructions = null, + usage = "required" +}: { + name?: string; + ifcVersion?: string[]; + identifier?: string | null; + description?: string | null; + instructions?: string | null; + usage?: string; +} = {}): Record { const spec = Specification.callKwargs({ name: name, ifcVersion: ifcVersion, @@ -79,7 +99,14 @@ export function createSpecification({name = "Unnamed", ifcVersion = ["IFC2X3", " } // @instructions -export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = null, instructions = null}) { +export function createEntityFacet( + clause: string, + {name = "IFCWALL", predefinedType = null, instructions = null}: { + name?: string; + predefinedType?: string | null; + instructions?: string | null; + } = {} +): Record { const entity = Entity.callKwargs({ name: name, predefinedType: predefinedType, @@ -89,7 +116,15 @@ export function createEntityFacet(clause, {name = "IFCWALL", predefinedType = nu } // @cardinality, @instructions -export function createAttributeFacet(clause, {name = "Name", value = null, cardinality = "required", instructions = null}) { +export function createAttributeFacet( + clause: string, + {name = "Name", value = null, cardinality = "required", instructions = null}: { + name?: string; + value?: string | null; + cardinality?: string; + instructions?: string | null; + } = {} +): Record { const attribute = Attribute.callKwargs({ name: name, value: value, @@ -100,7 +135,16 @@ export function createAttributeFacet(clause, {name = "Name", value = null, cardi } // @uri, @cardinality, @instructions -export function createClassificationFacet(clause, {value = null, system = null, uri = null, cardinality = "required", instructions = null}) { +export function createClassificationFacet( + clause: string, + {value = null, system = null, uri = null, cardinality = "required", instructions = null}: { + value?: string | null; + system?: string | null; + uri?: string | null; + cardinality?: string; + instructions?: string | null; + } = {} +): Record { const classification = Classification.callKwargs({ value: value, system: system, @@ -112,7 +156,16 @@ export function createClassificationFacet(clause, {value = null, system = null, } // @relation, @cardinality, @instructions -export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}) { +export function createPartOfFacet( + clause: string, + {name = "IFCWALL", predefinedType = null, relation = null, cardinality = "required", instructions = null}: { + name?: string; + predefinedType?: string | null; + relation?: string | null; + cardinality?: string; + instructions?: string | null; + } = {} +): Record { const part_of = PartOf.callKwargs({ name: name, predefinedType: predefinedType, @@ -124,7 +177,26 @@ export function createPartOfFacet(clause, {name = "IFCWALL", predefinedType = nu } // @dataType, @uri, @cardinality, @instructions -export function createPropertyFacet(clause, {propertySet = "Property_Set", baseName = "propertyName", value = null, dataType = null, uri = null, cardinality = "required", instructions = null}) { +export function createPropertyFacet( + clause: string, + { + propertySet = "Property_Set", + baseName = "propertyName", + value = null, + dataType = null, + uri = null, + cardinality = "required", + instructions = null + }: { + propertySet?: string; + baseName?: string; + value?: string | null; + dataType?: string | null; + uri?: string | null; + cardinality?: string; + instructions?: string | null; + } = {} +): Record { const property = Property.callKwargs({ propertySet: propertySet, baseName: baseName, @@ -138,7 +210,15 @@ export function createPropertyFacet(clause, {propertySet = "Property_Set", baseN } // @uri, @cardinality, @instructions -export function createMaterialFacet(clause, {value = null, uri = null, cardinality = "required", instructions = null}) { +export function createMaterialFacet( + clause: string, + {value = null, uri = null, cardinality = "required", instructions = null}: { + value?: string | null; + uri?: string | null; + cardinality?: string; + instructions?: string | null; + } = {} +): Record { const material = Material.callKwargs({ value: value, uri: uri, @@ -149,7 +229,7 @@ export function createMaterialFacet(clause, {value = null, uri = null, cardinali } // Helper function to convert date to ISO format string -export function formatDate(date) { +export function formatDate(date?: string | number | Date | null): string | null { if (!date) return null; const d = new Date(date); return d.toISOString().split('T')[0]; @@ -168,4 +248,4 @@ export const API = { "createPartOfFacet": createPartOfFacet, "createPropertyFacet": createPropertyFacet, "createMaterialFacet": createMaterialFacet, -}; \ No newline at end of file +}; diff --git a/src/ifctester/webapp/src/modules/wasm/worker/worker.js b/src/ifctester/webapp/src/modules/wasm/worker/worker.ts similarity index 61% rename from src/ifctester/webapp/src/modules/wasm/worker/worker.js rename to src/ifctester/webapp/src/modules/wasm/worker/worker.ts index 6bf47e46fe..fe4b188644 100644 --- a/src/ifctester/webapp/src/modules/wasm/worker/worker.js +++ b/src/ifctester/webapp/src/modules/wasm/worker/worker.ts @@ -4,13 +4,14 @@ import { MessageType } from '../index'; import config from '../../../config.json'; -import * as IDS from './ids.js'; +import * as IDS from './ids'; import * as API from './api'; +import type { ApiCallPayload, WorkerRequest } from "$src/types/wasm"; -let pyodide = null; +let pyodide: any = null; let ready = false; -self.addEventListener('message', async (event) => { +self.addEventListener('message', async (event: MessageEvent) => { console.log("[worker] Received message:", event.data); const { type, payload, id } = event.data; @@ -25,27 +26,33 @@ self.addEventListener('message', async (event) => { }); break; - case MessageType.API_CALL: + case MessageType.API_CALL: { if (!ready) { throw new Error('[worker] Pyodide not initialized'); } - const result = await handleApiCall(payload); + if (!payload) { + throw new Error('[worker] Missing payload for API call'); + } + const result = await handleApiCall(payload as ApiCallPayload); self.postMessage({ type: MessageType.API_RESPONSE, payload: result, id }); break; + } default: throw new Error(`[worker] Unknown message type: ${type}`); } } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const stack = error instanceof Error ? error.stack : undefined; self.postMessage({ type: MessageType.ERROR, payload: { - message: error.message, - stack: error.stack + message, + stack }, id }); @@ -76,7 +83,13 @@ async function initEnvironment() { await pyodide.loadPackage("shapely"); // Install IfcTester - await micropip.install('ifctester'); + const ifctesterManifest = await fetch('/worker/generated/ifctester.json').then((response) => { + if (!response.ok) { + throw new Error(`[worker] Failed to load IfcTester wheel manifest: ${response.status} ${response.statusText}`); + } + return response.json() as Promise<{ wheel_url: string }>; + }); + await micropip.install(ifctesterManifest.wheel_url); // Initialize IDS and API await API.init(pyodide); @@ -93,17 +106,17 @@ async function cleanupEnvironment() { console.log("[worker] Closed environment"); } -async function handleApiCall({ method, args = [] }) { +async function handleApiCall({ method, args = [] }: ApiCallPayload) { if (method === 'internal.cleanup') { await cleanupEnvironment(); return true; } if (method in API.API) { - return await API.API[method](...args); - } else if (method in IDS.API) { - return await IDS.API[method](...args); - } else { - throw new Error(`[worker] Unknown API method: ${method}`); + return await (API.API as Record unknown>)[method](...args); } -} \ No newline at end of file + if (method in IDS.API) { + return await (IDS.API as Record unknown>)[method](...args); + } + throw new Error(`[worker] Unknown API method: ${method}`); +} diff --git a/src/ifctester/webapp/src/pages/Home/ApplicabilityPanel.svelte b/src/ifctester/webapp/src/pages/Home/ApplicabilityPanel.svelte index 2bf687ecc0..df2cfbe9b0 100644 --- a/src/ifctester/webapp/src/pages/Home/ApplicabilityPanel.svelte +++ b/src/ifctester/webapp/src/pages/Home/ApplicabilityPanel.svelte @@ -1,37 +1,56 @@ -
@@ -40,22 +59,19 @@
- {#if activeSpecification?.applicability} - {#each Object.entries(activeSpecification.applicability) as [facetType, facets]} - {#if facetType !== "@minOccurs" && facetType !== "@maxOccurs"} - {#each facets as facet, index} - - {/each} - {/if} + {#if activeSpecification && applicabilityEntries.length > 0} + {#each applicabilityEntries as [facetType, facets]} + {#each facets as facet, index} + + {/each} {/each} {/if}
- \ No newline at end of file + diff --git a/src/ifctester/webapp/src/pages/Home/FacetEditor.svelte b/src/ifctester/webapp/src/pages/Home/FacetEditor.svelte index 4d807625b3..dbc6091f95 100644 --- a/src/ifctester/webapp/src/pages/Home/FacetEditor.svelte +++ b/src/ifctester/webapp/src/pages/Home/FacetEditor.svelte @@ -1,20 +1,40 @@ -
@@ -48,8 +68,8 @@
- - getSpecialProp("@relation"), (v) => setSpecialProp("@relation", v)}> @@ -62,8 +82,8 @@ {#if activeTab === 'requirements'} {#if facetType !== 'entity'}
- - getSpecialProp("@cardinality"), (v) => setSpecialProp("@cardinality", v)}> @@ -71,9 +91,9 @@
{/if}
- - + +
{/if}
-
\ No newline at end of file + diff --git a/src/ifctester/webapp/src/pages/Home/IdsMetadataEditor.svelte b/src/ifctester/webapp/src/pages/Home/IdsMetadataEditor.svelte index 59cd95c0a5..63b762fc5e 100644 --- a/src/ifctester/webapp/src/pages/Home/IdsMetadataEditor.svelte +++ b/src/ifctester/webapp/src/pages/Home/IdsMetadataEditor.svelte @@ -1,13 +1,17 @@ - @@ -18,36 +22,36 @@
- - getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title"> + + getProp("title"), (v) => setProp("title", v)} placeholder="Enter IDS title">
- - getProp("author"), (v) => setProp("author", v)} placeholder="Enter author"> + + getProp("author"), (v) => setProp("author", v)} placeholder="Enter author">
- - getProp("version"), (v) => setProp("version", v)} placeholder="Enter version"> + + getProp("version"), (v) => setProp("version", v)} placeholder="Enter version">
- - getProp("date"), (v) => setProp("date", v)}> + + getProp("date"), (v) => setProp("date", v)}>
- - + +
- - getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose"> + + getProp("purpose"), (v) => setProp("purpose", v)} placeholder="Enter purpose">
- - getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone"> + + getProp("milestone"), (v) => setProp("milestone", v)} placeholder="Enter milestone">
- - getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright"> + + getProp("copyright"), (v) => setProp("copyright", v)} placeholder="Enter copyright">
diff --git a/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte b/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte index 2fda5f0c2c..beadd151b8 100644 --- a/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte +++ b/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte @@ -1,18 +1,28 @@ -
@@ -207,8 +254,17 @@
{#each activeDocument.specifications.specification as spec, index} + {@const usage = getDocumentSpecificationUsage(spec)} + {@const requirementGroups = getRequirementGroups(spec)}
-
toggleSpecification(index)}> +
toggleSpecification(index)} + onkeydown={(event) => handleActivation(event, () => toggleSpecification(index))} + >

{spec["@name"] || `Specification ${index + 1}`}

@@ -237,19 +293,19 @@

{spec["@description"]}

{/if}
- {#if spec.applicability["@minOccurs"] === 1 && spec.applicability["@maxOccurs"] === 'unbounded'} + {#if usage === 'required'} Required {/if} - {#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 'unbounded'} + {#if usage === 'optional'} Optional {/if} - {#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 0} + {#if usage === 'prohibited'} Prohibited {/if} {#if auditReport} {@const stats = getSpecificationStats(index, auditReport.data)} {@const status = getSpecificationStatus(index, auditReport.data)} - {#if stats && spec.applicability["@maxOccurs"] !== 0 && status !== 'skipped'} + {#if stats && usage !== 'prohibited' && status !== 'skipped'} Checks: {stats.checksPassed}/{stats.checksTotal} Requirements: {stats.requirementsPassed}/{stats.requirements} {/if} @@ -302,12 +358,13 @@ {#if auditReport} {@const status = getSpecificationStatus(index, auditReport.data)} - {#if ! status && spec.applicability["@maxOccurs"] == 0} + {#if status === false && usage === 'prohibited'} {@const specReport = auditReport.data.specifications[index]} + {@const applicableEntities = specReport.applicable_entities ?? []}
- {#if specReport.applicable_entities && specReport.applicable_entities.length > 0} + {#if applicableEntities.length > 0}
-

Failed Elements ({specReport.applicable_entities.length})

+

Failed Elements ({applicableEntities.length})

@@ -323,7 +380,7 @@ - {#each specReport.applicable_entities.slice(0, 10) as entity} + {#each applicableEntities.slice(0, 10) as entity} @@ -379,9 +436,9 @@ {/each} - {#if specReport.applicable_entities.length > 10} + {#if applicableEntities.length > 10} - + {/if} @@ -397,37 +454,36 @@ - {#if Array.isArray(spec.requirements) && spec.requirements.length > 0} + {#if requirementGroups.length > 0}

Requirements

- {#each Object.entries(spec.requirements || {}) as [facetType, facets]} - {#if Array.isArray(facets) && facets.length > 0} -
- {#each facets as facet, facetIndex} - {@const reqAuditData = auditReport ? getRequirementStatus(index, facetIndex, auditReport.data) : null} - {@const specStatus = auditReport ? getSpecificationStatus(index, auditReport.data) : null} -
- - {#if isRequirementDetailsExpanded(index, facetIndex)} + {/if} + + {#if reqAuditData && isRequirementDetailsExpanded(index, item.reqIndex)}
{#if reqAuditData.passed_entities && reqAuditData.passed_entities.length > 0} @@ -592,11 +648,10 @@ {/if}
- {/if} -
- {/each} -
- {/if} + {/if} +
+ {/each} +
{/each} diff --git a/src/ifctester/webapp/src/pages/Home/RequirementsPanel.svelte b/src/ifctester/webapp/src/pages/Home/RequirementsPanel.svelte index 189d9c7066..b034778a67 100644 --- a/src/ifctester/webapp/src/pages/Home/RequirementsPanel.svelte +++ b/src/ifctester/webapp/src/pages/Home/RequirementsPanel.svelte @@ -1,37 +1,53 @@ -
@@ -40,8 +56,8 @@
- {#if activeSpecification?.requirements} - {#each Object.entries(activeSpecification.requirements) as [facetType, facets]} + {#if activeSpecification && requirementEntries.length > 0} + {#each requirementEntries as [facetType, facets]} {#each facets as facet, index} {/each} {/each} {/if}
- \ No newline at end of file + diff --git a/src/ifctester/webapp/src/pages/Home/RestrictionEditor.svelte b/src/ifctester/webapp/src/pages/Home/RestrictionEditor.svelte index 3bc5042065..c8942b2a52 100644 --- a/src/ifctester/webapp/src/pages/Home/RestrictionEditor.svelte +++ b/src/ifctester/webapp/src/pages/Home/RestrictionEditor.svelte @@ -1,34 +1,86 @@ -
- -
+ {label} +
{#if !isSpecialProp}
- handleTypeChange((e.target as HTMLSelectElement).value)} + > @@ -477,7 +537,7 @@ placeholder={placeholder} /> {:else} - getSimpleValue(), (v) => setSimpleValue(v)} {placeholder}> + getSimpleValue(), (v) => setSimpleValue(v)} {placeholder} aria-label={label}> {/if} {:else if restrictionType === 'Enumeration'} @@ -497,7 +557,14 @@ placeholder={placeholder} /> {:else} - updateEnumerationValue(index, e.target.value)} {placeholder}> + updateEnumerationValue(index, (e.target as HTMLInputElement).value)} + {placeholder} + aria-label={`${label} option ${index + 1}`} + > {/if}
{:else if restrictionType === 'Pattern'} - getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})"> + getPatternValue(), (v) => setPatternValue(v)} placeholder="Enter regex pattern (e.g., DT[0-9]{2})" aria-label={`${label} pattern`}> {:else if restrictionType === 'Range'} + {@const range = getRangeValues()}
- - getRangeValues().min, (v) => { const range = getRangeValues(); setRangeValues(v, range.max, range.minType, range.maxType); }} placeholder="0"> - range.min, (v) => setRangeValues(v, range.max, range.minType, range.maxType)} + placeholder="0" + > +
- - getRangeValues().max, (v) => { const range = getRangeValues(); setRangeValues(range.min, v, range.minType, range.maxType); }} placeholder="0"> - range.max, (v) => setRangeValues(range.min, v, range.minType, range.maxType)} + placeholder="0" + > + @@ -538,17 +626,18 @@
{:else if restrictionType === 'Length'} - getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length"> + getLengthValue(), (v) => setLengthValue(v)} placeholder="Enter exact length" aria-label={`${label} length`}> {:else if restrictionType === 'Length Range'} + {@const lengthRange = getLengthRangeValues()}
- - getLengthRangeValues().min, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(v, range.max); }} placeholder="0"> + + lengthRange.min, (v) => setLengthRangeValues(v, lengthRange.max)} placeholder="0">
- - getLengthRangeValues().max, (v) => { const range = getLengthRangeValues(); setLengthRangeValues(range.min, v); }} placeholder="0"> + + lengthRange.max, (v) => setLengthRangeValues(lengthRange.min, v)} placeholder="0">
{/if} @@ -656,4 +745,4 @@ color: #666; margin: 0; } - \ No newline at end of file + diff --git a/src/ifctester/webapp/src/pages/Home/SpecificationEditor.svelte b/src/ifctester/webapp/src/pages/Home/SpecificationEditor.svelte index be524ea411..cc340f6cbe 100644 --- a/src/ifctester/webapp/src/pages/Home/SpecificationEditor.svelte +++ b/src/ifctester/webapp/src/pages/Home/SpecificationEditor.svelte @@ -1,33 +1,44 @@ - {#if IDS.Module.status != "ready"} @@ -130,8 +158,7 @@ Import from IDS - {#each Object.entries(IDS.Module.documents) as [docId, doc]} - {#if docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0} + {#each importableDocuments as [docId, doc], docIndex} {doc.info?.title || 'Untitled Document'} @@ -146,12 +173,11 @@ {/each} - {#if Object.entries(IDS.Module.documents).filter(([id, d]) => id !== IDS.Module.activeDocument && d.specifications?.specification?.length > 0).indexOf([docId, doc]) < Object.entries(IDS.Module.documents).filter(([id, d]) => id !== IDS.Module.activeDocument && d.specifications?.specification?.length > 0).length - 1} + {#if docIndex < importableDocuments.length - 1} {/if} - {/if} {/each} - {#if Object.entries(IDS.Module.documents).filter(([docId, doc]) => docId !== IDS.Module.activeDocument && doc.specifications?.specification?.length > 0).length === 0} + {#if importableDocuments.length === 0} No specifications available to import @@ -162,16 +188,30 @@
-
{ if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }}> +
{ if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); }} + onkeydown={(event) => handleActivation(event, () => { if (IDS.Module.activeDocument) IDS.setDocumentState(IDS.Module.activeDocument, { activeSpecification: null }); })} + > ℹ️ IDS Information
{#if activeDocument?.specifications?.specification} {#each activeDocument.specifications.specification as spec, index} -
selectSpecification(index)}> +
selectSpecification(index)} + onkeydown={(event) => handleActivation(event, () => selectSpecification(index))} + > 📄 {spec["@name"] || "Specification " + (index + 1)} - -
@@ -209,18 +249,18 @@

{activeSpecification ? activeSpecification["@name"] || "Specification" : "Specification"}

- - - + + +
{#if documentState?.activeTab === 'info'} {:else if documentState?.activeTab === 'applicability'} - + {:else if documentState?.activeTab === 'requirements'} - + {/if}
{/if} diff --git a/src/ifctester/webapp/src/pages/index.js b/src/ifctester/webapp/src/pages/index.ts similarity index 100% rename from src/ifctester/webapp/src/pages/index.js rename to src/ifctester/webapp/src/pages/index.ts diff --git a/src/ifctester/webapp/src/routes.js b/src/ifctester/webapp/src/routes.ts similarity index 100% rename from src/ifctester/webapp/src/routes.js rename to src/ifctester/webapp/src/routes.ts diff --git a/src/ifctester/webapp/src/types/ids.ts b/src/ifctester/webapp/src/types/ids.ts new file mode 100644 index 0000000000..a8213e6baa --- /dev/null +++ b/src/ifctester/webapp/src/types/ids.ts @@ -0,0 +1,70 @@ +export type IdsCardinality = "required" | "optional" | "prohibited"; + +export type RestrictionValue = { + "@value": string; +}; + +export type Restriction = { + "@base"?: string; + enumeration?: RestrictionValue[]; + pattern?: RestrictionValue[]; + length?: RestrictionValue[]; + minLength?: RestrictionValue[]; + maxLength?: RestrictionValue[]; + minInclusive?: RestrictionValue[]; + maxInclusive?: RestrictionValue[]; + minExclusive?: RestrictionValue[]; + maxExclusive?: RestrictionValue[]; +}; + +export type SimpleValue = { + simpleValue: string; +}; + +export type FacetValue = { + simpleValue?: string; + restriction?: Restriction; +}; + +export type Facet = Record; + +export type FacetClause = Record; + +export type Specification = { + "@name"?: string; + "@identifier"?: string; + "@description"?: string; + "@instructions"?: string; + "@ifcVersion"?: string[]; + applicability?: FacetClause; + requirements?: FacetClause; +}; + +export type IdsInfo = { + title?: string; + copyright?: string; + version?: string; + description?: string; + author?: string; + date?: string; + purpose?: string; + milestone?: string; +}; + +export type IdsDocument = { + "@xmlns"?: string; + "@xmlns:xs"?: string; + "@xmlns:xsi"?: string; + "@xsi:schemaLocation"?: string; + info: IdsInfo; + specifications: { + specification: Specification[]; + }; +}; + +export type DocumentState = { + activeTab: "info" | "applicability" | "requirements"; + viewMode: "editor" | "viewer"; + activeSpecification: number | null; + auditReport?: string | null; +}; diff --git a/src/ifctester/webapp/src/types/report.ts b/src/ifctester/webapp/src/types/report.ts new file mode 100644 index 0000000000..ee683e0808 --- /dev/null +++ b/src/ifctester/webapp/src/types/report.ts @@ -0,0 +1,95 @@ +export type ResultsPercent = number | "N/A"; + +export type AuditReportEntity = { + reason?: string; + element?: unknown; + element_type?: unknown; + class?: string; + predefined_type?: string; + name?: string | null; + description?: string | null; + id?: number; + global_id?: string | null; + tag?: string | null; + type_name?: string; + type_tag?: string | null; + type_global_id?: string | null; + extra_of_type?: number; +}; + +export type AuditRequirement = { + facet_type: string; + metadata: Record; + label: string; + value: string; + description: string; + status: boolean; + passed_entities: AuditReportEntity[]; + failed_entities: AuditReportEntity[]; + total_applicable: number; + total_pass: number; + total_fail: number; + percent_pass: ResultsPercent; + instructions?: string; + total_failed_entities?: number; + total_omitted_failures?: number; + has_omitted_failures?: boolean; + total_passed_entities?: number; + total_omitted_passes?: number; + has_omitted_passes?: boolean; +}; + +export type AuditSpecification = { + name: string; + description: string; + instructions: string; + status: boolean; + is_skipped?: boolean; + is_ifc_version: boolean; + total_applicable: number; + total_applicable_pass: number; + total_applicable_fail: number; + percent_applicable_pass: ResultsPercent; + total_checks: number; + total_checks_pass: number; + total_checks_fail: number; + percent_checks_pass: ResultsPercent; + cardinality: string; + applicability: string[]; + applicable_entities?: AuditReportEntity[]; + requirements: AuditRequirement[]; + total_requirements?: number; + total_requirements_pass?: number; +}; + +export type AuditReportData = { + title: string; + date: string; + filepath: string | null; + filename: string | null; + hide_skipped: boolean; + specifications: AuditSpecification[]; + status: boolean; + total_specifications: number; + total_specifications_pass: number; + total_specifications_fail: number; + percent_specifications_pass: ResultsPercent; + total_requirements: number; + total_requirements_pass: number; + total_requirements_fail: number; + percent_requirements_pass: ResultsPercent; + total_checks: number; + total_checks_pass: number; + total_checks_fail: number; + percent_checks_pass: ResultsPercent; +}; + +export type AuditReport = { + id: string; + modelId: string; + modelName: string; + document: string; + date: string; + data: AuditReportData; + htmlReport?: string | null; +}; diff --git a/src/ifctester/webapp/src/types/wasm.ts b/src/ifctester/webapp/src/types/wasm.ts new file mode 100644 index 0000000000..101796bd11 --- /dev/null +++ b/src/ifctester/webapp/src/types/wasm.ts @@ -0,0 +1,24 @@ +export type WorkerMessageType = + | "init" + | "api_call" + | "ready" + | "api_response" + | "error" + | "disposed"; + +export type WorkerRequest = { + type: WorkerMessageType; + payload?: Record; + id: string; +}; + +export type WorkerResponse = { + type: WorkerMessageType; + payload?: Record; + id: string; +}; + +export type ApiCallPayload = { + method: string; + args?: unknown[]; +}; diff --git a/src/ifctester/webapp/tsconfig.json b/src/ifctester/webapp/tsconfig.json new file mode 100644 index 0000000000..39e15a0e10 --- /dev/null +++ b/src/ifctester/webapp/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "baseUrl": ".", + "paths": { + "$lib": ["./src/lib"], + "$lib/*": ["./src/lib/*"], + "$src": ["./src"], + "$src/*": ["./src/*"] + } + }, + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.svelte"] +} diff --git a/src/ifctester/webapp/vite.config.js b/src/ifctester/webapp/vite.config.js index cfc33f9356..46cecde534 100644 --- a/src/ifctester/webapp/vite.config.js +++ b/src/ifctester/webapp/vite.config.js @@ -1,7 +1,7 @@ import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; -import path from "path"; +import path from "node:path"; export default defineConfig({ plugins: [tailwindcss(), svelte()], @@ -11,4 +11,4 @@ export default defineConfig({ $src: path.resolve("./src"), }, }, -}); \ No newline at end of file +}); diff --git a/src/ifcwrap/IfcPython.i b/src/ifcwrap/IfcPython.i index db772ba26e..ca802cedfa 100644 --- a/src/ifcwrap/IfcPython.i +++ b/src/ifcwrap/IfcPython.i @@ -108,6 +108,7 @@ %ignore FloatingPointDigits; %ignore BaseUri; %ignore WktUseSection; +%ignore SeparateZUpNode; // ConversionSettings.h %ignore MesherLinearDeflection; %ignore MesherAngularDeflection; @@ -158,11 +159,11 @@ %ignore CgalEmitOriginalEdges; %ignore OcctNoCleanTriangulation; %ignore CacheShapes; +%ignore MakeVolume; %ignore DeferProcessingFirstElement; %ignore MaxOffset; %ignore MaxOffsetDeviation; %ignore ApplyOffset; -%ignore SeparateZUpNode; %ignore XmlSerializerFactory; %ignore JsonSerializerFactory; diff --git a/src/opencdeserver/api/app/security/secure.py b/src/opencdeserver/api/app/security/secure.py index fec837b868..e332c94647 100644 --- a/src/opencdeserver/api/app/security/secure.py +++ b/src/opencdeserver/api/app/security/secure.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from database.neo4j import db from fastapi import Depends, HTTPException, Security, status @@ -33,10 +33,8 @@ credentials_exception = HTTPException( def create_access_token(data: dict, expires_delta: timedelta | None = None): payload = data.copy() - if expires_delta: - expire = datetime.utcnow() + expires_delta - else: - expire = datetime.utcnow() + timedelta(minutes=15) + expires_delta = expires_delta or timedelta(minutes=15) + expire = datetime.now(timezone.utc) + expires_delta payload.update({"expires": str(expire)}) encoded_jwt = jwt.encode(payload, secrets["security_secret_key"], algorithm=os.environ["SECURITY_ALGORITHM"]) return encoded_jwt diff --git a/src/serializers/WavefrontObjSerializer.cpp b/src/serializers/WavefrontObjSerializer.cpp index f29612a65e..848dd9beb7 100644 --- a/src/serializers/WavefrontObjSerializer.cpp +++ b/src/serializers/WavefrontObjSerializer.cpp @@ -32,6 +32,7 @@ WaveFrontOBJSerializer::WaveFrontOBJSerializer(const stream_or_filename& obj_fil , obj_stream(obj_filename) , mtl_stream(mtl_filename) , vcount_total(1) + , ncount_total(1) { obj_stream.stream << std::setprecision(settings.get().get()); mtl_stream.stream << std::setprecision(settings.get().get()); diff --git a/win/build-all-win.py b/win/build-all-win.py index e1bd5cccdf..ded9a36bb6 100644 --- a/win/build-all-win.py +++ b/win/build-all-win.py @@ -5,11 +5,21 @@ but also archives them to '~/outputs'. """ import os +import platform import subprocess import zipfile from pathlib import Path from zipfile import ZipFile + +def is_arm64() -> bool: + arch = os.environ.get("TARGET_ARCH", "").lower() + if arch in ("arm64", "aarch64"): + return True + if arch in ("x64", "amd64", "x86_64"): + return False + return platform.machine().lower() in ("arm64", "aarch64") + assert Path.cwd() == Path(__file__).parent, "Run this script from the 'win' directory." PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.0", "3.14.0"] @@ -23,7 +33,7 @@ else: OUTPUT_DIR = Path.home() / "output" OUTPUT_DIR.mkdir(exist_ok=True) print("Output directory:", OUTPUT_DIR) -ZIP_TEMPLATE = f"{{package_name}}-v{VERSION}-{SHA}-win64.zip" +ZIP_TEMPLATE = f"{{package_name}}-v{VERSION}-{SHA}-{'win-arm64' if is_arm64() else 'win64'}.zip" def run(command: list[str]) -> None: @@ -52,7 +62,7 @@ def build() -> None: os.environ["PYTHON_VERSION"] = python_version print(f"Building for Python {python_version}...") subprocess.run( - [str(REPO_WIN / "build-deps.cmd"), "vs2022-x64", "Release"], + [str(REPO_WIN / "build-deps.cmd"), "vs2022-ARM64" if is_arm64() else "vs2022-x64", "Release"], check=True, text=True, input="y\n", @@ -61,13 +71,13 @@ def build() -> None: run( [ str(REPO_WIN / "run-cmake.bat"), - "vs2022-x64", + "vs2022-ARM64" if is_arm64() else "vs2022-x64", "-DENABLE_BUILD_OPTIMIZATIONS=ON", "-DGLTF_SUPPORT=ON", ] ) restore_env(*OLD_ADD_COMMIT_SHA) - run([str(REPO_WIN / "install-ifcopenshell.bat"), "vs2022-x64", "Release"]) + run([str(REPO_WIN / "install-ifcopenshell.bat"), "vs2022-ARM64" if is_arm64() else "vs2022-x64", "Release"]) def archive_executables() -> None: @@ -104,7 +114,7 @@ def archive_python_packages() -> None: deps_path = REPO_PATH / "_deps" python_versions: list[str] = [] for d in deps_path.iterdir(): - if d.is_dir() and d.name.startswith("python."): + if d.is_dir() and (d.name.startswith("python.") or d.name.startswith("pythonarm64.")): python_version = d.name.partition(".")[2] python_path = d / "tools" archive_python_package(python_version, python_path) diff --git a/win/build-deps.cmd b/win/build-deps.cmd index 811f381f19..8e543f1093 100644 --- a/win/build-deps.cmd +++ b/win/build-deps.cmd @@ -132,7 +132,7 @@ call cecho.cmd 0 10 "Script configuration:" call cecho.cmd 0 13 "* CMake Generator`t= '`"%GENERATOR%`'`t echo - Passed to CMake -G option. call cecho.cmd 0 13 "* Target Architecture`t= %TARGET_ARCH%" -echo - Whether were doing 32-bit (x86) or 64-bit (x64) build. +echo - Whether were doing 32-bit (x86) or 64-bit (x64, arm64) build. call cecho.cmd 0 13 "* Target Platform`t= %VS_PLATFORM%" echo - Passed to CMake -A option. call cecho.cmd 0 13 "* Target Toolset`t= %VS_TOOLSET%" @@ -187,8 +187,15 @@ IF DEFINED PYTHON_VERSION ( ) :: VERSION DERIVATIONS +for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do ( + set PY_VER_MAJOR_MINOR=%%a%%b +) IF "%IFCOS_INSTALL_PYTHON%"=="TRUE" ( - set PYTHONHOME=%DEPS_DIR%\python.%PYTHON_VERSION%\tools + IF /I "%TARGET_ARCH%"=="arm64" ( + set PYTHONHOME=%DEPS_DIR%\pythonarm64.%PYTHON_VERSION%\tools + ) ELSE ( + set PYTHONHOME=%DEPS_DIR%\python.%PYTHON_VERSION%\tools + ) ) :: Cache last used CMake generator and configurable dependency dirs for other scripts to use @@ -312,6 +319,11 @@ powershell -c "get-content %~dp0patches\mpir.patch | %%{$_ -replace \"sdk\",\"%U IF NOT %ERRORLEVEL%==0 GOTO :Error if NOT "%USE_STATIC_RUNTIME%"=="FALSE" git apply "%~dp0patches\mpir_runtime.patch" --unidiff-zero --ignore-whitespace IF NOT %ERRORLEVEL%==0 GOTO :Error +IF /I "%VS_PLATFORM%"=="ARM64" ( + echo "Applying ARM64 Patches for Mpir" + git apply "%~dp0patches\mpir-arm64-changes.patch" --unidiff-zero --ignore-whitespace +) +IF NOT %ERRORLEVEL%==0 GOTO :Error cd msvc cd vs%VS_VER:~2,2% call .\msbuild.bat gc LIB %VS_PLATFORM% %DEBUG_OR_RELEASE% @@ -338,6 +350,10 @@ powershell -c "get-content %~dp0patches\mpfr.patch | %%{$_ -replace \"sdk\",\"%U IF NOT %ERRORLEVEL%==0 GOTO :Error if NOT "%USE_STATIC_RUNTIME%"=="FALSE" git apply "%~dp0patches\mpfr_runtime.patch" --unidiff-zero --ignore-whitespace IF NOT %ERRORLEVEL%==0 GOTO :Error +IF /I "%VS_PLATFORM%"=="ARM64" ( + echo "Applying ARM64 Patches for Mpfr" + git apply "%~dp0patches\mpfr-arm64-changes.patch" --unidiff-zero --ignore-whitespace +) if "%VS_VER%"=="2017" ( set mpfr_sln=build.vc15 set orig_platform_toolset=v141 @@ -406,6 +422,7 @@ cd "%DEPS_DIR%" call :DownloadFile https://github.com/boostorg/boost/releases/download/boost-%BOOST_VERSION%/%BOOST_ZIP% "%DEPS_DIR%" %BOOST_ZIP% IF NOT %ERRORLEVEL%==0 GOTO :Error +cd "%DEPS_DIR%" call :ExtractArchive %BOOST_ZIP% "%DEPS_DIR%" %DEPENDENCY_DIR% IF NOT %ERRORLEVEL%==0 GOTO :Error @@ -429,13 +446,21 @@ if not exist "%DEPENDENCY_DIR%\project-config.jam". ( IF NOT %ERRORLEVEL%==0 GOTO :Error ) +if /I "%TARGET_ARCH%"=="x64" ( + set B2_ARCH_FEATURE=x86 +) else if /I "%TARGET_ARCH%"=="arm64" ( + set B2_ARCH_FEATURE=arm +) else ( + echo "Failed to identify architecture" + GOTO :Error +) set BOOST_LIBS=--with-system --with-regex --with-thread --with-program_options --with-date_time --with-iostreams --with-filesystem :: NOTE Boost is fast to build with limited set of libraries so build it always. cd "%DEPENDENCY_DIR%" call cecho.cmd 0 13 "Building %DEPENDENCY_NAME% %BOOST_LIBS% Please be patient, this will take a while." IF EXIST "%DEPENDENCY_DIR%\bin.v2\project-cache.jam" del "%DEPENDENCY_DIR%\bin.v2\project-cache.jam" -call .\b2 toolset=%BOOST_TOOLSET% runtime-link=shared address-model=%ARCH_BITS% --abbreviate-paths -j%IFCOS_NUM_BUILD_PROCS% ^ +call .\b2 toolset=%BOOST_TOOLSET% architecture=%B2_ARCH_FEATURE% runtime-link=shared address-model=%ARCH_BITS% --abbreviate-paths -j%IFCOS_NUM_BUILD_PROCS% ^ variant=%DEBUG_OR_RELEASE_LOWERCASE% %BOOST_WIN_API% %BOOST_LIBS% stage --stagedir=%DEPENDENCY_INSTALL_DIR% IF NOT %ERRORLEVEL%==0 GOTO :Error @@ -561,9 +586,10 @@ SET COMPILE_WITH_WPO=FALSE :Python set DEPENDENCY_NAME=Python %PYTHON_VERSION% set DEPENDENCY_DIR=N/A -set PYTHON_AMD64_POSTFIX=-amd64 -IF NOT %TARGET_ARCH%==x64 set PYTHON_AMD64_POSTFIX= -set PYTHON_INSTALLER=python-%PYTHON_VERSION%%PYTHON_AMD64_POSTFIX%.exe +set PYTHON_AMD64_POSTFIX= +IF /I "%TARGET_ARCH%"=="x64" set "PYTHON_AMD64_POSTFIX=-amd64" +IF /I "%TARGET_ARCH%"=="arm64" set "PYTHON_AMD64_POSTFIX=-arm64" +set "PYTHON_INSTALLER=python-%PYTHON_VERSION%%PYTHON_AMD64_POSTFIX%.exe" IF NOT "%IFCOS_INSTALL_PYTHON%"=="TRUE" ( call cecho.cmd 0 13 "IFCOS_INSTALL_PYTHON not 'TRUE', skipping installation of Python." @@ -571,22 +597,25 @@ IF NOT "%IFCOS_INSTALL_PYTHON%"=="TRUE" ( ) :: nuget doesn't support providing architecture for packages. -if NOT %TARGET_ARCH%==x64 ( +IF /I NOT "%TARGET_ARCH%"=="x64" IF /I NOT "%TARGET_ARCH%"=="arm64" ( call cecho.cmd 0 12 "Automatic insallation of Python for x86 builds is not supported," call cecho.cmd 0 12 "please install Python %PYTHON_VERSION% manually and ensure that it is available in PATH." call cecho.cmd 0 12 "https://www.python.org/ftp/python/%PYTHON_VERSION%/%PYTHON_INSTALLER%" goto :Error ) - if EXIST "%PYTHONHOME%" ( echo Found existing '%PYTHONHOME%', skipping installation. goto :SWIG ) -"%NUGET_EXE%" install Python -Version %PYTHON_VERSION% -OutputDirectory "%DEPS_DIR%" -IF NOT %ERRORLEVEL%==0 GOTO :Error - +IF /I "%TARGET_ARCH%"=="x64" ( + "%NUGET_EXE%" install Python -Version %PYTHON_VERSION% -OutputDirectory "%DEPS_DIR%" + IF NOT %ERRORLEVEL%==0 GOTO :Error +) ELSE ( + "%NUGET_EXE%" install pythonarm64 -Version %PYTHON_VERSION% -OutputDirectory "%DEPS_DIR%" + IF NOT %ERRORLEVEL%==0 GOTO :Error +) :SWIG set DEPENDENCY_NAME=SWIG @@ -723,6 +752,9 @@ call :RunCMake -DCMAKE_INSTALL_PREFIX="%INSTALL_DIR%\%DEPENDENCY_INSTALL_NAME%" -DWITH_CORE_TOOLS=OFF ^ -DROCKSDB_BUILD_SHARED=OFF ^ -DWITH_ZSTD=On ^ + -DZSTD_INCLUDE_DIR="%ZSTD_INCLUDE%" ^ + -DZSTD_LIBRARY_DEBUG="%ZSTD_LIB_DEBUG%" ^ + -DZSTD_LIBRARY_RELEASE="%ZSTD_LIB_RELEASE%" ^ -DPORTABLE=1 ^ -DCMAKE_DEBUG_POSTFIX="_d" IF NOT %ERRORLEVEL%==0 GOTO :Error @@ -960,4 +992,3 @@ echo - https://msdn.microsoft.com/en-us/library/ms229859(v=vs.110).aspx echo. echo NB: This script needs to be ran from the directory directly containing it. echo. - diff --git a/win/patches/mpfr-arm64-changes.patch b/win/patches/mpfr-arm64-changes.patch new file mode 100644 index 0000000000..82f7bbbbd6 --- /dev/null +++ b/win/patches/mpfr-arm64-changes.patch @@ -0,0 +1,26854 @@ +diff --git a/build.vs19/bench_lib/bench_lib.vcxproj b/build.vs19/bench_lib/bench_lib.vcxproj +index ceaf5abf..3447b804 100644 +--- a/build.vs19/bench_lib/bench_lib.vcxproj ++++ b/build.vs19/bench_lib/bench_lib.vcxproj +@@ -1,10 +1,18 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -32,6 +40,12 @@ + v142 + Unicode + ++ ++ Application ++ true ++ v142 ++ Unicode ++ + + Application + false +@@ -39,6 +53,13 @@ + true + Unicode + ++ ++ Application ++ false ++ v142 ++ true ++ Unicode ++ + + Application + true +@@ -60,9 +81,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -78,6 +105,11 @@ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ true ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + true + +@@ -86,6 +118,11 @@ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ false ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + + Level3 +@@ -118,6 +155,20 @@ + ..\..\lib\$(IntDir)\mpfr.lib;..\..\..\mpir\lib\$(IntDir)\mpir.lib;%(AdditionalDependencies) + + ++ ++ ++ ++ ++ Level3 ++ Disabled ++ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) ++ ..\..\src;..\..\..\mpir ++ ++ ++ Console ++ ..\..\lib\$(IntDir)\mpfr.lib;..\..\..\mpir\lib\$(IntDir)\mpir.lib;%(AdditionalDependencies) ++ ++ + + + +@@ -150,6 +201,24 @@ + ..\..\lib\$(IntDir)\mpfr.lib;..\..\..\mpir\lib\$(IntDir)\mpir.lib;%(AdditionalDependencies) + + ++ ++ ++ Level3 ++ ++ ++ MaxSpeed ++ true ++ true ++ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) ++ ..\..\src;..\..\..\mpir ++ ++ ++ Console ++ true ++ true ++ ..\..\lib\$(IntDir)\mpfr.lib;..\..\..\mpir\lib\$(IntDir)\mpir.lib;%(AdditionalDependencies) ++ ++ + + + +diff --git a/build.vs19/lib_mpfr.sln b/build.vs19/lib_mpfr.sln +index c4444383..f30891b2 100644 +--- a/build.vs19/lib_mpfr.sln ++++ b/build.vs19/lib_mpfr.sln +@@ -1,7 +1,7 @@ +  + Microsoft Visual Studio Solution File, Format Version 12.00 +-# Visual Studio 15 +-VisualStudioVersion = 15.0.27130.0 ++# Visual Studio Version 17 ++VisualStudioVersion = 17.14.36429.23 d17.14 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib_mpfr_tests", "lib_mpfr_tests", "{610C8F32-024C-4868-B514-3F2C9AFCE83F}" + EndProject +@@ -1098,1504 +1098,2254 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ttotal_order", "lib_mpfr_te + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution ++ Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 ++ Release|ARM64 = Release|ARM64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution ++ {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|ARM64.Build.0 = Debug|ARM64 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|Win32.Build.0 = Debug|Win32 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|x64.ActiveCfg = Debug|x64 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Debug|x64.Build.0 = Debug|x64 ++ {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|ARM64.Build.0 = Release|ARM64 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|Win32.ActiveCfg = Release|Win32 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|Win32.Build.0 = Release|Win32 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|x64.ActiveCfg = Release|x64 + {96DA1C71-3895-49FA-A4F1-2775C650AF3D}.Release|x64.Build.0 = Release|x64 ++ {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|ARM64.Build.0 = Debug|ARM64 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|Win32.ActiveCfg = Debug|Win32 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|Win32.Build.0 = Debug|Win32 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|x64.ActiveCfg = Debug|x64 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Debug|x64.Build.0 = Debug|x64 ++ {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|ARM64.Build.0 = Release|ARM64 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|Win32.ActiveCfg = Release|Win32 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|Win32.Build.0 = Release|Win32 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|x64.ActiveCfg = Release|x64 + {D40DAE6F-7CDB-4845-AE8C-BE9A9E7E6E0F}.Release|x64.Build.0 = Release|x64 ++ {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|ARM64.Build.0 = Debug|ARM64 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|Win32.ActiveCfg = Debug|Win32 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|Win32.Build.0 = Debug|Win32 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|x64.ActiveCfg = Debug|x64 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Debug|x64.Build.0 = Debug|x64 ++ {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|ARM64.Build.0 = Release|ARM64 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|Win32.ActiveCfg = Release|Win32 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|Win32.Build.0 = Release|Win32 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|x64.ActiveCfg = Release|x64 + {DA42D428-8779-45CA-825A-BE7BE71336EC}.Release|x64.Build.0 = Release|x64 ++ {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|ARM64.Build.0 = Debug|ARM64 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|Win32.ActiveCfg = Debug|Win32 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|Win32.Build.0 = Debug|Win32 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|x64.ActiveCfg = Debug|x64 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Debug|x64.Build.0 = Debug|x64 ++ {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|ARM64.Build.0 = Release|ARM64 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|Win32.ActiveCfg = Release|Win32 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|Win32.Build.0 = Release|Win32 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|x64.ActiveCfg = Release|x64 + {92BCDA65-6B9B-4447-AA93-C47B460194AD}.Release|x64.Build.0 = Release|x64 ++ {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|ARM64.Build.0 = Debug|ARM64 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|Win32.ActiveCfg = Debug|Win32 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|Win32.Build.0 = Debug|Win32 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|x64.ActiveCfg = Debug|x64 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Debug|x64.Build.0 = Debug|x64 ++ {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|ARM64.Build.0 = Release|ARM64 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|Win32.ActiveCfg = Release|Win32 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|Win32.Build.0 = Release|Win32 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|x64.ActiveCfg = Release|x64 + {09BDA649-C94B-47FA-83AF-5DB9A6AA8983}.Release|x64.Build.0 = Release|x64 ++ {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|ARM64.Build.0 = Debug|ARM64 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|Win32.ActiveCfg = Debug|Win32 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|Win32.Build.0 = Debug|Win32 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|x64.ActiveCfg = Debug|x64 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Debug|x64.Build.0 = Debug|x64 ++ {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|ARM64.Build.0 = Release|ARM64 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|Win32.ActiveCfg = Release|Win32 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|Win32.Build.0 = Release|Win32 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|x64.ActiveCfg = Release|x64 + {09681D3D-E6F5-4B5E-8CC8-B65E6A63D43B}.Release|x64.Build.0 = Release|x64 ++ {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|ARM64.Build.0 = Debug|ARM64 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|Win32.ActiveCfg = Debug|Win32 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|Win32.Build.0 = Debug|Win32 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|x64.ActiveCfg = Debug|x64 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Debug|x64.Build.0 = Debug|x64 ++ {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|ARM64.Build.0 = Release|ARM64 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|Win32.ActiveCfg = Release|Win32 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|Win32.Build.0 = Release|Win32 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|x64.ActiveCfg = Release|x64 + {017724C7-107D-4E09-AB81-635C22A1B4DF}.Release|x64.Build.0 = Release|x64 ++ {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|ARM64.Build.0 = Debug|ARM64 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|Win32.ActiveCfg = Debug|Win32 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|Win32.Build.0 = Debug|Win32 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|x64.ActiveCfg = Debug|x64 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Debug|x64.Build.0 = Debug|x64 ++ {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|ARM64.Build.0 = Release|ARM64 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|Win32.ActiveCfg = Release|Win32 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|Win32.Build.0 = Release|Win32 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|x64.ActiveCfg = Release|x64 + {366F59FE-A9B7-426E-9199-99BBAAA548FE}.Release|x64.Build.0 = Release|x64 ++ {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|ARM64.Build.0 = Debug|ARM64 + {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|Win32.ActiveCfg = Debug|Win32 + {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|Win32.Build.0 = Debug|Win32 + {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|x64.ActiveCfg = Debug|x64 + {FA416777-D0A2-4636-A7E1-35708380538C}.Debug|x64.Build.0 = Debug|x64 ++ {FA416777-D0A2-4636-A7E1-35708380538C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {FA416777-D0A2-4636-A7E1-35708380538C}.Release|ARM64.Build.0 = Release|ARM64 + {FA416777-D0A2-4636-A7E1-35708380538C}.Release|Win32.ActiveCfg = Release|Win32 + {FA416777-D0A2-4636-A7E1-35708380538C}.Release|Win32.Build.0 = Release|Win32 + {FA416777-D0A2-4636-A7E1-35708380538C}.Release|x64.ActiveCfg = Release|x64 + {FA416777-D0A2-4636-A7E1-35708380538C}.Release|x64.Build.0 = Release|x64 ++ {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|ARM64.Build.0 = Debug|ARM64 + {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|Win32.ActiveCfg = Debug|Win32 + {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|Win32.Build.0 = Debug|Win32 + {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|x64.ActiveCfg = Debug|x64 + {8E87763F-3C5F-4902-9328-3872F425447C}.Debug|x64.Build.0 = Debug|x64 ++ {8E87763F-3C5F-4902-9328-3872F425447C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8E87763F-3C5F-4902-9328-3872F425447C}.Release|ARM64.Build.0 = Release|ARM64 + {8E87763F-3C5F-4902-9328-3872F425447C}.Release|Win32.ActiveCfg = Release|Win32 + {8E87763F-3C5F-4902-9328-3872F425447C}.Release|Win32.Build.0 = Release|Win32 + {8E87763F-3C5F-4902-9328-3872F425447C}.Release|x64.ActiveCfg = Release|x64 + {8E87763F-3C5F-4902-9328-3872F425447C}.Release|x64.Build.0 = Release|x64 ++ {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|ARM64.Build.0 = Debug|ARM64 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|Win32.ActiveCfg = Debug|Win32 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|Win32.Build.0 = Debug|Win32 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|x64.ActiveCfg = Debug|x64 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Debug|x64.Build.0 = Debug|x64 ++ {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|ARM64.Build.0 = Release|ARM64 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|Win32.ActiveCfg = Release|Win32 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|Win32.Build.0 = Release|Win32 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|x64.ActiveCfg = Release|x64 + {A541016C-6F8A-4314-86D4-AC95878294DD}.Release|x64.Build.0 = Release|x64 ++ {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|ARM64.Build.0 = Debug|ARM64 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|Win32.ActiveCfg = Debug|Win32 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|Win32.Build.0 = Debug|Win32 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|x64.ActiveCfg = Debug|x64 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Debug|x64.Build.0 = Debug|x64 ++ {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|ARM64.Build.0 = Release|ARM64 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|Win32.ActiveCfg = Release|Win32 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|Win32.Build.0 = Release|Win32 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|x64.ActiveCfg = Release|x64 + {1D0FB421-6CEF-4C99-9778-587EE917CDD9}.Release|x64.Build.0 = Release|x64 ++ {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|ARM64.Build.0 = Debug|ARM64 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|Win32.ActiveCfg = Debug|Win32 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|Win32.Build.0 = Debug|Win32 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|x64.ActiveCfg = Debug|x64 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Debug|x64.Build.0 = Debug|x64 ++ {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|ARM64.Build.0 = Release|ARM64 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|Win32.ActiveCfg = Release|Win32 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|Win32.Build.0 = Release|Win32 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|x64.ActiveCfg = Release|x64 + {EAE91382-3BDE-45F9-B784-47228C572B3F}.Release|x64.Build.0 = Release|x64 ++ {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|ARM64.Build.0 = Debug|ARM64 + {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|Win32.ActiveCfg = Debug|Win32 + {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|Win32.Build.0 = Debug|Win32 + {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|x64.ActiveCfg = Debug|x64 + {FEC1769E-F942-4564-892C-CF5A68967153}.Debug|x64.Build.0 = Debug|x64 ++ {FEC1769E-F942-4564-892C-CF5A68967153}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {FEC1769E-F942-4564-892C-CF5A68967153}.Release|ARM64.Build.0 = Release|ARM64 + {FEC1769E-F942-4564-892C-CF5A68967153}.Release|Win32.ActiveCfg = Release|Win32 + {FEC1769E-F942-4564-892C-CF5A68967153}.Release|Win32.Build.0 = Release|Win32 + {FEC1769E-F942-4564-892C-CF5A68967153}.Release|x64.ActiveCfg = Release|x64 + {FEC1769E-F942-4564-892C-CF5A68967153}.Release|x64.Build.0 = Release|x64 ++ {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|ARM64.Build.0 = Debug|ARM64 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|Win32.ActiveCfg = Debug|Win32 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|Win32.Build.0 = Debug|Win32 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|x64.ActiveCfg = Debug|x64 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Debug|x64.Build.0 = Debug|x64 ++ {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|ARM64.Build.0 = Release|ARM64 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|Win32.ActiveCfg = Release|Win32 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|Win32.Build.0 = Release|Win32 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|x64.ActiveCfg = Release|x64 + {034672AB-E2D5-4CB9-9A27-77E5B9037B5E}.Release|x64.Build.0 = Release|x64 ++ {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|ARM64.Build.0 = Debug|ARM64 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|Win32.ActiveCfg = Debug|Win32 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|Win32.Build.0 = Debug|Win32 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|x64.ActiveCfg = Debug|x64 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Debug|x64.Build.0 = Debug|x64 ++ {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|ARM64.Build.0 = Release|ARM64 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|Win32.ActiveCfg = Release|Win32 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|Win32.Build.0 = Release|Win32 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|x64.ActiveCfg = Release|x64 + {FE6341F9-E211-45EA-92B4-D5784A53447B}.Release|x64.Build.0 = Release|x64 ++ {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|ARM64.Build.0 = Debug|ARM64 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|Win32.ActiveCfg = Debug|Win32 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|Win32.Build.0 = Debug|Win32 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|x64.ActiveCfg = Debug|x64 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Debug|x64.Build.0 = Debug|x64 ++ {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|ARM64.Build.0 = Release|ARM64 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|Win32.ActiveCfg = Release|Win32 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|Win32.Build.0 = Release|Win32 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|x64.ActiveCfg = Release|x64 + {D75E6142-D7D7-4F85-9D58-77BCD6B64F99}.Release|x64.Build.0 = Release|x64 ++ {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|ARM64.Build.0 = Debug|ARM64 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|Win32.ActiveCfg = Debug|Win32 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|Win32.Build.0 = Debug|Win32 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|x64.ActiveCfg = Debug|x64 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Debug|x64.Build.0 = Debug|x64 ++ {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|ARM64.Build.0 = Release|ARM64 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|Win32.ActiveCfg = Release|Win32 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|Win32.Build.0 = Release|Win32 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|x64.ActiveCfg = Release|x64 + {F49E86B3-8F94-4CDE-95A3-B1D895A3D86F}.Release|x64.Build.0 = Release|x64 ++ {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|ARM64.Build.0 = Debug|ARM64 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|Win32.ActiveCfg = Debug|Win32 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|Win32.Build.0 = Debug|Win32 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|x64.ActiveCfg = Debug|x64 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Debug|x64.Build.0 = Debug|x64 ++ {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|ARM64.Build.0 = Release|ARM64 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|Win32.ActiveCfg = Release|Win32 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|Win32.Build.0 = Release|Win32 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|x64.ActiveCfg = Release|x64 + {5496E6C5-E041-4FE5-9414-4A0121212452}.Release|x64.Build.0 = Release|x64 ++ {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|ARM64.Build.0 = Debug|ARM64 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|Win32.ActiveCfg = Debug|Win32 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|Win32.Build.0 = Debug|Win32 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|x64.ActiveCfg = Debug|x64 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Debug|x64.Build.0 = Debug|x64 ++ {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|ARM64.Build.0 = Release|ARM64 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|Win32.ActiveCfg = Release|Win32 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|Win32.Build.0 = Release|Win32 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|x64.ActiveCfg = Release|x64 + {5E26BF9C-6CBE-4A13-B5DF-229C738CE813}.Release|x64.Build.0 = Release|x64 ++ {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|ARM64.Build.0 = Debug|ARM64 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|Win32.ActiveCfg = Debug|Win32 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|Win32.Build.0 = Debug|Win32 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|x64.ActiveCfg = Debug|x64 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Debug|x64.Build.0 = Debug|x64 ++ {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|ARM64.Build.0 = Release|ARM64 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|Win32.ActiveCfg = Release|Win32 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|Win32.Build.0 = Release|Win32 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|x64.ActiveCfg = Release|x64 + {4501C9A9-EF51-43A8-A017-620B86BE4B14}.Release|x64.Build.0 = Release|x64 ++ {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|ARM64.Build.0 = Debug|ARM64 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|Win32.ActiveCfg = Debug|Win32 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|Win32.Build.0 = Debug|Win32 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|x64.ActiveCfg = Debug|x64 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Debug|x64.Build.0 = Debug|x64 ++ {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|ARM64.Build.0 = Release|ARM64 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|Win32.ActiveCfg = Release|Win32 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|Win32.Build.0 = Release|Win32 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|x64.ActiveCfg = Release|x64 + {56A453CE-2E66-4378-94C1-E5AA27B8941F}.Release|x64.Build.0 = Release|x64 ++ {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|ARM64.Build.0 = Debug|ARM64 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|Win32.ActiveCfg = Debug|Win32 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|Win32.Build.0 = Debug|Win32 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|x64.ActiveCfg = Debug|x64 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Debug|x64.Build.0 = Debug|x64 ++ {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|ARM64.Build.0 = Release|ARM64 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|Win32.ActiveCfg = Release|Win32 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|Win32.Build.0 = Release|Win32 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|x64.ActiveCfg = Release|x64 + {B35E3F13-8512-4DF1-8B85-22F1A041F1E7}.Release|x64.Build.0 = Release|x64 ++ {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|ARM64.Build.0 = Debug|ARM64 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|Win32.ActiveCfg = Debug|Win32 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|Win32.Build.0 = Debug|Win32 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|x64.ActiveCfg = Debug|x64 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Debug|x64.Build.0 = Debug|x64 ++ {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|ARM64.Build.0 = Release|ARM64 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|Win32.ActiveCfg = Release|Win32 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|Win32.Build.0 = Release|Win32 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|x64.ActiveCfg = Release|x64 + {E8C21063-72AF-49EA-A1BD-D9B7A42D3FB9}.Release|x64.Build.0 = Release|x64 ++ {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|ARM64.Build.0 = Debug|ARM64 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|Win32.ActiveCfg = Debug|Win32 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|Win32.Build.0 = Debug|Win32 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|x64.ActiveCfg = Debug|x64 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Debug|x64.Build.0 = Debug|x64 ++ {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|ARM64.Build.0 = Release|ARM64 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|Win32.ActiveCfg = Release|Win32 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|Win32.Build.0 = Release|Win32 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|x64.ActiveCfg = Release|x64 + {3FB4F222-0CBD-4D15-B967-A2582254C31C}.Release|x64.Build.0 = Release|x64 ++ {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|ARM64.Build.0 = Debug|ARM64 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|Win32.ActiveCfg = Debug|Win32 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|Win32.Build.0 = Debug|Win32 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|x64.ActiveCfg = Debug|x64 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Debug|x64.Build.0 = Debug|x64 ++ {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|ARM64.Build.0 = Release|ARM64 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|Win32.ActiveCfg = Release|Win32 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|Win32.Build.0 = Release|Win32 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|x64.ActiveCfg = Release|x64 + {5E12295C-00AA-4078-8F39-BB563E650D86}.Release|x64.Build.0 = Release|x64 ++ {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|ARM64.Build.0 = Debug|ARM64 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|Win32.ActiveCfg = Debug|Win32 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|Win32.Build.0 = Debug|Win32 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|x64.ActiveCfg = Debug|x64 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Debug|x64.Build.0 = Debug|x64 ++ {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|ARM64.Build.0 = Release|ARM64 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|Win32.ActiveCfg = Release|Win32 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|Win32.Build.0 = Release|Win32 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|x64.ActiveCfg = Release|x64 + {E580BC14-0DC6-4D4E-B0EC-E0124812886F}.Release|x64.Build.0 = Release|x64 ++ {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|ARM64.Build.0 = Debug|ARM64 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|Win32.ActiveCfg = Debug|Win32 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|Win32.Build.0 = Debug|Win32 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|x64.ActiveCfg = Debug|x64 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Debug|x64.Build.0 = Debug|x64 ++ {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|ARM64.Build.0 = Release|ARM64 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|Win32.ActiveCfg = Release|Win32 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|Win32.Build.0 = Release|Win32 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|x64.ActiveCfg = Release|x64 + {194DD0B4-DE77-4697-B629-D6FF7DDCA65D}.Release|x64.Build.0 = Release|x64 ++ {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|ARM64.Build.0 = Debug|ARM64 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|Win32.ActiveCfg = Debug|Win32 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|Win32.Build.0 = Debug|Win32 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|x64.ActiveCfg = Debug|x64 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Debug|x64.Build.0 = Debug|x64 ++ {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|ARM64.Build.0 = Release|ARM64 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|Win32.ActiveCfg = Release|Win32 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|Win32.Build.0 = Release|Win32 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|x64.ActiveCfg = Release|x64 + {677A8D67-7853-47E6-AE8B-5F8B40129DF3}.Release|x64.Build.0 = Release|x64 ++ {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|ARM64.Build.0 = Debug|ARM64 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|Win32.ActiveCfg = Debug|Win32 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|Win32.Build.0 = Debug|Win32 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|x64.ActiveCfg = Debug|x64 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Debug|x64.Build.0 = Debug|x64 ++ {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|ARM64.Build.0 = Release|ARM64 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|Win32.ActiveCfg = Release|Win32 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|Win32.Build.0 = Release|Win32 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|x64.ActiveCfg = Release|x64 + {A203C619-D9AC-4E6A-A96B-A8C2480ABA2B}.Release|x64.Build.0 = Release|x64 ++ {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|ARM64.Build.0 = Debug|ARM64 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|Win32.ActiveCfg = Debug|Win32 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|Win32.Build.0 = Debug|Win32 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|x64.ActiveCfg = Debug|x64 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Debug|x64.Build.0 = Debug|x64 ++ {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|ARM64.Build.0 = Release|ARM64 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|Win32.ActiveCfg = Release|Win32 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|Win32.Build.0 = Release|Win32 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|x64.ActiveCfg = Release|x64 + {14963081-DA64-4F44-9F58-612E8C71E9F0}.Release|x64.Build.0 = Release|x64 ++ {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|ARM64.Build.0 = Debug|ARM64 + {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|Win32.Build.0 = Debug|Win32 + {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|x64.ActiveCfg = Debug|x64 + {8B188707-F923-4055-B92B-0E8D909460A9}.Debug|x64.Build.0 = Debug|x64 ++ {8B188707-F923-4055-B92B-0E8D909460A9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8B188707-F923-4055-B92B-0E8D909460A9}.Release|ARM64.Build.0 = Release|ARM64 + {8B188707-F923-4055-B92B-0E8D909460A9}.Release|Win32.ActiveCfg = Release|Win32 + {8B188707-F923-4055-B92B-0E8D909460A9}.Release|Win32.Build.0 = Release|Win32 + {8B188707-F923-4055-B92B-0E8D909460A9}.Release|x64.ActiveCfg = Release|x64 + {8B188707-F923-4055-B92B-0E8D909460A9}.Release|x64.Build.0 = Release|x64 ++ {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|ARM64.Build.0 = Debug|ARM64 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|Win32.ActiveCfg = Debug|Win32 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|Win32.Build.0 = Debug|Win32 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|x64.ActiveCfg = Debug|x64 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Debug|x64.Build.0 = Debug|x64 ++ {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|ARM64.Build.0 = Release|ARM64 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|Win32.ActiveCfg = Release|Win32 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|Win32.Build.0 = Release|Win32 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|x64.ActiveCfg = Release|x64 + {937CA6A8-068B-4E11-A6C7-DBE3783600C4}.Release|x64.Build.0 = Release|x64 ++ {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|ARM64.Build.0 = Debug|ARM64 + {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|Win32.ActiveCfg = Debug|Win32 + {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|Win32.Build.0 = Debug|Win32 + {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|x64.ActiveCfg = Debug|x64 + {75CB1254-66B7-40B0-83E1-146C82043392}.Debug|x64.Build.0 = Debug|x64 ++ {75CB1254-66B7-40B0-83E1-146C82043392}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {75CB1254-66B7-40B0-83E1-146C82043392}.Release|ARM64.Build.0 = Release|ARM64 + {75CB1254-66B7-40B0-83E1-146C82043392}.Release|Win32.ActiveCfg = Release|Win32 + {75CB1254-66B7-40B0-83E1-146C82043392}.Release|Win32.Build.0 = Release|Win32 + {75CB1254-66B7-40B0-83E1-146C82043392}.Release|x64.ActiveCfg = Release|x64 + {75CB1254-66B7-40B0-83E1-146C82043392}.Release|x64.Build.0 = Release|x64 ++ {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|ARM64.Build.0 = Debug|ARM64 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|Win32.ActiveCfg = Debug|Win32 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|Win32.Build.0 = Debug|Win32 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|x64.ActiveCfg = Debug|x64 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Debug|x64.Build.0 = Debug|x64 ++ {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|ARM64.Build.0 = Release|ARM64 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|Win32.ActiveCfg = Release|Win32 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|Win32.Build.0 = Release|Win32 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|x64.ActiveCfg = Release|x64 + {8E9FE9AB-FDF6-412C-997E-1926F45BDD85}.Release|x64.Build.0 = Release|x64 ++ {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|ARM64.Build.0 = Debug|ARM64 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|Win32.ActiveCfg = Debug|Win32 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|Win32.Build.0 = Debug|Win32 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|x64.ActiveCfg = Debug|x64 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Debug|x64.Build.0 = Debug|x64 ++ {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|ARM64.Build.0 = Release|ARM64 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|Win32.ActiveCfg = Release|Win32 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|Win32.Build.0 = Release|Win32 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|x64.ActiveCfg = Release|x64 + {502DB345-C8D1-4555-87B2-39E890E9EA4E}.Release|x64.Build.0 = Release|x64 ++ {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|ARM64.Build.0 = Debug|ARM64 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|Win32.ActiveCfg = Debug|Win32 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|Win32.Build.0 = Debug|Win32 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|x64.ActiveCfg = Debug|x64 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Debug|x64.Build.0 = Debug|x64 ++ {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|ARM64.Build.0 = Release|ARM64 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|Win32.ActiveCfg = Release|Win32 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|Win32.Build.0 = Release|Win32 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|x64.ActiveCfg = Release|x64 + {C47034AB-1F38-43EC-9EE5-FF8D51F5C39B}.Release|x64.Build.0 = Release|x64 ++ {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|ARM64.Build.0 = Debug|ARM64 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|Win32.ActiveCfg = Debug|Win32 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|Win32.Build.0 = Debug|Win32 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|x64.ActiveCfg = Debug|x64 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Debug|x64.Build.0 = Debug|x64 ++ {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|ARM64.Build.0 = Release|ARM64 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|Win32.ActiveCfg = Release|Win32 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|Win32.Build.0 = Release|Win32 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|x64.ActiveCfg = Release|x64 + {BF983093-3FD9-457F-8DE1-1F50B92536C4}.Release|x64.Build.0 = Release|x64 ++ {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|ARM64.Build.0 = Debug|ARM64 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|Win32.ActiveCfg = Debug|Win32 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|Win32.Build.0 = Debug|Win32 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|x64.ActiveCfg = Debug|x64 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Debug|x64.Build.0 = Debug|x64 ++ {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|ARM64.Build.0 = Release|ARM64 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|Win32.ActiveCfg = Release|Win32 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|Win32.Build.0 = Release|Win32 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|x64.ActiveCfg = Release|x64 + {8772B3A3-F33A-4174-8006-C72DC40DE189}.Release|x64.Build.0 = Release|x64 ++ {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|ARM64.Build.0 = Debug|ARM64 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|Win32.ActiveCfg = Debug|Win32 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|Win32.Build.0 = Debug|Win32 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|x64.ActiveCfg = Debug|x64 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Debug|x64.Build.0 = Debug|x64 ++ {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|ARM64.Build.0 = Release|ARM64 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|Win32.ActiveCfg = Release|Win32 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|Win32.Build.0 = Release|Win32 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|x64.ActiveCfg = Release|x64 + {5CE429F3-E82C-42A8-A235-EDA309B34A47}.Release|x64.Build.0 = Release|x64 ++ {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|ARM64.Build.0 = Debug|ARM64 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|Win32.ActiveCfg = Debug|Win32 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|Win32.Build.0 = Debug|Win32 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|x64.ActiveCfg = Debug|x64 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Debug|x64.Build.0 = Debug|x64 ++ {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|ARM64.Build.0 = Release|ARM64 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|Win32.ActiveCfg = Release|Win32 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|Win32.Build.0 = Release|Win32 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|x64.ActiveCfg = Release|x64 + {3C4EEB75-4F9C-4016-AC37-21EF5BC87C67}.Release|x64.Build.0 = Release|x64 ++ {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|ARM64.Build.0 = Debug|ARM64 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|Win32.ActiveCfg = Debug|Win32 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|Win32.Build.0 = Debug|Win32 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|x64.ActiveCfg = Debug|x64 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Debug|x64.Build.0 = Debug|x64 ++ {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|ARM64.Build.0 = Release|ARM64 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|Win32.ActiveCfg = Release|Win32 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|Win32.Build.0 = Release|Win32 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|x64.ActiveCfg = Release|x64 + {7C43699D-0EC4-4776-8901-F78D84CC464F}.Release|x64.Build.0 = Release|x64 ++ {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|ARM64.Build.0 = Debug|ARM64 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|Win32.ActiveCfg = Debug|Win32 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|Win32.Build.0 = Debug|Win32 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|x64.ActiveCfg = Debug|x64 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Debug|x64.Build.0 = Debug|x64 ++ {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|ARM64.Build.0 = Release|ARM64 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|Win32.ActiveCfg = Release|Win32 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|Win32.Build.0 = Release|Win32 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|x64.ActiveCfg = Release|x64 + {1AC592D5-4F5B-4224-B36F-F43914891A54}.Release|x64.Build.0 = Release|x64 ++ {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|ARM64.Build.0 = Debug|ARM64 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|Win32.ActiveCfg = Debug|Win32 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|Win32.Build.0 = Debug|Win32 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|x64.ActiveCfg = Debug|x64 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Debug|x64.Build.0 = Debug|x64 ++ {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|ARM64.Build.0 = Release|ARM64 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|Win32.ActiveCfg = Release|Win32 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|Win32.Build.0 = Release|Win32 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|x64.ActiveCfg = Release|x64 + {555FE755-B744-4C13-9A7D-0F9D8FDEC132}.Release|x64.Build.0 = Release|x64 ++ {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|ARM64.Build.0 = Debug|ARM64 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|Win32.ActiveCfg = Debug|Win32 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|Win32.Build.0 = Debug|Win32 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|x64.ActiveCfg = Debug|x64 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Debug|x64.Build.0 = Debug|x64 ++ {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|ARM64.Build.0 = Release|ARM64 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|Win32.ActiveCfg = Release|Win32 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|Win32.Build.0 = Release|Win32 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|x64.ActiveCfg = Release|x64 + {6707C818-9BC2-4E4D-85DB-374C8CAA491E}.Release|x64.Build.0 = Release|x64 ++ {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|ARM64.Build.0 = Debug|ARM64 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|Win32.ActiveCfg = Debug|Win32 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|Win32.Build.0 = Debug|Win32 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|x64.ActiveCfg = Debug|x64 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Debug|x64.Build.0 = Debug|x64 ++ {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|ARM64.Build.0 = Release|ARM64 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|Win32.ActiveCfg = Release|Win32 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|Win32.Build.0 = Release|Win32 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|x64.ActiveCfg = Release|x64 + {B5534C9D-9886-44DE-920B-29F0F1C9DD28}.Release|x64.Build.0 = Release|x64 ++ {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|ARM64.Build.0 = Debug|ARM64 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|Win32.ActiveCfg = Debug|Win32 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|Win32.Build.0 = Debug|Win32 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|x64.ActiveCfg = Debug|x64 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Debug|x64.Build.0 = Debug|x64 ++ {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|ARM64.Build.0 = Release|ARM64 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|Win32.ActiveCfg = Release|Win32 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|Win32.Build.0 = Release|Win32 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|x64.ActiveCfg = Release|x64 + {800B208B-50A2-48B7-BA68-DC1CC6F42D8C}.Release|x64.Build.0 = Release|x64 ++ {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|ARM64.Build.0 = Debug|ARM64 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|Win32.ActiveCfg = Debug|Win32 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|Win32.Build.0 = Debug|Win32 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|x64.ActiveCfg = Debug|x64 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Debug|x64.Build.0 = Debug|x64 ++ {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|ARM64.Build.0 = Release|ARM64 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|Win32.ActiveCfg = Release|Win32 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|Win32.Build.0 = Release|Win32 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|x64.ActiveCfg = Release|x64 + {5D159FE5-DE77-4EDF-974E-D4FF448BD717}.Release|x64.Build.0 = Release|x64 ++ {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|ARM64.Build.0 = Debug|ARM64 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|Win32.ActiveCfg = Debug|Win32 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|Win32.Build.0 = Debug|Win32 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|x64.ActiveCfg = Debug|x64 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Debug|x64.Build.0 = Debug|x64 ++ {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|ARM64.Build.0 = Release|ARM64 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|Win32.ActiveCfg = Release|Win32 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|Win32.Build.0 = Release|Win32 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|x64.ActiveCfg = Release|x64 + {211B1F3D-33CA-4DB0-883A-203CF6402EFA}.Release|x64.Build.0 = Release|x64 ++ {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|ARM64.Build.0 = Debug|ARM64 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|Win32.ActiveCfg = Debug|Win32 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|Win32.Build.0 = Debug|Win32 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|x64.ActiveCfg = Debug|x64 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Debug|x64.Build.0 = Debug|x64 ++ {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|ARM64.Build.0 = Release|ARM64 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|Win32.ActiveCfg = Release|Win32 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|Win32.Build.0 = Release|Win32 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|x64.ActiveCfg = Release|x64 + {A84C7B38-C92A-4A05-9588-4D1FB71B1BE0}.Release|x64.Build.0 = Release|x64 ++ {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|ARM64.Build.0 = Debug|ARM64 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|Win32.ActiveCfg = Debug|Win32 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|Win32.Build.0 = Debug|Win32 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|x64.ActiveCfg = Debug|x64 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Debug|x64.Build.0 = Debug|x64 ++ {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|ARM64.Build.0 = Release|ARM64 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|Win32.ActiveCfg = Release|Win32 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|Win32.Build.0 = Release|Win32 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|x64.ActiveCfg = Release|x64 + {F5819E2D-1A7F-460E-B220-328A83A8FD2C}.Release|x64.Build.0 = Release|x64 ++ {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|ARM64.Build.0 = Debug|ARM64 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|Win32.ActiveCfg = Debug|Win32 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|Win32.Build.0 = Debug|Win32 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|x64.ActiveCfg = Debug|x64 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Debug|x64.Build.0 = Debug|x64 ++ {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|ARM64.Build.0 = Release|ARM64 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|Win32.ActiveCfg = Release|Win32 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|Win32.Build.0 = Release|Win32 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|x64.ActiveCfg = Release|x64 + {35798C92-CC45-4AC5-A33E-8D82F7CF847E}.Release|x64.Build.0 = Release|x64 ++ {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|ARM64.Build.0 = Debug|ARM64 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|Win32.ActiveCfg = Debug|Win32 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|Win32.Build.0 = Debug|Win32 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|x64.ActiveCfg = Debug|x64 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Debug|x64.Build.0 = Debug|x64 ++ {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|ARM64.Build.0 = Release|ARM64 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|Win32.ActiveCfg = Release|Win32 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|Win32.Build.0 = Release|Win32 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|x64.ActiveCfg = Release|x64 + {28734BFB-4C00-455D-96A7-2CA6C0D598E1}.Release|x64.Build.0 = Release|x64 ++ {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|ARM64.Build.0 = Debug|ARM64 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|Win32.ActiveCfg = Debug|Win32 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|Win32.Build.0 = Debug|Win32 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|x64.ActiveCfg = Debug|x64 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Debug|x64.Build.0 = Debug|x64 ++ {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|ARM64.Build.0 = Release|ARM64 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|Win32.ActiveCfg = Release|Win32 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|Win32.Build.0 = Release|Win32 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|x64.ActiveCfg = Release|x64 + {1738FE1E-34B4-4657-AE5A-94CA8A31A6E9}.Release|x64.Build.0 = Release|x64 ++ {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|ARM64.Build.0 = Debug|ARM64 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|Win32.ActiveCfg = Debug|Win32 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|Win32.Build.0 = Debug|Win32 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|x64.ActiveCfg = Debug|x64 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Debug|x64.Build.0 = Debug|x64 ++ {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|ARM64.Build.0 = Release|ARM64 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|Win32.ActiveCfg = Release|Win32 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|Win32.Build.0 = Release|Win32 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|x64.ActiveCfg = Release|x64 + {A6116D1B-1A43-4F56-AF6B-DF79D7A28317}.Release|x64.Build.0 = Release|x64 ++ {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|ARM64.Build.0 = Debug|ARM64 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|Win32.ActiveCfg = Debug|Win32 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|Win32.Build.0 = Debug|Win32 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|x64.ActiveCfg = Debug|x64 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Debug|x64.Build.0 = Debug|x64 ++ {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|ARM64.Build.0 = Release|ARM64 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|Win32.ActiveCfg = Release|Win32 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|Win32.Build.0 = Release|Win32 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|x64.ActiveCfg = Release|x64 + {40607BCA-7DC6-400F-BC4C-96A9AB208475}.Release|x64.Build.0 = Release|x64 ++ {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|ARM64.Build.0 = Debug|ARM64 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|Win32.ActiveCfg = Debug|Win32 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|Win32.Build.0 = Debug|Win32 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|x64.ActiveCfg = Debug|x64 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Debug|x64.Build.0 = Debug|x64 ++ {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|ARM64.Build.0 = Release|ARM64 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|Win32.ActiveCfg = Release|Win32 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|Win32.Build.0 = Release|Win32 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|x64.ActiveCfg = Release|x64 + {B59EE041-28C6-4919-80F9-52249A799B7B}.Release|x64.Build.0 = Release|x64 ++ {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|ARM64.Build.0 = Debug|ARM64 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|Win32.ActiveCfg = Debug|Win32 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|Win32.Build.0 = Debug|Win32 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|x64.ActiveCfg = Debug|x64 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Debug|x64.Build.0 = Debug|x64 ++ {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|ARM64.Build.0 = Release|ARM64 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|Win32.ActiveCfg = Release|Win32 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|Win32.Build.0 = Release|Win32 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|x64.ActiveCfg = Release|x64 + {0CAA738A-C56B-4F54-A8A3-B27C7220FC75}.Release|x64.Build.0 = Release|x64 ++ {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|ARM64.Build.0 = Debug|ARM64 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|Win32.ActiveCfg = Debug|Win32 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|Win32.Build.0 = Debug|Win32 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|x64.ActiveCfg = Debug|x64 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Debug|x64.Build.0 = Debug|x64 ++ {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|ARM64.Build.0 = Release|ARM64 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|Win32.ActiveCfg = Release|Win32 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|Win32.Build.0 = Release|Win32 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|x64.ActiveCfg = Release|x64 + {950ACA47-2721-4D2E-8F19-C48759F1E492}.Release|x64.Build.0 = Release|x64 ++ {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|ARM64.Build.0 = Debug|ARM64 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|Win32.ActiveCfg = Debug|Win32 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|Win32.Build.0 = Debug|Win32 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|x64.ActiveCfg = Debug|x64 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Debug|x64.Build.0 = Debug|x64 ++ {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|ARM64.Build.0 = Release|ARM64 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|Win32.ActiveCfg = Release|Win32 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|Win32.Build.0 = Release|Win32 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|x64.ActiveCfg = Release|x64 + {4C225734-B4C0-4D1D-94F6-2CC48144F12D}.Release|x64.Build.0 = Release|x64 ++ {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|ARM64.Build.0 = Debug|ARM64 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|Win32.ActiveCfg = Debug|Win32 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|Win32.Build.0 = Debug|Win32 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|x64.ActiveCfg = Debug|x64 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Debug|x64.Build.0 = Debug|x64 ++ {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|ARM64.Build.0 = Release|ARM64 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|Win32.ActiveCfg = Release|Win32 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|Win32.Build.0 = Release|Win32 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|x64.ActiveCfg = Release|x64 + {487DF829-9D13-4C6F-AA24-2C8A4115B657}.Release|x64.Build.0 = Release|x64 ++ {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|ARM64.Build.0 = Debug|ARM64 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|Win32.ActiveCfg = Debug|Win32 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|Win32.Build.0 = Debug|Win32 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|x64.ActiveCfg = Debug|x64 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Debug|x64.Build.0 = Debug|x64 ++ {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|ARM64.Build.0 = Release|ARM64 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|Win32.ActiveCfg = Release|Win32 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|Win32.Build.0 = Release|Win32 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|x64.ActiveCfg = Release|x64 + {DD8664D4-902B-493B-BAFA-E559100A2755}.Release|x64.Build.0 = Release|x64 ++ {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|ARM64.Build.0 = Debug|ARM64 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|Win32.ActiveCfg = Debug|Win32 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|Win32.Build.0 = Debug|Win32 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|x64.ActiveCfg = Debug|x64 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Debug|x64.Build.0 = Debug|x64 ++ {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|ARM64.Build.0 = Release|ARM64 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|Win32.ActiveCfg = Release|Win32 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|Win32.Build.0 = Release|Win32 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|x64.ActiveCfg = Release|x64 + {475193E3-1120-4D13-A9C1-C6B99558E44A}.Release|x64.Build.0 = Release|x64 ++ {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|ARM64.Build.0 = Debug|ARM64 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|Win32.ActiveCfg = Debug|Win32 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|Win32.Build.0 = Debug|Win32 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|x64.ActiveCfg = Debug|x64 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Debug|x64.Build.0 = Debug|x64 ++ {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|ARM64.Build.0 = Release|ARM64 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|Win32.ActiveCfg = Release|Win32 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|Win32.Build.0 = Release|Win32 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|x64.ActiveCfg = Release|x64 + {59759753-CAC1-4D61-9B98-E1DEDD2C3E69}.Release|x64.Build.0 = Release|x64 ++ {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|ARM64.Build.0 = Debug|ARM64 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|Win32.ActiveCfg = Debug|Win32 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|Win32.Build.0 = Debug|Win32 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|x64.ActiveCfg = Debug|x64 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Debug|x64.Build.0 = Debug|x64 ++ {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|ARM64.Build.0 = Release|ARM64 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|Win32.ActiveCfg = Release|Win32 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|Win32.Build.0 = Release|Win32 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|x64.ActiveCfg = Release|x64 + {4746E6DB-D6FB-4DDC-8B49-7F184231C15A}.Release|x64.Build.0 = Release|x64 ++ {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|ARM64.Build.0 = Debug|ARM64 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|Win32.ActiveCfg = Debug|Win32 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|Win32.Build.0 = Debug|Win32 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|x64.ActiveCfg = Debug|x64 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Debug|x64.Build.0 = Debug|x64 ++ {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|ARM64.Build.0 = Release|ARM64 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|Win32.ActiveCfg = Release|Win32 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|Win32.Build.0 = Release|Win32 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|x64.ActiveCfg = Release|x64 + {1F0FA3CB-10DD-4EEF-911D-D3D904B4EF4A}.Release|x64.Build.0 = Release|x64 ++ {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|ARM64.Build.0 = Debug|ARM64 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|Win32.ActiveCfg = Debug|Win32 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|Win32.Build.0 = Debug|Win32 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|x64.ActiveCfg = Debug|x64 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Debug|x64.Build.0 = Debug|x64 ++ {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|ARM64.Build.0 = Release|ARM64 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|Win32.ActiveCfg = Release|Win32 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|Win32.Build.0 = Release|Win32 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|x64.ActiveCfg = Release|x64 + {DE3219F6-E665-4A8E-A990-8BB9A929CCB7}.Release|x64.Build.0 = Release|x64 ++ {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|ARM64.Build.0 = Debug|ARM64 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|Win32.Build.0 = Debug|Win32 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|x64.ActiveCfg = Debug|x64 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Debug|x64.Build.0 = Debug|x64 ++ {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|ARM64.Build.0 = Release|ARM64 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|Win32.ActiveCfg = Release|Win32 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|Win32.Build.0 = Release|Win32 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|x64.ActiveCfg = Release|x64 + {E625B0EF-8AC1-489C-9E6B-EE249A507FC7}.Release|x64.Build.0 = Release|x64 ++ {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|ARM64.Build.0 = Debug|ARM64 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|Win32.ActiveCfg = Debug|Win32 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|Win32.Build.0 = Debug|Win32 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|x64.ActiveCfg = Debug|x64 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Debug|x64.Build.0 = Debug|x64 ++ {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|ARM64.Build.0 = Release|ARM64 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|Win32.ActiveCfg = Release|Win32 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|Win32.Build.0 = Release|Win32 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|x64.ActiveCfg = Release|x64 + {0188609D-EB9A-4B25-88C6-EB952B4E39E7}.Release|x64.Build.0 = Release|x64 ++ {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|ARM64.Build.0 = Debug|ARM64 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|Win32.ActiveCfg = Debug|Win32 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|Win32.Build.0 = Debug|Win32 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|x64.ActiveCfg = Debug|x64 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Debug|x64.Build.0 = Debug|x64 ++ {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|ARM64.Build.0 = Release|ARM64 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|Win32.ActiveCfg = Release|Win32 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|Win32.Build.0 = Release|Win32 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|x64.ActiveCfg = Release|x64 + {90B4302C-0A10-4987-A4DF-3F578D49CED2}.Release|x64.Build.0 = Release|x64 ++ {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|ARM64.Build.0 = Debug|ARM64 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|Win32.ActiveCfg = Debug|Win32 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|Win32.Build.0 = Debug|Win32 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|x64.ActiveCfg = Debug|x64 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Debug|x64.Build.0 = Debug|x64 ++ {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|ARM64.Build.0 = Release|ARM64 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|Win32.ActiveCfg = Release|Win32 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|Win32.Build.0 = Release|Win32 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|x64.ActiveCfg = Release|x64 + {13D31BD0-B598-4468-9AA2-5C5363DDB648}.Release|x64.Build.0 = Release|x64 ++ {85668C77-928A-49FB-9844-0E975140E32F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {85668C77-928A-49FB-9844-0E975140E32F}.Debug|ARM64.Build.0 = Debug|ARM64 + {85668C77-928A-49FB-9844-0E975140E32F}.Debug|Win32.ActiveCfg = Debug|Win32 + {85668C77-928A-49FB-9844-0E975140E32F}.Debug|Win32.Build.0 = Debug|Win32 + {85668C77-928A-49FB-9844-0E975140E32F}.Debug|x64.ActiveCfg = Debug|x64 + {85668C77-928A-49FB-9844-0E975140E32F}.Debug|x64.Build.0 = Debug|x64 ++ {85668C77-928A-49FB-9844-0E975140E32F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {85668C77-928A-49FB-9844-0E975140E32F}.Release|ARM64.Build.0 = Release|ARM64 + {85668C77-928A-49FB-9844-0E975140E32F}.Release|Win32.ActiveCfg = Release|Win32 + {85668C77-928A-49FB-9844-0E975140E32F}.Release|Win32.Build.0 = Release|Win32 + {85668C77-928A-49FB-9844-0E975140E32F}.Release|x64.ActiveCfg = Release|x64 + {85668C77-928A-49FB-9844-0E975140E32F}.Release|x64.Build.0 = Release|x64 ++ {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|ARM64.Build.0 = Debug|ARM64 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|Win32.ActiveCfg = Debug|Win32 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|Win32.Build.0 = Debug|Win32 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|x64.ActiveCfg = Debug|x64 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Debug|x64.Build.0 = Debug|x64 ++ {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|ARM64.Build.0 = Release|ARM64 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|Win32.ActiveCfg = Release|Win32 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|Win32.Build.0 = Release|Win32 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|x64.ActiveCfg = Release|x64 + {5D6BF8AC-E329-473C-8E66-020458740EC2}.Release|x64.Build.0 = Release|x64 ++ {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|ARM64.Build.0 = Debug|ARM64 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|Win32.ActiveCfg = Debug|Win32 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|Win32.Build.0 = Debug|Win32 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|x64.ActiveCfg = Debug|x64 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Debug|x64.Build.0 = Debug|x64 ++ {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|ARM64.Build.0 = Release|ARM64 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|Win32.ActiveCfg = Release|Win32 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|Win32.Build.0 = Release|Win32 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|x64.ActiveCfg = Release|x64 + {30690FC7-2E6D-493E-88D6-BF963BE8A8A2}.Release|x64.Build.0 = Release|x64 ++ {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|ARM64.Build.0 = Debug|ARM64 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|Win32.Build.0 = Debug|Win32 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|x64.ActiveCfg = Debug|x64 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Debug|x64.Build.0 = Debug|x64 ++ {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|ARM64.Build.0 = Release|ARM64 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|Win32.ActiveCfg = Release|Win32 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|Win32.Build.0 = Release|Win32 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|x64.ActiveCfg = Release|x64 + {BC4DC963-603B-4969-8141-ECAEFECD8D87}.Release|x64.Build.0 = Release|x64 ++ {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|ARM64.Build.0 = Debug|ARM64 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|Win32.ActiveCfg = Debug|Win32 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|Win32.Build.0 = Debug|Win32 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|x64.ActiveCfg = Debug|x64 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Debug|x64.Build.0 = Debug|x64 ++ {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|ARM64.Build.0 = Release|ARM64 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|Win32.ActiveCfg = Release|Win32 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|Win32.Build.0 = Release|Win32 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|x64.ActiveCfg = Release|x64 + {8502DF4F-A2FB-4033-AAA2-F4C707EB4AB3}.Release|x64.Build.0 = Release|x64 ++ {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|ARM64.Build.0 = Debug|ARM64 + {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|Win32.ActiveCfg = Debug|Win32 + {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|Win32.Build.0 = Debug|Win32 + {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|x64.ActiveCfg = Debug|x64 + {B2446452-DF81-48E3-8244-88A76549EE47}.Debug|x64.Build.0 = Debug|x64 ++ {B2446452-DF81-48E3-8244-88A76549EE47}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B2446452-DF81-48E3-8244-88A76549EE47}.Release|ARM64.Build.0 = Release|ARM64 + {B2446452-DF81-48E3-8244-88A76549EE47}.Release|Win32.ActiveCfg = Release|Win32 + {B2446452-DF81-48E3-8244-88A76549EE47}.Release|Win32.Build.0 = Release|Win32 + {B2446452-DF81-48E3-8244-88A76549EE47}.Release|x64.ActiveCfg = Release|x64 + {B2446452-DF81-48E3-8244-88A76549EE47}.Release|x64.Build.0 = Release|x64 ++ {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|ARM64.Build.0 = Debug|ARM64 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|Win32.ActiveCfg = Debug|Win32 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|Win32.Build.0 = Debug|Win32 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|x64.ActiveCfg = Debug|x64 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Debug|x64.Build.0 = Debug|x64 ++ {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|ARM64.Build.0 = Release|ARM64 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|Win32.ActiveCfg = Release|Win32 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|Win32.Build.0 = Release|Win32 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|x64.ActiveCfg = Release|x64 + {1E7722BB-1F2F-475A-8F12-36A6A4DB68C3}.Release|x64.Build.0 = Release|x64 ++ {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|ARM64.Build.0 = Debug|ARM64 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|Win32.ActiveCfg = Debug|Win32 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|Win32.Build.0 = Debug|Win32 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|x64.ActiveCfg = Debug|x64 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Debug|x64.Build.0 = Debug|x64 ++ {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|ARM64.Build.0 = Release|ARM64 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|Win32.ActiveCfg = Release|Win32 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|Win32.Build.0 = Release|Win32 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|x64.ActiveCfg = Release|x64 + {0E5EF163-AC52-4CD9-B680-F90DAE280DCE}.Release|x64.Build.0 = Release|x64 ++ {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|ARM64.Build.0 = Debug|ARM64 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|Win32.ActiveCfg = Debug|Win32 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|Win32.Build.0 = Debug|Win32 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|x64.ActiveCfg = Debug|x64 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Debug|x64.Build.0 = Debug|x64 ++ {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|ARM64.Build.0 = Release|ARM64 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|Win32.ActiveCfg = Release|Win32 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|Win32.Build.0 = Release|Win32 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|x64.ActiveCfg = Release|x64 + {15B97F60-510B-41E2-9B4F-80ED90497763}.Release|x64.Build.0 = Release|x64 ++ {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|ARM64.Build.0 = Debug|ARM64 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|Win32.ActiveCfg = Debug|Win32 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|Win32.Build.0 = Debug|Win32 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|x64.ActiveCfg = Debug|x64 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Debug|x64.Build.0 = Debug|x64 ++ {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|ARM64.Build.0 = Release|ARM64 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|Win32.ActiveCfg = Release|Win32 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|Win32.Build.0 = Release|Win32 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|x64.ActiveCfg = Release|x64 + {B095FDE3-CFD2-4612-8D99-202C275A2B76}.Release|x64.Build.0 = Release|x64 ++ {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|ARM64.Build.0 = Debug|ARM64 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|Win32.Build.0 = Debug|Win32 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|x64.ActiveCfg = Debug|x64 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Debug|x64.Build.0 = Debug|x64 ++ {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|ARM64.Build.0 = Release|ARM64 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|Win32.ActiveCfg = Release|Win32 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|Win32.Build.0 = Release|Win32 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|x64.ActiveCfg = Release|x64 + {8FA19AAE-38EF-42F9-BDD0-B77F08833068}.Release|x64.Build.0 = Release|x64 ++ {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|ARM64.Build.0 = Debug|ARM64 + {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|Win32.ActiveCfg = Debug|Win32 + {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|Win32.Build.0 = Debug|Win32 + {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|x64.ActiveCfg = Debug|x64 + {896E9492-0D80-4372-B385-1E5ACB805604}.Debug|x64.Build.0 = Debug|x64 ++ {896E9492-0D80-4372-B385-1E5ACB805604}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {896E9492-0D80-4372-B385-1E5ACB805604}.Release|ARM64.Build.0 = Release|ARM64 + {896E9492-0D80-4372-B385-1E5ACB805604}.Release|Win32.ActiveCfg = Release|Win32 + {896E9492-0D80-4372-B385-1E5ACB805604}.Release|Win32.Build.0 = Release|Win32 + {896E9492-0D80-4372-B385-1E5ACB805604}.Release|x64.ActiveCfg = Release|x64 + {896E9492-0D80-4372-B385-1E5ACB805604}.Release|x64.Build.0 = Release|x64 ++ {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|ARM64.Build.0 = Debug|ARM64 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|Win32.ActiveCfg = Debug|Win32 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|Win32.Build.0 = Debug|Win32 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|x64.ActiveCfg = Debug|x64 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Debug|x64.Build.0 = Debug|x64 ++ {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|ARM64.Build.0 = Release|ARM64 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|Win32.ActiveCfg = Release|Win32 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|Win32.Build.0 = Release|Win32 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|x64.ActiveCfg = Release|x64 + {0414F249-0D60-46C7-B70E-16FD9D25C8D7}.Release|x64.Build.0 = Release|x64 ++ {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|ARM64.Build.0 = Debug|ARM64 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|Win32.ActiveCfg = Debug|Win32 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|Win32.Build.0 = Debug|Win32 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|x64.ActiveCfg = Debug|x64 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Debug|x64.Build.0 = Debug|x64 ++ {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|ARM64.Build.0 = Release|ARM64 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|Win32.ActiveCfg = Release|Win32 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|Win32.Build.0 = Release|Win32 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|x64.ActiveCfg = Release|x64 + {2DE033B4-1CD2-44C0-A824-09AFCE213C42}.Release|x64.Build.0 = Release|x64 ++ {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|ARM64.Build.0 = Debug|ARM64 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|Win32.ActiveCfg = Debug|Win32 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|Win32.Build.0 = Debug|Win32 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|x64.ActiveCfg = Debug|x64 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Debug|x64.Build.0 = Debug|x64 ++ {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|ARM64.Build.0 = Release|ARM64 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|Win32.ActiveCfg = Release|Win32 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|Win32.Build.0 = Release|Win32 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|x64.ActiveCfg = Release|x64 + {D52FB1F4-FFF9-4546-B691-3EBEEB982E5D}.Release|x64.Build.0 = Release|x64 ++ {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|ARM64.Build.0 = Debug|ARM64 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|Win32.ActiveCfg = Debug|Win32 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|Win32.Build.0 = Debug|Win32 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|x64.ActiveCfg = Debug|x64 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Debug|x64.Build.0 = Debug|x64 ++ {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|ARM64.Build.0 = Release|ARM64 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|Win32.ActiveCfg = Release|Win32 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|Win32.Build.0 = Release|Win32 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|x64.ActiveCfg = Release|x64 + {95B42F70-8AB5-4CC6-8C7D-A466F78CE119}.Release|x64.Build.0 = Release|x64 ++ {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|ARM64.Build.0 = Debug|ARM64 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|Win32.ActiveCfg = Debug|Win32 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|Win32.Build.0 = Debug|Win32 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|x64.ActiveCfg = Debug|x64 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Debug|x64.Build.0 = Debug|x64 ++ {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|ARM64.Build.0 = Release|ARM64 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|Win32.ActiveCfg = Release|Win32 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|Win32.Build.0 = Release|Win32 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|x64.ActiveCfg = Release|x64 + {F6B45CEC-339B-4153-A8A3-696EEF12C058}.Release|x64.Build.0 = Release|x64 ++ {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|ARM64.Build.0 = Debug|ARM64 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|Win32.ActiveCfg = Debug|Win32 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|Win32.Build.0 = Debug|Win32 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|x64.ActiveCfg = Debug|x64 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Debug|x64.Build.0 = Debug|x64 ++ {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|ARM64.Build.0 = Release|ARM64 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|Win32.ActiveCfg = Release|Win32 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|Win32.Build.0 = Release|Win32 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|x64.ActiveCfg = Release|x64 + {B49D5853-266E-4C8C-A05E-DEA26051D0F4}.Release|x64.Build.0 = Release|x64 ++ {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|ARM64.Build.0 = Debug|ARM64 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|Win32.ActiveCfg = Debug|Win32 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|Win32.Build.0 = Debug|Win32 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|x64.ActiveCfg = Debug|x64 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Debug|x64.Build.0 = Debug|x64 ++ {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|ARM64.Build.0 = Release|ARM64 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|Win32.ActiveCfg = Release|Win32 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|Win32.Build.0 = Release|Win32 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|x64.ActiveCfg = Release|x64 + {86A79561-EC9B-451D-A535-4066F0F0E722}.Release|x64.Build.0 = Release|x64 ++ {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|ARM64.Build.0 = Debug|ARM64 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|Win32.ActiveCfg = Debug|Win32 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|Win32.Build.0 = Debug|Win32 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|x64.ActiveCfg = Debug|x64 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Debug|x64.Build.0 = Debug|x64 ++ {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|ARM64.Build.0 = Release|ARM64 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|Win32.ActiveCfg = Release|Win32 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|Win32.Build.0 = Release|Win32 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|x64.ActiveCfg = Release|x64 + {F5A61A1F-C1C6-490B-90F6-28002FA0650E}.Release|x64.Build.0 = Release|x64 ++ {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|ARM64.Build.0 = Debug|ARM64 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|Win32.ActiveCfg = Debug|Win32 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|Win32.Build.0 = Debug|Win32 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|x64.ActiveCfg = Debug|x64 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Debug|x64.Build.0 = Debug|x64 ++ {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|ARM64.Build.0 = Release|ARM64 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|Win32.ActiveCfg = Release|Win32 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|Win32.Build.0 = Release|Win32 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|x64.ActiveCfg = Release|x64 + {0837655D-CF8A-4625-B9A2-C49E2B7FDC0C}.Release|x64.Build.0 = Release|x64 ++ {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|ARM64.Build.0 = Debug|ARM64 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|Win32.ActiveCfg = Debug|Win32 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|Win32.Build.0 = Debug|Win32 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|x64.ActiveCfg = Debug|x64 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Debug|x64.Build.0 = Debug|x64 ++ {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|ARM64.Build.0 = Release|ARM64 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|Win32.ActiveCfg = Release|Win32 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|Win32.Build.0 = Release|Win32 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|x64.ActiveCfg = Release|x64 + {E03D617B-BDA4-4EC8-A935-0D926E22E364}.Release|x64.Build.0 = Release|x64 ++ {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|ARM64.Build.0 = Debug|ARM64 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|Win32.ActiveCfg = Debug|Win32 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|Win32.Build.0 = Debug|Win32 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|x64.ActiveCfg = Debug|x64 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Debug|x64.Build.0 = Debug|x64 ++ {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|ARM64.Build.0 = Release|ARM64 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|Win32.ActiveCfg = Release|Win32 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|Win32.Build.0 = Release|Win32 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|x64.ActiveCfg = Release|x64 + {5633803A-9A09-4087-84B0-0C63D425F72C}.Release|x64.Build.0 = Release|x64 ++ {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|ARM64.Build.0 = Debug|ARM64 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|Win32.ActiveCfg = Debug|Win32 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|Win32.Build.0 = Debug|Win32 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|x64.ActiveCfg = Debug|x64 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Debug|x64.Build.0 = Debug|x64 ++ {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|ARM64.Build.0 = Release|ARM64 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|Win32.ActiveCfg = Release|Win32 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|Win32.Build.0 = Release|Win32 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|x64.ActiveCfg = Release|x64 + {BC1CE36E-B05B-41BB-8432-213DAF1568EA}.Release|x64.Build.0 = Release|x64 ++ {25413149-E392-470D-9B40-4FA285C71094}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {25413149-E392-470D-9B40-4FA285C71094}.Debug|ARM64.Build.0 = Debug|ARM64 + {25413149-E392-470D-9B40-4FA285C71094}.Debug|Win32.ActiveCfg = Debug|Win32 + {25413149-E392-470D-9B40-4FA285C71094}.Debug|Win32.Build.0 = Debug|Win32 + {25413149-E392-470D-9B40-4FA285C71094}.Debug|x64.ActiveCfg = Debug|x64 + {25413149-E392-470D-9B40-4FA285C71094}.Debug|x64.Build.0 = Debug|x64 ++ {25413149-E392-470D-9B40-4FA285C71094}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {25413149-E392-470D-9B40-4FA285C71094}.Release|ARM64.Build.0 = Release|ARM64 + {25413149-E392-470D-9B40-4FA285C71094}.Release|Win32.ActiveCfg = Release|Win32 + {25413149-E392-470D-9B40-4FA285C71094}.Release|Win32.Build.0 = Release|Win32 + {25413149-E392-470D-9B40-4FA285C71094}.Release|x64.ActiveCfg = Release|x64 + {25413149-E392-470D-9B40-4FA285C71094}.Release|x64.Build.0 = Release|x64 ++ {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|ARM64.Build.0 = Debug|ARM64 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|Win32.ActiveCfg = Debug|Win32 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|Win32.Build.0 = Debug|Win32 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|x64.ActiveCfg = Debug|x64 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Debug|x64.Build.0 = Debug|x64 ++ {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|ARM64.Build.0 = Release|ARM64 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|Win32.ActiveCfg = Release|Win32 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|Win32.Build.0 = Release|Win32 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|x64.ActiveCfg = Release|x64 + {D8143866-9AEF-4820-B712-89FF16876ABD}.Release|x64.Build.0 = Release|x64 ++ {004E35BF-4455-42C5-94DA-468597F76156}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {004E35BF-4455-42C5-94DA-468597F76156}.Debug|ARM64.Build.0 = Debug|ARM64 + {004E35BF-4455-42C5-94DA-468597F76156}.Debug|Win32.ActiveCfg = Debug|Win32 + {004E35BF-4455-42C5-94DA-468597F76156}.Debug|Win32.Build.0 = Debug|Win32 + {004E35BF-4455-42C5-94DA-468597F76156}.Debug|x64.ActiveCfg = Debug|x64 + {004E35BF-4455-42C5-94DA-468597F76156}.Debug|x64.Build.0 = Debug|x64 ++ {004E35BF-4455-42C5-94DA-468597F76156}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {004E35BF-4455-42C5-94DA-468597F76156}.Release|ARM64.Build.0 = Release|ARM64 + {004E35BF-4455-42C5-94DA-468597F76156}.Release|Win32.ActiveCfg = Release|Win32 + {004E35BF-4455-42C5-94DA-468597F76156}.Release|Win32.Build.0 = Release|Win32 + {004E35BF-4455-42C5-94DA-468597F76156}.Release|x64.ActiveCfg = Release|x64 + {004E35BF-4455-42C5-94DA-468597F76156}.Release|x64.Build.0 = Release|x64 ++ {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|ARM64.Build.0 = Debug|ARM64 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|Win32.ActiveCfg = Debug|Win32 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|Win32.Build.0 = Debug|Win32 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|x64.ActiveCfg = Debug|x64 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Debug|x64.Build.0 = Debug|x64 ++ {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|ARM64.Build.0 = Release|ARM64 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|Win32.ActiveCfg = Release|Win32 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|Win32.Build.0 = Release|Win32 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|x64.ActiveCfg = Release|x64 + {E4F400E9-A717-4D73-ACBB-29399DA25E7F}.Release|x64.Build.0 = Release|x64 ++ {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|ARM64.Build.0 = Debug|ARM64 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|Win32.ActiveCfg = Debug|Win32 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|Win32.Build.0 = Debug|Win32 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|x64.ActiveCfg = Debug|x64 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Debug|x64.Build.0 = Debug|x64 ++ {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|ARM64.Build.0 = Release|ARM64 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|Win32.ActiveCfg = Release|Win32 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|Win32.Build.0 = Release|Win32 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|x64.ActiveCfg = Release|x64 + {6114120D-110E-4C81-A7F0-63EC013C56D6}.Release|x64.Build.0 = Release|x64 ++ {165E9831-B8EF-4857-ACA4-261677950214}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {165E9831-B8EF-4857-ACA4-261677950214}.Debug|ARM64.Build.0 = Debug|ARM64 + {165E9831-B8EF-4857-ACA4-261677950214}.Debug|Win32.ActiveCfg = Debug|Win32 + {165E9831-B8EF-4857-ACA4-261677950214}.Debug|Win32.Build.0 = Debug|Win32 + {165E9831-B8EF-4857-ACA4-261677950214}.Debug|x64.ActiveCfg = Debug|x64 + {165E9831-B8EF-4857-ACA4-261677950214}.Debug|x64.Build.0 = Debug|x64 ++ {165E9831-B8EF-4857-ACA4-261677950214}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {165E9831-B8EF-4857-ACA4-261677950214}.Release|ARM64.Build.0 = Release|ARM64 + {165E9831-B8EF-4857-ACA4-261677950214}.Release|Win32.ActiveCfg = Release|Win32 + {165E9831-B8EF-4857-ACA4-261677950214}.Release|Win32.Build.0 = Release|Win32 + {165E9831-B8EF-4857-ACA4-261677950214}.Release|x64.ActiveCfg = Release|x64 + {165E9831-B8EF-4857-ACA4-261677950214}.Release|x64.Build.0 = Release|x64 ++ {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|ARM64.Build.0 = Debug|ARM64 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|Win32.ActiveCfg = Debug|Win32 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|Win32.Build.0 = Debug|Win32 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|x64.ActiveCfg = Debug|x64 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Debug|x64.Build.0 = Debug|x64 ++ {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|ARM64.Build.0 = Release|ARM64 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|Win32.ActiveCfg = Release|Win32 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|Win32.Build.0 = Release|Win32 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|x64.ActiveCfg = Release|x64 + {E3C009AF-69B7-4732-8509-DD72DBA757B1}.Release|x64.Build.0 = Release|x64 ++ {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|ARM64.Build.0 = Debug|ARM64 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|Win32.ActiveCfg = Debug|Win32 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|Win32.Build.0 = Debug|Win32 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|x64.ActiveCfg = Debug|x64 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Debug|x64.Build.0 = Debug|x64 ++ {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|ARM64.Build.0 = Release|ARM64 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|Win32.ActiveCfg = Release|Win32 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|Win32.Build.0 = Release|Win32 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|x64.ActiveCfg = Release|x64 + {32C0D774-5C56-46A3-B14A-625691E3B626}.Release|x64.Build.0 = Release|x64 ++ {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|ARM64.Build.0 = Debug|ARM64 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|Win32.ActiveCfg = Debug|Win32 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|Win32.Build.0 = Debug|Win32 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|x64.ActiveCfg = Debug|x64 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Debug|x64.Build.0 = Debug|x64 ++ {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|ARM64.Build.0 = Release|ARM64 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|Win32.ActiveCfg = Release|Win32 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|Win32.Build.0 = Release|Win32 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|x64.ActiveCfg = Release|x64 + {4C3B7646-88AC-4915-A92D-7C4096EDAE24}.Release|x64.Build.0 = Release|x64 ++ {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|ARM64.Build.0 = Debug|ARM64 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|Win32.ActiveCfg = Debug|Win32 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|Win32.Build.0 = Debug|Win32 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|x64.ActiveCfg = Debug|x64 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Debug|x64.Build.0 = Debug|x64 ++ {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|ARM64.Build.0 = Release|ARM64 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|Win32.ActiveCfg = Release|Win32 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|Win32.Build.0 = Release|Win32 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|x64.ActiveCfg = Release|x64 + {7905E464-EAC1-4DA4-962C-D20DAC6F3327}.Release|x64.Build.0 = Release|x64 ++ {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|ARM64.Build.0 = Debug|ARM64 + {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|Win32.ActiveCfg = Debug|Win32 + {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|Win32.Build.0 = Debug|Win32 + {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|x64.ActiveCfg = Debug|x64 + {123FA41A-5844-4ED0-821C-D465530818F9}.Debug|x64.Build.0 = Debug|x64 ++ {123FA41A-5844-4ED0-821C-D465530818F9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {123FA41A-5844-4ED0-821C-D465530818F9}.Release|ARM64.Build.0 = Release|ARM64 + {123FA41A-5844-4ED0-821C-D465530818F9}.Release|Win32.ActiveCfg = Release|Win32 + {123FA41A-5844-4ED0-821C-D465530818F9}.Release|Win32.Build.0 = Release|Win32 + {123FA41A-5844-4ED0-821C-D465530818F9}.Release|x64.ActiveCfg = Release|x64 + {123FA41A-5844-4ED0-821C-D465530818F9}.Release|x64.Build.0 = Release|x64 ++ {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|ARM64.Build.0 = Debug|ARM64 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|Win32.ActiveCfg = Debug|Win32 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|Win32.Build.0 = Debug|Win32 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|x64.ActiveCfg = Debug|x64 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Debug|x64.Build.0 = Debug|x64 ++ {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|ARM64.Build.0 = Release|ARM64 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|Win32.ActiveCfg = Release|Win32 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|Win32.Build.0 = Release|Win32 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|x64.ActiveCfg = Release|x64 + {A104B1FB-A0E0-4AA0-ABCC-D473054BB979}.Release|x64.Build.0 = Release|x64 ++ {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|ARM64.Build.0 = Debug|ARM64 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|Win32.ActiveCfg = Debug|Win32 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|Win32.Build.0 = Debug|Win32 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|x64.ActiveCfg = Debug|x64 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Debug|x64.Build.0 = Debug|x64 ++ {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|ARM64.Build.0 = Release|ARM64 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|Win32.ActiveCfg = Release|Win32 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|Win32.Build.0 = Release|Win32 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|x64.ActiveCfg = Release|x64 + {549E1B95-D3F2-4ABE-BD3D-BDE49E75B927}.Release|x64.Build.0 = Release|x64 ++ {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|ARM64.Build.0 = Debug|ARM64 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|Win32.ActiveCfg = Debug|Win32 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|Win32.Build.0 = Debug|Win32 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|x64.ActiveCfg = Debug|x64 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Debug|x64.Build.0 = Debug|x64 ++ {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|ARM64.Build.0 = Release|ARM64 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|Win32.ActiveCfg = Release|Win32 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|Win32.Build.0 = Release|Win32 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|x64.ActiveCfg = Release|x64 + {329EF5E3-BE7D-45EC-83CB-6F80D1D97FFB}.Release|x64.Build.0 = Release|x64 ++ {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|ARM64.Build.0 = Debug|ARM64 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|Win32.ActiveCfg = Debug|Win32 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|Win32.Build.0 = Debug|Win32 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|x64.ActiveCfg = Debug|x64 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Debug|x64.Build.0 = Debug|x64 ++ {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|ARM64.Build.0 = Release|ARM64 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|Win32.ActiveCfg = Release|Win32 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|Win32.Build.0 = Release|Win32 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|x64.ActiveCfg = Release|x64 + {92B49C5E-5F18-445C-B290-92AB03B27A6B}.Release|x64.Build.0 = Release|x64 ++ {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|ARM64.Build.0 = Debug|ARM64 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|Win32.ActiveCfg = Debug|Win32 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|Win32.Build.0 = Debug|Win32 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|x64.ActiveCfg = Debug|x64 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Debug|x64.Build.0 = Debug|x64 ++ {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|ARM64.Build.0 = Release|ARM64 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|Win32.ActiveCfg = Release|Win32 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|Win32.Build.0 = Release|Win32 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|x64.ActiveCfg = Release|x64 + {FC8A14DB-8D5B-4609-8838-675291632ADA}.Release|x64.Build.0 = Release|x64 ++ {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|ARM64.Build.0 = Debug|ARM64 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|Win32.ActiveCfg = Debug|Win32 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|Win32.Build.0 = Debug|Win32 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|x64.ActiveCfg = Debug|x64 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Debug|x64.Build.0 = Debug|x64 ++ {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|ARM64.Build.0 = Release|ARM64 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|Win32.ActiveCfg = Release|Win32 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|Win32.Build.0 = Release|Win32 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|x64.ActiveCfg = Release|x64 + {31423127-18E5-4C60-AFF9-AE36EFE1C511}.Release|x64.Build.0 = Release|x64 ++ {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|ARM64.Build.0 = Debug|ARM64 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|Win32.ActiveCfg = Debug|Win32 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|Win32.Build.0 = Debug|Win32 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|x64.ActiveCfg = Debug|x64 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Debug|x64.Build.0 = Debug|x64 ++ {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|ARM64.Build.0 = Release|ARM64 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|Win32.ActiveCfg = Release|Win32 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|Win32.Build.0 = Release|Win32 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|x64.ActiveCfg = Release|x64 + {C31DD6A8-7C99-40CE-B3BE-0F411525E1C6}.Release|x64.Build.0 = Release|x64 ++ {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|ARM64.Build.0 = Debug|ARM64 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|Win32.ActiveCfg = Debug|Win32 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|Win32.Build.0 = Debug|Win32 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|x64.ActiveCfg = Debug|x64 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Debug|x64.Build.0 = Debug|x64 ++ {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|ARM64.Build.0 = Release|ARM64 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|Win32.ActiveCfg = Release|Win32 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|Win32.Build.0 = Release|Win32 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|x64.ActiveCfg = Release|x64 + {0AE4CB71-FE7F-4969-BA2F-0C6ABF131229}.Release|x64.Build.0 = Release|x64 ++ {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|ARM64.Build.0 = Debug|ARM64 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|Win32.ActiveCfg = Debug|Win32 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|Win32.Build.0 = Debug|Win32 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|x64.ActiveCfg = Debug|x64 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Debug|x64.Build.0 = Debug|x64 ++ {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|ARM64.Build.0 = Release|ARM64 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|Win32.ActiveCfg = Release|Win32 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|Win32.Build.0 = Release|Win32 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|x64.ActiveCfg = Release|x64 + {F89148E0-94F1-4B8A-B25E-8484558047BC}.Release|x64.Build.0 = Release|x64 ++ {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|ARM64.Build.0 = Debug|ARM64 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|Win32.ActiveCfg = Debug|Win32 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|Win32.Build.0 = Debug|Win32 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|x64.ActiveCfg = Debug|x64 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Debug|x64.Build.0 = Debug|x64 ++ {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|ARM64.Build.0 = Release|ARM64 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|Win32.ActiveCfg = Release|Win32 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|Win32.Build.0 = Release|Win32 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|x64.ActiveCfg = Release|x64 + {517A628D-6961-4E71-B5EB-A85A1C1425BE}.Release|x64.Build.0 = Release|x64 ++ {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|ARM64.Build.0 = Debug|ARM64 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|Win32.ActiveCfg = Debug|Win32 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|Win32.Build.0 = Debug|Win32 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|x64.ActiveCfg = Debug|x64 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Debug|x64.Build.0 = Debug|x64 ++ {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|ARM64.Build.0 = Release|ARM64 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|Win32.ActiveCfg = Release|Win32 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|Win32.Build.0 = Release|Win32 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|x64.ActiveCfg = Release|x64 + {7D1AA370-21E1-4B03-B7AE-75B9654BBCFA}.Release|x64.Build.0 = Release|x64 ++ {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|ARM64.Build.0 = Debug|ARM64 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|Win32.ActiveCfg = Debug|Win32 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|Win32.Build.0 = Debug|Win32 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|x64.ActiveCfg = Debug|x64 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Debug|x64.Build.0 = Debug|x64 ++ {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|ARM64.Build.0 = Release|ARM64 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|Win32.ActiveCfg = Release|Win32 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|Win32.Build.0 = Release|Win32 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|x64.ActiveCfg = Release|x64 + {1D0C1AC1-D607-40ED-B4A0-F013F469D10F}.Release|x64.Build.0 = Release|x64 ++ {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|ARM64.Build.0 = Debug|ARM64 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|Win32.ActiveCfg = Debug|Win32 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|Win32.Build.0 = Debug|Win32 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|x64.ActiveCfg = Debug|x64 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Debug|x64.Build.0 = Debug|x64 ++ {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|ARM64.Build.0 = Release|ARM64 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|Win32.ActiveCfg = Release|Win32 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|Win32.Build.0 = Release|Win32 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|x64.ActiveCfg = Release|x64 + {02D6A1E4-E2C7-400B-9429-5E3D5D9480DA}.Release|x64.Build.0 = Release|x64 ++ {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|ARM64.Build.0 = Debug|ARM64 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|Win32.ActiveCfg = Debug|Win32 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|Win32.Build.0 = Debug|Win32 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|x64.ActiveCfg = Debug|x64 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Debug|x64.Build.0 = Debug|x64 ++ {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|ARM64.Build.0 = Release|ARM64 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|Win32.ActiveCfg = Release|Win32 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|Win32.Build.0 = Release|Win32 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|x64.ActiveCfg = Release|x64 + {589879B3-C37E-4EE9-A063-6FF419DC8CD1}.Release|x64.Build.0 = Release|x64 ++ {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|ARM64.Build.0 = Debug|ARM64 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|Win32.Build.0 = Debug|Win32 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|x64.ActiveCfg = Debug|x64 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Debug|x64.Build.0 = Debug|x64 ++ {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|ARM64.Build.0 = Release|ARM64 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|Win32.ActiveCfg = Release|Win32 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|Win32.Build.0 = Release|Win32 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|x64.ActiveCfg = Release|x64 + {2A6A40B9-0D5A-4457-A77B-831BD00772A7}.Release|x64.Build.0 = Release|x64 ++ {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|ARM64.Build.0 = Debug|ARM64 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|Win32.ActiveCfg = Debug|Win32 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|Win32.Build.0 = Debug|Win32 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|x64.ActiveCfg = Debug|x64 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Debug|x64.Build.0 = Debug|x64 ++ {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|ARM64.Build.0 = Release|ARM64 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|Win32.ActiveCfg = Release|Win32 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|Win32.Build.0 = Release|Win32 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|x64.ActiveCfg = Release|x64 + {CAC13AAE-ABF9-47E2-8DFB-08AA506FF50A}.Release|x64.Build.0 = Release|x64 ++ {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|ARM64.Build.0 = Debug|ARM64 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|Win32.ActiveCfg = Debug|Win32 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|Win32.Build.0 = Debug|Win32 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|x64.ActiveCfg = Debug|x64 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Debug|x64.Build.0 = Debug|x64 ++ {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|ARM64.Build.0 = Release|ARM64 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|Win32.ActiveCfg = Release|Win32 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|Win32.Build.0 = Release|Win32 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|x64.ActiveCfg = Release|x64 + {A18471D1-BEDD-464A-8581-6B128A828B07}.Release|x64.Build.0 = Release|x64 ++ {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|ARM64.Build.0 = Debug|ARM64 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|Win32.ActiveCfg = Debug|Win32 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|Win32.Build.0 = Debug|Win32 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|x64.ActiveCfg = Debug|x64 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Debug|x64.Build.0 = Debug|x64 ++ {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|ARM64.Build.0 = Release|ARM64 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|Win32.ActiveCfg = Release|Win32 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|Win32.Build.0 = Release|Win32 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|x64.ActiveCfg = Release|x64 + {7DED61E4-5229-4F03-8E52-165FE173E1A2}.Release|x64.Build.0 = Release|x64 ++ {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|ARM64.Build.0 = Debug|ARM64 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|Win32.ActiveCfg = Debug|Win32 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|Win32.Build.0 = Debug|Win32 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|x64.ActiveCfg = Debug|x64 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Debug|x64.Build.0 = Debug|x64 ++ {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|ARM64.Build.0 = Release|ARM64 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|Win32.ActiveCfg = Release|Win32 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|Win32.Build.0 = Release|Win32 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|x64.ActiveCfg = Release|x64 + {18D3EF75-6C36-46C0-B102-377B37F6C3E2}.Release|x64.Build.0 = Release|x64 ++ {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|ARM64.Build.0 = Debug|ARM64 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|Win32.Build.0 = Debug|Win32 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|x64.ActiveCfg = Debug|x64 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Debug|x64.Build.0 = Debug|x64 ++ {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|ARM64.Build.0 = Release|ARM64 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|Win32.ActiveCfg = Release|Win32 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|Win32.Build.0 = Release|Win32 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|x64.ActiveCfg = Release|x64 + {EC50393D-5E56-4F43-80F5-7C816AFFBEF0}.Release|x64.Build.0 = Release|x64 ++ {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|ARM64.Build.0 = Debug|ARM64 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|Win32.ActiveCfg = Debug|Win32 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|Win32.Build.0 = Debug|Win32 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|x64.ActiveCfg = Debug|x64 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Debug|x64.Build.0 = Debug|x64 ++ {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|ARM64.Build.0 = Release|ARM64 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|Win32.ActiveCfg = Release|Win32 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|Win32.Build.0 = Release|Win32 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|x64.ActiveCfg = Release|x64 + {FACD3CA8-671C-4A05-A7BF-B5D345F96337}.Release|x64.Build.0 = Release|x64 ++ {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|ARM64.Build.0 = Debug|ARM64 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|Win32.ActiveCfg = Debug|Win32 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|Win32.Build.0 = Debug|Win32 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|x64.ActiveCfg = Debug|x64 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Debug|x64.Build.0 = Debug|x64 ++ {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|ARM64.Build.0 = Release|ARM64 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|Win32.ActiveCfg = Release|Win32 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|Win32.Build.0 = Release|Win32 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|x64.ActiveCfg = Release|x64 + {E651C0A1-4574-43E9-897E-38E1A0B24F07}.Release|x64.Build.0 = Release|x64 ++ {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|ARM64.Build.0 = Debug|ARM64 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|Win32.ActiveCfg = Debug|Win32 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|Win32.Build.0 = Debug|Win32 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|x64.ActiveCfg = Debug|x64 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Debug|x64.Build.0 = Debug|x64 ++ {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|ARM64.Build.0 = Release|ARM64 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|Win32.ActiveCfg = Release|Win32 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|Win32.Build.0 = Release|Win32 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|x64.ActiveCfg = Release|x64 + {8AFFEB34-67F5-4AF5-ACBF-380FF5CDB689}.Release|x64.Build.0 = Release|x64 ++ {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|ARM64.Build.0 = Debug|ARM64 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|Win32.ActiveCfg = Debug|Win32 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|Win32.Build.0 = Debug|Win32 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|x64.ActiveCfg = Debug|x64 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Debug|x64.Build.0 = Debug|x64 ++ {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|ARM64.Build.0 = Release|ARM64 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|Win32.ActiveCfg = Release|Win32 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|Win32.Build.0 = Release|Win32 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|x64.ActiveCfg = Release|x64 + {C18CA7DE-01C1-4380-B5A4-E131C891476B}.Release|x64.Build.0 = Release|x64 ++ {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|ARM64.Build.0 = Debug|ARM64 + {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|Win32.ActiveCfg = Debug|Win32 + {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|Win32.Build.0 = Debug|Win32 + {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|x64.ActiveCfg = Debug|x64 + {9847994C-E043-4E29-9263-AB7C3E961878}.Debug|x64.Build.0 = Debug|x64 ++ {9847994C-E043-4E29-9263-AB7C3E961878}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9847994C-E043-4E29-9263-AB7C3E961878}.Release|ARM64.Build.0 = Release|ARM64 + {9847994C-E043-4E29-9263-AB7C3E961878}.Release|Win32.ActiveCfg = Release|Win32 + {9847994C-E043-4E29-9263-AB7C3E961878}.Release|Win32.Build.0 = Release|Win32 + {9847994C-E043-4E29-9263-AB7C3E961878}.Release|x64.ActiveCfg = Release|x64 + {9847994C-E043-4E29-9263-AB7C3E961878}.Release|x64.Build.0 = Release|x64 ++ {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|ARM64.Build.0 = Debug|ARM64 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|Win32.ActiveCfg = Debug|Win32 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|Win32.Build.0 = Debug|Win32 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|x64.ActiveCfg = Debug|x64 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Debug|x64.Build.0 = Debug|x64 ++ {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|ARM64.Build.0 = Release|ARM64 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|Win32.ActiveCfg = Release|Win32 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|Win32.Build.0 = Release|Win32 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|x64.ActiveCfg = Release|x64 + {CF89180E-B469-4E07-A2CB-01D0329A996D}.Release|x64.Build.0 = Release|x64 ++ {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|ARM64.Build.0 = Debug|ARM64 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|Win32.ActiveCfg = Debug|Win32 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|Win32.Build.0 = Debug|Win32 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|x64.ActiveCfg = Debug|x64 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Debug|x64.Build.0 = Debug|x64 ++ {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|ARM64.Build.0 = Release|ARM64 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|Win32.ActiveCfg = Release|Win32 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|Win32.Build.0 = Release|Win32 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|x64.ActiveCfg = Release|x64 + {96623DCD-5CBF-4D67-8619-34FD31900908}.Release|x64.Build.0 = Release|x64 ++ {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|ARM64.Build.0 = Debug|ARM64 + {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|Win32.ActiveCfg = Debug|Win32 + {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|Win32.Build.0 = Debug|Win32 + {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|x64.ActiveCfg = Debug|x64 + {6011B9C8-463C-464E-AB74-592218D89B41}.Debug|x64.Build.0 = Debug|x64 ++ {6011B9C8-463C-464E-AB74-592218D89B41}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {6011B9C8-463C-464E-AB74-592218D89B41}.Release|ARM64.Build.0 = Release|ARM64 + {6011B9C8-463C-464E-AB74-592218D89B41}.Release|Win32.ActiveCfg = Release|Win32 + {6011B9C8-463C-464E-AB74-592218D89B41}.Release|Win32.Build.0 = Release|Win32 + {6011B9C8-463C-464E-AB74-592218D89B41}.Release|x64.ActiveCfg = Release|x64 + {6011B9C8-463C-464E-AB74-592218D89B41}.Release|x64.Build.0 = Release|x64 ++ {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|ARM64.Build.0 = Debug|ARM64 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|Win32.ActiveCfg = Debug|Win32 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|Win32.Build.0 = Debug|Win32 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|x64.ActiveCfg = Debug|x64 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Debug|x64.Build.0 = Debug|x64 ++ {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|ARM64.Build.0 = Release|ARM64 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|Win32.ActiveCfg = Release|Win32 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|Win32.Build.0 = Release|Win32 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|x64.ActiveCfg = Release|x64 + {9FE67414-4051-4208-B4BB-B114EABE139A}.Release|x64.Build.0 = Release|x64 ++ {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|ARM64.Build.0 = Debug|ARM64 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|Win32.ActiveCfg = Debug|Win32 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|Win32.Build.0 = Debug|Win32 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|x64.ActiveCfg = Debug|x64 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Debug|x64.Build.0 = Debug|x64 ++ {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|ARM64.Build.0 = Release|ARM64 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|Win32.ActiveCfg = Release|Win32 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|Win32.Build.0 = Release|Win32 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|x64.ActiveCfg = Release|x64 + {BADABF03-AD0E-4717-9473-BD23B72FAA39}.Release|x64.Build.0 = Release|x64 ++ {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|ARM64.Build.0 = Debug|ARM64 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|Win32.ActiveCfg = Debug|Win32 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|Win32.Build.0 = Debug|Win32 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|x64.ActiveCfg = Debug|x64 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Debug|x64.Build.0 = Debug|x64 ++ {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|ARM64.Build.0 = Release|ARM64 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|Win32.ActiveCfg = Release|Win32 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|Win32.Build.0 = Release|Win32 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|x64.ActiveCfg = Release|x64 + {C93DF7EF-78AC-4E29-AA7C-A3600BB4AA76}.Release|x64.Build.0 = Release|x64 ++ {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|ARM64.Build.0 = Debug|ARM64 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|Win32.ActiveCfg = Debug|Win32 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|Win32.Build.0 = Debug|Win32 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|x64.ActiveCfg = Debug|x64 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Debug|x64.Build.0 = Debug|x64 ++ {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|ARM64.Build.0 = Release|ARM64 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|Win32.ActiveCfg = Release|Win32 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|Win32.Build.0 = Release|Win32 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|x64.ActiveCfg = Release|x64 + {D705539E-37BF-4CF1-B828-8D3D2665EB0F}.Release|x64.Build.0 = Release|x64 ++ {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|ARM64.Build.0 = Debug|ARM64 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|Win32.ActiveCfg = Debug|Win32 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|Win32.Build.0 = Debug|Win32 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|x64.ActiveCfg = Debug|x64 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Debug|x64.Build.0 = Debug|x64 ++ {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|ARM64.Build.0 = Release|ARM64 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|Win32.ActiveCfg = Release|Win32 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|Win32.Build.0 = Release|Win32 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|x64.ActiveCfg = Release|x64 + {225FE63C-6AA5-47CF-8605-F6D39854A042}.Release|x64.Build.0 = Release|x64 ++ {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|ARM64.Build.0 = Debug|ARM64 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|Win32.ActiveCfg = Debug|Win32 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|Win32.Build.0 = Debug|Win32 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|x64.ActiveCfg = Debug|x64 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Debug|x64.Build.0 = Debug|x64 ++ {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|ARM64.Build.0 = Release|ARM64 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|Win32.ActiveCfg = Release|Win32 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|Win32.Build.0 = Release|Win32 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|x64.ActiveCfg = Release|x64 + {9B757965-0ACF-4289-B7A0-08230AB59F79}.Release|x64.Build.0 = Release|x64 ++ {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|ARM64.Build.0 = Debug|ARM64 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|Win32.ActiveCfg = Debug|Win32 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|Win32.Build.0 = Debug|Win32 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|x64.ActiveCfg = Debug|x64 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Debug|x64.Build.0 = Debug|x64 ++ {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|ARM64.Build.0 = Release|ARM64 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|Win32.ActiveCfg = Release|Win32 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|Win32.Build.0 = Release|Win32 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|x64.ActiveCfg = Release|x64 + {EDA93DE7-D2C9-496A-A6E5-960A067D9772}.Release|x64.Build.0 = Release|x64 ++ {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|ARM64.Build.0 = Debug|ARM64 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|Win32.ActiveCfg = Debug|Win32 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|Win32.Build.0 = Debug|Win32 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|x64.ActiveCfg = Debug|x64 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Debug|x64.Build.0 = Debug|x64 ++ {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|ARM64.Build.0 = Release|ARM64 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|Win32.ActiveCfg = Release|Win32 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|Win32.Build.0 = Release|Win32 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|x64.ActiveCfg = Release|x64 + {11F4418F-D6C2-43E3-886D-5E60758B0B44}.Release|x64.Build.0 = Release|x64 ++ {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|ARM64.Build.0 = Debug|ARM64 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|Win32.ActiveCfg = Debug|Win32 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|Win32.Build.0 = Debug|Win32 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|x64.ActiveCfg = Debug|x64 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Debug|x64.Build.0 = Debug|x64 ++ {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|ARM64.Build.0 = Release|ARM64 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|Win32.ActiveCfg = Release|Win32 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|Win32.Build.0 = Release|Win32 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|x64.ActiveCfg = Release|x64 + {26C258B1-9751-487A-9971-FF1813E5BE9F}.Release|x64.Build.0 = Release|x64 ++ {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|ARM64.Build.0 = Debug|ARM64 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|Win32.ActiveCfg = Debug|Win32 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|Win32.Build.0 = Debug|Win32 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|x64.ActiveCfg = Debug|x64 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Debug|x64.Build.0 = Debug|x64 ++ {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|ARM64.Build.0 = Release|ARM64 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|Win32.ActiveCfg = Release|Win32 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|Win32.Build.0 = Release|Win32 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|x64.ActiveCfg = Release|x64 + {11AEFA4F-1EEF-46C7-B08D-E4F2213A45B7}.Release|x64.Build.0 = Release|x64 ++ {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|ARM64.Build.0 = Debug|ARM64 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|Win32.ActiveCfg = Debug|Win32 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|Win32.Build.0 = Debug|Win32 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|x64.ActiveCfg = Debug|x64 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Debug|x64.Build.0 = Debug|x64 ++ {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|ARM64.Build.0 = Release|ARM64 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|Win32.ActiveCfg = Release|Win32 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|Win32.Build.0 = Release|Win32 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|x64.ActiveCfg = Release|x64 + {9D5F7763-FF7B-4936-9861-819B5BDD9BA1}.Release|x64.Build.0 = Release|x64 ++ {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|ARM64.Build.0 = Debug|ARM64 + {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|Win32.ActiveCfg = Debug|Win32 + {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|Win32.Build.0 = Debug|Win32 + {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|x64.ActiveCfg = Debug|x64 + {A9AD6430-C35C-4A75-979C-391490242F86}.Debug|x64.Build.0 = Debug|x64 ++ {A9AD6430-C35C-4A75-979C-391490242F86}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {A9AD6430-C35C-4A75-979C-391490242F86}.Release|ARM64.Build.0 = Release|ARM64 + {A9AD6430-C35C-4A75-979C-391490242F86}.Release|Win32.ActiveCfg = Release|Win32 + {A9AD6430-C35C-4A75-979C-391490242F86}.Release|Win32.Build.0 = Release|Win32 + {A9AD6430-C35C-4A75-979C-391490242F86}.Release|x64.ActiveCfg = Release|x64 + {A9AD6430-C35C-4A75-979C-391490242F86}.Release|x64.Build.0 = Release|x64 ++ {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|ARM64.Build.0 = Debug|ARM64 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|Win32.ActiveCfg = Debug|Win32 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|Win32.Build.0 = Debug|Win32 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|x64.ActiveCfg = Debug|x64 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Debug|x64.Build.0 = Debug|x64 ++ {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|ARM64.Build.0 = Release|ARM64 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|Win32.ActiveCfg = Release|Win32 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|Win32.Build.0 = Release|Win32 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|x64.ActiveCfg = Release|x64 + {D68B75F1-A6F1-425D-9923-03D67AC62D54}.Release|x64.Build.0 = Release|x64 ++ {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|ARM64.Build.0 = Debug|ARM64 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|Win32.ActiveCfg = Debug|Win32 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|Win32.Build.0 = Debug|Win32 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|x64.ActiveCfg = Debug|x64 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Debug|x64.Build.0 = Debug|x64 ++ {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|ARM64.Build.0 = Release|ARM64 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|Win32.ActiveCfg = Release|Win32 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|Win32.Build.0 = Release|Win32 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|x64.ActiveCfg = Release|x64 + {943E7822-6E58-4F55-BD2F-A4A421D577E5}.Release|x64.Build.0 = Release|x64 ++ {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|ARM64.Build.0 = Debug|ARM64 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|Win32.ActiveCfg = Debug|Win32 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|Win32.Build.0 = Debug|Win32 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|x64.ActiveCfg = Debug|x64 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Debug|x64.Build.0 = Debug|x64 ++ {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|ARM64.Build.0 = Release|ARM64 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|Win32.ActiveCfg = Release|Win32 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|Win32.Build.0 = Release|Win32 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|x64.ActiveCfg = Release|x64 + {0B7831C0-52EF-4A09-AD37-5E6F4CBA28E4}.Release|x64.Build.0 = Release|x64 ++ {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|ARM64.Build.0 = Debug|ARM64 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|Win32.ActiveCfg = Debug|Win32 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|Win32.Build.0 = Debug|Win32 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|x64.ActiveCfg = Debug|x64 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Debug|x64.Build.0 = Debug|x64 ++ {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|ARM64.Build.0 = Release|ARM64 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|Win32.ActiveCfg = Release|Win32 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|Win32.Build.0 = Release|Win32 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|x64.ActiveCfg = Release|x64 + {2FD12E1A-40CD-4BC3-9C27-BD87B8F23A60}.Release|x64.Build.0 = Release|x64 ++ {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|ARM64.Build.0 = Debug|ARM64 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|Win32.ActiveCfg = Debug|Win32 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|Win32.Build.0 = Debug|Win32 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|x64.ActiveCfg = Debug|x64 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Debug|x64.Build.0 = Debug|x64 ++ {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|ARM64.Build.0 = Release|ARM64 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|Win32.ActiveCfg = Release|Win32 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|Win32.Build.0 = Release|Win32 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|x64.ActiveCfg = Release|x64 + {4E16E373-475F-4F4A-B394-D88D0532EF0E}.Release|x64.Build.0 = Release|x64 ++ {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|ARM64.Build.0 = Debug|ARM64 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|Win32.Build.0 = Debug|Win32 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|x64.ActiveCfg = Debug|x64 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Debug|x64.Build.0 = Debug|x64 ++ {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|ARM64.Build.0 = Release|ARM64 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|Win32.ActiveCfg = Release|Win32 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|Win32.Build.0 = Release|Win32 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|x64.ActiveCfg = Release|x64 + {0CB70131-B8C0-4780-B62E-776CD3F98BC7}.Release|x64.Build.0 = Release|x64 ++ {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|ARM64.Build.0 = Debug|ARM64 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|Win32.ActiveCfg = Debug|Win32 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|Win32.Build.0 = Debug|Win32 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|x64.ActiveCfg = Debug|x64 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Debug|x64.Build.0 = Debug|x64 ++ {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|ARM64.Build.0 = Release|ARM64 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|Win32.ActiveCfg = Release|Win32 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|Win32.Build.0 = Release|Win32 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|x64.ActiveCfg = Release|x64 + {9140227A-2900-4DE4-BD22-BFDD954F9BFB}.Release|x64.Build.0 = Release|x64 ++ {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|ARM64.Build.0 = Debug|ARM64 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|Win32.ActiveCfg = Debug|Win32 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|Win32.Build.0 = Debug|Win32 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|x64.ActiveCfg = Debug|x64 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Debug|x64.Build.0 = Debug|x64 ++ {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|ARM64.Build.0 = Release|ARM64 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|Win32.ActiveCfg = Release|Win32 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|Win32.Build.0 = Release|Win32 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|x64.ActiveCfg = Release|x64 + {9931ACC4-18E3-4251-A432-CD287DF0883C}.Release|x64.Build.0 = Release|x64 ++ {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|ARM64.Build.0 = Debug|ARM64 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|Win32.ActiveCfg = Debug|Win32 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|Win32.Build.0 = Debug|Win32 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|x64.ActiveCfg = Debug|x64 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Debug|x64.Build.0 = Debug|x64 ++ {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|ARM64.Build.0 = Release|ARM64 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|Win32.ActiveCfg = Release|Win32 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|Win32.Build.0 = Release|Win32 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|x64.ActiveCfg = Release|x64 + {8A8D1E59-166A-4C6F-8E64-CE6CC494F2F2}.Release|x64.Build.0 = Release|x64 ++ {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|ARM64.Build.0 = Debug|ARM64 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|Win32.ActiveCfg = Debug|Win32 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|Win32.Build.0 = Debug|Win32 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|x64.ActiveCfg = Debug|x64 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Debug|x64.Build.0 = Debug|x64 ++ {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|ARM64.Build.0 = Release|ARM64 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|Win32.ActiveCfg = Release|Win32 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|Win32.Build.0 = Release|Win32 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|x64.ActiveCfg = Release|x64 + {F5CA9AEE-FD4D-43B8-9DE5-2A13F1AFF457}.Release|x64.Build.0 = Release|x64 ++ {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|ARM64.Build.0 = Debug|ARM64 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|Win32.ActiveCfg = Debug|Win32 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|Win32.Build.0 = Debug|Win32 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|x64.ActiveCfg = Debug|x64 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Debug|x64.Build.0 = Debug|x64 ++ {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|ARM64.Build.0 = Release|ARM64 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|Win32.ActiveCfg = Release|Win32 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|Win32.Build.0 = Release|Win32 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|x64.ActiveCfg = Release|x64 + {DAB0C701-06F3-4FEE-AE96-262A5CBD87C7}.Release|x64.Build.0 = Release|x64 ++ {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|ARM64.Build.0 = Debug|ARM64 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|Win32.ActiveCfg = Debug|Win32 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|Win32.Build.0 = Debug|Win32 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|x64.ActiveCfg = Debug|x64 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Debug|x64.Build.0 = Debug|x64 ++ {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|ARM64.Build.0 = Release|ARM64 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|Win32.ActiveCfg = Release|Win32 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|Win32.Build.0 = Release|Win32 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|x64.ActiveCfg = Release|x64 + {75C62084-AF84-94A1-751B-1DDBBD96F648}.Release|x64.Build.0 = Release|x64 ++ {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|ARM64.Build.0 = Debug|ARM64 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|Win32.ActiveCfg = Debug|Win32 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|Win32.Build.0 = Debug|Win32 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|x64.ActiveCfg = Debug|x64 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Debug|x64.Build.0 = Debug|x64 ++ {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|ARM64.Build.0 = Release|ARM64 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|Win32.ActiveCfg = Release|Win32 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|Win32.Build.0 = Release|Win32 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|x64.ActiveCfg = Release|x64 + {C94BF7C7-CEDD-4CAF-9371-BDAABB419E8C}.Release|x64.Build.0 = Release|x64 ++ {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|ARM64.Build.0 = Debug|ARM64 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|Win32.ActiveCfg = Debug|Win32 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|Win32.Build.0 = Debug|Win32 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|x64.ActiveCfg = Debug|x64 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Debug|x64.Build.0 = Debug|x64 ++ {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|ARM64.Build.0 = Release|ARM64 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|Win32.ActiveCfg = Release|Win32 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|Win32.Build.0 = Release|Win32 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|x64.ActiveCfg = Release|x64 + {73F41343-D63E-CF15-D549-DF9483F260B9}.Release|x64.Build.0 = Release|x64 ++ {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|ARM64.Build.0 = Debug|ARM64 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|Win32.ActiveCfg = Debug|Win32 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|Win32.Build.0 = Debug|Win32 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|x64.ActiveCfg = Debug|x64 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Debug|x64.Build.0 = Debug|x64 ++ {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|ARM64.Build.0 = Release|ARM64 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|Win32.ActiveCfg = Release|Win32 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|Win32.Build.0 = Release|Win32 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|x64.ActiveCfg = Release|x64 + {EF613D11-70B1-5F25-5B2C-A561F2098B82}.Release|x64.Build.0 = Release|x64 ++ {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|ARM64.Build.0 = Debug|ARM64 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|Win32.ActiveCfg = Debug|Win32 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|Win32.Build.0 = Debug|Win32 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|x64.ActiveCfg = Debug|x64 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Debug|x64.Build.0 = Debug|x64 ++ {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|ARM64.Build.0 = Release|ARM64 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|Win32.ActiveCfg = Release|Win32 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|Win32.Build.0 = Release|Win32 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|x64.ActiveCfg = Release|x64 + {4614B956-8BFC-40A7-89D0-18AE31671D7D}.Release|x64.Build.0 = Release|x64 ++ {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|ARM64.Build.0 = Debug|ARM64 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|Win32.ActiveCfg = Debug|Win32 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|Win32.Build.0 = Debug|Win32 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|x64.ActiveCfg = Debug|x64 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Debug|x64.Build.0 = Debug|x64 ++ {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|ARM64.Build.0 = Release|ARM64 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|Win32.ActiveCfg = Release|Win32 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|Win32.Build.0 = Release|Win32 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|x64.ActiveCfg = Release|x64 + {78C5B90C-6509-48E8-85BD-3D4F5060351D}.Release|x64.Build.0 = Release|x64 ++ {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|ARM64.Build.0 = Debug|ARM64 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|Win32.ActiveCfg = Debug|Win32 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|Win32.Build.0 = Debug|Win32 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|x64.ActiveCfg = Debug|x64 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Debug|x64.Build.0 = Debug|x64 ++ {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|ARM64.Build.0 = Release|ARM64 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|Win32.ActiveCfg = Release|Win32 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|Win32.Build.0 = Release|Win32 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|x64.ActiveCfg = Release|x64 + {AE57384E-BA9D-D3FB-9F69-043F9BF618CE}.Release|x64.Build.0 = Release|x64 ++ {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|ARM64.Build.0 = Debug|ARM64 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|Win32.ActiveCfg = Debug|Win32 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|Win32.Build.0 = Debug|Win32 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|x64.ActiveCfg = Debug|x64 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Debug|x64.Build.0 = Debug|x64 ++ {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|ARM64.Build.0 = Release|ARM64 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|Win32.ActiveCfg = Release|Win32 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|Win32.Build.0 = Release|Win32 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|x64.ActiveCfg = Release|x64 + {F90EB29D-FD0E-327C-7DCF-BDDC5819B937}.Release|x64.Build.0 = Release|x64 ++ {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|ARM64.Build.0 = Debug|ARM64 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|Win32.ActiveCfg = Debug|Win32 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|Win32.Build.0 = Debug|Win32 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|x64.ActiveCfg = Debug|x64 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Debug|x64.Build.0 = Debug|x64 ++ {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|ARM64.Build.0 = Release|ARM64 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|Win32.ActiveCfg = Release|Win32 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|Win32.Build.0 = Release|Win32 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|x64.ActiveCfg = Release|x64 + {B4E1761A-1226-BB87-9B56-B4A6A4622391}.Release|x64.Build.0 = Release|x64 ++ {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|ARM64.Build.0 = Debug|ARM64 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|Win32.ActiveCfg = Debug|Win32 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|Win32.Build.0 = Debug|Win32 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|x64.ActiveCfg = Debug|x64 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Debug|x64.Build.0 = Debug|x64 ++ {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|ARM64.Build.0 = Release|ARM64 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|Win32.ActiveCfg = Release|Win32 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|Win32.Build.0 = Release|Win32 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|x64.ActiveCfg = Release|x64 + {6384E1A6-151A-3FAC-A932-26D0D9119020}.Release|x64.Build.0 = Release|x64 ++ {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|ARM64.Build.0 = Debug|ARM64 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|Win32.ActiveCfg = Debug|Win32 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|Win32.Build.0 = Debug|Win32 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|x64.ActiveCfg = Debug|x64 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Debug|x64.Build.0 = Debug|x64 ++ {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|ARM64.Build.0 = Release|ARM64 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|Win32.ActiveCfg = Release|Win32 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|Win32.Build.0 = Release|Win32 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|x64.ActiveCfg = Release|x64 + {D649BB77-A3D2-7879-0DE9-0407D1D07A07}.Release|x64.Build.0 = Release|x64 ++ {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|ARM64.Build.0 = Debug|ARM64 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|Win32.ActiveCfg = Debug|Win32 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|Win32.Build.0 = Debug|Win32 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|x64.ActiveCfg = Debug|x64 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Debug|x64.Build.0 = Debug|x64 ++ {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|ARM64.Build.0 = Release|ARM64 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|Win32.ActiveCfg = Release|Win32 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|Win32.Build.0 = Release|Win32 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|x64.ActiveCfg = Release|x64 + {D72015D0-0E47-B5D8-1832-15289D2D14D7}.Release|x64.Build.0 = Release|x64 ++ {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|ARM64.Build.0 = Debug|ARM64 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|Win32.ActiveCfg = Debug|Win32 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|Win32.Build.0 = Debug|Win32 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|x64.ActiveCfg = Debug|x64 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Debug|x64.Build.0 = Debug|x64 ++ {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|ARM64.Build.0 = Release|ARM64 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|Win32.ActiveCfg = Release|Win32 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|Win32.Build.0 = Release|Win32 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|x64.ActiveCfg = Release|x64 + {AECB4999-B617-40C8-BC32-6FCFD810F462}.Release|x64.Build.0 = Release|x64 ++ {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|ARM64.Build.0 = Debug|ARM64 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|Win32.ActiveCfg = Debug|Win32 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|Win32.Build.0 = Debug|Win32 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|x64.ActiveCfg = Debug|x64 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Debug|x64.Build.0 = Debug|x64 ++ {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|ARM64.Build.0 = Release|ARM64 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|Win32.ActiveCfg = Release|Win32 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|Win32.Build.0 = Release|Win32 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|x64.ActiveCfg = Release|x64 + {BDF5959C-CB5E-4A41-8906-D9C0E7E437EF}.Release|x64.Build.0 = Release|x64 ++ {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|ARM64.Build.0 = Debug|ARM64 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|Win32.ActiveCfg = Debug|Win32 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|Win32.Build.0 = Debug|Win32 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|x64.ActiveCfg = Debug|x64 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Debug|x64.Build.0 = Debug|x64 ++ {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|ARM64.Build.0 = Release|ARM64 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|Win32.ActiveCfg = Release|Win32 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|Win32.Build.0 = Release|Win32 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|x64.ActiveCfg = Release|x64 + {4F1C9BE1-7C8C-4E84-B0A4-3AE06E970920}.Release|x64.Build.0 = Release|x64 ++ {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|ARM64.Build.0 = Debug|ARM64 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|Win32.ActiveCfg = Debug|Win32 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|Win32.Build.0 = Debug|Win32 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|x64.ActiveCfg = Debug|x64 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Debug|x64.Build.0 = Debug|x64 ++ {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|ARM64.Build.0 = Release|ARM64 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|Win32.ActiveCfg = Release|Win32 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|Win32.Build.0 = Release|Win32 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|x64.ActiveCfg = Release|x64 + {89264F07-C21B-4C98-A76F-2635D40CFF96}.Release|x64.Build.0 = Release|x64 ++ {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|ARM64.Build.0 = Debug|ARM64 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|Win32.ActiveCfg = Debug|Win32 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|Win32.Build.0 = Debug|Win32 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|x64.ActiveCfg = Debug|x64 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Debug|x64.Build.0 = Debug|x64 ++ {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|ARM64.Build.0 = Release|ARM64 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|Win32.ActiveCfg = Release|Win32 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|Win32.Build.0 = Release|Win32 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|x64.ActiveCfg = Release|x64 + {0A440012-109E-4CFF-AFD7-BF6D59628D87}.Release|x64.Build.0 = Release|x64 ++ {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|ARM64.Build.0 = Debug|ARM64 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|Win32.ActiveCfg = Debug|Win32 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|Win32.Build.0 = Debug|Win32 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|x64.ActiveCfg = Debug|x64 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Debug|x64.Build.0 = Debug|x64 ++ {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|ARM64.Build.0 = Release|ARM64 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|Win32.ActiveCfg = Release|Win32 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|Win32.Build.0 = Release|Win32 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|x64.ActiveCfg = Release|x64 + {1B635A04-9265-4274-9B57-C5F1C4027A4E}.Release|x64.Build.0 = Release|x64 ++ {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|ARM64.Build.0 = Debug|ARM64 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|Win32.ActiveCfg = Debug|Win32 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|Win32.Build.0 = Debug|Win32 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|x64.ActiveCfg = Debug|x64 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Debug|x64.Build.0 = Debug|x64 ++ {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|ARM64.Build.0 = Release|ARM64 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|Win32.ActiveCfg = Release|Win32 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|Win32.Build.0 = Release|Win32 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|x64.ActiveCfg = Release|x64 + {9C50623C-7A82-424E-8DD4-E03D53F95B9B}.Release|x64.Build.0 = Release|x64 ++ {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|ARM64.Build.0 = Debug|ARM64 + {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|Win32.ActiveCfg = Debug|Win32 + {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|Win32.Build.0 = Debug|Win32 + {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|x64.ActiveCfg = Debug|x64 + {31832E59-29F0-44C7-A19E-E322B1142425}.Debug|x64.Build.0 = Debug|x64 ++ {31832E59-29F0-44C7-A19E-E322B1142425}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {31832E59-29F0-44C7-A19E-E322B1142425}.Release|ARM64.Build.0 = Release|ARM64 + {31832E59-29F0-44C7-A19E-E322B1142425}.Release|Win32.ActiveCfg = Release|Win32 + {31832E59-29F0-44C7-A19E-E322B1142425}.Release|Win32.Build.0 = Release|Win32 + {31832E59-29F0-44C7-A19E-E322B1142425}.Release|x64.ActiveCfg = Release|x64 + {31832E59-29F0-44C7-A19E-E322B1142425}.Release|x64.Build.0 = Release|x64 ++ {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|ARM64.Build.0 = Debug|ARM64 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|Win32.ActiveCfg = Debug|Win32 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|Win32.Build.0 = Debug|Win32 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|x64.ActiveCfg = Debug|x64 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Debug|x64.Build.0 = Debug|x64 ++ {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|ARM64.Build.0 = Release|ARM64 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|Win32.ActiveCfg = Release|Win32 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|Win32.Build.0 = Release|Win32 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|x64.ActiveCfg = Release|x64 + {541BB0AF-2A9D-4254-AEA2-C4AF64B072AE}.Release|x64.Build.0 = Release|x64 ++ {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|ARM64.Build.0 = Debug|ARM64 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|Win32.ActiveCfg = Debug|Win32 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|Win32.Build.0 = Debug|Win32 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|x64.ActiveCfg = Debug|x64 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Debug|x64.Build.0 = Debug|x64 ++ {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|ARM64.Build.0 = Release|ARM64 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|Win32.ActiveCfg = Release|Win32 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|Win32.Build.0 = Release|Win32 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|x64.ActiveCfg = Release|x64 + {94535CF0-A2CB-4A1B-88F6-B9883D209B81}.Release|x64.Build.0 = Release|x64 ++ {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|ARM64.Build.0 = Debug|ARM64 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|Win32.ActiveCfg = Debug|Win32 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|Win32.Build.0 = Debug|Win32 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|x64.ActiveCfg = Debug|x64 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Debug|x64.Build.0 = Debug|x64 ++ {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|ARM64.Build.0 = Release|ARM64 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|Win32.ActiveCfg = Release|Win32 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|Win32.Build.0 = Release|Win32 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|x64.ActiveCfg = Release|x64 + {324BFA7D-8AF3-4F2B-8B3E-1D2E3EE2BB19}.Release|x64.Build.0 = Release|x64 ++ {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|ARM64.Build.0 = Debug|ARM64 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|Win32.ActiveCfg = Debug|Win32 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|Win32.Build.0 = Debug|Win32 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|x64.ActiveCfg = Debug|x64 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Debug|x64.Build.0 = Debug|x64 ++ {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|ARM64.Build.0 = Release|ARM64 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|Win32.ActiveCfg = Release|Win32 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|Win32.Build.0 = Release|Win32 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|x64.ActiveCfg = Release|x64 + {44A27326-22B1-4838-85F2-0748CB9F5FB5}.Release|x64.Build.0 = Release|x64 ++ {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|ARM64.Build.0 = Debug|ARM64 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|Win32.ActiveCfg = Debug|Win32 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|Win32.Build.0 = Debug|Win32 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|x64.ActiveCfg = Debug|x64 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Debug|x64.Build.0 = Debug|x64 ++ {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|ARM64.Build.0 = Release|ARM64 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|Win32.ActiveCfg = Release|Win32 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|Win32.Build.0 = Release|Win32 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|x64.ActiveCfg = Release|x64 + {1BA5CA1A-2CFD-44B6-8FEF-34A20D4E533C}.Release|x64.Build.0 = Release|x64 ++ {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|ARM64.Build.0 = Debug|ARM64 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|Win32.ActiveCfg = Debug|Win32 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|Win32.Build.0 = Debug|Win32 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|x64.ActiveCfg = Debug|x64 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Debug|x64.Build.0 = Debug|x64 ++ {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|ARM64.Build.0 = Release|ARM64 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|Win32.ActiveCfg = Release|Win32 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|Win32.Build.0 = Release|Win32 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|x64.ActiveCfg = Release|x64 + {F937F792-25ED-4DE4-AA9F-104163DB24DF}.Release|x64.Build.0 = Release|x64 ++ {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|ARM64.Build.0 = Debug|ARM64 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|Win32.ActiveCfg = Debug|Win32 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|Win32.Build.0 = Debug|Win32 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|x64.ActiveCfg = Debug|x64 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Debug|x64.Build.0 = Debug|x64 ++ {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|ARM64.Build.0 = Release|ARM64 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|Win32.ActiveCfg = Release|Win32 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|Win32.Build.0 = Release|Win32 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|x64.ActiveCfg = Release|x64 + {37FA0D6B-E032-4E01-A2EE-2BAF59A551AC}.Release|x64.Build.0 = Release|x64 ++ {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|ARM64.Build.0 = Debug|ARM64 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|Win32.ActiveCfg = Debug|Win32 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|Win32.Build.0 = Debug|Win32 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|x64.ActiveCfg = Debug|x64 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Debug|x64.Build.0 = Debug|x64 ++ {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|ARM64.Build.0 = Release|ARM64 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|Win32.ActiveCfg = Release|Win32 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|Win32.Build.0 = Release|Win32 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|x64.ActiveCfg = Release|x64 + {44CE44BF-BC91-4564-B890-0EA6677EF3B3}.Release|x64.Build.0 = Release|x64 ++ {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|ARM64.Build.0 = Debug|ARM64 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|Win32.ActiveCfg = Debug|Win32 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|Win32.Build.0 = Debug|Win32 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|x64.ActiveCfg = Debug|x64 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Debug|x64.Build.0 = Debug|x64 ++ {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|ARM64.Build.0 = Release|ARM64 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|Win32.ActiveCfg = Release|Win32 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|Win32.Build.0 = Release|Win32 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|x64.ActiveCfg = Release|x64 + {52CE9EB2-E62B-4126-A4F8-D4F68ADD9EC1}.Release|x64.Build.0 = Release|x64 ++ {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|ARM64.ActiveCfg = Debug|ARM64 ++ {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|ARM64.Build.0 = Debug|ARM64 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|Win32.ActiveCfg = Debug|Win32 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|Win32.Build.0 = Debug|Win32 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|x64.ActiveCfg = Debug|x64 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Debug|x64.Build.0 = Debug|x64 ++ {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Release|ARM64.ActiveCfg = Release|ARM64 ++ {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Release|ARM64.Build.0 = Release|ARM64 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Release|Win32.ActiveCfg = Release|Win32 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Release|Win32.Build.0 = Release|Win32 + {66E288D7-106F-42B2-8BB9-64ADFCAFE283}.Release|x64.ActiveCfg = Release|x64 +diff --git a/build.vs19/lib_mpfr/lib_mpfr.vcxproj b/build.vs19/lib_mpfr/lib_mpfr.vcxproj +index 6fb09bdc..584acf4a 100644 +--- a/build.vs19/lib_mpfr/lib_mpfr.vcxproj ++++ b/build.vs19/lib_mpfr/lib_mpfr.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -27,19 +35,27 @@ + + + StaticLibrary +- v142 ++ v143 ++ ++ ++ StaticLibrary ++ v143 + + + StaticLibrary +- v142 ++ v143 ++ ++ ++ StaticLibrary ++ v143 + + + StaticLibrary +- v142 ++ v143 + + + StaticLibrary +- v142 ++ v143 + + + +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,21 +82,55 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ ++ $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ ++ $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)..\lib\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + mpfr + mpfr + mpfr ++ mpfr + mpfr ++ mpfr + + + + ..\out_copy_rename.bat ..\..\src\mpfr.h ..\..\lib\$(IntDir) mpfr.h ++..\out_copy_rename.bat ..\..\src\mparam_h.in ..\..\ mparam.h ++ ++ ++ ++ ++ ++ ++ Disabled ++ ..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_WIN32;HAVE_CONFIG_H;_DEBUG;_LIB;_GMP_IEEE_FLOATS;_CRT_SECURE_NO_WARNINGS;MPFR_HAVE_GMP_IMPL ++ EnableFastChecks ++ MultiThreadedDebug ++ ++ ++ $(TargetDir)$(TargetName).pdb ++ true ++ ++ ++ ..\..\..\mpir\lib\$(IntDir)mpir.lib;%(AdditionalDependencies) ++ ++ ++ ++ ++ ++ ++ ++ ++ ..\out_copy_rename.bat ..\..\src\mpfr.h ..\..\lib\$(IntDir) mpfr.h + ..\out_copy_rename.bat ..\..\src\mparam_h.in ..\..\ mparam.h + + +@@ -135,6 +191,34 @@ + + + ..\out_copy_rename.bat ..\..\src\mpfr.h ..\..\lib\$(IntDir) mpfr.h ++..\out_copy_rename.bat ..\..\src\mparam_h.in ..\..\ mparam.h ++ ++ ++ ++ ++ ++ ++ Full ++ ..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_WIN32;HAVE_CONFIG_H;NDEBUG;_LIB;_GMP_IEEE_FLOATS;_CRT_SECURE_NO_WARNINGS;MPFR_HAVE_GMP_IMPL ++ MultiThreaded ++ false ++ ++ ++ $(TargetDir)$(TargetName).pdb ++ true ++ ++ ++ ..\..\..\mpir\lib\$(IntDir)mpir.lib;%(AdditionalDependencies) ++ ++ ++ ++ ++ ++ ++ ++ ++ ..\out_copy_rename.bat ..\..\src\mpfr.h ..\..\lib\$(IntDir) mpfr.h + ..\out_copy_rename.bat ..\..\src\mparam_h.in ..\..\ mparam.h + + +@@ -312,7 +396,9 @@ + + + true ++ true + true ++ true + true + true + +@@ -331,7 +417,9 @@ + + + true ++ true + true ++ true + true + true + +@@ -370,7 +458,9 @@ + + + true ++ true + true ++ true + true + true + +diff --git a/build.vs19/lib_mpfr_tests/lib_tests/lib_tests.vcxproj b/build.vs19/lib_mpfr_tests/lib_tests/lib_tests.vcxproj +index 1df4a01c..fa416169 100644 +--- a/build.vs19/lib_mpfr_tests/lib_tests/lib_tests.vcxproj ++++ b/build.vs19/lib_mpfr_tests/lib_tests/lib_tests.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + StaticLibrary + v142 + ++ ++ StaticLibrary ++ v142 ++ + + StaticLibrary + v142 + ++ ++ StaticLibrary ++ v142 ++ + + StaticLibrary + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -85,6 +111,23 @@ + MachineX86 + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;HAVE_CONFIG_H;_DEBUG;_LIB;MPFR_HAVE_GMP_IMPL ++ EnableFastChecks ++ ++ ++ Level3 ++ MultiThreadedDebug ++ true ++ ++ ++ ++ MachineX86 ++ ++ + + + X64 +@@ -119,6 +162,23 @@ + MachineX86 + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;HAVE_CONFIG_H;NDEBUG;_LIB;MPFR_HAVE_GMP_IMPL ++ ++ ++ Level3 ++ ProgramDatabase ++ MultiThreaded ++ true ++ ++ ++ ++ MachineX86 ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/mpf_compat/mpf_compat.vcxproj b/build.vs19/lib_mpfr_tests/mpf_compat/mpf_compat.vcxproj +index 044eae06..8e927cb5 100644 +--- a/build.vs19/lib_mpfr_tests/mpf_compat/mpf_compat.vcxproj ++++ b/build.vs19/lib_mpfr_tests/mpf_compat/mpf_compat.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/mpfr_compat/mpfr_compat.vcxproj b/build.vs19/lib_mpfr_tests/mpfr_compat/mpfr_compat.vcxproj +index dd3cc537..96e874a8 100644 +--- a/build.vs19/lib_mpfr_tests/mpfr_compat/mpfr_compat.vcxproj ++++ b/build.vs19/lib_mpfr_tests/mpfr_compat/mpfr_compat.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -132,6 +177,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/reuse/reuse.vcxproj b/build.vs19/lib_mpfr_tests/reuse/reuse.vcxproj +index 922bd519..3794683d 100644 +--- a/build.vs19/lib_mpfr_tests/reuse/reuse.vcxproj ++++ b/build.vs19/lib_mpfr_tests/reuse/reuse.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tabort_defalloc1/tabort_defalloc1.vcxproj b/build.vs19/lib_mpfr_tests/tabort_defalloc1/tabort_defalloc1.vcxproj +index ad66dcd4..38fa2d9b 100644 +--- a/build.vs19/lib_mpfr_tests/tabort_defalloc1/tabort_defalloc1.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tabort_defalloc1/tabort_defalloc1.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tabort_defalloc2/tabort_defalloc2.vcxproj b/build.vs19/lib_mpfr_tests/tabort_defalloc2/tabort_defalloc2.vcxproj +index e1e65d16..ed586676 100644 +--- a/build.vs19/lib_mpfr_tests/tabort_defalloc2/tabort_defalloc2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tabort_defalloc2/tabort_defalloc2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tabort_prec_max/tabort_prec_max.vcxproj b/build.vs19/lib_mpfr_tests/tabort_prec_max/tabort_prec_max.vcxproj +index 47dc575c..6b5bf6bb 100644 +--- a/build.vs19/lib_mpfr_tests/tabort_prec_max/tabort_prec_max.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tabort_prec_max/tabort_prec_max.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tabs/tabs.vcxproj b/build.vs19/lib_mpfr_tests/tabs/tabs.vcxproj +index 4a6c9c57..b332838a 100644 +--- a/build.vs19/lib_mpfr_tests/tabs/tabs.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tabs/tabs.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tacos/tacos.vcxproj b/build.vs19/lib_mpfr_tests/tacos/tacos.vcxproj +index d3436267..56d2087d 100644 +--- a/build.vs19/lib_mpfr_tests/tacos/tacos.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tacos/tacos.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tacosh/tacosh.vcxproj b/build.vs19/lib_mpfr_tests/tacosh/tacosh.vcxproj +index 295f982e..4b64914f 100644 +--- a/build.vs19/lib_mpfr_tests/tacosh/tacosh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tacosh/tacosh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tadd/tadd.vcxproj b/build.vs19/lib_mpfr_tests/tadd/tadd.vcxproj +index c254f526..780ce818 100644 +--- a/build.vs19/lib_mpfr_tests/tadd/tadd.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tadd/tadd.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tadd1sp/tadd1sp.vcxproj b/build.vs19/lib_mpfr_tests/tadd1sp/tadd1sp.vcxproj +index 739ea71e..3da0b2e0 100644 +--- a/build.vs19/lib_mpfr_tests/tadd1sp/tadd1sp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tadd1sp/tadd1sp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tadd_d/tadd_d.vcxproj b/build.vs19/lib_mpfr_tests/tadd_d/tadd_d.vcxproj +index 3d9cc010..da98318d 100644 +--- a/build.vs19/lib_mpfr_tests/tadd_d/tadd_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tadd_d/tadd_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tadd_ui/tadd_ui.vcxproj b/build.vs19/lib_mpfr_tests/tadd_ui/tadd_ui.vcxproj +index 97f96e8d..98267ebb 100644 +--- a/build.vs19/lib_mpfr_tests/tadd_ui/tadd_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tadd_ui/tadd_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tagm/tagm.vcxproj b/build.vs19/lib_mpfr_tests/tagm/tagm.vcxproj +index c15d19fa..1b58594b 100644 +--- a/build.vs19/lib_mpfr_tests/tagm/tagm.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tagm/tagm.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tai/tai.vcxproj b/build.vs19/lib_mpfr_tests/tai/tai.vcxproj +index 6c19a221..570f1deb 100644 +--- a/build.vs19/lib_mpfr_tests/tai/tai.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tai/tai.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/talloc/talloc.vcxproj b/build.vs19/lib_mpfr_tests/talloc/talloc.vcxproj +index 6e321198..2cad9dd7 100644 +--- a/build.vs19/lib_mpfr_tests/talloc/talloc.vcxproj ++++ b/build.vs19/lib_mpfr_tests/talloc/talloc.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tasin/tasin.vcxproj b/build.vs19/lib_mpfr_tests/tasin/tasin.vcxproj +index 8527e32f..82c21bfd 100644 +--- a/build.vs19/lib_mpfr_tests/tasin/tasin.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tasin/tasin.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tasinh/tasinh.vcxproj b/build.vs19/lib_mpfr_tests/tasinh/tasinh.vcxproj +index 3504fde7..2eaaace4 100644 +--- a/build.vs19/lib_mpfr_tests/tasinh/tasinh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tasinh/tasinh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tassert/tassert.vcxproj b/build.vs19/lib_mpfr_tests/tassert/tassert.vcxproj +index 5d29eb11..96835b6c 100644 +--- a/build.vs19/lib_mpfr_tests/tassert/tassert.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tassert/tassert.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tatan/tatan.vcxproj b/build.vs19/lib_mpfr_tests/tatan/tatan.vcxproj +index 63bed877..79eabfbe 100644 +--- a/build.vs19/lib_mpfr_tests/tatan/tatan.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tatan/tatan.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tatanh/tatanh.vcxproj b/build.vs19/lib_mpfr_tests/tatanh/tatanh.vcxproj +index 49507185..ef88a2f2 100644 +--- a/build.vs19/lib_mpfr_tests/tatanh/tatanh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tatanh/tatanh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/taway/taway.vcxproj b/build.vs19/lib_mpfr_tests/taway/taway.vcxproj +index 85d5459b..cf0e60aa 100644 +--- a/build.vs19/lib_mpfr_tests/taway/taway.vcxproj ++++ b/build.vs19/lib_mpfr_tests/taway/taway.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tbeta/tbeta.vcxproj b/build.vs19/lib_mpfr_tests/tbeta/tbeta.vcxproj +index 0ee0e856..4e58d7bc 100644 +--- a/build.vs19/lib_mpfr_tests/tbeta/tbeta.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tbeta/tbeta.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,15 +82,20 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + ++ + + + +@@ -91,6 +118,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -134,6 +180,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tbuildopt/tbuildopt.vcxproj b/build.vs19/lib_mpfr_tests/tbuildopt/tbuildopt.vcxproj +index d637aa2b..4e75c919 100644 +--- a/build.vs19/lib_mpfr_tests/tbuildopt/tbuildopt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tbuildopt/tbuildopt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcan_round/tcan_round.vcxproj b/build.vs19/lib_mpfr_tests/tcan_round/tcan_round.vcxproj +index 9afc1d7d..e47b4382 100644 +--- a/build.vs19/lib_mpfr_tests/tcan_round/tcan_round.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcan_round/tcan_round.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcbrt/tcbrt.vcxproj b/build.vs19/lib_mpfr_tests/tcbrt/tcbrt.vcxproj +index 99d75d55..3ffccbd9 100644 +--- a/build.vs19/lib_mpfr_tests/tcbrt/tcbrt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcbrt/tcbrt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcheck/tcheck.vcxproj b/build.vs19/lib_mpfr_tests/tcheck/tcheck.vcxproj +index a6b8ad85..503bc912 100644 +--- a/build.vs19/lib_mpfr_tests/tcheck/tcheck.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcheck/tcheck.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmp/tcmp.vcxproj b/build.vs19/lib_mpfr_tests/tcmp/tcmp.vcxproj +index 08a100f2..9ae72794 100644 +--- a/build.vs19/lib_mpfr_tests/tcmp/tcmp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmp/tcmp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmp2/tcmp2.vcxproj b/build.vs19/lib_mpfr_tests/tcmp2/tcmp2.vcxproj +index 81ca4cdf..4976721f 100644 +--- a/build.vs19/lib_mpfr_tests/tcmp2/tcmp2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmp2/tcmp2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmp_d/tcmp_d.vcxproj b/build.vs19/lib_mpfr_tests/tcmp_d/tcmp_d.vcxproj +index 6f28fc37..42b54dff 100644 +--- a/build.vs19/lib_mpfr_tests/tcmp_d/tcmp_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmp_d/tcmp_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmp_ld/tcmp_ld.vcxproj b/build.vs19/lib_mpfr_tests/tcmp_ld/tcmp_ld.vcxproj +index 2545b85e..36fa53de 100644 +--- a/build.vs19/lib_mpfr_tests/tcmp_ld/tcmp_ld.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmp_ld/tcmp_ld.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmp_ui/tcmp_ui.vcxproj b/build.vs19/lib_mpfr_tests/tcmp_ui/tcmp_ui.vcxproj +index e7347613..52aa391a 100644 +--- a/build.vs19/lib_mpfr_tests/tcmp_ui/tcmp_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmp_ui/tcmp_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcmpabs/tcmpabs.vcxproj b/build.vs19/lib_mpfr_tests/tcmpabs/tcmpabs.vcxproj +index 5cd00dba..35ca20cc 100644 +--- a/build.vs19/lib_mpfr_tests/tcmpabs/tcmpabs.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcmpabs/tcmpabs.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcomparisons/tcomparisons.vcxproj b/build.vs19/lib_mpfr_tests/tcomparisons/tcomparisons.vcxproj +index 8f669540..9719a0c0 100644 +--- a/build.vs19/lib_mpfr_tests/tcomparisons/tcomparisons.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcomparisons/tcomparisons.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tconst_catalan/tconst_catalan.vcxproj b/build.vs19/lib_mpfr_tests/tconst_catalan/tconst_catalan.vcxproj +index 944da3e1..56b0f559 100644 +--- a/build.vs19/lib_mpfr_tests/tconst_catalan/tconst_catalan.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tconst_catalan/tconst_catalan.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tconst_euler/tconst_euler.vcxproj b/build.vs19/lib_mpfr_tests/tconst_euler/tconst_euler.vcxproj +index 51b7ffcf..c23185de 100644 +--- a/build.vs19/lib_mpfr_tests/tconst_euler/tconst_euler.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tconst_euler/tconst_euler.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tconst_log2/tconst_log2.vcxproj b/build.vs19/lib_mpfr_tests/tconst_log2/tconst_log2.vcxproj +index f0e19243..2b6346c7 100644 +--- a/build.vs19/lib_mpfr_tests/tconst_log2/tconst_log2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tconst_log2/tconst_log2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tconst_pi/tconst_pi.vcxproj b/build.vs19/lib_mpfr_tests/tconst_pi/tconst_pi.vcxproj +index 73ac73c6..9a72dcd6 100644 +--- a/build.vs19/lib_mpfr_tests/tconst_pi/tconst_pi.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tconst_pi/tconst_pi.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcopysign/tcopysign.vcxproj b/build.vs19/lib_mpfr_tests/tcopysign/tcopysign.vcxproj +index bc9341ed..9bc12521 100644 +--- a/build.vs19/lib_mpfr_tests/tcopysign/tcopysign.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcopysign/tcopysign.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcos/tcos.vcxproj b/build.vs19/lib_mpfr_tests/tcos/tcos.vcxproj +index 7d2a52b0..31d14582 100644 +--- a/build.vs19/lib_mpfr_tests/tcos/tcos.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcos/tcos.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcosh/tcosh.vcxproj b/build.vs19/lib_mpfr_tests/tcosh/tcosh.vcxproj +index cbabd95b..d75f7d06 100644 +--- a/build.vs19/lib_mpfr_tests/tcosh/tcosh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcosh/tcosh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcot/tcot.vcxproj b/build.vs19/lib_mpfr_tests/tcot/tcot.vcxproj +index 08a27985..4d0b291c 100644 +--- a/build.vs19/lib_mpfr_tests/tcot/tcot.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcot/tcot.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcoth/tcoth.vcxproj b/build.vs19/lib_mpfr_tests/tcoth/tcoth.vcxproj +index 741db798..90a6f5ff 100644 +--- a/build.vs19/lib_mpfr_tests/tcoth/tcoth.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcoth/tcoth.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcsc/tcsc.vcxproj b/build.vs19/lib_mpfr_tests/tcsc/tcsc.vcxproj +index ef5e85cb..51e81e8d 100644 +--- a/build.vs19/lib_mpfr_tests/tcsc/tcsc.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcsc/tcsc.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tcsch/tcsch.vcxproj b/build.vs19/lib_mpfr_tests/tcsch/tcsch.vcxproj +index 1238bc9d..c45dd81e 100644 +--- a/build.vs19/lib_mpfr_tests/tcsch/tcsch.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tcsch/tcsch.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/td_div/td_div.vcxproj b/build.vs19/lib_mpfr_tests/td_div/td_div.vcxproj +index 98997599..90fcd188 100644 +--- a/build.vs19/lib_mpfr_tests/td_div/td_div.vcxproj ++++ b/build.vs19/lib_mpfr_tests/td_div/td_div.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/td_sub/td_sub.vcxproj b/build.vs19/lib_mpfr_tests/td_sub/td_sub.vcxproj +index dd6b6f92..e8e70ba7 100644 +--- a/build.vs19/lib_mpfr_tests/td_sub/td_sub.vcxproj ++++ b/build.vs19/lib_mpfr_tests/td_sub/td_sub.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdigamma/tdigamma.vcxproj b/build.vs19/lib_mpfr_tests/tdigamma/tdigamma.vcxproj +index d2091fe3..3403b69e 100644 +--- a/build.vs19/lib_mpfr_tests/tdigamma/tdigamma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdigamma/tdigamma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdim/tdim.vcxproj b/build.vs19/lib_mpfr_tests/tdim/tdim.vcxproj +index ae13962c..d2840c9c 100644 +--- a/build.vs19/lib_mpfr_tests/tdim/tdim.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdim/tdim.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdiv/tdiv.vcxproj b/build.vs19/lib_mpfr_tests/tdiv/tdiv.vcxproj +index 4029ff65..fc3a3284 100644 +--- a/build.vs19/lib_mpfr_tests/tdiv/tdiv.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdiv/tdiv.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdiv_d/tdiv_d.vcxproj b/build.vs19/lib_mpfr_tests/tdiv_d/tdiv_d.vcxproj +index b17386a0..133816ad 100644 +--- a/build.vs19/lib_mpfr_tests/tdiv_d/tdiv_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdiv_d/tdiv_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdiv_ui/tdiv_ui.vcxproj b/build.vs19/lib_mpfr_tests/tdiv_ui/tdiv_ui.vcxproj +index e19231b2..e6a37332 100644 +--- a/build.vs19/lib_mpfr_tests/tdiv_ui/tdiv_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdiv_ui/tdiv_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tdot/tdot.vcxproj b/build.vs19/lib_mpfr_tests/tdot/tdot.vcxproj +index cda09602..f2d09002 100644 +--- a/build.vs19/lib_mpfr_tests/tdot/tdot.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tdot/tdot.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/teint/teint.vcxproj b/build.vs19/lib_mpfr_tests/teint/teint.vcxproj +index 3e211aaf..4bf78e95 100644 +--- a/build.vs19/lib_mpfr_tests/teint/teint.vcxproj ++++ b/build.vs19/lib_mpfr_tests/teint/teint.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/teq/teq.vcxproj b/build.vs19/lib_mpfr_tests/teq/teq.vcxproj +index d4d132a0..a84222c5 100644 +--- a/build.vs19/lib_mpfr_tests/teq/teq.vcxproj ++++ b/build.vs19/lib_mpfr_tests/teq/teq.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/terandom/terandom.vcxproj b/build.vs19/lib_mpfr_tests/terandom/terandom.vcxproj +index 87d74358..6199749e 100644 +--- a/build.vs19/lib_mpfr_tests/terandom/terandom.vcxproj ++++ b/build.vs19/lib_mpfr_tests/terandom/terandom.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/terandom_chisq/terandom_chisq.vcxproj b/build.vs19/lib_mpfr_tests/terandom_chisq/terandom_chisq.vcxproj +index 3b92a130..21ea4480 100644 +--- a/build.vs19/lib_mpfr_tests/terandom_chisq/terandom_chisq.vcxproj ++++ b/build.vs19/lib_mpfr_tests/terandom_chisq/terandom_chisq.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/terf/terf.vcxproj b/build.vs19/lib_mpfr_tests/terf/terf.vcxproj +index 9d81665e..8b6ca199 100644 +--- a/build.vs19/lib_mpfr_tests/terf/terf.vcxproj ++++ b/build.vs19/lib_mpfr_tests/terf/terf.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/texceptions/texceptions.vcxproj b/build.vs19/lib_mpfr_tests/texceptions/texceptions.vcxproj +index 35091ba8..84a5c813 100644 +--- a/build.vs19/lib_mpfr_tests/texceptions/texceptions.vcxproj ++++ b/build.vs19/lib_mpfr_tests/texceptions/texceptions.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/texp/texp.vcxproj b/build.vs19/lib_mpfr_tests/texp/texp.vcxproj +index fe32e215..036a3062 100644 +--- a/build.vs19/lib_mpfr_tests/texp/texp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/texp/texp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/texp10/texp10.vcxproj b/build.vs19/lib_mpfr_tests/texp10/texp10.vcxproj +index ec322bfe..c506d7f6 100644 +--- a/build.vs19/lib_mpfr_tests/texp10/texp10.vcxproj ++++ b/build.vs19/lib_mpfr_tests/texp10/texp10.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/texp2/texp2.vcxproj b/build.vs19/lib_mpfr_tests/texp2/texp2.vcxproj +index 06b71fa5..68202cab 100644 +--- a/build.vs19/lib_mpfr_tests/texp2/texp2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/texp2/texp2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/texpm1/texpm1.vcxproj b/build.vs19/lib_mpfr_tests/texpm1/texpm1.vcxproj +index 82e27bf3..82c11ab6 100644 +--- a/build.vs19/lib_mpfr_tests/texpm1/texpm1.vcxproj ++++ b/build.vs19/lib_mpfr_tests/texpm1/texpm1.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfactorial/tfactorial.vcxproj b/build.vs19/lib_mpfr_tests/tfactorial/tfactorial.vcxproj +index 3b19fd78..c2e346d8 100644 +--- a/build.vs19/lib_mpfr_tests/tfactorial/tfactorial.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfactorial/tfactorial.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfits/tfits.vcxproj b/build.vs19/lib_mpfr_tests/tfits/tfits.vcxproj +index 110c88c1..d7a36c49 100644 +--- a/build.vs19/lib_mpfr_tests/tfits/tfits.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfits/tfits.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfma/tfma.vcxproj b/build.vs19/lib_mpfr_tests/tfma/tfma.vcxproj +index 7e727b6b..7aa91f21 100644 +--- a/build.vs19/lib_mpfr_tests/tfma/tfma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfma/tfma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfmma/tfmma.vcxproj b/build.vs19/lib_mpfr_tests/tfmma/tfmma.vcxproj +index cffb8b8e..61d1479c 100644 +--- a/build.vs19/lib_mpfr_tests/tfmma/tfmma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfmma/tfmma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfmod/tfmod.vcxproj b/build.vs19/lib_mpfr_tests/tfmod/tfmod.vcxproj +index 9cee910c..06e6eaf5 100644 +--- a/build.vs19/lib_mpfr_tests/tfmod/tfmod.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfmod/tfmod.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfms/tfms.vcxproj b/build.vs19/lib_mpfr_tests/tfms/tfms.vcxproj +index 08c17acb..2ac3fe9a 100644 +--- a/build.vs19/lib_mpfr_tests/tfms/tfms.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfms/tfms.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfpif/tfpif.vcxproj b/build.vs19/lib_mpfr_tests/tfpif/tfpif.vcxproj +index 239cdc7d..61a73809 100644 +--- a/build.vs19/lib_mpfr_tests/tfpif/tfpif.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfpif/tfpif.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfprintf/tfprintf.vcxproj b/build.vs19/lib_mpfr_tests/tfprintf/tfprintf.vcxproj +index 032cbb5e..ef9f2a3a 100644 +--- a/build.vs19/lib_mpfr_tests/tfprintf/tfprintf.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfprintf/tfprintf.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfrac/tfrac.vcxproj b/build.vs19/lib_mpfr_tests/tfrac/tfrac.vcxproj +index 7a8cfe03..bbf9044f 100644 +--- a/build.vs19/lib_mpfr_tests/tfrac/tfrac.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfrac/tfrac.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tfrexp/tfrexp.vcxproj b/build.vs19/lib_mpfr_tests/tfrexp/tfrexp.vcxproj +index 5db653ef..827c345e 100644 +--- a/build.vs19/lib_mpfr_tests/tfrexp/tfrexp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tfrexp/tfrexp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tgamma/tgamma.vcxproj b/build.vs19/lib_mpfr_tests/tgamma/tgamma.vcxproj +index b347f469..c73e142e 100644 +--- a/build.vs19/lib_mpfr_tests/tgamma/tgamma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tgamma/tgamma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tgamma_inc/tgamma_inc.vcxproj b/build.vs19/lib_mpfr_tests/tgamma_inc/tgamma_inc.vcxproj +index 5921e7a3..ac3a89bc 100644 +--- a/build.vs19/lib_mpfr_tests/tgamma_inc/tgamma_inc.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tgamma_inc/tgamma_inc.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_d/tget_d.vcxproj b/build.vs19/lib_mpfr_tests/tget_d/tget_d.vcxproj +index 640dbb4e..4e9d36a5 100644 +--- a/build.vs19/lib_mpfr_tests/tget_d/tget_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_d/tget_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_d_2exp/tget_d_2exp.vcxproj b/build.vs19/lib_mpfr_tests/tget_d_2exp/tget_d_2exp.vcxproj +index 5606fd21..404b1f9e 100644 +--- a/build.vs19/lib_mpfr_tests/tget_d_2exp/tget_d_2exp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_d_2exp/tget_d_2exp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_f/tget_f.vcxproj b/build.vs19/lib_mpfr_tests/tget_f/tget_f.vcxproj +index e4184fd0..33b0b8eb 100644 +--- a/build.vs19/lib_mpfr_tests/tget_f/tget_f.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_f/tget_f.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_flt/tget_flt.vcxproj b/build.vs19/lib_mpfr_tests/tget_flt/tget_flt.vcxproj +index d45ed832..78c44f6d 100644 +--- a/build.vs19/lib_mpfr_tests/tget_flt/tget_flt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_flt/tget_flt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_ld_2exp/tget_ld_2exp.vcxproj b/build.vs19/lib_mpfr_tests/tget_ld_2exp/tget_ld_2exp.vcxproj +index acc5dda9..72afcb70 100644 +--- a/build.vs19/lib_mpfr_tests/tget_ld_2exp/tget_ld_2exp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_ld_2exp/tget_ld_2exp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_q/tget_q.vcxproj b/build.vs19/lib_mpfr_tests/tget_q/tget_q.vcxproj +index d687f476..db284bd8 100644 +--- a/build.vs19/lib_mpfr_tests/tget_q/tget_q.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_q/tget_q.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_set_d128/tget_set_d128.vcxproj b/build.vs19/lib_mpfr_tests/tget_set_d128/tget_set_d128.vcxproj +index 4c971b0e..7b1f9c05 100644 +--- a/build.vs19/lib_mpfr_tests/tget_set_d128/tget_set_d128.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_set_d128/tget_set_d128.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_set_d64/tget_set_d64.vcxproj b/build.vs19/lib_mpfr_tests/tget_set_d64/tget_set_d64.vcxproj +index 93723815..d3122846 100644 +--- a/build.vs19/lib_mpfr_tests/tget_set_d64/tget_set_d64.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_set_d64/tget_set_d64.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_sj/tget_sj.vcxproj b/build.vs19/lib_mpfr_tests/tget_sj/tget_sj.vcxproj +index 5e3b49e4..ca59196e 100644 +--- a/build.vs19/lib_mpfr_tests/tget_sj/tget_sj.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_sj/tget_sj.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_str/tget_str.vcxproj b/build.vs19/lib_mpfr_tests/tget_str/tget_str.vcxproj +index 27c54d4d..5b0dc506 100644 +--- a/build.vs19/lib_mpfr_tests/tget_str/tget_str.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_str/tget_str.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tget_z/tget_z.vcxproj b/build.vs19/lib_mpfr_tests/tget_z/tget_z.vcxproj +index a5a89611..bb6c84d6 100644 +--- a/build.vs19/lib_mpfr_tests/tget_z/tget_z.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tget_z/tget_z.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tgmpop/tgmpop.vcxproj b/build.vs19/lib_mpfr_tests/tgmpop/tgmpop.vcxproj +index d74ffa81..04836906 100644 +--- a/build.vs19/lib_mpfr_tests/tgmpop/tgmpop.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tgmpop/tgmpop.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tgrandom/tgrandom.vcxproj b/build.vs19/lib_mpfr_tests/tgrandom/tgrandom.vcxproj +index e30f8f2c..e231fcae 100644 +--- a/build.vs19/lib_mpfr_tests/tgrandom/tgrandom.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tgrandom/tgrandom.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/thyperbolic/thyperbolic.vcxproj b/build.vs19/lib_mpfr_tests/thyperbolic/thyperbolic.vcxproj +index 7e7d77b4..fee6588f 100644 +--- a/build.vs19/lib_mpfr_tests/thyperbolic/thyperbolic.vcxproj ++++ b/build.vs19/lib_mpfr_tests/thyperbolic/thyperbolic.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/thypot/thypot.vcxproj b/build.vs19/lib_mpfr_tests/thypot/thypot.vcxproj +index 7365e9f7..882449a5 100644 +--- a/build.vs19/lib_mpfr_tests/thypot/thypot.vcxproj ++++ b/build.vs19/lib_mpfr_tests/thypot/thypot.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tinits/tinits.vcxproj b/build.vs19/lib_mpfr_tests/tinits/tinits.vcxproj +index 1b5c74d4..be26d4c1 100644 +--- a/build.vs19/lib_mpfr_tests/tinits/tinits.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tinits/tinits.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tinp_str/tinp_str.vcxproj b/build.vs19/lib_mpfr_tests/tinp_str/tinp_str.vcxproj +index 30ca1d95..effd7896 100644 +--- a/build.vs19/lib_mpfr_tests/tinp_str/tinp_str.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tinp_str/tinp_str.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tinternals/tinternals.vcxproj b/build.vs19/lib_mpfr_tests/tinternals/tinternals.vcxproj +index 6429d8ab..0ca5e287 100644 +--- a/build.vs19/lib_mpfr_tests/tinternals/tinternals.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tinternals/tinternals.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tisnan/tisnan.vcxproj b/build.vs19/lib_mpfr_tests/tisnan/tisnan.vcxproj +index a2882f7f..60466c1d 100644 +--- a/build.vs19/lib_mpfr_tests/tisnan/tisnan.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tisnan/tisnan.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tisqrt/tisqrt.vcxproj b/build.vs19/lib_mpfr_tests/tisqrt/tisqrt.vcxproj +index 5eef6715..5762bd33 100644 +--- a/build.vs19/lib_mpfr_tests/tisqrt/tisqrt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tisqrt/tisqrt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tj0/tj0.vcxproj b/build.vs19/lib_mpfr_tests/tj0/tj0.vcxproj +index fd79adb1..b0f8f81e 100644 +--- a/build.vs19/lib_mpfr_tests/tj0/tj0.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tj0/tj0.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tj1/tj1.vcxproj b/build.vs19/lib_mpfr_tests/tj1/tj1.vcxproj +index fd490e18..ebc63114 100644 +--- a/build.vs19/lib_mpfr_tests/tj1/tj1.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tj1/tj1.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tjn/tjn.vcxproj b/build.vs19/lib_mpfr_tests/tjn/tjn.vcxproj +index 90722cc6..2578d20f 100644 +--- a/build.vs19/lib_mpfr_tests/tjn/tjn.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tjn/tjn.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tl2b/tl2b.vcxproj b/build.vs19/lib_mpfr_tests/tl2b/tl2b.vcxproj +index fa271e03..7650cb39 100644 +--- a/build.vs19/lib_mpfr_tests/tl2b/tl2b.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tl2b/tl2b.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlgamma/tlgamma.vcxproj b/build.vs19/lib_mpfr_tests/tlgamma/tlgamma.vcxproj +index e06fd5c5..6bca619c 100644 +--- a/build.vs19/lib_mpfr_tests/tlgamma/tlgamma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlgamma/tlgamma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tli2/tli2.vcxproj b/build.vs19/lib_mpfr_tests/tli2/tli2.vcxproj +index ee89e213..6da21466 100644 +--- a/build.vs19/lib_mpfr_tests/tli2/tli2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tli2/tli2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlngamma/tlngamma.vcxproj b/build.vs19/lib_mpfr_tests/tlngamma/tlngamma.vcxproj +index 0b96011d..b8b0fffc 100644 +--- a/build.vs19/lib_mpfr_tests/tlngamma/tlngamma.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlngamma/tlngamma.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlog/tlog.vcxproj b/build.vs19/lib_mpfr_tests/tlog/tlog.vcxproj +index 343a1df1..787c71c4 100644 +--- a/build.vs19/lib_mpfr_tests/tlog/tlog.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlog/tlog.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlog10/tlog10.vcxproj b/build.vs19/lib_mpfr_tests/tlog10/tlog10.vcxproj +index 1e67345e..04fd8ba5 100644 +--- a/build.vs19/lib_mpfr_tests/tlog10/tlog10.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlog10/tlog10.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlog1p/tlog1p.vcxproj b/build.vs19/lib_mpfr_tests/tlog1p/tlog1p.vcxproj +index c7ec0cc3..b6bb18e2 100644 +--- a/build.vs19/lib_mpfr_tests/tlog1p/tlog1p.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlog1p/tlog1p.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlog2/tlog2.vcxproj b/build.vs19/lib_mpfr_tests/tlog2/tlog2.vcxproj +index 2724e950..d3e18d2f 100644 +--- a/build.vs19/lib_mpfr_tests/tlog2/tlog2.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlog2/tlog2.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tlog_ui/tlog_ui.vcxproj b/build.vs19/lib_mpfr_tests/tlog_ui/tlog_ui.vcxproj +index 5aa95269..9898a4b1 100644 +--- a/build.vs19/lib_mpfr_tests/tlog_ui/tlog_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tlog_ui/tlog_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmin_prec/tmin_prec.vcxproj b/build.vs19/lib_mpfr_tests/tmin_prec/tmin_prec.vcxproj +index 7d222c1d..0970be07 100644 +--- a/build.vs19/lib_mpfr_tests/tmin_prec/tmin_prec.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmin_prec/tmin_prec.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tminmax/tminmax.vcxproj b/build.vs19/lib_mpfr_tests/tminmax/tminmax.vcxproj +index c2de145c..d8389934 100644 +--- a/build.vs19/lib_mpfr_tests/tminmax/tminmax.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tminmax/tminmax.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmodf/tmodf.vcxproj b/build.vs19/lib_mpfr_tests/tmodf/tmodf.vcxproj +index c9731b07..e5ae3a6b 100644 +--- a/build.vs19/lib_mpfr_tests/tmodf/tmodf.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmodf/tmodf.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmul/tmul.vcxproj b/build.vs19/lib_mpfr_tests/tmul/tmul.vcxproj +index 95f1b933..e52a2336 100644 +--- a/build.vs19/lib_mpfr_tests/tmul/tmul.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmul/tmul.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmul_2exp/tmul_2exp.vcxproj b/build.vs19/lib_mpfr_tests/tmul_2exp/tmul_2exp.vcxproj +index d75b7e9e..60edcf95 100644 +--- a/build.vs19/lib_mpfr_tests/tmul_2exp/tmul_2exp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmul_2exp/tmul_2exp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmul_d/tmul_d.vcxproj b/build.vs19/lib_mpfr_tests/tmul_d/tmul_d.vcxproj +index c78956dd..2f9c5f82 100644 +--- a/build.vs19/lib_mpfr_tests/tmul_d/tmul_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmul_d/tmul_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tmul_ui/tmul_ui.vcxproj b/build.vs19/lib_mpfr_tests/tmul_ui/tmul_ui.vcxproj +index 9c8f56c1..956bcc08 100644 +--- a/build.vs19/lib_mpfr_tests/tmul_ui/tmul_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tmul_ui/tmul_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tnext/tnext.vcxproj b/build.vs19/lib_mpfr_tests/tnext/tnext.vcxproj +index 1e9ee402..f9ad8e9c 100644 +--- a/build.vs19/lib_mpfr_tests/tnext/tnext.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tnext/tnext.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tnrandom/tnrandom.vcxproj b/build.vs19/lib_mpfr_tests/tnrandom/tnrandom.vcxproj +index 211e9b41..fe6a6dd7 100644 +--- a/build.vs19/lib_mpfr_tests/tnrandom/tnrandom.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tnrandom/tnrandom.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tnrandom_chisq/tnrandom_chisq.vcxproj b/build.vs19/lib_mpfr_tests/tnrandom_chisq/tnrandom_chisq.vcxproj +index 18c539cb..4ed4f610 100644 +--- a/build.vs19/lib_mpfr_tests/tnrandom_chisq/tnrandom_chisq.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tnrandom_chisq/tnrandom_chisq.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tout_str/tout_str.vcxproj b/build.vs19/lib_mpfr_tests/tout_str/tout_str.vcxproj +index 4a0ea540..32b82d97 100644 +--- a/build.vs19/lib_mpfr_tests/tout_str/tout_str.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tout_str/tout_str.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/toutimpl/toutimpl.vcxproj b/build.vs19/lib_mpfr_tests/toutimpl/toutimpl.vcxproj +index 58f44ac3..bee2343d 100644 +--- a/build.vs19/lib_mpfr_tests/toutimpl/toutimpl.vcxproj ++++ b/build.vs19/lib_mpfr_tests/toutimpl/toutimpl.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tpow/tpow.vcxproj b/build.vs19/lib_mpfr_tests/tpow/tpow.vcxproj +index 6614d5bd..eb5b5853 100644 +--- a/build.vs19/lib_mpfr_tests/tpow/tpow.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tpow/tpow.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tpow3/tpow3.vcxproj b/build.vs19/lib_mpfr_tests/tpow3/tpow3.vcxproj +index 0d530fb9..2319585d 100644 +--- a/build.vs19/lib_mpfr_tests/tpow3/tpow3.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tpow3/tpow3.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tpow_all/tpow_all.vcxproj b/build.vs19/lib_mpfr_tests/tpow_all/tpow_all.vcxproj +index 1467fea6..734f6578 100644 +--- a/build.vs19/lib_mpfr_tests/tpow_all/tpow_all.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tpow_all/tpow_all.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tpow_z/tpow_z.vcxproj b/build.vs19/lib_mpfr_tests/tpow_z/tpow_z.vcxproj +index 68295aae..58d18369 100644 +--- a/build.vs19/lib_mpfr_tests/tpow_z/tpow_z.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tpow_z/tpow_z.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tprec_round/tprec_round.vcxproj b/build.vs19/lib_mpfr_tests/tprec_round/tprec_round.vcxproj +index 5ef28572..efa27c73 100644 +--- a/build.vs19/lib_mpfr_tests/tprec_round/tprec_round.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tprec_round/tprec_round.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tprintf/tprintf.vcxproj b/build.vs19/lib_mpfr_tests/tprintf/tprintf.vcxproj +index 339411cc..63b86106 100644 +--- a/build.vs19/lib_mpfr_tests/tprintf/tprintf.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tprintf/tprintf.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trandom/trandom.vcxproj b/build.vs19/lib_mpfr_tests/trandom/trandom.vcxproj +index 8506b3a5..e1e6f898 100644 +--- a/build.vs19/lib_mpfr_tests/trandom/trandom.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trandom/trandom.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trandom_deviate/trandom_deviate.vcxproj b/build.vs19/lib_mpfr_tests/trandom_deviate/trandom_deviate.vcxproj +index 60fd4f79..9fd3b616 100644 +--- a/build.vs19/lib_mpfr_tests/trandom_deviate/trandom_deviate.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trandom_deviate/trandom_deviate.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trec_sqrt/trec_sqrt.vcxproj b/build.vs19/lib_mpfr_tests/trec_sqrt/trec_sqrt.vcxproj +index 6f0e0d5e..45a03c4e 100644 +--- a/build.vs19/lib_mpfr_tests/trec_sqrt/trec_sqrt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trec_sqrt/trec_sqrt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tremquo/tremquo.vcxproj b/build.vs19/lib_mpfr_tests/tremquo/tremquo.vcxproj +index 7580c8db..b54c9fc2 100644 +--- a/build.vs19/lib_mpfr_tests/tremquo/tremquo.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tremquo/tremquo.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trint/trint.vcxproj b/build.vs19/lib_mpfr_tests/trint/trint.vcxproj +index d3c6474b..4e880e95 100644 +--- a/build.vs19/lib_mpfr_tests/trint/trint.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trint/trint.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trndna/trndna.vcxproj b/build.vs19/lib_mpfr_tests/trndna/trndna.vcxproj +index 72bf49f5..c4165609 100644 +--- a/build.vs19/lib_mpfr_tests/trndna/trndna.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trndna/trndna.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/troot/troot.vcxproj b/build.vs19/lib_mpfr_tests/troot/troot.vcxproj +index 617799a5..d74551cb 100644 +--- a/build.vs19/lib_mpfr_tests/troot/troot.vcxproj ++++ b/build.vs19/lib_mpfr_tests/troot/troot.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/trootn_ui/trootn_ui.vcxproj b/build.vs19/lib_mpfr_tests/trootn_ui/trootn_ui.vcxproj +index a22f29c8..a47bef48 100644 +--- a/build.vs19/lib_mpfr_tests/trootn_ui/trootn_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/trootn_ui/trootn_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsec/tsec.vcxproj b/build.vs19/lib_mpfr_tests/tsec/tsec.vcxproj +index c41638b8..ccfd8d7c 100644 +--- a/build.vs19/lib_mpfr_tests/tsec/tsec.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsec/tsec.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsech/tsech.vcxproj b/build.vs19/lib_mpfr_tests/tsech/tsech.vcxproj +index 89c5ab31..6cebc9ec 100644 +--- a/build.vs19/lib_mpfr_tests/tsech/tsech.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsech/tsech.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset/tset.vcxproj b/build.vs19/lib_mpfr_tests/tset/tset.vcxproj +index c18e0092..9cc175b9 100644 +--- a/build.vs19/lib_mpfr_tests/tset/tset.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset/tset.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_d/tset_d.vcxproj b/build.vs19/lib_mpfr_tests/tset_d/tset_d.vcxproj +index 36b9719f..ea47dc29 100644 +--- a/build.vs19/lib_mpfr_tests/tset_d/tset_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_d/tset_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_exp/tset_exp.vcxproj b/build.vs19/lib_mpfr_tests/tset_exp/tset_exp.vcxproj +index 95810c3e..c234607a 100644 +--- a/build.vs19/lib_mpfr_tests/tset_exp/tset_exp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_exp/tset_exp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_f/tset_f.vcxproj b/build.vs19/lib_mpfr_tests/tset_f/tset_f.vcxproj +index bc09744b..9afce049 100644 +--- a/build.vs19/lib_mpfr_tests/tset_f/tset_f.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_f/tset_f.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_float128/tset_float128.vcxproj b/build.vs19/lib_mpfr_tests/tset_float128/tset_float128.vcxproj +index 67f2d41c..5f2c8924 100644 +--- a/build.vs19/lib_mpfr_tests/tset_float128/tset_float128.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_float128/tset_float128.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_ld/tset_ld.vcxproj b/build.vs19/lib_mpfr_tests/tset_ld/tset_ld.vcxproj +index ca1c364a..14689211 100644 +--- a/build.vs19/lib_mpfr_tests/tset_ld/tset_ld.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_ld/tset_ld.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_q/tset_q.vcxproj b/build.vs19/lib_mpfr_tests/tset_q/tset_q.vcxproj +index 45ecbd6f..1f6952b6 100644 +--- a/build.vs19/lib_mpfr_tests/tset_q/tset_q.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_q/tset_q.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_si/tset_si.vcxproj b/build.vs19/lib_mpfr_tests/tset_si/tset_si.vcxproj +index f8fe5e0b..cd609e3c 100644 +--- a/build.vs19/lib_mpfr_tests/tset_si/tset_si.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_si/tset_si.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_sj/tset_sj.vcxproj b/build.vs19/lib_mpfr_tests/tset_sj/tset_sj.vcxproj +index c030c0d0..b3d93425 100644 +--- a/build.vs19/lib_mpfr_tests/tset_sj/tset_sj.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_sj/tset_sj.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_str/tset_str.vcxproj b/build.vs19/lib_mpfr_tests/tset_str/tset_str.vcxproj +index 0f96aa4d..d7248918 100644 +--- a/build.vs19/lib_mpfr_tests/tset_str/tset_str.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_str/tset_str.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_z/tset_z.vcxproj b/build.vs19/lib_mpfr_tests/tset_z/tset_z.vcxproj +index 826d40d6..1c6bd5c2 100644 +--- a/build.vs19/lib_mpfr_tests/tset_z/tset_z.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_z/tset_z.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tset_z_exp/tset_z_exp.vcxproj b/build.vs19/lib_mpfr_tests/tset_z_exp/tset_z_exp.vcxproj +index 9cdd2468..42283be7 100644 +--- a/build.vs19/lib_mpfr_tests/tset_z_exp/tset_z_exp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tset_z_exp/tset_z_exp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.30128.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsgn/tsgn.vcxproj b/build.vs19/lib_mpfr_tests/tsgn/tsgn.vcxproj +index 70b5ef80..e416afaf 100644 +--- a/build.vs19/lib_mpfr_tests/tsgn/tsgn.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsgn/tsgn.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsi_op/tsi_op.vcxproj b/build.vs19/lib_mpfr_tests/tsi_op/tsi_op.vcxproj +index 25e54898..f4b76c14 100644 +--- a/build.vs19/lib_mpfr_tests/tsi_op/tsi_op.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsi_op/tsi_op.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsin/tsin.vcxproj b/build.vs19/lib_mpfr_tests/tsin/tsin.vcxproj +index 7fb3bed9..c4289447 100644 +--- a/build.vs19/lib_mpfr_tests/tsin/tsin.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsin/tsin.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsin_cos/tsin_cos.vcxproj b/build.vs19/lib_mpfr_tests/tsin_cos/tsin_cos.vcxproj +index 2e814100..2538aa97 100644 +--- a/build.vs19/lib_mpfr_tests/tsin_cos/tsin_cos.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsin_cos/tsin_cos.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsinh/tsinh.vcxproj b/build.vs19/lib_mpfr_tests/tsinh/tsinh.vcxproj +index c12b52f2..5bec973d 100644 +--- a/build.vs19/lib_mpfr_tests/tsinh/tsinh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsinh/tsinh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsinh_cosh/tsinh_cosh.vcxproj b/build.vs19/lib_mpfr_tests/tsinh_cosh/tsinh_cosh.vcxproj +index 9655d1d2..68f75ff8 100644 +--- a/build.vs19/lib_mpfr_tests/tsinh_cosh/tsinh_cosh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsinh_cosh/tsinh_cosh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsprintf/tsprintf.vcxproj b/build.vs19/lib_mpfr_tests/tsprintf/tsprintf.vcxproj +index 492019c8..57a38cf1 100644 +--- a/build.vs19/lib_mpfr_tests/tsprintf/tsprintf.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsprintf/tsprintf.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsqr/tsqr.vcxproj b/build.vs19/lib_mpfr_tests/tsqr/tsqr.vcxproj +index b05525e5..28c57196 100644 +--- a/build.vs19/lib_mpfr_tests/tsqr/tsqr.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsqr/tsqr.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsqrt/tsqrt.vcxproj b/build.vs19/lib_mpfr_tests/tsqrt/tsqrt.vcxproj +index 0d30a4a7..682ff75a 100644 +--- a/build.vs19/lib_mpfr_tests/tsqrt/tsqrt.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsqrt/tsqrt.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsqrt_ui/tsqrt_ui.vcxproj b/build.vs19/lib_mpfr_tests/tsqrt_ui/tsqrt_ui.vcxproj +index 9ffe5915..afcff798 100644 +--- a/build.vs19/lib_mpfr_tests/tsqrt_ui/tsqrt_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsqrt_ui/tsqrt_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tstckintc/tstckintc.vcxproj b/build.vs19/lib_mpfr_tests/tstckintc/tstckintc.vcxproj +index 07f1eab2..433a540b 100644 +--- a/build.vs19/lib_mpfr_tests/tstckintc/tstckintc.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tstckintc/tstckintc.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tstdint/tstdint.vcxproj b/build.vs19/lib_mpfr_tests/tstdint/tstdint.vcxproj +index 19120d35..52046835 100644 +--- a/build.vs19/lib_mpfr_tests/tstdint/tstdint.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tstdint/tstdint.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.30128.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tstrtofr/tstrtofr.vcxproj b/build.vs19/lib_mpfr_tests/tstrtofr/tstrtofr.vcxproj +index 3ee7785a..b1e8cf3a 100644 +--- a/build.vs19/lib_mpfr_tests/tstrtofr/tstrtofr.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tstrtofr/tstrtofr.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsub/tsub.vcxproj b/build.vs19/lib_mpfr_tests/tsub/tsub.vcxproj +index b1b3b4ef..e69ac125 100644 +--- a/build.vs19/lib_mpfr_tests/tsub/tsub.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsub/tsub.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsub1sp/tsub1sp.vcxproj b/build.vs19/lib_mpfr_tests/tsub1sp/tsub1sp.vcxproj +index 7b34e819..ab0dcf27 100644 +--- a/build.vs19/lib_mpfr_tests/tsub1sp/tsub1sp.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsub1sp/tsub1sp.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsub_d/tsub_d.vcxproj b/build.vs19/lib_mpfr_tests/tsub_d/tsub_d.vcxproj +index b9836a5f..c5d5ebe4 100644 +--- a/build.vs19/lib_mpfr_tests/tsub_d/tsub_d.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsub_d/tsub_d.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsub_ui/tsub_ui.vcxproj b/build.vs19/lib_mpfr_tests/tsub_ui/tsub_ui.vcxproj +index 65b6d044..1bec0dc1 100644 +--- a/build.vs19/lib_mpfr_tests/tsub_ui/tsub_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsub_ui/tsub_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsubnormal/tsubnormal.vcxproj b/build.vs19/lib_mpfr_tests/tsubnormal/tsubnormal.vcxproj +index 525979e3..613358d4 100644 +--- a/build.vs19/lib_mpfr_tests/tsubnormal/tsubnormal.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsubnormal/tsubnormal.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tsum/tsum.vcxproj b/build.vs19/lib_mpfr_tests/tsum/tsum.vcxproj +index 0a287acf..6ee64388 100644 +--- a/build.vs19/lib_mpfr_tests/tsum/tsum.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tsum/tsum.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tswap/tswap.vcxproj b/build.vs19/lib_mpfr_tests/tswap/tswap.vcxproj +index a68407a0..ce845fb0 100644 +--- a/build.vs19/lib_mpfr_tests/tswap/tswap.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tswap/tswap.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ttan/ttan.vcxproj b/build.vs19/lib_mpfr_tests/ttan/ttan.vcxproj +index aa41d82b..07ee149b 100644 +--- a/build.vs19/lib_mpfr_tests/ttan/ttan.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ttan/ttan.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ttanh/ttanh.vcxproj b/build.vs19/lib_mpfr_tests/ttanh/ttanh.vcxproj +index 77cbc857..dc726624 100644 +--- a/build.vs19/lib_mpfr_tests/ttanh/ttanh.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ttanh/ttanh.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ttotal_order/ttotal_order.vcxproj b/build.vs19/lib_mpfr_tests/ttotal_order/ttotal_order.vcxproj +index 56e8db44..787faa91 100644 +--- a/build.vs19/lib_mpfr_tests/ttotal_order/ttotal_order.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ttotal_order/ttotal_order.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ttrunc/ttrunc.vcxproj b/build.vs19/lib_mpfr_tests/ttrunc/ttrunc.vcxproj +index 567c35f5..744cd9f7 100644 +--- a/build.vs19/lib_mpfr_tests/ttrunc/ttrunc.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ttrunc/ttrunc.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tui_div/tui_div.vcxproj b/build.vs19/lib_mpfr_tests/tui_div/tui_div.vcxproj +index 31d0c2e6..10a32af3 100644 +--- a/build.vs19/lib_mpfr_tests/tui_div/tui_div.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tui_div/tui_div.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tui_pow/tui_pow.vcxproj b/build.vs19/lib_mpfr_tests/tui_pow/tui_pow.vcxproj +index 917ea1df..0609cf92 100644 +--- a/build.vs19/lib_mpfr_tests/tui_pow/tui_pow.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tui_pow/tui_pow.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tui_sub/tui_sub.vcxproj b/build.vs19/lib_mpfr_tests/tui_sub/tui_sub.vcxproj +index ad059e52..e246b094 100644 +--- a/build.vs19/lib_mpfr_tests/tui_sub/tui_sub.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tui_sub/tui_sub.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/turandom/turandom.vcxproj b/build.vs19/lib_mpfr_tests/turandom/turandom.vcxproj +index 00d8383e..5fb75f83 100644 +--- a/build.vs19/lib_mpfr_tests/turandom/turandom.vcxproj ++++ b/build.vs19/lib_mpfr_tests/turandom/turandom.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tvalist/tvalist.vcxproj b/build.vs19/lib_mpfr_tests/tvalist/tvalist.vcxproj +index 7ff65985..d4d32dac 100644 +--- a/build.vs19/lib_mpfr_tests/tvalist/tvalist.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tvalist/tvalist.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tversion/tversion.vcxproj b/build.vs19/lib_mpfr_tests/tversion/tversion.vcxproj +index 08d5a137..c79c9639 100644 +--- a/build.vs19/lib_mpfr_tests/tversion/tversion.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tversion/tversion.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ty0/ty0.vcxproj b/build.vs19/lib_mpfr_tests/ty0/ty0.vcxproj +index f478c767..9e8eb23a 100644 +--- a/build.vs19/lib_mpfr_tests/ty0/ty0.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ty0/ty0.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/ty1/ty1.vcxproj b/build.vs19/lib_mpfr_tests/ty1/ty1.vcxproj +index 6440cf69..104b5362 100644 +--- a/build.vs19/lib_mpfr_tests/ty1/ty1.vcxproj ++++ b/build.vs19/lib_mpfr_tests/ty1/ty1.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tyn/tyn.vcxproj b/build.vs19/lib_mpfr_tests/tyn/tyn.vcxproj +index 88710e2f..017d7f58 100644 +--- a/build.vs19/lib_mpfr_tests/tyn/tyn.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tyn/tyn.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tzeta/tzeta.vcxproj b/build.vs19/lib_mpfr_tests/tzeta/tzeta.vcxproj +index f6af2e0f..4dc890dd 100644 +--- a/build.vs19/lib_mpfr_tests/tzeta/tzeta.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tzeta/tzeta.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/lib_mpfr_tests/tzeta_ui/tzeta_ui.vcxproj b/build.vs19/lib_mpfr_tests/tzeta_ui/tzeta_ui.vcxproj +index 66332f60..11a9501d 100644 +--- a/build.vs19/lib_mpfr_tests/tzeta_ui/tzeta_ui.vcxproj ++++ b/build.vs19/lib_mpfr_tests/tzeta_ui/tzeta_ui.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -29,10 +37,18 @@ + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 + ++ ++ Application ++ v142 ++ + + Application + v142 +@@ -47,9 +63,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -60,11 +82,15 @@ + + <_ProjectFileVersion>10.0.21006.1 + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ ++ $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ + $(SolutionDir)lib_mpfr_tests\$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + +@@ -87,6 +113,25 @@ + + + ++ ++ ++ Full ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ++ ++ MultiThreaded ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ true ++ true ++ ++ ++ ++ + + + X64 +@@ -130,6 +175,27 @@ + + + ++ ++ ++ Disabled ++ ..\..\;..\..\..\src\;..\..\..\..\mpir\lib\$(IntDir);%(AdditionalIncludeDirectories) ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ MultiThreadedDebug ++ ++ ++ true ++ ..\..\..\..\mpir\lib\$(IntDir)\config.h ++ ++ ++ ..\$(IntDir)lib_tests.lib;..\..\..\lib\$(IntDir)mpfr.lib;..\..\..\..\mpir\lib\$(IntDir)mpir.lib ++ ++ ++ ++ ++ ++ ++ ++ + + + X64 +diff --git a/build.vs19/timing/timing.vcxproj b/build.vs19/timing/timing.vcxproj +index 71a2f7ab..f4ff21c3 100644 +--- a/build.vs19/timing/timing.vcxproj ++++ b/build.vs19/timing/timing.vcxproj +@@ -1,10 +1,18 @@ + + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -34,6 +42,12 @@ + v142 + MultiByte + ++ ++ Application ++ true ++ v142 ++ MultiByte ++ + + Application + false +@@ -41,6 +55,13 @@ + true + MultiByte + ++ ++ Application ++ false ++ v142 ++ true ++ MultiByte ++ + + Application + true +@@ -62,9 +83,15 @@ + + + ++ ++ ++ + + + ++ ++ ++ + + + +@@ -76,10 +103,18 @@ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + + Level3 +@@ -110,6 +145,19 @@ + ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) + + ++ ++ ++ Level3 ++ Disabled ++ true ++ true ++ MultiThreadedDebug ++ ..\;..\..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);..\..\..\mpir\tune;..\..\..\mpir\build.vc;%(AdditionalIncludeDirectories) ++ ++ ++ ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) ++ ++ + + + Level3 +@@ -140,6 +188,23 @@ + ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) + + ++ ++ ++ Level3 ++ MaxSpeed ++ true ++ true ++ true ++ true ++ MultiThreaded ++ ..\;..\..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);..\..\..\mpir\tune;..\..\..\mpir\build.vc;%(AdditionalIncludeDirectories) ++ ++ ++ true ++ true ++ ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) ++ ++ + + + +diff --git a/build.vs19/tuneup/tuneup.vcxproj b/build.vs19/tuneup/tuneup.vcxproj +index d076c235..61ef8867 100644 +--- a/build.vs19/tuneup/tuneup.vcxproj ++++ b/build.vs19/tuneup/tuneup.vcxproj +@@ -1,6 +1,10 @@ +  + + ++ ++ Debug ++ ARM64 ++ + + Debug + Win32 +@@ -9,6 +13,10 @@ + Debug + x64 + ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -31,6 +39,12 @@ + NotSet + v142 + ++ ++ Application ++ true ++ NotSet ++ v142 ++ + + Application + true +@@ -44,6 +58,13 @@ + NotSet + v142 + ++ ++ Application ++ false ++ true ++ NotSet ++ v142 ++ + + Application + false +@@ -57,12 +78,18 @@ + + + ++ ++ ++ + + + + + + ++ ++ ++ + + + +@@ -72,6 +99,11 @@ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ true ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + true + $(SolutionDir)$(Platform)\$(Configuration)\ +@@ -82,6 +114,11 @@ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + ++ ++ false ++ $(SolutionDir)$(Platform)\$(Configuration)\ ++ $(Platform)\$(Configuration)\ ++ + + false + $(SolutionDir)$(Platform)\$(Configuration)\ +@@ -107,6 +144,26 @@ + ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) + + ++ ++ ++ ++ ++ Level3 ++ Disabled ++ WIN32;_DEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ..\;..\..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);..\..\..\mpir\tune;..\..\..\mpir\msvc;%(AdditionalIncludeDirectories) ++ MultiThreadedDebug ++ ++ ++ true ++ true ++ ++ ++ Console ++ true ++ ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) ++ ++ + + + +@@ -150,6 +207,29 @@ + ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) + + ++ ++ ++ Level3 ++ ++ ++ Full ++ true ++ true ++ WIN32;NDEBUG;_CONSOLE;MPFR_HAVE_GMP_IMPL ++ ..\;..\..\;..\..\src\;..\..\..\mpir\lib\$(IntDir);..\..\..\mpir\tune;..\..\..\mpir\msvc;%(AdditionalIncludeDirectories) ++ MultiThreaded ++ ++ ++ true ++ ++ ++ Console ++ true ++ true ++ true ++ ..\..\lib\$(IntDir)mpfr.lib;..\..\..\mpir\lib\$(IntDir)mpir.lib;..\..\..\mpir\msvc\vs17\$(IntDir)lib_speed.lib;%(AdditionalDependencies) ++ ++ + + + Level3 diff --git a/win/patches/mpir-arm64-changes.patch b/win/patches/mpir-arm64-changes.patch new file mode 100644 index 0000000000..ab68519c2c --- /dev/null +++ b/win/patches/mpir-arm64-changes.patch @@ -0,0 +1,519 @@ +diff --git a/msvc/postbuild.bat b/msvc/postbuild.bat +index 1c301d1a..7d5362be 100644 +--- a/msvc/postbuild.bat ++++ b/msvc/postbuild.bat +@@ -1,6 +1,6 @@ + @echo off + rem %1 = full target path +-rem %2 = last two digits of the Visual Studio version number (e.g. 17) ++rem %2 = last two digits of the Visual Studio version number (e.g. 17 or 22) + + set vs_ver=%2 + set str=%~1 +@@ -20,11 +20,13 @@ goto dele + set str=%str_confirmed% + echo "Final subpath %str%" + +-rem we now have: msvc.\vs\\\\mpir. +-rem extract: project_directory, platform (plat=), configuration (conf=) and file name ++rem we now have: msvc\vs\\\\mpir(.xx). ++rem extract: project_directory, platform (plat), configuration (conf) and file name + + set file= +-for /f "tokens=1,2,3,4,5,6 delims=\" %%a in ("%str%") do set tloc=%%c&set plat=%%d&set conf=%%e&set file=%%f ++for /f "tokens=1,2,3,4,5,6 delims=\" %%a in ("%str%") do ( ++ set tloc=%%c&set plat=%%d&set conf=%%e&set file=%%f ++) + if /i "%file%" NEQ "" (goto next) + call :seterr & echo ERROR: %1 is not supported & exit /b %errorlevel% + +@@ -42,9 +44,9 @@ call :seterr & echo "postbuild copy error ERROR: target=%tloc%, plat=%plat%, con + + :is2nd: + rem set the target and final binary output directories +-set tgt_dir="vs%vs_ver%\%loc%%plat%\%conf%\" +-set bin_dir="..\%extn%\%plat%\%conf%\" +-set hdr_dir="..\%extn%\%plat%\%conf%\" ++set "tgt_dir=vs%vs_ver%\%loc%%plat%\%conf%" ++set "bin_dir=..\%extn%\%plat%\%conf%" ++set "hdr_dir=..\%extn%\%plat%\%conf%" + + rem output parametrers for the MPIR tests + if /i "%filename%" EQU "mpirxx" goto skip +@@ -66,43 +68,42 @@ rem %1 = target (build output) directory + rem %2 = binary destination directory + rem %3 = configuration (debug/release) + rem %4 = library (lib/dll) +-rem %5 = file name ++rem %5 = file name (mpir | mpirxx) + :copyb + if "%4" EQU "dll" ( +- copy %1mpir.dll %2mpir.dll > nul 2>&1 +- copy %1mpir.exp %2mpir.exp > nul 2>&1 +- copy %1mpir.lib %2mpir.lib > nul 2>&1 +- if exist %1mpir.pdb (copy %1mpir.pdb %2mpir.pdb > nul 2>&1) ++ copy "%~1\mpir.dll" "%~2\mpir.dll" > nul 2>&1 ++ copy "%~1\mpir.exp" "%~2\mpir.exp" > nul 2>&1 ++ copy "%~1\mpir.lib" "%~2\mpir.lib" > nul 2>&1 ++ if exist "%~1\mpir.pdb" copy "%~1\mpir.pdb" "%~2\mpir.pdb" > nul 2>&1 + ) else if "%4" EQU "lib" ( + if "%5" EQU "mpir" ( +- if exist %1mpir.lib ( +- copy %1mpir.lib %2mpir.lib > nul 2>&1 +- if exist %1mpir.pdb (copy %1mpir.pdb %2mpir.pdb > nul 2>&1) ++ if exist "%~1\mpir.lib" ( ++ copy "%~1\mpir.lib" "%~2\mpir.lib" > nul 2>&1 ++ if exist "%~1\mpir.pdb" copy "%~1\mpir.pdb" "%~2\mpir.pdb" > nul 2>&1 ++ ) else ( ++ echo "Not Found MPIR at location" "%~1\mpir.lib" + ) + ) else if "%5" EQU "mpirxx" ( +- if exist %1mpirxx.lib ( +- copy %1mpirxx.lib %2mpirxx.lib > nul 2>&1 +- if exist %1mpirxx.pdb (copy %1mpirxx.pdb %2mpirxx.pdb > nul 2>&1) ++ if exist "%~1\mpirxx.lib" ( ++ copy "%~1\mpirxx.lib" "%~2\mpirxx.lib" > nul 2>&1 ++ if exist "%~1\mpirxx.pdb" copy "%~1\mpirxx.pdb" "%~2\mpirxx.pdb" > nul 2>&1 + ) + ) + ) else ( +- call :seterr & echo ERROR: illegal library type %4 & exit /b %errorlevel% ++ call :seterr & echo ERROR: illegal library type %4 & exit /b %errorlevel% + ) +- +-rem set configuration for the tests +-call gen_test_config_props %plat% %conf% %vs_ver% + exit /b 0 + + rem copy headers to final destination directory + :copyh +-copy ..\config.h %1config.h > nul 2>&1 +-copy ..\gmp-mparam.h %1gmp-mparam.h > nul 2>&1 +-copy ..\mpir.h %1mpir.h > nul 2>&1 +-copy ..\mpir.h %1gmp.h > nul 2>&1 +-copy ..\gmp-impl.h %1gmp-impl.h > nul 2>&1 +-copy ..\longlong.h %1longlong.h > nul 2>&1 +-copy ..\mpirxx.h %1mpirxx.h > nul 2>&1 +-copy ..\mpirxx.h %1gmpxx.h > nul 2>&1 ++copy "..\config.h" "%~1\config.h" > nul 2>&1 ++copy "..\gmp-mparam.h" "%~1\gmp-mparam.h" > nul 2>&1 ++copy "..\mpir.h" "%~1\mpir.h" > nul 2>&1 ++copy "..\mpir.h" "%~1\gmp.h" > nul 2>&1 ++copy "..\gmp-impl.h" "%~1\gmp-impl.h" > nul 2>&1 ++copy "..\longlong.h" "%~1\longlong.h" > nul 2>&1 ++copy "..\mpirxx.h" "%~1\mpirxx.h" > nul 2>&1 ++copy "..\mpirxx.h" "%~1\gmpxx.h" > nul 2>&1 + exit /b 0 + + :seterr +diff --git a/msvc/prebuild.bat b/msvc/prebuild.bat +index 243d054f..c1d49c67 100644 +--- a/msvc/prebuild.bat ++++ b/msvc/prebuild.bat +@@ -1,29 +1,56 @@ + @echo off +-rem %1 = mpn directory (generic, x86\... or x86_64\...) +-rem %2 = platform (win32 or x64) ++rem %1 = mpn directory (generic, x86\... or x86_64\... or arm64\...) ++rem %2 = platform (win32, x64/amd64, or arm64) + rem %3 = MSVC version number (e.g. 14) + +-if /i "%2" EQU "win32" ((set platform=win32) & (set bdir=x86w\)) else ((set platform=x64) & (set bdir=x86_64w\)) +-set sdir= +-if /i "%1" EQU "gc" ((set sdir=generic) & (set bdir=generic)) else (set sdir=%bdir%%1) +-if not exist ..\mpn\%sdir% (call :seterr & echo ERROR: %1 is not supported & exit /b %errorlevel%) ++set "platform=" ++set "bdir=" ++ ++if /i "%2"=="win32" ( ++ set "platform=win32" ++ set "bdir=x86w\" ++) else if /i "%2"=="x64" ( ++ set "platform=x64" ++ set "bdir=x86_64w\" ++) else if /i "%2"=="arm64" ( ++ set "platform=arm64" ++ set "bdir=arm64w\" ++) else ( ++ call :seterr & echo ERROR: Unsupported platform "%2" (expected win32, x64/amd64, or arm64) & exit /b %errorlevel% ++) ++ ++set "sdir=" ++if /i "%1"=="gc" ( ++ rem Generic C (portable) implementation ++ set "sdir=generic" ++ set "bdir=generic" ++) else ( ++ set "sdir=%bdir%%~1" ++) ++ ++if not exist "..\mpn\%sdir%" ( ++ call :seterr & echo ERROR: %1 is not supported & exit /b %errorlevel% ++) + + echo building MPIR for %1 (%platform%) from directory mpn\%sdir% + +-set cdir=vs%3\cdata\mpn\%sdir%\ +-set sdir=..\mpn\%sdir%\ +-set bdir=..\mpn\%bdir%\ ++set "cdir=vs%3\cdata\mpn\%sdir%\" ++set "sdir=..\mpn\%sdir%\" ++set "bdir=..\mpn\%bdir%\" + + call gen_mpir_h %platform% + call gen_config_h %cdir% + +-if exist %sdir%\gmp-mparam.h (call out_copy_rename %sdir%\gmp-mparam.h ..\ gmp-mparam.h) else ( +- call out_copy_rename %bdir%\gmp-mparam.h ..\ gmp-mparam.h) ++if exist "%sdir%\gmp-mparam.h" ( ++ call out_copy_rename "%sdir%\gmp-mparam.h" "..\" "gmp-mparam.h" ++) else ( ++ call out_copy_rename "%bdir%\gmp-mparam.h" "..\" "gmp-mparam.h" ++) + +-type ..\longlong_pre.h >tmp.h +-type %bdir%\longlong_inc.h >>tmp.h +-type ..\longlong_post.h >>tmp.h +-call out_copy_rename tmp.h ..\ longlong.h ++type "..\longlong_pre.h" > tmp.h ++type "%bdir%\longlong_inc.h" >> tmp.h ++type "..\longlong_post.h" >> tmp.h ++call out_copy_rename tmp.h "..\" "longlong.h" + del tmp.h + + exit /b 0 +diff --git a/msvc/vs22/lib_mpir_cxx/lib_mpir_cxx.vcxproj b/msvc/vs22/lib_mpir_cxx/lib_mpir_cxx.vcxproj +index 0aca0c6e..e4bcf619 100644 +--- a/msvc/vs22/lib_mpir_cxx/lib_mpir_cxx.vcxproj ++++ b/msvc/vs22/lib_mpir_cxx/lib_mpir_cxx.vcxproj +@@ -1,6 +1,14 @@ + + + ++ ++ Debug ++ ARM64 ++ ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -40,11 +48,21 @@ + false + v143 + ++ ++ StaticLibrary ++ false ++ v143 ++ + + StaticLibrary + true + v143 + ++ ++ StaticLibrary ++ true ++ v143 ++ + + + +@@ -58,17 +76,27 @@ + + + ++ ++ ++ ++ + + + + ++ ++ ++ ++ + + + <_ProjectFileVersion>10.0.21006.1 + mpirxx + mpirxx + mpirxx ++ mpirxx + mpirxx ++ mpirxx + + + +@@ -99,6 +127,17 @@ postbuild "$(TargetPath)" 22 + + + cd ..\..\ ++postbuild "$(TargetPath)" 22 ++ ++ ++ ++ ++ ++ ..\..\..\ ++ NDEBUG;WIN32;_LIB;HAVE_CONFIG_H;_WIN64;%(PreprocessorDefinitions) ++ ++ ++ cd ..\..\ + postbuild "$(TargetPath)" 22 + + +@@ -110,6 +149,17 @@ postbuild "$(TargetPath)" 22 + + + cd ..\..\ ++postbuild "$(TargetPath)" 22 ++ ++ ++ ++ ++ ++ ..\..\..\ ++ _DEBUG;WIN32;_LIB;HAVE_CONFIG_H;_WIN64;%(PreprocessorDefinitions) ++ ++ ++ cd ..\..\ + postbuild "$(TargetPath)" 22 + + +@@ -141,7 +191,7 @@ postbuild "$(TargetPath)" 22 + + + +- ++ + +- +- ++ ++ +\ No newline at end of file +diff --git a/msvc/vs22/lib_mpir_gc/lib_mpir_gc.vcxproj b/msvc/vs22/lib_mpir_gc/lib_mpir_gc.vcxproj +index 350ee110..faf49ac3 100644 +--- a/msvc/vs22/lib_mpir_gc/lib_mpir_gc.vcxproj ++++ b/msvc/vs22/lib_mpir_gc/lib_mpir_gc.vcxproj +@@ -1,6 +1,14 @@ + + + ++ ++ Debug ++ ARM64 ++ ++ ++ Release ++ ARM64 ++ + + Release + Win32 +@@ -40,11 +48,21 @@ + false + v143 + ++ ++ StaticLibrary ++ false ++ v143 ++ + + StaticLibrary + true + v143 + ++ ++ StaticLibrary ++ true ++ v143 ++ + + + +@@ -58,23 +76,32 @@ + + + ++ ++ ++ ++ + + + + ++ ++ ++ ++ + + + <_ProjectFileVersion>10.0.21006.1 + mpir + mpir + mpir ++ mpir + mpir ++ mpir + + + + cd ..\..\ +-prebuild gc Win32 22 +- ++prebuild gc $(Platform) 22 + + + ..\..\..\ +@@ -82,15 +109,13 @@ prebuild gc Win32 22 + + + cd ..\..\ +-postbuild "$(TargetPath)" 22 +- ++postbuild "$(TargetPath)" 22 + + + + + cd ..\..\ +-prebuild gc Win32 22 +- ++prebuild gc $(Platform) 22 + + + ..\..\..\ +@@ -98,15 +123,27 @@ prebuild gc Win32 22 + + + cd ..\..\ +-postbuild "$(TargetPath)" 22 +- ++postbuild "$(TargetPath)" 22 + + + + + cd ..\..\ +-prebuild gc x64 22 +- ++prebuild gc $(Platform) 22 ++ ++ ++ ..\..\..\ ++ NDEBUG;WIN32;_LIB;HAVE_CONFIG_H;_WIN64;%(PreprocessorDefinitions) ++ ++ ++ cd ..\..\ ++postbuild "$(TargetPath)" 22 ++ ++ ++ ++ ++ cd ..\..\ ++prebuild gc $(Platform) 22 + + + ..\..\..\ +@@ -114,15 +151,13 @@ prebuild gc x64 22 + + + cd ..\..\ +-postbuild "$(TargetPath)" 22 +- ++postbuild "$(TargetPath)" 22 + + + + + cd ..\..\ +-prebuild gc x64 22 +- ++prebuild gc $(Platform) 22 + + + ..\..\..\ +@@ -130,8 +165,21 @@ prebuild gc x64 22 + + + cd ..\..\ +-postbuild "$(TargetPath)" 22 +- ++postbuild "$(TargetPath)" 22 ++ ++ ++ ++ ++ cd ..\..\ ++prebuild gc $(Platform) 22 ++ ++ ++ ..\..\..\ ++ _DEBUG;WIN32;_LIB;HAVE_CONFIG_H;_WIN64;%(PreprocessorDefinitions) ++ ++ ++ cd ..\..\ ++postbuild "$(TargetPath)" 22 + + + +@@ -667,7 +715,7 @@ postbuild "$(TargetPath)" 22 + + + +- ++ + +- +- ++ ++ +\ No newline at end of file +diff --git a/msvc/vs22/msbuild.bat b/msvc/vs22/msbuild.bat +index f119c3e9..36bfe28b 100644 +--- a/msvc/vs22/msbuild.bat ++++ b/msvc/vs22/msbuild.bat +@@ -1,7 +1,7 @@ + @echo off + rem %1 = architecture + rem %2 = library type (LIB|DLL) +-rem %3 = platform (Win32|x64) ++rem %3 = platform (Win32|x64|ARM64) + rem %4 = configuration (Release|Debug) + rem %5 = Windows SDK Version + rem %6 = build tests (|+tests) +@@ -31,15 +31,21 @@ if not exist !msb_exe! ( + + if "%4" NEQ "" if "%3" NEQ "" if "%2" NEQ "" if "%1" NEQ "" goto cont + call :get_architectures - +-echo usage: msbuild architecture=^<%architectures:|=^|%^> library_type=^ platform=^ configuration=^ [Windows_SDK_Version=^] [+tests] ++echo usage: msbuild architecture=^<%architectures:|=^|%^> library_type=^ platform=^ configuration=^ [Windows_SDK_Version=^] [+tests] + goto :eof + + :cont + rem example use: msbuild sandybridge_ivybridge dll x64 release + if not exist "lib_mpir_%1" (call :get_architectures & call :seterr & echo ERROR: architecture is one of ^(%architectures%^) ^(not %1^) & exit /b %errorlevel%) + if /i "%2" EQU "DLL" (set libp=dll) else (if /i "%2" EQU "LIB" (set libp=lib) else ((call :seterr & echo ERROR: library type is "lib" or "dll" ^(not "%2"^) & exit /b %errorlevel%))) +-if /i "%3" EQU "x64" (set plat=x64) else (if /i "%3" EQU "Win32" (set plat=win32) else (call :seterr & echo ERROR: platform is "Win32" or "x64" ^(not "%3"^) & exit /b %errorlevel%)) +-if /i "%4" EQU "Debug" (set conf=Debug) else (if /i "%4" EQU "Release" (set conf=Release) else (call :seterr & echo ERROR: configuration is "Release" or "Debug" ^(not "%4"^) & exit /b %errorlevel%)) ++if /i "%3" EQU "x64" (set plat=x64) else ( ++ if /i "%3" EQU "Win32" (set plat=win32) else ( ++ if /i "%3" EQU "ARM64" (set plat=ARM64) else ( ++ call :seterr & echo ERROR: platform is "Win32", "x64", or "ARM64" ^(not %3^) & exit /b %errorlevel% ++ ) ++ ) ++) ++if /i "%4" EQU "Debug" (set conf=Debug) else (if /i "%4" EQU "Release" (set conf=Release) else (call :seterr & echo ERROR: configuration is "Release" or "Debug" ^(not %4^) & exit /b %errorlevel%)) + if /i "%5" NEQ "" if "%5" EQU "+tests" (set run_tests=y) else (set win_sdk=%5) + if /i "%6" NEQ "" if "%6" EQU "+tests" (set run_tests=y) +
{entity.class} {entity.predefined_type || '-'}
... {specReport.applicable_entities.length - 10} more failing elements not shown ...... {applicableEntities.length - 10} more failing elements not shown ...