mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 01:11:40 +00:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ace1b56467 | |||
| a675365dfd | |||
| 5ab6deb121 | |||
| cbd26896ba | |||
| ceeca752ea | |||
| 9d437b5bc3 | |||
| 1d0b4675c0 | |||
| 497ebc55e7 | |||
| 97a501036b | |||
| 592c51b526 | |||
| 3dd7290ee1 | |||
| d7ac5f2441 | |||
| 461404cd45 | |||
| bfd5b1bede | |||
| 0d0b6cc6b2 | |||
| 57bc709b6f | |||
| b7b6e4aadf | |||
| 9c39ac0a48 | |||
| a77f1b9fd1 | |||
| 63921e3fd8 | |||
| 27d0b04db5 | |||
| 87283874a1 | |||
| 584d37de50 | |||
| ca2058d56b | |||
| 481676e51b | |||
| fe502577c9 | |||
| 65e8ca17d2 | |||
| 6bd7f02208 | |||
| 452d0f6023 | |||
| 07005a9d3b | |||
| 450ce689ba | |||
| 7c210721e6 | |||
| 7e48d712a2 | |||
| 9eedbc6f72 | |||
| 68b021b0e6 | |||
| a559b72e18 | |||
| 32496c40b3 | |||
| 1b249b1013 | |||
| d3fcf39c76 | |||
| 692ef5ecea | |||
| f56a97512a | |||
| 4f5ebef5da | |||
| 57a724e80f | |||
| 1b8652a789 | |||
| c1aa702896 | |||
| 5b43d2b75e | |||
| 3330e79556 | |||
| 88d09c83b7 | |||
| 5e6b96ee99 | |||
| bd07078def | |||
| 1d01d1f184 | |||
| 3cee0811d4 | |||
| a46228a57e | |||
| 158fe92644 | |||
| 12166ddbba | |||
| 36a0c22217 | |||
| 59cc9d4a23 | |||
| f3d918f76c | |||
| 613015e37e | |||
| be0eea1bed | |||
| 37bbc0b693 | |||
| 30093499fa | |||
| 8e95bf73f1 | |||
| 05f7aac853 | |||
| b135dd6994 | |||
| a5e7d7c4cc | |||
| 9680f6b29c | |||
| bc8dd3a3ab | |||
| 9caf47b41f | |||
| 5e1787b146 | |||
| 07147f4b7f | |||
| 4dfe15bd88 | |||
| 5896630630 | |||
| 48b1493c5c | |||
| ee75ea1182 | |||
| 1876a788a9 | |||
| 9d28628157 | |||
| 9b257b0a10 | |||
| db7900027e | |||
| 2af7470950 | |||
| e1c3c44fb3 | |||
| e7c422469a | |||
| 52d3bae543 | |||
| b130925c5f | |||
| 890665b77b | |||
| 99c95bc92d |
@@ -0,0 +1,8 @@
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: build-logs-osx-${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -9,13 +9,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
path: IfcOpenShell
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ifcopenshell_build
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: build-logs-pyodide
|
||||
path: |
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
git push || echo "Push failed"
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
aws --version
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -48,8 +48,8 @@ jobs:
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
|
||||
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
|
||||
# TODO: Use tag after 1.2.20 releases.
|
||||
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: build-logs-rocky
|
||||
path: |
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
aws --version
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -48,8 +48,8 @@ jobs:
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
# Latest release (1.2.19) doesn't support Rocky, so using a specific commit.
|
||||
uses: hendrikmuhs/ccache-action@eca11c308176d48942455b9e5b1b70ff6950a778
|
||||
# TODO: Use tag after 1.2.20 releases.
|
||||
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: build-logs-rocky-arm64
|
||||
path: |
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -12,12 +12,12 @@ jobs:
|
||||
arch: ['x64']
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: _deps-vs2022-x64-installed
|
||||
@@ -37,8 +37,8 @@ jobs:
|
||||
}
|
||||
|
||||
- name: ccache
|
||||
# Use fork to resolve cache misses / duplicated cache entries on Windows.
|
||||
uses: Andrej730/ccache-action@main
|
||||
# TODO: Use tag after 1.2.20 releases.
|
||||
uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639
|
||||
with:
|
||||
key: win-${{ matrix.arch }}
|
||||
# Windows ccache needs ~1GB
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
git push || echo "Push failed"
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
|
||||
@@ -19,8 +19,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
|
||||
@@ -9,15 +9,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Action - checkout repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -54,8 +54,8 @@ jobs:
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout bonsai_unstable_repo repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/bonsai_unstable_repo
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
|
||||
@@ -43,8 +43,8 @@ jobs:
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -37,8 +37,8 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba
|
||||
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
|
||||
with:
|
||||
environment-name: test-env
|
||||
create-args: >-
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
date: ${{ steps.date.outputs.date }}
|
||||
verdate: ${{ steps.verdate.outputs.verdate }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
make package
|
||||
working-directory: build
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
@@ -89,31 +89,31 @@ jobs:
|
||||
name: Docker Build, Tag, Push
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v6.0.0
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
path: artifacts/
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Login to Dockerhub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: aecgeeks
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
-
|
||||
name: Build container image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: artifacts
|
||||
repository: aecgeeks/ifcopenshell
|
||||
|
||||
@@ -47,10 +47,10 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
|
||||
@@ -38,10 +38,10 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
|
||||
@@ -19,8 +19,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
|
||||
@@ -10,9 +10,9 @@ jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v5
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
|
||||
@@ -35,12 +35,12 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
@@ -70,7 +70,10 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
# TODO: temporarily pointing to 1.2.19 to get notified by dependabot when 1.2.20 is released
|
||||
# to update hardcoded references to commits in some other workflows.
|
||||
# Then we can switch back to 1.2 in all actions.
|
||||
uses: hendrikmuhs/ccache-action@v1.2.19
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (recursive)
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload static files as artifact
|
||||
id: deployment
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: src/pyodide/demo-app/
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install C++ dependencies
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# General Python residue
|
||||
__pycache__
|
||||
*.py.bak
|
||||
venv
|
||||
|
||||
# Visual Studio Code files
|
||||
.vscode
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||
url = https://github.com/IfcOpenShell/ids-test-files
|
||||
[submodule "src/ifcconvert/cityjson"]
|
||||
path = src/ifcconvert/cityjson
|
||||
url = https://github.com/IfcOpenShell/ifc-to-cityjson
|
||||
[submodule "docs/cpp-api/assets/doxygen-awesome-css"]
|
||||
path = docs/cpp-api/assets/doxygen-awesome-css
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
|
||||
+20
-538
@@ -79,7 +79,6 @@ option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
|
||||
option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF)
|
||||
option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
option(CITYJSON_SUPPORT "Build IfcConvert with CityJSON support (requires CityJSON library)." OFF)
|
||||
option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF)
|
||||
option(WITH_ROCKSDB "Support a RocksDB key-value store as a file backend in IfcOpenShell" OFF)
|
||||
option(WITH_ZSTD "Use Zstd compression in RocksDB writes" OFF)
|
||||
@@ -199,30 +198,13 @@ if(BUILD_SHARED_LIBS)
|
||||
set(IFCOPENSHELL_LIBRARY_DIR "${LIBDIR}")
|
||||
endif()
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(EIGEN_DIR)
|
||||
|
||||
if(NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES)
|
||||
UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_LIBRARY)
|
||||
UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
endif()
|
||||
|
||||
# Get a list of all OPTION flags from the CMakeLists.txt
|
||||
@@ -233,53 +215,13 @@ foreach(option_flag IN LISTS option_flags)
|
||||
convert_env_var_to_bool("${option_flag}")
|
||||
endforeach()
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
|
||||
|
||||
macro(clear_wasm_sysroot)
|
||||
if(WASM_BUILD)
|
||||
# when using the nix/build-all.py build script we should not
|
||||
# look into the sysroot for most of the dependencies but rather
|
||||
# in the designated build/ folder created by the script.
|
||||
set(CMAKE_FIND_ROOT_PATH "")
|
||||
endif()
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
|
||||
endmacro()
|
||||
|
||||
macro(restore_wasm_sysroot)
|
||||
if(WASM_BUILD)
|
||||
# reset to use sysroot
|
||||
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
|
||||
endif()
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
if(NOT CGAL_INCLUDE_DIR)
|
||||
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
|
||||
# and sometiems it's getting in the way.
|
||||
if(NOT DEFINED Boost_USE_STATIC_LIBS)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS OFF)
|
||||
else()
|
||||
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
find_package(CGAL REQUIRED)
|
||||
if(NOT CGAL_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
|
||||
if(TARGET CGAL::CGAL)
|
||||
link_libraries(CGAL::CGAL)
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_CGAL)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
|
||||
|
||||
if(CITYJSON_SUPPORT)
|
||||
add_definitions(-DIFOPSH_WITH_CITYJSON)
|
||||
endif()
|
||||
|
||||
list(APPEND GEOMETRY_KERNELS cgal)
|
||||
endif()
|
||||
|
||||
@@ -289,7 +231,7 @@ if(WITH_OPENCASCADE)
|
||||
list(APPEND GEOMETRY_KERNELS opencascade)
|
||||
endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
if(GLTF_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
if(NOT JSON_INCLUDE_DIR)
|
||||
find_package(nlohmann_json CONFIG)
|
||||
@@ -301,9 +243,7 @@ if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(NOT nlohmann_json_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
restore_wasm_sysroot()
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_header_path)
|
||||
@@ -319,49 +259,7 @@ endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
if(USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
|
||||
if("${USD_INCLUDE_DIR}" STREQUAL "")
|
||||
find_path(USD_INCLUDE_DIR pxr.h
|
||||
PATHS
|
||||
/usr/include/pxr
|
||||
/usr/local/include/pxr
|
||||
REQUIRED
|
||||
)
|
||||
if(USD_INCLUDE_DIR)
|
||||
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
|
||||
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
find_library(USD_LIBRARY
|
||||
NAMES ${USD_LIBRARIES}
|
||||
PATHS ${USD_LIBRARY_DIR})
|
||||
if(USD_LIBRARY)
|
||||
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
|
||||
link_directories(${USD_LIBRARY_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_USD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
find_package(USD REQUIRED)
|
||||
endif(USD_SUPPORT)
|
||||
|
||||
if (WITH_ROCKSDB)
|
||||
@@ -443,44 +341,15 @@ if(USE_MMAP)
|
||||
add_definitions(-DUSE_MMAP)
|
||||
endif()
|
||||
|
||||
clear_wasm_sysroot()
|
||||
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
restore_wasm_sysroot()
|
||||
message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
|
||||
message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
|
||||
if(NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
clear_wasm_sysroot()
|
||||
if(IFCXML_SUPPORT)
|
||||
if((NOT LIBXML2_INCLUDE_DIR AND NOT LIBXML2_LIBRARIES))
|
||||
# First try config mode (probably works with vcpkg, Conan, macOS brew installs, but not on ubuntu 22.04)
|
||||
# CONFIG is provided using root path, so no need to clear sysroot here.
|
||||
restore_wasm_sysroot()
|
||||
find_package(LibXml2 QUIET CONFIG)
|
||||
clear_wasm_sysroot()
|
||||
|
||||
if(NOT LibXml2_FOUND)
|
||||
# Fallback to CMake's builtin FindLibXml2 module (works on Ubuntu)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
else()
|
||||
message(STATUS "Found LibXml2 config: ${LibXml2_DIR}")
|
||||
endif()
|
||||
|
||||
if(TARGET LibXml2::LibXml2)
|
||||
# Config mode already gives us the target
|
||||
set(LIBXML2_LIBRARIES LibXml2::LibXml2)
|
||||
get_target_property(LIBXML2_INCLUDE_DIR LibXml2::LibXml2 INTERFACE_INCLUDE_DIRECTORIES)
|
||||
else()
|
||||
# Module mode (Ubuntu)
|
||||
set(LIBXML2_LIBRARIES ${LibXml2_LIBRARIES})
|
||||
set(LIBXML2_INCLUDE_DIR ${LibXml2_INCLUDE_DIRS})
|
||||
endif()
|
||||
else()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
endif()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
endif()
|
||||
restore_wasm_sysroot()
|
||||
endif()
|
||||
|
||||
if(IFCXML_SUPPORT)
|
||||
@@ -489,248 +358,15 @@ if(IFCXML_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(MSVC AND NOT LibXml2_DIR)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
# Open CASCADE
|
||||
if(WITH_OPENCASCADE)
|
||||
if(OCC_INCLUDE_DIR)
|
||||
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
||||
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(OCC_LIBRARY_DIR)
|
||||
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
# No specific paths specified, try to find package.
|
||||
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
|
||||
message(
|
||||
STATUS
|
||||
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
|
||||
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
|
||||
)
|
||||
# Parse OCC_VERSION_STRING.
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
|
||||
REGEX "#define OCC_VERSION_MAJOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
||||
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
||||
elseif(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
|
||||
# OCE is not supported for find_package, because it's using a different name (`oce`)
|
||||
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
|
||||
find_package(OpenCASCADE CONFIG REQUIRED)
|
||||
set(OCC_INCLUDE_DIR ${OpenCASCADE_INCLUDE_DIR})
|
||||
# Do not use OpenCASCADE_LIBRARY_DIR for OCC_LIBRARY_DIR - check target property explicitly.
|
||||
# On Windows there is a case with OpenCASCADE_LIBRARY_DIR points to `lib` folder,
|
||||
# while TKernel is actually in `libi`.
|
||||
get_target_property(TKERNEL_LIB_PATH TKernel LOCATION)
|
||||
get_filename_component(OCC_LIBRARY_DIR "${TKERNEL_LIB_PATH}" DIRECTORY)
|
||||
set(OCC_VERSION_STRING ${OpenCASCADE_VERSION})
|
||||
message(
|
||||
STATUS
|
||||
"Found Open CASCADE package at '${OpenCASCADE_DIR}', "
|
||||
"deducing from it OCC_INCLUDE_DIR: '${OCC_INCLUDE_DIR}' "
|
||||
"and OCC_LIBRARY_DIR: '${OCC_LIBRARY_DIR}'."
|
||||
)
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Couldn't find Open CASCADE installation. "
|
||||
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
|
||||
"must be specified or OpenCASCADE package should be discoverable. "
|
||||
"If you're using OCE, then providing a package is not available "
|
||||
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(
|
||||
OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKXSBase TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
|
||||
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
|
||||
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
|
||||
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
|
||||
clear_wasm_sysroot()
|
||||
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
restore_wasm_sysroot()
|
||||
|
||||
if(libTKernel)
|
||||
message(STATUS "Required Open Cascade Library files found")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Use the found libTKernel as a template for all other OCC libraries
|
||||
# TODO Extract this into macro/function
|
||||
foreach(lib ${OPENCASCADE_LIBRARY_NAMES})
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
|
||||
string(REPLACE TKerneld "${lib}" lib_path "${libTKernel}")
|
||||
string(REPLACE TKernel "${lib}" lib_path "${lib_path}")
|
||||
list(APPEND OPENCASCADE_LIBRARIES "${lib_path}")
|
||||
endforeach()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-DHAVE_NO_DLL)
|
||||
add_debug_variants(OPENCASCADE_LIBRARIES "${OPENCASCADE_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# OCC might require linking to Winsock depending on the version and build configuration
|
||||
list(APPEND OPENCASCADE_LIBRARIES ws2_32.lib)
|
||||
endif()
|
||||
|
||||
# Make sure cross-referenced symbols between static OCC libraries get
|
||||
# resolved. Also add thread and rt libraries.
|
||||
get_filename_component(libTKernelExt ${libTKernel} EXT)
|
||||
if("${libTKernelExt}" STREQUAL ".a")
|
||||
set(OCCT_STATIC ON)
|
||||
endif()
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
# OPENCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
|
||||
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
|
||||
if(APPLE)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "dl")
|
||||
endif()
|
||||
endif()
|
||||
find_package(OpenCASCADE REQUIRED)
|
||||
endif(WITH_OPENCASCADE)
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(COLLADA_SUPPORT AND (NOT OPENCOLLADA_INCLUDE_DIR AND NOT OPENCOLLADA_LIBRARY_DIR))
|
||||
# If package is found, automatically sets
|
||||
# OPENCOLLADA_INCLUDE_DIRS and OPENCOLLADA_LIBRARIES (list of targets, not paths).
|
||||
find_package(OpenCOLLADA CONFIG)
|
||||
if(OpenCOLLADA_DIR)
|
||||
message(STATUS "Found OpenCOLLADA: '${OpenCOLLADA_DIR}'.")
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
else()
|
||||
message(STATUS "OpenCOLLADA package not found, falling back to manual search.")
|
||||
endif()
|
||||
if(COLLADA_SUPPORT)
|
||||
find_package(OpenCOLLADA REQUIRED)
|
||||
endif()
|
||||
if(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
|
||||
# Find OpenCOLLADA
|
||||
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA include directory specified")
|
||||
set(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files")
|
||||
else()
|
||||
set(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files")
|
||||
endif()
|
||||
|
||||
if("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA library directory specified")
|
||||
find_library(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework
|
||||
PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib)
|
||||
get_filename_component(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH)
|
||||
endif()
|
||||
|
||||
find_library(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
|
||||
if(OpenCOLLADAFramework)
|
||||
message(STATUS "OpenCOLLADA library files found")
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA libraries. "
|
||||
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
|
||||
endif()
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files")
|
||||
|
||||
set(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter")
|
||||
|
||||
find_file(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS})
|
||||
|
||||
if(COLLADASWStreamWriter_h)
|
||||
message(STATUS "OpenCOLLADA header files found")
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
OpenCOLLADAStreamWriter UTF buffer ftoa
|
||||
)
|
||||
|
||||
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
|
||||
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
|
||||
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
|
||||
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
|
||||
endforeach()
|
||||
|
||||
if("${PCRE_LIBRARY_DIR}" STREQUAL "")
|
||||
if(WIN32)
|
||||
find_library(pcre_library NAMES pcre pcred PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
else()
|
||||
find_library(pcre_library NAMES pcre PATHS ${OPENCOLLADA_LIBRARY_DIR})
|
||||
endif()
|
||||
|
||||
get_filename_component(PCRE_LIBRARY_DIR ${pcre_library} PATH)
|
||||
else()
|
||||
find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
endif()
|
||||
|
||||
if(pcre_library)
|
||||
set(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR})
|
||||
|
||||
if(MSVC)
|
||||
# Add release lib regardless whether release or debug found. Debug version will be appended below.
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${PCRE_LIBRARY_DIR}/pcre.lib")
|
||||
else()
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${pcre_library}")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find PCRE. "
|
||||
"Disable COLLADA_SUPPORT or fix PCRE_LIBRARY_DIR path to proceed.")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. "
|
||||
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
|
||||
endif()
|
||||
endif(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
|
||||
if(COLLADA_SUPPORT AND OPENCOLLADA_FOUND)
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
endif()
|
||||
|
||||
|
||||
if(HDF5_SUPPORT)
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
@@ -850,25 +486,12 @@ else()
|
||||
endif(MSVC)
|
||||
|
||||
|
||||
# Ensure other dependencies are provided.
|
||||
if(NOT EXISTS "${EIGEN_DIR}")
|
||||
find_package(Eigen3 CONFIG)
|
||||
if(Eigen3_DIR)
|
||||
message(STATUS "Eigen3: found config at '${Eigen3_DIR}'.")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"EIGEN_DIR is not provided or provided folder doesn't exist (current value: '${EIGEN_DIR}'). "
|
||||
"Also couldn't find Eigen3 as a package."
|
||||
)
|
||||
endif()
|
||||
link_libraries(Eigen3::Eigen)
|
||||
endif()
|
||||
find_package(Eigen3 REQUIRED)
|
||||
link_libraries(Eigen3::Eigen)
|
||||
|
||||
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIRS}
|
||||
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
${TBB_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
@@ -968,18 +591,13 @@ if(BUILD_IFCGEOM)
|
||||
endif()
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${schema})
|
||||
endforeach()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} Serializers ${SERIALIZER_SCHEMA_LIBRARIES})
|
||||
|
||||
if(WITH_OPENCASCADE)
|
||||
if(WITH_OPENCASCADE)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
set(GEOM_SERIALIZER_SCHEMA_LIBRARIES ${GEOM_SERIALIZER_SCHEMA_LIBRARIES} GeometrySerializers_ifc${schema})
|
||||
|
||||
add_library(geometry_serializer_ifc${schema} STATIC ../src/ifcgeom/Serialization/schema/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_serializer_ifc${schema} ${OPENCASCADE_LIBRARIES})
|
||||
target_link_libraries(geometry_serializer_ifc${schema} ${OpenCASCADE_LIBRARIES})
|
||||
list(APPEND geometry_serializer_libraries geometry_serializer_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
@@ -996,10 +614,8 @@ if(BUILD_IFCGEOM)
|
||||
set(CGAL_LIBRARIES CGAL::CGAL)
|
||||
message(STATUS "Using found CGAL package at '${CGAL_DIR}'")
|
||||
elseif(WITH_CGAL AND NOT CGAL_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
restore_wasm_sysroot()
|
||||
if(NOT libGMP)
|
||||
message(FATAL_ERROR "Unable to find GMP library files, aborting")
|
||||
endif()
|
||||
@@ -1011,33 +627,8 @@ if(BUILD_IFCGEOM)
|
||||
list(APPEND CGAL_LIBRARIES "${libGMP}")
|
||||
endif()
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
string(TOUPPER ${kernel} KERNEL_UPPER)
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/kernels/${kernel}/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
add_library(geometry_kernel_${kernel} ${IFCGEOM_FILES} ../src/ifcgeom/kernels/ifc_geomlibrary_api.h)
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel} ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel})
|
||||
|
||||
if(${kernel} STREQUAL "cgal")
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
|
||||
|
||||
add_library(geometry_kernel_${kernel}_simple ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_kernel_${kernel}_simple PROPERTIES COMPILE_FLAGS "-DIFC_GEOMLIBRARY_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
# needed?
|
||||
# if(NOT WASM_BUILD)
|
||||
# endif()
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel}_simple)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_subdirectory(../src/ifcgeom ifcgeom)
|
||||
|
||||
# IfcGeom
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
file(GLOB IFCGEOM_I_FILES ../src/ifcgeom/mapping/*.i)
|
||||
@@ -1066,110 +657,17 @@ if(BUILD_IFCGEOM)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT} ${OpenCASCADE_LIBRARIES})
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
# Serializers
|
||||
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
|
||||
file(GLOB SERIALIZERS_CPP_FILES ../src/serializers/*.cpp)
|
||||
set(SERIALIZERS_FILES ${SERIALIZERS_H_FILES} ${SERIALIZERS_CPP_FILES})
|
||||
file(GLOB SERIALIZERS_S_H_FILES ../src/serializers/schema_dependent/*.h)
|
||||
file(GLOB SERIALIZERS_S_CPP_FILES ../src/serializers/schema_dependent/*.cpp)
|
||||
set(SERIALIZERS_S_FILES ${SERIALIZERS_S_H_FILES} ${SERIALIZERS_S_CPP_FILES})
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
|
||||
else()
|
||||
target_link_libraries(Serializers_ifc${schema} IfcGeom ${OPENCASCADE_LIBRARIES} ${HDF5_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DSERIALIZERS_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
if(WITH_PROJ)
|
||||
target_compile_definitions(Serializers PRIVATE "WITH_PROJ")
|
||||
if (PROJ_STATIC)
|
||||
target_compile_definitions(Serializers PRIVATE "PROJ_DLL=")
|
||||
endif()
|
||||
target_include_directories(Serializers PRIVATE ${PROJ_INCLUDE_DIR} ${SQLITE_INCLUDE_DIR})
|
||||
target_link_libraries(Serializers ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} IfcGeom ${OPENCASCADE_LIBRARIES} ${kernel_libraries} IfcParse)
|
||||
|
||||
add_subdirectory(../src/serializers serializers)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} ${SERIALIZER_SCHEMA_LIBRARIES})
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
if(BUILD_CONVERT)
|
||||
if(WITH_CGAL AND CITYJSON_SUPPORT)
|
||||
message(STATUS "Building CityJSON support")
|
||||
set(CITYJSON_CONVERT_FILES
|
||||
../src/ifcconvert/cityjson/geobim.cpp
|
||||
../src/ifcconvert/cityjson/global_execution_context.cpp
|
||||
../src/ifcconvert/cityjson/opening_collector.cpp
|
||||
../src/ifcconvert/cityjson/processing.cpp
|
||||
../src/ifcconvert/cityjson/radius_comparison.cpp
|
||||
../src/ifcconvert/cityjson/radius_execution_context.cpp
|
||||
../src/ifcconvert/cityjson/settings.cpp
|
||||
../src/ifcconvert/cityjson/writer.cpp
|
||||
)
|
||||
add_library(cityjson_converter ${CITYJSON_CONVERT_FILES})
|
||||
target_include_directories(cityjson_converter PRIVATE ../src)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} cityjson_converter)
|
||||
|
||||
install(TARGETS cityjson_converter
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
)
|
||||
|
||||
add_executable(cityjson_converter_exe ${CITYJSON_CONVERT_FILES})
|
||||
set_target_properties(cityjson_converter_exe PROPERTIES COMPILE_FLAGS "-DCITYJSON_EXECUTABLE")
|
||||
target_include_directories(cityjson_converter_exe PRIVATE ../src)
|
||||
target_link_libraries(cityjson_converter_exe ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
install(TARGETS cityjson_converter_exe
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# IfcConvert
|
||||
if (WITH_RELATIONSHIP_VALIDATION)
|
||||
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
|
||||
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
|
||||
else()
|
||||
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/IfcConvert.cpp)
|
||||
file(GLOB IFCCONVERT_H_FILES)
|
||||
endif()
|
||||
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
add_executable(IfcConvert ${IFCCONVERT_FILES})
|
||||
|
||||
target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
if (WITH_RELATIONSHIP_VALIDATION)
|
||||
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DWITH_RELATIONSHIP_VALIDATION")
|
||||
endif()
|
||||
|
||||
if(WITH_CGAL AND CITYJSON_SUPPORT)
|
||||
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DIFOPSH_WITH_CITYJSON")
|
||||
endif()
|
||||
|
||||
if((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
# Only set RPATHs when building shared libraries (i.e. IfcParse and
|
||||
# IfcGeom are dynamically linked). Not necessarily a perfect solution
|
||||
# but probably a good indication of whether RPATHs are necessary.
|
||||
SET_INSTALL_RPATHS(IfcConvert "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS};${OPENCOLLADA_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
install(TARGETS IfcConvert
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
add_subdirectory(../src/ifcconvert ifcconvert)
|
||||
endif(BUILD_CONVERT)
|
||||
|
||||
# IfcGeomServer
|
||||
@@ -1289,22 +787,6 @@ if(BUILD_IFCGEOM)
|
||||
)
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(BUILD_CONVERT)
|
||||
install(TARGETS Serializers ${SERIALIZER_SCHEMA_LIBRARIES}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
|
||||
install(FILES ${SERIALIZERS_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/serializers/
|
||||
)
|
||||
|
||||
install(FILES ${SERIALIZERS_S_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
|
||||
)
|
||||
endif(BUILD_CONVERT)
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
if(WITH_OPENCASCADE)
|
||||
install(TARGETS geometry_serializer ${geometry_serializer_libraries}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"IFCXML_SUPPORT": "ON",
|
||||
"HDF5_SUPPORT": "ON",
|
||||
"SCHEMA_VERSIONS": "4x3_add2",
|
||||
"CITYJSON_SUPPORT": "OFF",
|
||||
"CMAKE_GENERATOR_PLATFORM": "",
|
||||
"CMAKE_GENERATOR_TOOLSET": ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `CGAL_INCLUDE_DIR`
|
||||
# - `CGAL_LIBRARY_DIR`
|
||||
# - `GMP_INCLUDE_DIR`
|
||||
# - `GMP_LIBRARY_DIR`
|
||||
# - `MPFR_INCLUDE_DIR`
|
||||
# - `MPFR_LIBRARY_DIR`
|
||||
# If input variables are not specified, try to find HDF5 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables:
|
||||
# - `CGAL_INCLUDE_DIR`
|
||||
#
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
|
||||
if(NOT CGAL_INCLUDE_DIR)
|
||||
# CGAL is not respecting default Boost_USE_STATIC_LIBS value
|
||||
# and sometiems it's getting in the way.
|
||||
if(NOT DEFINED Boost_USE_STATIC_LIBS)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS OFF)
|
||||
else()
|
||||
set(CGAL_Boost_USE_STATIC_LIBS "${Boost_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
find_package(CGAL CONFIG REQUIRED)
|
||||
if(NOT CGAL_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"CGAL_SUPPORT enabled, but CGAL_INCLUDE_DIR wasn't provided and CGAL package couldn't be found."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "CGAL: found config at '${CGAL_DIR}'.")
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_CGAL)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_CGAL)
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `EIGEN_DIR`
|
||||
# If input variables are not specified, try to find Eigen3 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output targets:
|
||||
# - `Eigen3::Eigen`
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(EIGEN_DIR)
|
||||
|
||||
if(EXISTS "${EIGEN_DIR}")
|
||||
# Mimic Eigen3Config.cmake target.
|
||||
add_library(Eigen3::Eigen INTERFACE IMPORTED)
|
||||
target_include_directories(Eigen3::Eigen INTERFACE "${EIGEN_DIR}")
|
||||
else()
|
||||
find_package(Eigen3 CONFIG)
|
||||
if(Eigen3_DIR)
|
||||
message(STATUS "Eigen3: found config at '${Eigen3_DIR}'.")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"EIGEN_DIR is not provided or provided folder doesn't exist (current value: '${EIGEN_DIR}'). "
|
||||
"Also couldn't find Eigen3 as a package."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,5 +1,23 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `HDF5_INCLUDE_DIR`
|
||||
# - `HDF5_LIBRARY_DIR`
|
||||
# - `HDF5_LIBRARIES`
|
||||
# If input variables are not specified, try to find HDF5 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables:
|
||||
# - `HDF5_INCLUDE_DIR`
|
||||
# - `HDF5_LIBRARY_DIR`
|
||||
# - `HDF5_LIBRARIES`
|
||||
#
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
|
||||
# To avoid cyclic calls to this file
|
||||
list(REMOVE_ITEM CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
|
||||
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
|
||||
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No HDF5 include directory specified")
|
||||
@@ -86,3 +104,6 @@ if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Restore module path.
|
||||
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `LIBXML2_INCLUDE_DIR`
|
||||
# - `LIBXML2_LIBRARIES`
|
||||
# If input variables are not specified, try to find LibXml2 config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables:
|
||||
# - `LIBXML2_INCLUDE_DIR`
|
||||
# - `LIBXML2_LIBRARIES`
|
||||
#
|
||||
|
||||
# To avoid cyclic calls to this file
|
||||
list(REMOVE_ITEM CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_LIBRARIES)
|
||||
|
||||
if((NOT LIBXML2_INCLUDE_DIR AND NOT LIBXML2_LIBRARIES))
|
||||
# First try config mode (probably works with vcpkg, Conan, macOS brew installs, but not on ubuntu 22.04)
|
||||
# CONFIG is provided using root path, so no need to clear sysroot here.
|
||||
find_package(LibXml2 QUIET CONFIG)
|
||||
|
||||
if(NOT LibXml2_FOUND)
|
||||
# Fallback to CMake's builtin FindLibXml2 module (works on Ubuntu)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
else()
|
||||
message(STATUS "Found LibXml2 config: ${LibXml2_DIR}")
|
||||
endif()
|
||||
|
||||
if(TARGET LibXml2::LibXml2)
|
||||
# Config mode already gives us the target
|
||||
set(LIBXML2_LIBRARIES LibXml2::LibXml2)
|
||||
get_target_property(LIBXML2_INCLUDE_DIR LibXml2::LibXml2 INTERFACE_INCLUDE_DIRECTORIES)
|
||||
else()
|
||||
# Module mode (Ubuntu)
|
||||
set(LIBXML2_LIBRARIES ${LibXml2_LIBRARIES})
|
||||
set(LIBXML2_INCLUDE_DIR ${LibXml2_INCLUDE_DIRS})
|
||||
endif()
|
||||
else()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
endif()
|
||||
|
||||
if(MSVC AND NOT LibXml2_DIR)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
# Restore module path.
|
||||
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR})
|
||||
@@ -0,0 +1,143 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `OCC_INCLUDE_DIR`
|
||||
# - `OCC_LIBRARY_DIR`
|
||||
# If input variables are not specified, try to find OpenCASCADE config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables
|
||||
# - `OpenCASCADE_LIBRARIES`
|
||||
#
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
set(OCC_INCLUDE_DIR ${OCC_INCLUDE_DIR} CACHE FILEPATH "Open CASCADE header files")
|
||||
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(OCC_LIBRARY_DIR)
|
||||
set(OCC_LIBRARY_DIR ${OCC_LIBRARY_DIR} CACHE FILEPATH "Open CASCADE library files")
|
||||
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
|
||||
if(NOT OCC_INCLUDE_DIR AND NOT OCC_LIBRARY_DIR)
|
||||
# OCE is not supported for find_package, because it's using a different name (`oce`)
|
||||
# and also has an odd directory structure (install/lib/oce-0.18/*.cmake).
|
||||
# find_package creates variables:
|
||||
# - `OpenCASCADE_INCLUDE_DIR`
|
||||
# - `OpenCASCADE_LIBRARIES`
|
||||
|
||||
# OpenCASCADE may be built with VTK support. Try to find VTK first to avoid
|
||||
# CMake errors when OpenCASCADE's config references VTK targets.
|
||||
find_package(VTK QUIET)
|
||||
|
||||
find_package(OpenCASCADE CONFIG REQUIRED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# No specific paths specified, try to find package.
|
||||
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
|
||||
message(
|
||||
STATUS
|
||||
"Using provided OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
|
||||
"and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}')."
|
||||
)
|
||||
# Parse OCC_VERSION_STRING.
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
|
||||
REGEX "#define OCC_VERSION_MAJOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
||||
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Couldn't find Open CASCADE installation. "
|
||||
"Either both OCC_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') and OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}') "
|
||||
"must be specified or OpenCASCADE package should be discoverable. "
|
||||
"If you're using OCE, then providing a package is not available "
|
||||
"and you need to provide OCE_INCLUDE_DIR and OCE_LIBRARY_DIR directly."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(
|
||||
OpenCASCADE_LIBRARIES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKXSBase TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
|
||||
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OpenCASCADE_LIBRARIES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
|
||||
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OpenCASCADE_LIBRARIES TKDESTEP TKDEIGES)
|
||||
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
|
||||
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
|
||||
if(libTKernel)
|
||||
message(STATUS "Required Open Cascade Library files found")
|
||||
else()
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Unable to find Open Cascade library files in OCC_LIBRARY_DIR ('${OCC_LIBRARY_DIR}'), aborting"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-DHAVE_NO_DLL)
|
||||
add_debug_variants(OpenCASCADE_LIBRARIES "${OpenCASCADE_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# OCC might require linking to Winsock depending on the version and build configuration
|
||||
list(APPEND OpenCASCADE_LIBRARIES ws2_32.lib)
|
||||
endif()
|
||||
|
||||
# Make sure cross-referenced symbols between static OCC libraries get
|
||||
# resolved. Also add thread and rt libraries.
|
||||
get_filename_component(libTKernelExt ${libTKernel} EXT)
|
||||
if("${libTKernelExt}" STREQUAL ".a")
|
||||
set(OCCT_STATIC ON)
|
||||
endif()
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
# OpenCASCADE_LIBRARIES repeated N times below in order to fix cyclic dependencies
|
||||
# tfk: --start-group ... --end-group didn't work on the apple linker when last tested
|
||||
if(APPLE)
|
||||
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${OpenCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
set(OpenCASCADE_LIBRARIES -Wl,--start-group ${OpenCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
set(OpenCASCADE_LIBRARIES ${OpenCASCADE_LIBRARIES} "dl")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(OpenCASCADE_INTERFACE INTERFACE IMPORTED)
|
||||
target_include_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_INCLUDE_DIR}")
|
||||
target_link_libraries(OpenCASCADE_INTERFACE INTERFACE ${OpenCASCADE_LIBRARIES})
|
||||
target_link_directories(OpenCASCADE_INTERFACE INTERFACE "${OCC_LIBRARY_DIR}")
|
||||
set(OpenCASCADE_LIBRARIES OpenCASCADE_INTERFACE)
|
||||
@@ -0,0 +1,118 @@
|
||||
|
||||
#
|
||||
# Input variables:
|
||||
# - `OPENCOLLADA_INCLUDE_DIR`
|
||||
# - `OPENCOLLADA_LIBRARY_DIR`
|
||||
# - `PCRE_LIBRARY_DIR`
|
||||
# If input variables are not specified, try to find OpenCOLLADA config.
|
||||
# Input variables could also be provided as environment variables.
|
||||
#
|
||||
# Output variables:
|
||||
# - `OPENCOLLADA_INCLUDE_DIR`
|
||||
# - `OPENCOLLADA_LIBRARY_DIR`
|
||||
# - `OPENCOLLADA_LIBRARIES`
|
||||
#
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(PCRE_LIBRARY_DIR)
|
||||
|
||||
if(NOT OPENCOLLADA_INCLUDE_DIR AND NOT OPENCOLLADA_LIBRARY_DIR)
|
||||
# If package is found, automatically sets
|
||||
# OPENCOLLADA_INCLUDE_DIRS and OPENCOLLADA_LIBRARIES (list of targets, not paths).
|
||||
find_package(OpenCOLLADA CONFIG)
|
||||
if(OpenCOLLADA_DIR)
|
||||
message(STATUS "Found OpenCOLLADA: '${OpenCOLLADA_DIR}'.")
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
else()
|
||||
message(STATUS "OpenCOLLADA package not found, falling back to manual search.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT OpenCOLLADA_DIR)
|
||||
# Find OpenCOLLADA
|
||||
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA include directory specified")
|
||||
set(OPENCOLLADA_INCLUDE_DIR "/usr/include/opencollada" CACHE FILEPATH "OpenCOLLADA header files")
|
||||
else()
|
||||
set(OPENCOLLADA_INCLUDE_DIR "${OPENCOLLADA_INCLUDE_DIR}" CACHE FILEPATH "OpenCOLLADA header files")
|
||||
endif()
|
||||
|
||||
if("${OPENCOLLADA_LIBRARY_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA library directory specified")
|
||||
find_library(OPENCOLLADA_FRAMEWORK_LIB NAMES OpenCOLLADAFramework
|
||||
PATHS /usr/lib64/opencollada /usr/lib/opencollada /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib)
|
||||
get_filename_component(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_FRAMEWORK_LIB} PATH)
|
||||
endif()
|
||||
|
||||
find_library(OpenCOLLADAFramework NAMES OpenCOLLADAFramework OpenCOLLADAFrameworkd PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
|
||||
if(OpenCOLLADAFramework)
|
||||
message(STATUS "OpenCOLLADA library files found")
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA libraries. "
|
||||
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
|
||||
endif()
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_DIR "${OPENCOLLADA_LIBRARY_DIR}" CACHE FILEPATH "OpenCOLLADA library files")
|
||||
|
||||
set(OPENCOLLADA_INCLUDE_DIRS "${OPENCOLLADA_INCLUDE_DIR}/COLLADABaseUtils" "${OPENCOLLADA_INCLUDE_DIR}/COLLADAStreamWriter")
|
||||
|
||||
find_file(COLLADASWStreamWriter_h "COLLADASWStreamWriter.h" ${OPENCOLLADA_INCLUDE_DIRS})
|
||||
|
||||
if(COLLADASWStreamWriter_h)
|
||||
message(STATUS "OpenCOLLADA header files found")
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
OpenCOLLADAStreamWriter UTF buffer ftoa
|
||||
)
|
||||
|
||||
# Use the found OpenCOLLADAFramework as a template for all other OpenCOLLADA libraries
|
||||
foreach(lib ${OPENCOLLADA_LIBRARY_NAMES})
|
||||
# Make sure we'll handle the Windows/MSVC debug postfix convention too.
|
||||
string(REPLACE OpenCOLLADAFrameworkd "${lib}" lib_path "${OpenCOLLADAFramework}")
|
||||
string(REPLACE OpenCOLLADAFramework "${lib}" lib_path "${lib_path}")
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${lib_path}")
|
||||
endforeach()
|
||||
|
||||
if("${PCRE_LIBRARY_DIR}" STREQUAL "")
|
||||
if(WIN32)
|
||||
find_library(pcre_library NAMES pcre pcred PATHS ${OPENCOLLADA_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
else()
|
||||
find_library(pcre_library NAMES pcre PATHS ${OPENCOLLADA_LIBRARY_DIR})
|
||||
endif()
|
||||
|
||||
get_filename_component(PCRE_LIBRARY_DIR ${pcre_library} PATH)
|
||||
else()
|
||||
find_library(pcre_library NAMES pcre pcred PATHS ${PCRE_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
endif()
|
||||
|
||||
if(pcre_library)
|
||||
set(OPENCOLLADA_LIBRARY_DIR ${OPENCOLLADA_LIBRARY_DIR} ${PCRE_LIBRARY_DIR})
|
||||
|
||||
if(MSVC)
|
||||
# Add release lib regardless whether release or debug found. Debug version will be appended below.
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${PCRE_LIBRARY_DIR}/pcre.lib")
|
||||
else()
|
||||
list(APPEND OPENCOLLADA_LIBRARIES "${pcre_library}")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find PCRE. "
|
||||
"Disable COLLADA_SUPPORT or fix PCRE_LIBRARY_DIR path to proceed.")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_debug_variants(OPENCOLLADA_LIBRARIES "${OPENCOLLADA_LIBRARIES}" d)
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "COLLADA_SUPPORT enabled, but unable to find OpenCOLLADA headers. "
|
||||
"Disable COLLADA_SUPPORT or fix OpenCOLLADA paths to proceed.")
|
||||
endif()
|
||||
endif(NOT OpenCOLLADA_DIR)
|
||||
|
||||
if(OPENCOLLADA_FOUND)
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
endif()
|
||||
@@ -0,0 +1,54 @@
|
||||
#
|
||||
# Input variables:
|
||||
# - `USD_INCLUDE_DIR`
|
||||
# - `USD_LIBRARY_DIR`
|
||||
# Input variables could also be provided as environment variables.
|
||||
# TODO: Try to find USD config if varibales are not provided.
|
||||
# TODO: does usd have a config file?
|
||||
#
|
||||
# Output variables:
|
||||
# - `USD_LIBRARIES`
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
|
||||
if("${USD_INCLUDE_DIR}" STREQUAL "")
|
||||
find_path(USD_INCLUDE_DIR pxr.h
|
||||
PATHS
|
||||
/usr/include/pxr
|
||||
/usr/local/include/pxr
|
||||
REQUIRED
|
||||
)
|
||||
if(USD_INCLUDE_DIR)
|
||||
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
|
||||
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
find_library(USD_LIBRARY
|
||||
NAMES ${USD_LIBRARIES}
|
||||
PATHS ${USD_LIBRARY_DIR})
|
||||
if(USD_LIBRARY)
|
||||
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
|
||||
link_directories(${USD_LIBRARY_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_USD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
@@ -42,7 +42,6 @@ cmake -G "Ninja" ^
|
||||
-D Boost_LIBRARY_DIR:FILEPATH="%LIBRARY_PREFIX%\lib" ^
|
||||
-D Boost_INCLUDE_DIR:FILEPATH="%LIBRARY_PREFIX%\include" ^
|
||||
-D Boost_USE_STATIC_LIBS:BOOL=OFF ^
|
||||
-D CITYJSON_SUPPORT:BOOL=OFF ^
|
||||
../cmake
|
||||
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
@@ -42,7 +42,6 @@ cmake ${CMAKE_ARGS} -G Ninja \
|
||||
-DBUILD_IFCGEOM:BOOL=ON \
|
||||
-DBUILD_GEOMSERVER:BOOL=OFF \
|
||||
-DBOOST_USE_STATIC_LIBS:BOOL=OFF \
|
||||
-DCITYJSON_SUPPORT:BOOL=OFF \
|
||||
./cmake
|
||||
|
||||
ninja
|
||||
|
||||
+3
-1
@@ -243,7 +243,9 @@ if WASM:
|
||||
# Pyodide still in transition from `FLAGS` to `FLAGS_INIT`.
|
||||
# `FLAGS_INIT` allow us to provide flags using environment variables
|
||||
# and providing `FLAGS` directly would break pyodide toolchain.
|
||||
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (0, 30, 8)
|
||||
# NOTE: changes in pyodide-build currently got postponed:
|
||||
# https://github.com/pyodide/pyodide-build/pull/249
|
||||
WASM_CMAKE_IS_USING_INIT_VARS = get_pyodide_build_version() >= (99, 0, 0)
|
||||
|
||||
# pyodide provide empty `CXXFLAGS`, leading to issues using C++ files compiled with `-fexceptions`
|
||||
# which is used by OCCT.
|
||||
|
||||
@@ -9,7 +9,6 @@ extend-exclude = '''
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifcopenshell-python/ifcopenshell/simple_spf/*
|
||||
|src/ifc2ca/templates/*
|
||||
|src/ifcconvert/cityjson/*
|
||||
|src/svgfill
|
||||
|src/exterior-shell-extractor
|
||||
'''
|
||||
@@ -25,7 +24,6 @@ reportUnnecessaryTypeIgnoreComment = true
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
# Submodules.
|
||||
"src/ifcconvert/cityjson",
|
||||
"src/ifcopenshell-python/ifcopenshell/express",
|
||||
"src/ifcopenshell-python/ifcopenshell/mvd",
|
||||
"src/ifcopenshell-python/ifcopenshell/simple_spf",
|
||||
|
||||
@@ -21,7 +21,7 @@ import bpy
|
||||
import bpy.utils.previews
|
||||
import importlib
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from . import handler, ui, prop, operator
|
||||
from . import handler, ui, prop, operator, gizmo
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
|
||||
@@ -148,7 +148,9 @@ classes = [
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.DocPreferences,
|
||||
ui.BIM_ADDON_preferences,
|
||||
ui.GizmoPreferencesDoor, # Register before GizmoPreferences
|
||||
ui.GizmoPreferences,
|
||||
# ui.DefaultParameters and ui.BIM_ADDON_preferences are registered separately after modules (see late_classes below)
|
||||
# Tabs panel
|
||||
ui.BIM_PT_tabs,
|
||||
# Project overview
|
||||
@@ -202,6 +204,14 @@ classes = [
|
||||
ui.BIM_PT_section_with_cappings,
|
||||
ui.BIM_PT_decorators_overlay,
|
||||
ui.BIM_PT_snappping,
|
||||
# Gizmos
|
||||
gizmo.GizmoArrow,
|
||||
gizmo.GizmoCone,
|
||||
gizmo.GizmoLock,
|
||||
gizmo.GizmoArc,
|
||||
gizmo.GizmoPen,
|
||||
gizmo.GizmoValidate,
|
||||
gizmo.GizmoCancel,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
@@ -225,14 +235,33 @@ def on_register(scene):
|
||||
is_registering = False
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
# Classes that need to be registered after modules (due to cross-module dependencies)
|
||||
late_classes = (
|
||||
ui.DefaultParameters, # Register before BIM_ADDON_preferences
|
||||
ui.BIM_ADDON_preferences,
|
||||
)
|
||||
|
||||
|
||||
def register_classes(classes_to_register):
|
||||
for cls in classes_to_register:
|
||||
# Prevent crashes in Blender 4.4.0, see #6420.
|
||||
if issubclass(cls, (ImportHelper, ExportHelper)):
|
||||
assert getattr(cls, "bl_description", "") or cls.__doc__, cls
|
||||
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
|
||||
def unregister_classes(classes_to_unregister):
|
||||
for cls in reversed(classes_to_unregister):
|
||||
if getattr(cls, "is_registered", None) is None:
|
||||
bpy.utils.unregister_class(cls)
|
||||
elif cls.is_registered:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
def register():
|
||||
register_classes(classes)
|
||||
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.undo_post.append(handler.undo_post)
|
||||
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||
@@ -256,6 +285,9 @@ def register():
|
||||
for mod in modules.values():
|
||||
mod.register()
|
||||
|
||||
# Delay registering classes that depend on module classes
|
||||
register_classes(late_classes)
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
|
||||
@@ -287,11 +319,7 @@ def unregister():
|
||||
|
||||
bpy.utils.previews.remove(icons)
|
||||
|
||||
for cls in reversed(classes):
|
||||
if getattr(cls, "is_registered", None) is None:
|
||||
bpy.utils.unregister_class(cls)
|
||||
elif cls.is_registered:
|
||||
bpy.utils.unregister_class(cls)
|
||||
unregister_classes(classes)
|
||||
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
@@ -306,6 +334,9 @@ def unregister():
|
||||
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
|
||||
|
||||
# Unregister late classes before modules
|
||||
unregister_classes(late_classes)
|
||||
|
||||
for mod in reversed(list(modules.values())):
|
||||
mod.unregister()
|
||||
|
||||
|
||||
@@ -0,0 +1,805 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
Shared gizmo components for reuse across modules.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
import math
|
||||
from typing import Optional
|
||||
from mathutils import Vector
|
||||
from mathutils.bvhtree import BVHTree
|
||||
from mathutils.geometry import intersect_line_line
|
||||
from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d
|
||||
import gpu
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
SNAP_CIRCLE_SEGMENTS = 16
|
||||
SNAP_CIRCLE_RADIUS = 0.1
|
||||
SNAP_CIRCLE_COLOR = (1.0, 0.5, 0.0, 1.0)
|
||||
SNAP_LINE_WIDTH = 3.0
|
||||
SNAP_MAX_RADIUS = 10.0
|
||||
|
||||
ARROW_SHAFT_LENGTH = 0.8
|
||||
ARROW_HEAD_LENGTH = 0.2
|
||||
ARROW_WIDTH = 0.015
|
||||
ARROW_HEAD_WIDTH_MULTIPLIER = 10
|
||||
ARROW_CIRCLE_SEGMENTS = 8
|
||||
|
||||
CONE_LENGTH = 1.0
|
||||
CONE_RADIUS = 0.35
|
||||
CONE_SEGMENTS = 16
|
||||
|
||||
ARC_SEGMENTS = 24
|
||||
ARC_LINE_WIDTH = 0.015
|
||||
|
||||
PRECISION_MODE_MULTIPLIER = 0.1
|
||||
|
||||
|
||||
class SnapManager:
|
||||
"""Manages snap point visualization and mesh snapping."""
|
||||
|
||||
def __init__(self):
|
||||
self._snap_point: Optional[tuple[float, float, float]] = None
|
||||
self._draw_handler = None
|
||||
self._shader = None
|
||||
|
||||
def set_snap_point(self, point: Optional[tuple[float, float, float]]) -> None:
|
||||
"""Set snap point and register draw handler if needed."""
|
||||
self._snap_point = point
|
||||
if self._draw_handler is None and point is not None:
|
||||
self._draw_handler = bpy.types.SpaceView3D.draw_handler_add(self._draw, (), "WINDOW", "POST_VIEW")
|
||||
self._redraw_viewport()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear snap point and unregister handler."""
|
||||
self._snap_point = None
|
||||
if self._draw_handler is not None:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self._draw_handler, "WINDOW")
|
||||
self._draw_handler = None
|
||||
self._redraw_viewport()
|
||||
|
||||
def _draw(self) -> None:
|
||||
"""Draw snap point circles."""
|
||||
if self._snap_point is None:
|
||||
return
|
||||
|
||||
if self._shader is None:
|
||||
self._shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
self._shader.bind()
|
||||
self._shader.uniform_float("color", SNAP_CIRCLE_COLOR)
|
||||
gpu.state.line_width_set(SNAP_LINE_WIDTH)
|
||||
|
||||
for plane in ("XY", "XZ", "YZ"):
|
||||
vertices = generate_circle_vertices(self._snap_point, SNAP_CIRCLE_RADIUS, SNAP_CIRCLE_SEGMENTS, plane)
|
||||
batch = batch_for_shader(self._shader, "LINE_STRIP", {"pos": vertices})
|
||||
batch.draw(self._shader)
|
||||
|
||||
gpu.state.line_width_set(1.0)
|
||||
|
||||
@staticmethod
|
||||
def _redraw_viewport() -> None:
|
||||
"""Force 3D viewport redraw."""
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
area.tag_redraw()
|
||||
|
||||
@staticmethod
|
||||
def snap_to_mesh(
|
||||
location: Vector, context: bpy.types.Context, axis_vector: Vector, active_obj: bpy.types.Object
|
||||
) -> Vector:
|
||||
"""Snap a location to the nearest mesh element if snapping is enabled.
|
||||
|
||||
Args:
|
||||
location: The world space location to snap
|
||||
context: The Blender context
|
||||
axis_vector: The axis direction of the gizmo
|
||||
active_obj: The active object to exclude from snapping
|
||||
|
||||
Returns:
|
||||
The snapped location or original location if snapping is disabled
|
||||
"""
|
||||
tool_settings = context.scene.tool_settings
|
||||
|
||||
if not tool_settings.use_snap:
|
||||
return location
|
||||
|
||||
snap_elements = tool_settings.snap_elements_base
|
||||
|
||||
mesh_objects = [
|
||||
obj for obj in context.visible_objects if obj.type == "MESH" and obj != active_obj and obj.visible_get()
|
||||
]
|
||||
|
||||
if not mesh_objects:
|
||||
return location
|
||||
|
||||
nearby_objects = []
|
||||
for obj in mesh_objects:
|
||||
bbox_corners = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
|
||||
|
||||
for corner in bbox_corners:
|
||||
if (corner - location).length <= SNAP_MAX_RADIUS:
|
||||
nearby_objects.append(obj)
|
||||
break
|
||||
|
||||
if not nearby_objects:
|
||||
return location
|
||||
|
||||
closest_point = None
|
||||
closest_distance = float("inf")
|
||||
|
||||
for obj in nearby_objects:
|
||||
if not obj.data.vertices:
|
||||
continue
|
||||
|
||||
depsgraph = context.evaluated_depsgraph_get()
|
||||
obj_eval = obj.evaluated_get(depsgraph)
|
||||
mesh = obj_eval.to_mesh()
|
||||
|
||||
if not mesh.vertices:
|
||||
obj_eval.to_mesh_clear()
|
||||
continue
|
||||
|
||||
world_vertices = [obj.matrix_world @ v.co for v in mesh.vertices]
|
||||
|
||||
if "VERTEX" in snap_elements:
|
||||
for v_co in world_vertices:
|
||||
dist = (v_co - location).length
|
||||
if dist < closest_distance:
|
||||
closest_distance = dist
|
||||
closest_point = v_co
|
||||
|
||||
if "EDGE" in snap_elements:
|
||||
for edge in mesh.edges:
|
||||
v1 = world_vertices[edge.vertices[0]]
|
||||
v2 = world_vertices[edge.vertices[1]]
|
||||
|
||||
edge_vec = v2 - v1
|
||||
edge_len_sq = edge_vec.length_squared
|
||||
|
||||
if edge_len_sq > 0:
|
||||
t = max(0, min(1, (location - v1).dot(edge_vec) / edge_len_sq))
|
||||
closest_on_edge = v1 + t * edge_vec
|
||||
dist = (closest_on_edge - location).length
|
||||
|
||||
if dist < closest_distance:
|
||||
closest_distance = dist
|
||||
closest_point = closest_on_edge
|
||||
|
||||
if "FACE" in snap_elements:
|
||||
bvh = BVHTree.FromPolygons(world_vertices, [p.vertices for p in mesh.polygons])
|
||||
|
||||
nearest_loc, normal, index, dist = bvh.find_nearest(location)
|
||||
|
||||
if nearest_loc and dist < closest_distance:
|
||||
closest_distance = dist
|
||||
closest_point = nearest_loc
|
||||
|
||||
obj_eval.to_mesh_clear()
|
||||
|
||||
if closest_point and closest_distance < SNAP_MAX_RADIUS:
|
||||
return closest_point
|
||||
|
||||
return location
|
||||
|
||||
|
||||
_snap_manager = SnapManager()
|
||||
|
||||
|
||||
def set_snap_point(point: Optional[tuple[float, float, float]]) -> None:
|
||||
_snap_manager.set_snap_point(point)
|
||||
|
||||
|
||||
def clear_snap_point() -> None:
|
||||
_snap_manager.clear()
|
||||
|
||||
|
||||
def snap_to_mesh(
|
||||
location: Vector, context: bpy.types.Context, axis_vector: Vector, active_obj: bpy.types.Object
|
||||
) -> Vector:
|
||||
return _snap_manager.snap_to_mesh(location, context, axis_vector, active_obj)
|
||||
|
||||
|
||||
def generate_circle_vertices(
|
||||
center: tuple[float, float, float], radius: float, segments: int, plane: str = "XY"
|
||||
) -> list[tuple[float, float, float]]:
|
||||
"""Generate circle vertices in specified plane.
|
||||
|
||||
Args:
|
||||
center: (x, y, z) tuple for circle center
|
||||
radius: Circle radius
|
||||
segments: Number of segments
|
||||
plane: 'XY', 'XZ', or 'YZ'
|
||||
|
||||
Returns:
|
||||
List of (x, y, z) vertices
|
||||
"""
|
||||
vertices = []
|
||||
for i in range(segments + 1):
|
||||
angle = (2 * math.pi * i) / segments
|
||||
cos_a = radius * math.cos(angle)
|
||||
sin_a = radius * math.sin(angle)
|
||||
|
||||
if plane == "XY":
|
||||
vertices.append((center[0] + cos_a, center[1] + sin_a, center[2]))
|
||||
elif plane == "XZ":
|
||||
vertices.append((center[0] + cos_a, center[1], center[2] + sin_a))
|
||||
else:
|
||||
vertices.append((center[0], center[1] + cos_a, center[2] + sin_a))
|
||||
|
||||
return vertices
|
||||
|
||||
|
||||
def create_quarter_circle_arc(
|
||||
radius: float = 1.0, segments: int = ARC_SEGMENTS, direction: str = "LEFT", line_width: float = ARC_LINE_WIDTH
|
||||
) -> tuple[tuple[float, float, float], ...]:
|
||||
"""Create a quarter circle arc with cross-section thickness for visibility from all angles.
|
||||
|
||||
Args:
|
||||
radius: Radius of the arc
|
||||
segments: Number of segments for smoothness
|
||||
direction: 'LEFT' for counterclockwise (0 to 90°), 'RIGHT' for clockwise (0 to -90°)
|
||||
line_width: Width of the arc line in both perpendicular directions
|
||||
|
||||
Returns:
|
||||
Tuple of arc triangles for drawing geometry visible from all angles
|
||||
"""
|
||||
half_width = line_width / 2
|
||||
|
||||
arc_points = []
|
||||
if direction == "LEFT":
|
||||
for i in range(segments + 1):
|
||||
angle = (math.pi / 2) * (i / segments)
|
||||
x = radius * math.cos(angle)
|
||||
y = radius * math.sin(angle)
|
||||
arc_points.append((x, y))
|
||||
else:
|
||||
for i in range(segments + 1):
|
||||
angle = (math.pi / 2) * (i / segments)
|
||||
x = -radius * math.cos(angle)
|
||||
y = radius * math.sin(angle)
|
||||
arc_points.append((x, y))
|
||||
|
||||
arc_triangles = []
|
||||
for i in range(len(arc_points) - 1):
|
||||
x1, y1 = arc_points[i]
|
||||
x2, y2 = arc_points[i + 1]
|
||||
|
||||
dx, dy = x2 - x1, y2 - y1
|
||||
length = (dx**2 + dy**2) ** 0.5
|
||||
if length > 0:
|
||||
px, py = -dy / length * half_width, dx / length * half_width
|
||||
|
||||
arc_triangles.extend(
|
||||
[
|
||||
(x1 + px, y1 + py, 0.0),
|
||||
(x1 - px, y1 - py, 0.0),
|
||||
(x2 + px, y2 + py, 0.0),
|
||||
]
|
||||
)
|
||||
arc_triangles.extend(
|
||||
[
|
||||
(x2 + px, y2 + py, 0.0),
|
||||
(x1 - px, y1 - py, 0.0),
|
||||
(x2 - px, y2 - py, 0.0),
|
||||
]
|
||||
)
|
||||
|
||||
arc_triangles.extend(
|
||||
[
|
||||
(x1, y1, -half_width),
|
||||
(x2, y2, -half_width),
|
||||
(x1, y1, +half_width),
|
||||
]
|
||||
)
|
||||
arc_triangles.extend(
|
||||
[
|
||||
(x1, y1, +half_width),
|
||||
(x2, y2, -half_width),
|
||||
(x2, y2, +half_width),
|
||||
]
|
||||
)
|
||||
|
||||
return tuple(arc_triangles)
|
||||
|
||||
|
||||
class GizmoMovable(bpy.types.Gizmo):
|
||||
"""Base class for movable gizmos with snapping and precision controls.
|
||||
|
||||
This class provides common functionality for gizmos that can be dragged
|
||||
to modify values, including:
|
||||
- Snap to mesh support (toggle with Ctrl)
|
||||
- Precision mode (hold Shift for 10x slower movement)
|
||||
- Header text with value and modifier hints
|
||||
- Consistent invoke/exit/modal behavior
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"custom_shape",
|
||||
"init_value",
|
||||
"move_get_cb",
|
||||
"move_set_cb",
|
||||
"axis",
|
||||
"local_axis",
|
||||
"start_location",
|
||||
"active_obj",
|
||||
"initial_snap_state",
|
||||
)
|
||||
|
||||
def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> set:
|
||||
self.init_value = self.move_get_cb() if self.move_get_cb else 0.0
|
||||
self.start_location = self.matrix_basis.translation.copy()
|
||||
self.active_obj = context.active_object
|
||||
self.initial_snap_state = context.scene.tool_settings.use_snap
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def exit(self, context: bpy.types.Context, cancel: bool) -> None:
|
||||
context.area.header_text_set(None)
|
||||
if cancel and self.move_set_cb:
|
||||
self.move_set_cb(self.init_value)
|
||||
if hasattr(self, "initial_snap_state"):
|
||||
context.scene.tool_settings.use_snap = self.initial_snap_state
|
||||
clear_snap_point()
|
||||
|
||||
def get_axis_direction(self) -> Vector:
|
||||
return self.axis
|
||||
|
||||
def modal(self, context: bpy.types.Context, event: bpy.types.Event, tweak) -> set:
|
||||
region = context.region
|
||||
rv3d = context.region_data
|
||||
tool_settings = context.scene.tool_settings
|
||||
|
||||
tool_settings.use_snap = not self.initial_snap_state if event.ctrl else self.initial_snap_state
|
||||
|
||||
current_coord = (event.mouse_region_x, event.mouse_region_y)
|
||||
view_origin = region_2d_to_origin_3d(region, rv3d, current_coord)
|
||||
view_direction = region_2d_to_vector_3d(region, rv3d, current_coord)
|
||||
|
||||
axis_direction = self.get_axis_direction()
|
||||
|
||||
result = intersect_line_line(
|
||||
view_origin,
|
||||
view_origin + view_direction * 1000,
|
||||
self.start_location,
|
||||
self.start_location + axis_direction * 1000,
|
||||
)
|
||||
current_3d = result[1] if result else self.start_location
|
||||
|
||||
delta = (current_3d - self.start_location).dot(axis_direction)
|
||||
|
||||
if tool_settings.use_snap and self.active_obj:
|
||||
snapped_pos = snap_to_mesh(current_3d, context, axis_direction, self.active_obj)
|
||||
if snapped_pos != current_3d:
|
||||
delta = (snapped_pos - self.start_location).dot(axis_direction)
|
||||
set_snap_point(snapped_pos)
|
||||
else:
|
||||
clear_snap_point()
|
||||
else:
|
||||
clear_snap_point()
|
||||
|
||||
if event.shift:
|
||||
delta *= PRECISION_MODE_MULTIPLIER
|
||||
|
||||
if self.move_set_cb:
|
||||
self.move_set_cb(self.init_value + delta)
|
||||
|
||||
self._update_header(context, self.init_value + delta, tool_settings.use_snap, event.shift)
|
||||
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def _update_header(self, context: bpy.types.Context, value: float, is_snapping: bool, is_precision: bool) -> None:
|
||||
header_text = f"Value: {value:.3f}m"
|
||||
hints = []
|
||||
if is_snapping:
|
||||
hints.append("Snapping: ON")
|
||||
if is_precision:
|
||||
hints.append("Precision Mode (0.1x)")
|
||||
hints.extend(["Ctrl: Toggle Snap", "Shift: Precision"])
|
||||
|
||||
if hints:
|
||||
header_text += " | " + " | ".join(hints)
|
||||
context.area.header_text_set(header_text)
|
||||
|
||||
|
||||
class GizmoLock(bpy.types.Gizmo):
|
||||
"""Reusable lock icon gizmo that switches between closed and open states."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_lock"
|
||||
|
||||
__slots__ = (
|
||||
"custom_shape_closed",
|
||||
"custom_shape_open",
|
||||
"prop_path",
|
||||
)
|
||||
|
||||
tris_closed = (
|
||||
(0.16803650558, 0.18791499734, 0.0),
|
||||
(-0.07805634290, 0.18791499734, 0.0),
|
||||
(0.16803650558, 0.44701099396, 0.0),
|
||||
(0.16803650558, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.18791499734, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(0.20165449381, 0.13603900373, 0.0),
|
||||
(-0.11167433113, 0.13603900373, 0.0),
|
||||
(0.20165449381, -0.44701099396, 0.0),
|
||||
(0.20165449381, -0.44701099396, 0.0),
|
||||
(-0.11167433113, 0.13603900373, 0.0),
|
||||
(-0.11167433113, -0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.18791499734, 0.0),
|
||||
(-0.39353451133, 0.18791499734, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(-0.39353451133, 0.18791499734, 0.0),
|
||||
(-0.39353451133, 0.30746498704, 0.0),
|
||||
(-0.44701099396, 0.18791499734, 0.0),
|
||||
(-0.44701099396, -0.04477182776, 0.0),
|
||||
(-0.39353451133, 0.18791499734, 0.0),
|
||||
(-0.39353451133, 0.18791499734, 0.0),
|
||||
(-0.44701099396, -0.04477182776, 0.0),
|
||||
(-0.39353451133, -0.04477182776, 0.0),
|
||||
)
|
||||
|
||||
tris_open = (
|
||||
(0.16803650558, 0.18791499734, 0.0),
|
||||
(-0.07805634290, 0.18791499734, 0.0),
|
||||
(0.16803650558, 0.44701099396, 0.0),
|
||||
(0.16803650558, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.18791499734, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(0.20165449381, 0.13603900373, 0.0),
|
||||
(-0.11167433113, 0.13603900373, 0.0),
|
||||
(0.20165449381, -0.44701099396, 0.0),
|
||||
(0.20165449381, -0.44701099396, 0.0),
|
||||
(-0.11167433113, 0.13603900373, 0.0),
|
||||
(-0.11167433113, -0.44701099396, 0.0),
|
||||
(0.16803650558, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(0.16803650558, 0.70610702038, 0.0),
|
||||
(0.16803650558, 0.70610702038, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.58656096458, 0.0),
|
||||
(-0.11167433113, 0.44701099396, 0.0),
|
||||
(-0.11167433113, 0.73432201147, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(-0.07805634290, 0.44701099396, 0.0),
|
||||
(-0.11167433113, 0.73432201147, 0.0),
|
||||
(-0.07805634290, 0.73432201147, 0.0),
|
||||
)
|
||||
|
||||
def get_custom_shape(self, context: bpy.types.Context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return self.custom_shape_closed
|
||||
|
||||
try:
|
||||
is_open = obj.path_resolve(self.prop_path)
|
||||
return self.custom_shape_open if is_open else self.custom_shape_closed
|
||||
except:
|
||||
return self.custom_shape_closed
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape_closed = self.new_custom_shape("TRIS", self.tris_closed)
|
||||
self.custom_shape_open = self.new_custom_shape("TRIS", self.tris_open)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.get_custom_shape(context))
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.get_custom_shape(context), select_id=select_id)
|
||||
|
||||
|
||||
class GizmoArc(bpy.types.Gizmo):
|
||||
"""Reusable quarter circle arc gizmo."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_arc"
|
||||
|
||||
__slots__ = (
|
||||
"custom_shape_left",
|
||||
"custom_shape_right",
|
||||
"prop_path",
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
"""Create quarter circle shapes for both LEFT and RIGHT directions."""
|
||||
arc_left = create_quarter_circle_arc(radius=1.0, direction="LEFT")
|
||||
arc_right = create_quarter_circle_arc(radius=1.0, direction="RIGHT")
|
||||
|
||||
self.custom_shape_left = self.new_custom_shape(type="TRIS", verts=arc_left)
|
||||
self.custom_shape_right = self.new_custom_shape(type="TRIS", verts=arc_right)
|
||||
|
||||
def _get_shape_for_direction(self, context: bpy.types.Context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return self.custom_shape_left
|
||||
|
||||
try:
|
||||
direction_value = obj.path_resolve(self.prop_path)
|
||||
if "RIGHT" in str(direction_value):
|
||||
return self.custom_shape_right
|
||||
except:
|
||||
pass
|
||||
|
||||
return self.custom_shape_left
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self._get_shape_for_direction(context))
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self._get_shape_for_direction(context), select_id=select_id)
|
||||
|
||||
|
||||
class GizmoPen(bpy.types.Gizmo):
|
||||
"""Reusable pen/edit icon gizmo for entering edit mode."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_pen"
|
||||
|
||||
__slots__ = ("custom_shape",)
|
||||
|
||||
tris = (
|
||||
(-0.07595771551132202, -0.2948460578918457, 0.0),
|
||||
(0.16886109113693237, 0.23203276097774506, 0.0),
|
||||
(0.062240585684776306, 0.28157487511634827, 0.0),
|
||||
(0.07201281189918518, 0.30260589718818665, 0.0),
|
||||
(0.21042980253696442, 0.321493536233902, 0.0),
|
||||
(0.17863331735134125, 0.25306373834609985, 0.0),
|
||||
(0.062240585684776306, 0.28157487511634827, 0.0),
|
||||
(-0.1825782209634781, -0.2453039139509201, 0.0),
|
||||
(-0.07595771551132202, -0.2948460578918457, 0.0),
|
||||
(-0.1825782209634781, -0.2453039139509201, 0.0),
|
||||
(-0.19114767014980316, -0.4032624065876007, 0.0),
|
||||
(-0.07595771551132202, -0.2948460578918457, 0.0),
|
||||
(0.07201281189918518, 0.30260589718818665, 0.0),
|
||||
(0.10380929708480835, 0.371035635471344, 0.0),
|
||||
(0.21042980253696442, 0.321493536233902, 0.0),
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class GizmoValidate(bpy.types.Gizmo):
|
||||
"""Reusable validate/checkmark icon gizmo for confirming edits."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_validate"
|
||||
|
||||
__slots__ = ("custom_shape",)
|
||||
|
||||
tris = (
|
||||
(0.36775994300842285, 0.205583393573761, 0.0),
|
||||
(0.030080009251832962, -0.1881658434867859, 0.0),
|
||||
(0.030080009251832962, -0.3380376696586609, 0.0),
|
||||
(-0.22017201781272888, -0.16090886294841766, 0.0),
|
||||
(0.030080009251832962, -0.1881658434867859, 0.0),
|
||||
(-0.22017201781272888, -0.011037036776542664, 0.0),
|
||||
(0.36775994300842285, 0.205583393573761, 0.0),
|
||||
(0.36775994300842285, 0.355455219745636, 0.0),
|
||||
(0.030080009251832962, -0.1881658434867859, 0.0),
|
||||
(-0.22017201781272888, -0.16090886294841766, 0.0),
|
||||
(0.030080009251832962, -0.3380376696586609, 0.0),
|
||||
(0.030080009251832962, -0.1881658434867859, 0.0),
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class GizmoCancel(bpy.types.Gizmo):
|
||||
"""Reusable cancel/X icon gizmo for canceling edits."""
|
||||
|
||||
bl_idname = "VIEW3D_GT_cancel"
|
||||
|
||||
__slots__ = ("custom_shape",)
|
||||
|
||||
tris = (
|
||||
(0.0, 0.048707593232393265, 0.0),
|
||||
(0.0, -0.048707593232393265, 0.0),
|
||||
(0.048707593232393265, 0.0, 0.0),
|
||||
(0.0, 0.048707593232393265, 0.0),
|
||||
(-0.21918421983718872, 0.2678918242454529, 0.0),
|
||||
(-0.048707593232393265, 0.0, 0.0),
|
||||
(-0.21918421983718872, 0.2678918242454529, 0.0),
|
||||
(-0.2678918242454529, 0.21918421983718872, 0.0),
|
||||
(-0.048707593232393265, 0.0, 0.0),
|
||||
(-0.048707593232393265, 0.0, 0.0),
|
||||
(-0.2678918242454529, -0.21918421983718872, 0.0),
|
||||
(-0.21918421983718872, -0.2678918242454529, 0.0),
|
||||
(0.2678918242454529, 0.21918421983718872, 0.0),
|
||||
(0.21918421983718872, 0.2678918242454529, 0.0),
|
||||
(0.0, 0.048707593232393265, 0.0),
|
||||
(0.21918421983718872, -0.2678918242454529, 0.0),
|
||||
(0.2678918242454529, -0.21918421983718872, 0.0),
|
||||
(0.048707593232393265, 0.0, 0.0),
|
||||
(0.048707593232393265, 0.0, 0.0),
|
||||
(0.2678918242454529, 0.21918421983718872, 0.0),
|
||||
(0.0, 0.048707593232393265, 0.0),
|
||||
(0.0, 0.048707593232393265, 0.0),
|
||||
(-0.048707593232393265, 0.0, 0.0),
|
||||
(0.0, -0.048707593232393265, 0.0),
|
||||
(-0.048707593232393265, 0.0, 0.0),
|
||||
(-0.21918421983718872, -0.2678918242454529, 0.0),
|
||||
(0.0, -0.048707593232393265, 0.0),
|
||||
(0.0, -0.048707593232393265, 0.0),
|
||||
(0.21918421983718872, -0.2678918242454529, 0.0),
|
||||
(0.048707593232393265, 0.0, 0.0),
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape = self.new_custom_shape("TRIS", self.tris)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class GizmoArrow(GizmoMovable):
|
||||
"""Arrow gizmo for directional value editing."""
|
||||
|
||||
bl_idname = "BIM_GT_gizmo_arrow"
|
||||
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
def _get_arrow_triangles(self) -> tuple[tuple[float, float, float], ...]:
|
||||
"""Generate arrow geometry along +X axis."""
|
||||
triangles = []
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(0, -ARROW_WIDTH, 0),
|
||||
(ARROW_SHAFT_LENGTH, -ARROW_WIDTH, 0),
|
||||
(0, +ARROW_WIDTH, 0),
|
||||
]
|
||||
)
|
||||
triangles.extend(
|
||||
[
|
||||
(0, +ARROW_WIDTH, 0),
|
||||
(ARROW_SHAFT_LENGTH, -ARROW_WIDTH, 0),
|
||||
(ARROW_SHAFT_LENGTH, +ARROW_WIDTH, 0),
|
||||
]
|
||||
)
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(0, 0, -ARROW_WIDTH),
|
||||
(ARROW_SHAFT_LENGTH, 0, -ARROW_WIDTH),
|
||||
(0, 0, +ARROW_WIDTH),
|
||||
]
|
||||
)
|
||||
triangles.extend(
|
||||
[
|
||||
(0, 0, +ARROW_WIDTH),
|
||||
(ARROW_SHAFT_LENGTH, 0, -ARROW_WIDTH),
|
||||
(ARROW_SHAFT_LENGTH, 0, +ARROW_WIDTH),
|
||||
]
|
||||
)
|
||||
|
||||
head_width = ARROW_WIDTH * ARROW_HEAD_WIDTH_MULTIPLIER
|
||||
triangles.extend(
|
||||
[
|
||||
(ARROW_SHAFT_LENGTH, -head_width, 0),
|
||||
(ARROW_SHAFT_LENGTH + ARROW_HEAD_LENGTH, 0, 0),
|
||||
(ARROW_SHAFT_LENGTH, +head_width, 0),
|
||||
]
|
||||
)
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(ARROW_SHAFT_LENGTH, 0, -head_width),
|
||||
(ARROW_SHAFT_LENGTH + ARROW_HEAD_LENGTH, 0, 0),
|
||||
(ARROW_SHAFT_LENGTH, 0, +head_width),
|
||||
]
|
||||
)
|
||||
|
||||
for i in range(ARROW_CIRCLE_SEGMENTS):
|
||||
angle1 = (2 * math.pi * i) / ARROW_CIRCLE_SEGMENTS
|
||||
angle2 = (2 * math.pi * (i + 1)) / ARROW_CIRCLE_SEGMENTS
|
||||
|
||||
y1 = head_width * math.cos(angle1)
|
||||
z1 = head_width * math.sin(angle1)
|
||||
y2 = head_width * math.cos(angle2)
|
||||
z2 = head_width * math.sin(angle2)
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(ARROW_SHAFT_LENGTH, 0, 0),
|
||||
(ARROW_SHAFT_LENGTH, y1, z1),
|
||||
(ARROW_SHAFT_LENGTH, y2, z2),
|
||||
]
|
||||
)
|
||||
|
||||
return tuple(triangles)
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape = self.new_custom_shape("TRIS", self._get_arrow_triangles())
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
class GizmoCone(GizmoMovable):
|
||||
"""Cone gizmo for directional value editing with local axis support."""
|
||||
|
||||
bl_idname = "BIM_GT_gizmo_cone"
|
||||
bl_target_properties = ({"id": "offset", "type": "FLOAT", "array_length": 1},)
|
||||
|
||||
def get_axis_direction(self) -> Vector:
|
||||
if hasattr(self, "local_axis") and self.active_obj:
|
||||
obj_rotation = self.active_obj.matrix_world.to_3x3()
|
||||
axis_direction = obj_rotation @ self.local_axis
|
||||
axis_direction.normalize()
|
||||
return axis_direction
|
||||
return self.axis
|
||||
|
||||
def _get_cone_triangles(self) -> tuple[tuple[float, float, float], ...]:
|
||||
"""Generate cone geometry along +X axis."""
|
||||
triangles = []
|
||||
cone_tip_x = CONE_LENGTH
|
||||
|
||||
for i in range(CONE_SEGMENTS):
|
||||
angle1 = (2 * math.pi * i) / CONE_SEGMENTS
|
||||
angle2 = (2 * math.pi * (i + 1)) / CONE_SEGMENTS
|
||||
|
||||
y1 = CONE_RADIUS * math.cos(angle1)
|
||||
z1 = CONE_RADIUS * math.sin(angle1)
|
||||
y2 = CONE_RADIUS * math.cos(angle2)
|
||||
z2 = CONE_RADIUS * math.sin(angle2)
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(cone_tip_x, 0, 0),
|
||||
(0, y1, z1),
|
||||
(0, y2, z2),
|
||||
]
|
||||
)
|
||||
|
||||
triangles.extend(
|
||||
[
|
||||
(0, 0, 0),
|
||||
(0, y2, z2),
|
||||
(0, y1, z1),
|
||||
]
|
||||
)
|
||||
|
||||
return tuple(triangles)
|
||||
|
||||
def setup(self) -> None:
|
||||
self.custom_shape = self.new_custom_shape("TRIS", self._get_cone_triangles())
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
self.draw_custom_shape(self.custom_shape)
|
||||
|
||||
def draw_select(self, context: bpy.types.Context, select_id: int) -> None:
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
@@ -127,6 +127,10 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# Second pass: delete aggregates that now have no parts
|
||||
deleted_aggregates = []
|
||||
for aggregate in aggregates_to_check:
|
||||
# Skip spatial elements - they should not be deleted even if empty
|
||||
if aggregate.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
|
||||
related_objects = ifcopenshell.util.element.get_parts(aggregate)
|
||||
if len(related_objects) == 0:
|
||||
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
|
||||
|
||||
@@ -489,7 +489,7 @@ class BaseDecorator:
|
||||
self.draw_label(context, text=text, line_no=line_number_start, multiline=True, **draw_label_kwargs)
|
||||
|
||||
@cache
|
||||
def format_value(self, context, value, custom_unit=None):
|
||||
def format_value(self, context, value, suppress_zero_inches=False, custom_unit=None, in_unit_length=False):
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
precision = drawing_pset_data.get("MetricPrecision", None)
|
||||
if not precision:
|
||||
@@ -500,8 +500,9 @@ class BaseDecorator:
|
||||
value,
|
||||
precision=precision,
|
||||
decimal_places=decimal_places,
|
||||
suppress_zero_inches=True,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
custom_unit=custom_unit,
|
||||
in_unit_length=in_unit_length,
|
||||
)
|
||||
|
||||
def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
|
||||
@@ -743,7 +744,12 @@ class DimensionDecorator(BaseDecorator):
|
||||
|
||||
if not show_description_only:
|
||||
length = (v1 - v0).length
|
||||
text = self.format_value(context, length, custom_unit=dimension_data["custom_unit"])
|
||||
text = self.format_value(
|
||||
context,
|
||||
length,
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
custom_unit=dimension_data["custom_unit"],
|
||||
)
|
||||
if isinstance(self, DiameterDecorator):
|
||||
text = "D" + text
|
||||
text = text_prefix + text + text_suffix
|
||||
@@ -1198,7 +1204,11 @@ class PlanLevelDecorator(BaseDecorator):
|
||||
abs_z = verts[0].z
|
||||
z = helper.get_relative_z(obj, element, abs_z)
|
||||
|
||||
rl = self.format_value(context, z)
|
||||
# Z is in meters (Blender world space), convert to project units (feet)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
z = z / unit_scale # Convert meters to feet
|
||||
|
||||
rl = self.format_value(context, z, in_unit_length=True)
|
||||
text = "{}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
@@ -1271,7 +1281,11 @@ class SectionLevelDecorator(BaseDecorator):
|
||||
|
||||
def get_text():
|
||||
z = verts[0].z
|
||||
rl = self.format_value(context, z)
|
||||
# Z is in meters (Blender world space), convert to project units (feet)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
z = z / unit_scale # Convert meters to feet
|
||||
|
||||
rl = self.format_value(context, z, in_unit_length=True)
|
||||
text = "RL {}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
@@ -1739,13 +1753,27 @@ class CutDecorator:
|
||||
self.cache_camera_matrix()
|
||||
return True # Let's be conservative
|
||||
obj = bpy.context.scene.camera
|
||||
loc_check = np.frombuffer(eval(DecoratorData.camera_location_checksum), dtype=np.float32)
|
||||
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||
|
||||
# Handle both old float64 and new float32 checksums for version compatibility
|
||||
loc_checksum_bytes = eval(DecoratorData.camera_location_checksum)
|
||||
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
|
||||
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
|
||||
else: # New format: 3 * 4 bytes (float32)
|
||||
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
|
||||
|
||||
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
|
||||
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
|
||||
self.cache_camera_matrix()
|
||||
return True
|
||||
rot_check = np.frombuffer(eval(DecoratorData.camera_rotation_checksum), dtype=np.float32).reshape(3, 3)
|
||||
rot_real = np.array(obj.matrix_world.to_3x3())
|
||||
|
||||
# Handle both old float64 and new float32 checksums for version compatibility
|
||||
rot_checksum_bytes = eval(DecoratorData.camera_rotation_checksum)
|
||||
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
|
||||
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
|
||||
else: # New format: 9 * 4 bytes (float32)
|
||||
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
|
||||
|
||||
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
|
||||
rot_dot = np.dot(rot_check, rot_real.T)
|
||||
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
|
||||
if angle_rad > 0.0017453292519943296: # 0.1 degrees
|
||||
@@ -2016,7 +2044,7 @@ class DecorationsHandler:
|
||||
return # Don't draw decorations in local view
|
||||
|
||||
# disable decorations when not in camera view
|
||||
if context.region_data.view_perspective != 'CAMERA':
|
||||
if context.region_data.view_perspective != "CAMERA":
|
||||
return
|
||||
|
||||
if not DrawingsData.is_loaded:
|
||||
|
||||
@@ -122,6 +122,45 @@ class BoundingBox:
|
||||
return retVal
|
||||
|
||||
|
||||
def find_best_precision(fractional_inches, max_precision=256, base_tolerance=0.0001):
|
||||
"""
|
||||
Find the simplest fraction denominator that accurately represents the value.
|
||||
Uses a sliding tolerance - smaller denominators get more tolerance.
|
||||
|
||||
:param fractional_inches: The fractional part of inches (0.0 to 1.0)
|
||||
:param max_precision: Maximum denominator to try (256 for imperial)
|
||||
:param base_tolerance: Base tolerance in inches (very strict)
|
||||
:return: Best denominator (power of 2: 2, 4, 8, 16, 32, 64, 128, 256)
|
||||
"""
|
||||
if abs(fractional_inches) < 0.00001:
|
||||
return 1 # No fraction needed
|
||||
|
||||
# Try denominators from coarsest to finest: 2, 4, 8, 16, 32, 64, 128, 256
|
||||
for exp in range(1, 9): # 2^1 to 2^8
|
||||
denom = 2**exp
|
||||
if denom > max_precision:
|
||||
break
|
||||
|
||||
# Find nearest numerator for this denominator
|
||||
numer = round(fractional_inches * denom)
|
||||
|
||||
# Skip if numerator is 0
|
||||
if numer == 0:
|
||||
continue
|
||||
|
||||
# Check if this fraction is close enough
|
||||
fraction_value = numer / denom
|
||||
error = abs(fraction_value - fractional_inches)
|
||||
|
||||
# Very strict tolerance - only simplify if it's a near-perfect match
|
||||
# This keeps 3/256 as 3/256, but allows floating point errors to be cleaned up
|
||||
if error < base_tolerance:
|
||||
return denom
|
||||
|
||||
# If no simpler fraction works, use max precision
|
||||
return max_precision
|
||||
|
||||
|
||||
# This function stolen from https://github.com/kevancress/MeasureIt_ARCH/blob/dcf607ce0896aa2284463c6b4ae9cd023fc54cbe/measureit_arch_baseclass.py
|
||||
# MeasureIt-ARCH is GPL-v3
|
||||
def format_distance(
|
||||
@@ -187,14 +226,18 @@ def format_distance(
|
||||
if unit_length == "INCHES":
|
||||
toInches = 1
|
||||
if unit_length == "FEET":
|
||||
toInches = 11.9999
|
||||
toInches = 12
|
||||
else:
|
||||
toInches = 39.3700787401574887
|
||||
inPerFoot = 11.9999
|
||||
inPerFoot = 12.0
|
||||
|
||||
if isArea:
|
||||
toInches = 1550
|
||||
inPerFoot = 143.999
|
||||
inPerFoot = 144
|
||||
|
||||
decInches = value * toInches
|
||||
decFeet = decInches / 12
|
||||
|
||||
if not precision:
|
||||
precision = 256
|
||||
elif precision == "1":
|
||||
@@ -203,8 +246,6 @@ def format_distance(
|
||||
precision = int(precision.split("/")[1])
|
||||
|
||||
base = int(precision)
|
||||
decInches = value * toInches
|
||||
decFeet = decInches / 12
|
||||
|
||||
# Separate ft and inches
|
||||
# Unless Inches are the specified Length Unit or unit_fraction is False
|
||||
@@ -220,20 +261,33 @@ def format_distance(
|
||||
# Separate Fractional Inches
|
||||
decInches = abs(decInches) # ignore the sign for inches
|
||||
inches = math.floor(decInches) # remove decimal
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
|
||||
# Clean up floating point errors
|
||||
fractional_inches = decInches - inches
|
||||
tolerance = 0.01 # About 1/100 of an inch
|
||||
|
||||
# If the fractional part is very small, treat it as zero
|
||||
if fractional_inches < tolerance:
|
||||
frac = 0
|
||||
# If very close to the next whole inch, round up
|
||||
elif fractional_inches > (1.0 - tolerance):
|
||||
frac = 0
|
||||
inches += 1
|
||||
else:
|
||||
frac = round(base * (decInches))
|
||||
# Calculate fraction normally
|
||||
if inches != 0:
|
||||
frac = round(base * fractional_inches)
|
||||
else:
|
||||
frac = round(base * fractional_inches)
|
||||
|
||||
# Set proper numerator and denominator
|
||||
if frac != base:
|
||||
numcycles = int(math.log2(base))
|
||||
for i in range(numcycles):
|
||||
if frac % 2 == 0:
|
||||
frac = int(frac / 2)
|
||||
base = int(base / 2)
|
||||
else:
|
||||
break
|
||||
# Simplify using GCD
|
||||
from math import gcd
|
||||
|
||||
divisor = gcd(int(frac), int(base))
|
||||
frac = int(frac / divisor)
|
||||
base = int(base / divisor)
|
||||
else:
|
||||
frac = 0
|
||||
inches += 1
|
||||
@@ -252,31 +306,46 @@ def format_distance(
|
||||
frac = None
|
||||
if not isArea:
|
||||
add_inches = bool(inches) or not suppress_zero_inches or (inches == 0 and frac)
|
||||
|
||||
tx_dist = ""
|
||||
if feet:
|
||||
tx_dist += str(feet) + "'"
|
||||
if not feet and not add_inches:
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and add_inches:
|
||||
|
||||
# Add "0' - " when we have inches but no feet
|
||||
# But only add " - " separator if we actually have inches to show
|
||||
if not feet and add_inches:
|
||||
tx_dist += "0' - "
|
||||
elif feet and add_inches:
|
||||
tx_dist += " - "
|
||||
|
||||
if not feet and value < 0:
|
||||
tx_dist += "-"
|
||||
if add_inches:
|
||||
if feet == 0 and inches == 0:
|
||||
pass
|
||||
if feet == 0 and inches == 0 and not frac:
|
||||
# Special case: exactly zero, show "0"
|
||||
tx_dist += "0"
|
||||
elif feet == 0 and inches == 0:
|
||||
pass # Has fraction, will be added below
|
||||
else:
|
||||
tx_dist += str(inches)
|
||||
if add_inches and frac:
|
||||
if feet == 0 and inches == 0:
|
||||
pass
|
||||
pass # Has fraction, will be added below
|
||||
else:
|
||||
tx_dist += " "
|
||||
if frac:
|
||||
tx_dist += str(frac) + "/" + str(base)
|
||||
if add_inches or frac:
|
||||
tx_dist += '"'
|
||||
# Only add inch symbol if we actually added inch content
|
||||
if inches > 0 or frac > 0 or feet == 0:
|
||||
tx_dist += '"'
|
||||
|
||||
if precision == "12" and unit_system == "IMPERIAL":
|
||||
tx_dist = str(round(decFeet)) + "'"
|
||||
if tx_dist == '"':
|
||||
tx_dist = "0' - 0\""
|
||||
else:
|
||||
fmt = "%1.3f"
|
||||
sq_feet = round(value * toInches / inPerFoot, 4)
|
||||
|
||||
@@ -975,6 +975,10 @@ class OverrideDelete(bpy.types.Operator):
|
||||
except:
|
||||
continue
|
||||
|
||||
# Skip spatial elements - they should not be deleted even if empty
|
||||
if aggregate.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
|
||||
related_objects = ifcopenshell.util.element.get_parts(aggregate)
|
||||
if len(related_objects) == 0:
|
||||
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
|
||||
|
||||
@@ -178,6 +178,8 @@ classes = (
|
||||
door.FinishEditingDoor,
|
||||
door.EnableEditingDoor,
|
||||
door.RemoveDoor,
|
||||
door.ToggleDoorSwing,
|
||||
door.GizmoDoorEdition,
|
||||
railing.BIM_OT_add_railing,
|
||||
railing.CopyRailingParameters,
|
||||
railing.AddRailing,
|
||||
|
||||
@@ -34,6 +34,7 @@ import bonsai.core.geometry as core
|
||||
import bonsai.core.root
|
||||
from bonsai.bim.module.model.window import create_bm_window, create_bm_box
|
||||
|
||||
import math
|
||||
from mathutils import Vector, Matrix
|
||||
from typing import Union, Any, Optional
|
||||
|
||||
@@ -278,7 +279,7 @@ def create_bm_door_lining(
|
||||
new_faces = [bm.faces.new([new_verts[vi] for vi in face[1]]) for face in faces]
|
||||
|
||||
extruded = bmesh.ops.extrude_face_region(bm, geom=new_faces)
|
||||
extrusion_vector = Vector((0, 1, 0)) * depth
|
||||
extrusion_vector = V_(0, 1, 0) * depth
|
||||
translate_verts = [v for v in extruded["geom"] if isinstance(v, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts)
|
||||
|
||||
@@ -291,6 +292,8 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Model.get_door_props(obj)
|
||||
if not props.is_editing:
|
||||
return
|
||||
|
||||
overall_width = props.overall_width
|
||||
overall_height = props.overall_height
|
||||
@@ -408,7 +411,10 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
|
||||
|
||||
if door_swing_type == "LEFT":
|
||||
bm_mirror(
|
||||
bm, door_handle_verts, mirror_axes=V_(1, 0, 0), mirror_point=panel_position + V_(panel_size.x / 2, 0, 0)
|
||||
bm,
|
||||
door_handle_verts,
|
||||
mirror_axes=V_(1, 0, 0),
|
||||
mirror_point=panel_position + V_(panel_size.x / 2, 0, 0),
|
||||
)
|
||||
|
||||
door_handle_mirrored_verts = bm_mirror(
|
||||
@@ -482,10 +488,10 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return tool.Ifc.get() and context.mode == "OBJECT"
|
||||
|
||||
def _execute(self, context):
|
||||
def _execute(self, context: bpy.types.Context) -> set[str]:
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"ERROR"}, "You need to start IFC project first to create a door.")
|
||||
@@ -528,6 +534,8 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert element
|
||||
props = tool.Model.get_door_props(obj)
|
||||
|
||||
tool.Blender.get_addon_preferences().default_parameters.door.copy_to(props)
|
||||
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
@@ -546,7 +554,7 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
update_door_modifier_representation(obj)
|
||||
|
||||
def _execute(self, context):
|
||||
def _execute(self, context: bpy.types.Context) -> set[str]:
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
if not tool.Blender.Modifier.is_eligible_for_door_modifier(obj):
|
||||
continue
|
||||
@@ -557,6 +565,7 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.cancel_editing_door"
|
||||
bl_label = "Cancel Editing Door on Selected Objects"
|
||||
bl_description = "Cancel editing and revert door parameters to their previous values"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def cancel_editing_door_on_object(self, obj: bpy.types.Object) -> None:
|
||||
@@ -591,6 +600,7 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.finish_editing_door"
|
||||
bl_label = "Finish Editing Door on Selected Objects"
|
||||
bl_description = "Apply changes and finish editing door parameters"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def finish_editing_door_on_object(self, obj):
|
||||
@@ -627,6 +637,7 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_door"
|
||||
bl_label = "Enable Editing Door on Selected Objects"
|
||||
bl_description = "Enter edit mode to modify door parameters interactively"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def edit_door_on_obj(self, obj):
|
||||
@@ -670,3 +681,321 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
self.remove_door_on_object(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleDoorSwing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""Toggle door swing direction between LEFT and RIGHT"""
|
||||
|
||||
bl_idname = "bim.toggle_door_swing"
|
||||
bl_label = "Toggle Door Swing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = tool.Blender.get_active_object()
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Blender.Modifier.is_door(element):
|
||||
return {"CANCELLED"}
|
||||
|
||||
props = tool.Model.get_door_props(obj)
|
||||
current_type = props.door_type
|
||||
|
||||
if "LEFT" in current_type:
|
||||
props.door_type = current_type.replace("LEFT", "RIGHT")
|
||||
elif "RIGHT" in current_type:
|
||||
props.door_type = current_type.replace("RIGHT", "LEFT")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GizmoDoorEdition(bpy.types.GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_bim_door_edition"
|
||||
bl_label = "Door Editing Gizmo"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options = {"3D", "PERSISTENT"}
|
||||
|
||||
COLOR_RED = (1.0, 0.2, 0.2)
|
||||
COLOR_GREEN = (0.1, 0.8, 0.1)
|
||||
COLOR_BLUE = (0.2, 0.2, 1.0)
|
||||
ARROW_SCALE = 0.25
|
||||
|
||||
@classmethod
|
||||
def get_arrow_color_from_axis(cls, axis):
|
||||
"""Get arrow color based on axis direction (X=red, Y=green, Z=blue)."""
|
||||
if axis[0] != 0:
|
||||
return cls.COLOR_RED
|
||||
elif axis[1] != 0:
|
||||
return cls.COLOR_GREEN
|
||||
else:
|
||||
return cls.COLOR_BLUE
|
||||
|
||||
gizmo_props = [
|
||||
{"attr_name": "overall_height", "axis": (0, 0, 1)},
|
||||
{"attr_name": "overall_width", "axis": (1, 0, 0)},
|
||||
{"attr_name": "threshold_thickness", "axis": (0, 0, 1)},
|
||||
{"attr_name": "threshold_depth", "axis": (0, 1, 0)},
|
||||
{"attr_name": "lining_depth", "axis": (0, 1, 0)},
|
||||
{"attr_name": "lining_thickness", "axis": (1, 0, 0)},
|
||||
{"attr_name": "transom_offset", "axis": (0, 0, 1)},
|
||||
{"attr_name": "transom_thickness", "axis": (0, 0, 1)},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
"""Show gizmo only when a single door object is selected and active."""
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if not prefs.gizmos.draw_gizmos_in_3d_viewport:
|
||||
return False
|
||||
|
||||
obj = tool.Blender.get_active_object(is_selected=True)
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
if len(tool.Blender.get_selected_objects()) != 1:
|
||||
return False
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Blender.Modifier.is_door(element):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_axis_rotation_matrix(self, axis):
|
||||
"""Get rotation matrix to align arrow (default +X direction) with the given axis.
|
||||
|
||||
Args:
|
||||
axis: Tuple of (x, y, z) representing the target axis direction
|
||||
|
||||
Returns:
|
||||
Rotation matrix to align arrow with the axis
|
||||
"""
|
||||
axis_vec = V_(*axis).normalized()
|
||||
default_dir = V_(1, 0, 0)
|
||||
return default_dir.rotation_difference(axis_vec).to_matrix().to_4x4()
|
||||
|
||||
def get_gizmo_matrix_overall_height(self, props):
|
||||
translation = Matrix.Translation(V_(props.overall_width - 0.05, 0.0, props.overall_height))
|
||||
rotation = self.get_axis_rotation_matrix((0, 0, 1))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_overall_width(self, props):
|
||||
translation = Matrix.Translation(V_(props.overall_width, 0.0, props.overall_height - 0.05))
|
||||
rotation = self.get_axis_rotation_matrix((1, 0, 0))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_threshold_thickness(self, props):
|
||||
translation = Matrix.Translation(
|
||||
V_(
|
||||
props.overall_width / 2,
|
||||
props.threshold_offset + props.threshold_depth / 2,
|
||||
props.threshold_thickness,
|
||||
)
|
||||
)
|
||||
rotation = self.get_axis_rotation_matrix((0, 0, 1))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_threshold_depth(self, props):
|
||||
translation = Matrix.Translation(
|
||||
V_(props.overall_width / 2, props.threshold_depth, props.threshold_thickness / 2)
|
||||
)
|
||||
rotation = self.get_axis_rotation_matrix((0, 1, 0))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_lining_depth(self, props):
|
||||
translation = Matrix.Translation(
|
||||
V_(props.overall_width / 2, props.lining_depth + props.lining_offset, props.overall_height)
|
||||
)
|
||||
rotation = self.get_axis_rotation_matrix((0, 1, 0))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_lining_thickness(self, props):
|
||||
translation = Matrix.Translation(
|
||||
V_(props.lining_thickness, props.lining_depth / 2 + props.lining_offset, props.overall_height / 2)
|
||||
)
|
||||
rotation = self.get_axis_rotation_matrix((1, 0, 0))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_transom_offset(self, props):
|
||||
translation = Matrix.Translation(V_(props.overall_width / 2, 0.0, props.transom_offset))
|
||||
rotation = self.get_axis_rotation_matrix((0, 0, 1))
|
||||
return translation @ rotation
|
||||
|
||||
def get_gizmo_matrix_transom_thickness(self, props):
|
||||
translation = Matrix.Translation(
|
||||
V_(props.overall_width / 2, 0.0, props.transom_offset + props.transom_thickness)
|
||||
)
|
||||
rotation = self.get_axis_rotation_matrix((0, 0, 1))
|
||||
return translation @ rotation
|
||||
|
||||
def setup(self, context):
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
default_color = prefs.decorations_colour[:3]
|
||||
highlight_color = prefs.decorator_color_selected[:3]
|
||||
inactive_color = prefs.decorator_color_background[:3]
|
||||
special_color = prefs.decorator_color_special[:3]
|
||||
|
||||
def add_gizmo_prop(prop_config):
|
||||
attr_name = prop_config["attr_name"]
|
||||
gizmo = self.gizmos.new("BIM_GT_gizmo_cone")
|
||||
|
||||
def move_get():
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
return 0.0
|
||||
props = tool.Model.get_door_props(obj)
|
||||
return getattr(props, attr_name)
|
||||
|
||||
def move_set(value):
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
return
|
||||
props = tool.Model.get_door_props(obj)
|
||||
setattr(props, attr_name, max(0.0, value))
|
||||
|
||||
# Set callbacks on the gizmo instance
|
||||
gizmo.move_get_cb = move_get
|
||||
gizmo.move_set_cb = move_set
|
||||
gizmo.axis = V_(*prop_config["axis"])
|
||||
gizmo.local_axis = V_(*prop_config["axis"])
|
||||
|
||||
gizmo.color = self.get_arrow_color_from_axis(prop_config["axis"])
|
||||
gizmo.color_highlight = highlight_color
|
||||
gizmo.alpha = 0.99
|
||||
gizmo.use_draw_modal = True
|
||||
gizmo.use_draw_scale = True
|
||||
|
||||
setattr(self, f"gizmo_{attr_name}", gizmo)
|
||||
|
||||
for prop_config in self.gizmo_props:
|
||||
add_gizmo_prop(prop_config)
|
||||
|
||||
self.gizmo_door_type = self.gizmos.new("VIEW3D_GT_arc")
|
||||
self.gizmo_door_type.use_draw_scale = False
|
||||
self.gizmo_door_type.color = special_color
|
||||
self.gizmo_door_type.alpha = 0.5
|
||||
self.gizmo_door_type.color_highlight = highlight_color
|
||||
self.gizmo_door_type.prop_path = "BIMDoorProperties.door_type"
|
||||
self.gizmo_door_type.target_set_operator("bim.toggle_door_swing")
|
||||
|
||||
# Flip arc gizmo - mirrors the swing arc along X axis, flips door when clicked
|
||||
self.gizmo_flip_arc = self.gizmos.new("VIEW3D_GT_arc")
|
||||
self.gizmo_flip_arc.use_draw_scale = False
|
||||
self.gizmo_flip_arc.color = inactive_color
|
||||
self.gizmo_flip_arc.alpha = 0.5
|
||||
self.gizmo_flip_arc.color_highlight = highlight_color
|
||||
self.gizmo_flip_arc.prop_path = "BIMDoorProperties.door_type"
|
||||
op = self.gizmo_flip_arc.target_set_operator("bim.flip_object")
|
||||
op.flip_local_axes = "XY"
|
||||
|
||||
self.pen_gizmo = self.gizmos.new("VIEW3D_GT_pen")
|
||||
self.pen_gizmo.use_draw_scale = False
|
||||
self.pen_gizmo.color = default_color
|
||||
self.pen_gizmo.color_highlight = highlight_color
|
||||
self.pen_gizmo.alpha = 0.8
|
||||
self.pen_gizmo.target_set_operator("bim.enable_editing_door")
|
||||
|
||||
self.validate_gizmo = self.gizmos.new("VIEW3D_GT_validate")
|
||||
self.validate_gizmo.use_draw_scale = False
|
||||
self.validate_gizmo.color = self.COLOR_GREEN
|
||||
self.validate_gizmo.color_highlight = highlight_color
|
||||
self.validate_gizmo.alpha = 0.8
|
||||
self.validate_gizmo.target_set_operator("bim.finish_editing_door")
|
||||
|
||||
self.cancel_gizmo = self.gizmos.new("VIEW3D_GT_cancel")
|
||||
self.cancel_gizmo.use_draw_scale = False
|
||||
self.cancel_gizmo.color = self.COLOR_RED
|
||||
self.cancel_gizmo.color_highlight = highlight_color
|
||||
self.cancel_gizmo.alpha = 0.8
|
||||
self.cancel_gizmo.target_set_operator("bim.cancel_editing_door")
|
||||
|
||||
def refresh(self, context):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return
|
||||
|
||||
props = tool.Model.get_door_props(obj)
|
||||
mw = obj.matrix_world
|
||||
self.update_arrows(mw, props)
|
||||
self.update_swing_gizmo(mw, props)
|
||||
self.update_editing_gizmos(mw, props)
|
||||
|
||||
def update_arrows(self, mw, props):
|
||||
"""Update arrow gizmos position and visibility based on editing state."""
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
door_gizmo_prefs = prefs.gizmos.door
|
||||
|
||||
for prop_config in self.gizmo_props:
|
||||
attr_name = prop_config["attr_name"]
|
||||
gizmo = getattr(self, f"gizmo_{attr_name}", None)
|
||||
if gizmo is None:
|
||||
continue
|
||||
|
||||
if not getattr(door_gizmo_prefs, attr_name, True):
|
||||
gizmo.hide = True
|
||||
continue
|
||||
|
||||
if (
|
||||
not props.is_editing
|
||||
or (attr_name == "threshold_depth" and props.threshold_thickness == 0.0)
|
||||
or (attr_name == "transom_offset" and props.transom_thickness == 0.0)
|
||||
):
|
||||
gizmo.hide = True
|
||||
continue
|
||||
|
||||
# Update position and show arrow when editing
|
||||
gizmo.hide = False
|
||||
matrix_method = getattr(self, f"get_gizmo_matrix_{attr_name}", None)
|
||||
if matrix_method:
|
||||
gizmo.matrix_basis = mw @ matrix_method(props)
|
||||
gizmo.matrix_offset = Matrix.Scale(self.ARROW_SCALE, 4)
|
||||
|
||||
def update_swing_gizmo(self, mw, props):
|
||||
"""Update swing gizmo position and color based on editing state."""
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
door_gizmo_prefs = prefs.gizmos.door
|
||||
|
||||
self.gizmo_door_type.hide = not props.is_editing or not door_gizmo_prefs.swing_arc
|
||||
self.gizmo_flip_arc.hide = not props.is_editing or not door_gizmo_prefs.flip_arc
|
||||
|
||||
# If both are hidden, no need to calculate transforms
|
||||
if self.gizmo_door_type.hide and self.gizmo_flip_arc.hide:
|
||||
return
|
||||
|
||||
# Calculate base swing transformation (common to both arcs)
|
||||
swing_x_offset = props.overall_width if "RIGHT" in props.door_type else 0.0
|
||||
base_swing_transform = Matrix.Translation(V_(swing_x_offset, 0, 0)) @ Matrix.Scale(props.overall_width, 4)
|
||||
|
||||
if not self.gizmo_door_type.hide:
|
||||
self.gizmo_door_type.matrix_basis = mw @ base_swing_transform
|
||||
self.gizmo_door_type.color = prefs.decorations_colour[:3]
|
||||
|
||||
if not self.gizmo_flip_arc.hide:
|
||||
mirror_x = Matrix.Scale(-1, 4, (0, 1, 0))
|
||||
self.gizmo_flip_arc.matrix_basis = mw @ base_swing_transform @ mirror_x
|
||||
|
||||
def update_editing_gizmos(self, mw, props):
|
||||
"""Update editing control gizmos (pen/validate/cancel) visibility and position."""
|
||||
|
||||
icon_z = props.overall_height + 0.5
|
||||
local_transform = (
|
||||
Matrix.Translation(V_(0, 0.0, icon_z))
|
||||
@ Matrix.Rotation(math.radians(90), 4, (1.0, 0.0, 0.0))
|
||||
@ Matrix.Scale(0.5, 4)
|
||||
)
|
||||
icon_matrix_base = mw @ local_transform
|
||||
|
||||
if props.is_editing:
|
||||
self.pen_gizmo.hide = True
|
||||
self.validate_gizmo.hide = False
|
||||
self.validate_gizmo.matrix_basis = icon_matrix_base
|
||||
self.cancel_gizmo.hide = False
|
||||
cancel_local = Matrix.Translation(V_(0.5, 0.0, 0.0)) @ local_transform
|
||||
self.cancel_gizmo.matrix_basis = mw @ cancel_local
|
||||
|
||||
else:
|
||||
self.pen_gizmo.hide = False
|
||||
self.pen_gizmo.matrix_basis = icon_matrix_base
|
||||
self.validate_gizmo.hide = True
|
||||
self.cancel_gizmo.hide = True
|
||||
|
||||
@@ -645,6 +645,15 @@ class BIMStairProperties(PropertyGroup):
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def copy_to(self, target_props: "BIMStairProperties") -> None:
|
||||
"""Copy preset values to target stair properties."""
|
||||
target_props.custom_tread_lock = self.custom_tread_lock
|
||||
for prop_name, prop_value in self.get_props_kwargs().items():
|
||||
# Skip custom_first_last_tread_run if it contains None values (when lock is enabled)
|
||||
if prop_name == "custom_first_last_tread_run" and None in prop_value:
|
||||
continue
|
||||
setattr(target_props, prop_name, prop_value)
|
||||
|
||||
|
||||
class BIMSverchokProperties(PropertyGroup):
|
||||
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
|
||||
@@ -863,6 +872,14 @@ class BIMWindowProperties(PropertyGroup):
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def copy_to(self, target_props: "BIMWindowProperties") -> None:
|
||||
"""Copy preset values to target window properties, excluding materials."""
|
||||
exclude_props = {"lining_material", "framing_material", "glazing_material"}
|
||||
for kwargs_dict in (self.get_general_kwargs(), self.get_lining_kwargs(), self.get_panel_kwargs()):
|
||||
for prop_name, prop_value in kwargs_dict.items():
|
||||
if prop_name not in exclude_props:
|
||||
setattr(target_props, prop_name, prop_value)
|
||||
|
||||
|
||||
DoorType = Literal[
|
||||
"SINGLE_SWING_LEFT",
|
||||
@@ -892,13 +909,35 @@ class BIMDoorProperties(PropertyGroup):
|
||||
default="SINGLE_SWING_LEFT",
|
||||
update=update_door,
|
||||
)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE", update=update_door)
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE", update=update_door)
|
||||
overall_height: bpy.props.FloatProperty(
|
||||
name="Overall Height",
|
||||
default=2.0,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
overall_width: bpy.props.FloatProperty(
|
||||
name="Overall Width",
|
||||
default=0.9,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
|
||||
# lining properties
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE", update=update_door)
|
||||
lining_depth: bpy.props.FloatProperty(
|
||||
name="Lining Depth",
|
||||
default=0.050,
|
||||
min=0.001,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
lining_thickness: bpy.props.FloatProperty(
|
||||
name="Lining Thickness", default=0.050, subtype="DISTANCE", update=update_door
|
||||
name="Lining Thickness",
|
||||
default=0.050,
|
||||
min=0.001,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
lining_offset: bpy.props.FloatProperty(
|
||||
name="Lining Offset",
|
||||
@@ -920,6 +959,7 @@ class BIMDoorProperties(PropertyGroup):
|
||||
name="Transom Thickness",
|
||||
description="Set values > 0 to add a transom.\n" "`0.050 mm` is good as default value",
|
||||
default=0.000,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
@@ -927,6 +967,7 @@ class BIMDoorProperties(PropertyGroup):
|
||||
name="Transom Offset",
|
||||
description="Distance from the bottom door opening to the beginning of the transom (unlike windows)",
|
||||
default=1.525,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
@@ -935,20 +976,32 @@ class BIMDoorProperties(PropertyGroup):
|
||||
name="Casing Thickness",
|
||||
description="Set values > 0 and LiningOffset = 0 to add a casing.",
|
||||
default=0.075,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
casing_depth: bpy.props.FloatProperty(
|
||||
name="Casing Depth",
|
||||
default=0.005,
|
||||
min=0.001,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005, subtype="DISTANCE", update=update_door)
|
||||
|
||||
threshold_thickness: bpy.props.FloatProperty(
|
||||
name="Threshold Thickness",
|
||||
description="Set values > 0 to add a threshold.",
|
||||
default=0.025,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
threshold_depth: bpy.props.FloatProperty(
|
||||
name="Threshold Depth", default=0.1, subtype="DISTANCE", update=update_door
|
||||
name="Threshold Depth",
|
||||
default=0.1,
|
||||
min=0.001,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
threshold_offset: bpy.props.FloatProperty(
|
||||
name="Threshold Offset",
|
||||
@@ -959,7 +1012,13 @@ class BIMDoorProperties(PropertyGroup):
|
||||
)
|
||||
|
||||
# panel properties
|
||||
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035, subtype="DISTANCE", update=update_door)
|
||||
panel_depth: bpy.props.FloatProperty(
|
||||
name="Panel Depth",
|
||||
default=0.035,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
panel_width_ratio: bpy.props.FloatProperty(
|
||||
name="Panel Width Ratio",
|
||||
description="Width of this panel, given as ratio " "relative to the total clear opening width of the door",
|
||||
@@ -969,10 +1028,18 @@ class BIMDoorProperties(PropertyGroup):
|
||||
update=update_door,
|
||||
)
|
||||
frame_thickness: bpy.props.FloatProperty(
|
||||
name="Window Frame Thickness", default=0.035, subtype="DISTANCE", update=update_door
|
||||
name="Window Frame Thickness",
|
||||
default=0.035,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
frame_depth: bpy.props.FloatProperty(
|
||||
name="Window Frame Depth", default=0.035, subtype="DISTANCE", update=update_door
|
||||
name="Window Frame Depth",
|
||||
default=0.035,
|
||||
min=0,
|
||||
subtype="DISTANCE",
|
||||
update=update_door,
|
||||
)
|
||||
|
||||
# Material properties
|
||||
@@ -1080,6 +1147,14 @@ class BIMDoorProperties(PropertyGroup):
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def copy_to(self, target_props: "BIMDoorProperties") -> None:
|
||||
"""Copy properties to target door properties, excluding materials."""
|
||||
exclude_props = {"lining_material", "framing_material", "glazing_material"}
|
||||
for kwargs_dict in (self.get_general_kwargs(), self.get_lining_kwargs(), self.get_panel_kwargs()):
|
||||
for prop_name, prop_value in kwargs_dict.items():
|
||||
if prop_name not in exclude_props:
|
||||
setattr(target_props, prop_name, prop_value)
|
||||
|
||||
|
||||
RailingType = Literal["FRAMELESS_PANEL", "WALL_MOUNTED_HANDRAIL"]
|
||||
CapType = Literal["TO_END_POST_AND_FLOOR", "TO_END_POST", "TO_FLOOR", "TO_WALL", "180", "NONE"]
|
||||
@@ -1179,6 +1254,11 @@ class BIMRailingProperties(PropertyGroup):
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def copy_to(self, target_props: "BIMRailingProperties") -> None:
|
||||
"""Copy preset values to target railing properties."""
|
||||
for prop_name, prop_value in self.get_general_kwargs().items():
|
||||
setattr(target_props, prop_name, prop_value)
|
||||
|
||||
|
||||
def to_angle(percentage: float) -> float:
|
||||
return math.atan(percentage / 100)
|
||||
@@ -1281,6 +1361,11 @@ class BIMRoofProperties(PropertyGroup):
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
def copy_to(self, target_props: "BIMRoofProperties") -> None:
|
||||
"""Copy preset values to target roof properties."""
|
||||
for prop_name, prop_value in self.get_general_kwargs().items():
|
||||
setattr(target_props, prop_name, prop_value)
|
||||
|
||||
|
||||
class SnapMousePoint(PropertyGroup):
|
||||
x: bpy.props.FloatProperty(name="X")
|
||||
|
||||
@@ -106,6 +106,13 @@ def update_railing_modifier_ifc_data(context: bpy.types.Context) -> None:
|
||||
model_representation = ifcopenshell.api.geometry.add_railing_representation(ifc_file, **representation_data)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# recalculate normals to ensure correct shading
|
||||
mesh = obj.data
|
||||
if isinstance(mesh, bpy.types.Mesh):
|
||||
bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=False)
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
|
||||
tool.Blender.apply_bmesh(mesh, bm)
|
||||
|
||||
elif props.railing_type == "FRAMELESS_PANEL":
|
||||
tool.Model.add_body_representation(obj)
|
||||
|
||||
@@ -335,6 +342,8 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Model.get_railing_props(obj)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
tool.Blender.get_addon_preferences().default_parameters.railing.copy_to(props)
|
||||
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
|
||||
|
||||
@@ -570,6 +570,8 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Model.get_roof_props(obj)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
tool.Blender.get_addon_preferences().default_parameters.roof.copy_to(props)
|
||||
|
||||
# rejecting original roof shape to be safe
|
||||
# taking into account only it's bounding box dimensions
|
||||
if obj.dimensions.x == 0 or obj.dimensions.y == 0:
|
||||
|
||||
@@ -188,6 +188,8 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Model.get_stair_props(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
tool.Blender.get_addon_preferences().default_parameters.stair.copy_to(props)
|
||||
|
||||
# Use the special method that includes custom_tread_lock for IFC storage
|
||||
stair_data = props.get_props_kwargs_for_ifc_export(convert_to_project_units=True)
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
|
||||
@@ -305,48 +305,9 @@ class BIM_PT_stair(bpy.types.Panel):
|
||||
row.operator("bim.finish_editing_stair", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_stair", icon="CANCEL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
for prop_name in props.get_props_kwargs():
|
||||
# Skip custom_tread_lock as it's handled with custom_first_last_tread_run
|
||||
if prop_name == "custom_tread_lock":
|
||||
continue
|
||||
|
||||
prop_value = getattr(props, prop_name)
|
||||
draw_stair_properties(self.layout, props)
|
||||
|
||||
# Special handling for custom_first_last_tread_run
|
||||
if prop_name == "custom_first_last_tread_run":
|
||||
# Draw the lock toggle
|
||||
row_lock = self.layout.row(align=True)
|
||||
lock_text = (
|
||||
"Lock First/Last Treads" if not props.custom_tread_lock else "Unlock First/Last Treads"
|
||||
)
|
||||
row_lock.prop(
|
||||
props,
|
||||
"custom_tread_lock",
|
||||
text=lock_text,
|
||||
icon="LOCKED" if props.custom_tread_lock else "UNLOCKED",
|
||||
)
|
||||
|
||||
# Only show the custom values input if unlocked
|
||||
if not props.custom_tread_lock:
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
self.layout.label(text=f"{prop_readable_name}:")
|
||||
self.layout.prop(props, prop_name, text="")
|
||||
elif isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
self.layout.label(text=f"{prop_readable_name}:")
|
||||
self.layout.prop(props, prop_name, text="")
|
||||
else:
|
||||
self.layout.prop(props, prop_name)
|
||||
|
||||
if prop_name == "height": # Weak but we just want to insert this inside props drawing
|
||||
row_length = self.layout.row(align=True)
|
||||
row_length.prop(props, "total_length_target")
|
||||
row_length.prop(
|
||||
props,
|
||||
"total_length_lock",
|
||||
text="",
|
||||
icon="LOCKED" if props.total_length_lock else "UNLOCKED",
|
||||
)
|
||||
regenerate_stair_mesh(obj)
|
||||
else:
|
||||
calculated_params = StairData.data["calculated_params"]
|
||||
@@ -440,39 +401,11 @@ class BIM_PT_window(bpy.types.Panel):
|
||||
row.label(text="Window parameters", icon="OUTLINER_OB_LATTICE")
|
||||
|
||||
if props.is_editing:
|
||||
number_of_panels, panels_data = props.window_types_panels[props.window_type]
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.finish_editing_window", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_window", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
lining_props = props.get_lining_kwargs()
|
||||
self.layout.label(text="Lining properties")
|
||||
for prop in lining_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
panel_props = props.get_panel_kwargs()
|
||||
self.layout.label(text="Panel properties")
|
||||
|
||||
panel_box = self.layout.box()
|
||||
row = panel_box.row()
|
||||
cols = [row.column(align=True) for i in range(number_of_panels + 1)]
|
||||
|
||||
cols[0].label(text="")
|
||||
|
||||
for panel_i in range(number_of_panels):
|
||||
r = cols[panel_i + 1].row()
|
||||
r.alignment = "CENTER"
|
||||
r.label(text=f"#{panel_i}")
|
||||
r = cols[panel_i + 1].row()
|
||||
|
||||
for prop in panel_props:
|
||||
cols[0].label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
for panel_i in range(number_of_panels):
|
||||
cols[panel_i + 1].prop(props, prop, index=panel_i, text="")
|
||||
draw_window_properties(self.layout, props)
|
||||
|
||||
self.layout.use_property_split = True
|
||||
self.layout.label(text="Material Properties")
|
||||
@@ -560,19 +493,7 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
row.operator("bim.finish_editing_door", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_door", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
lining_props = props.get_lining_kwargs()
|
||||
self.layout.label(text="Lining Properties")
|
||||
for prop in lining_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
panel_props = props.get_panel_kwargs()
|
||||
self.layout.label(text="Panel Properties")
|
||||
for prop in panel_props:
|
||||
self.layout.prop(props, prop)
|
||||
draw_door_properties(self.layout, props)
|
||||
|
||||
self.layout.use_property_split = True
|
||||
self.layout.label(text="Material Properties")
|
||||
@@ -643,14 +564,7 @@ class BIM_PT_railing(bpy.types.Panel):
|
||||
row.operator("bim.finish_editing_railing", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_railing", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
if prop == "support_spacing" and props.use_manual_supports:
|
||||
row = self.layout.row()
|
||||
row.prop(props, prop)
|
||||
row.active = False
|
||||
continue
|
||||
self.layout.prop(props, prop)
|
||||
draw_railing_properties(self.layout, props)
|
||||
|
||||
update_railing_modifier_bmesh(context)
|
||||
|
||||
@@ -708,9 +622,7 @@ class BIM_PT_roof(bpy.types.Panel):
|
||||
row.operator("bim.finish_editing_roof", icon="CHECKMARK", text="Finish Editing")
|
||||
row.operator("bim.cancel_editing_roof", icon="CANCEL", text="")
|
||||
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
self.layout.prop(props, prop)
|
||||
draw_roof_properties(self.layout, props)
|
||||
|
||||
update_roof_modifier_bmesh(obj)
|
||||
elif props.is_editing_path:
|
||||
@@ -776,6 +688,126 @@ class BIM_PT_external_parametric_geometry(bpy.types.Panel):
|
||||
row.prop(input, "default_value", text=input.name)
|
||||
|
||||
|
||||
def draw_door_properties(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
|
||||
"""Draw door properties UI (shared between properties panel and preferences)."""
|
||||
# General properties
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
# Lining properties
|
||||
layout.label(text="Lining Properties")
|
||||
lining_props = props.get_lining_kwargs()
|
||||
for prop in lining_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
# Panel properties
|
||||
layout.label(text="Panel Properties")
|
||||
panel_props = props.get_panel_kwargs()
|
||||
for prop in panel_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
|
||||
def draw_window_properties(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
|
||||
"""Draw window properties UI (shared between properties panel and preferences)."""
|
||||
number_of_panels, panels_data = props.window_types_panels[props.window_type]
|
||||
|
||||
# General and lining properties
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
layout.label(text="Lining Properties")
|
||||
lining_props = props.get_lining_kwargs()
|
||||
for prop in lining_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
# Panel properties (special layout for multiple panels)
|
||||
panel_props = props.get_panel_kwargs()
|
||||
layout.label(text="Panel Properties")
|
||||
|
||||
panel_box = layout.box()
|
||||
row = panel_box.row()
|
||||
cols = [row.column(align=True) for i in range(number_of_panels + 1)]
|
||||
|
||||
cols[0].label(text="")
|
||||
|
||||
for panel_i in range(number_of_panels):
|
||||
r = cols[panel_i + 1].row()
|
||||
r.alignment = "CENTER"
|
||||
r.label(text=f"#{panel_i}")
|
||||
r = cols[panel_i + 1].row()
|
||||
|
||||
for prop in panel_props:
|
||||
cols[0].label(text=f"{props.bl_rna.properties[prop].name}")
|
||||
for panel_i in range(number_of_panels):
|
||||
cols[panel_i + 1].prop(props, prop, index=panel_i, text="")
|
||||
|
||||
|
||||
def draw_railing_properties(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
|
||||
"""Draw railing properties UI (shared between properties panel and preferences)."""
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
if prop == "support_spacing" and props.use_manual_supports:
|
||||
row = layout.row()
|
||||
row.prop(props, prop)
|
||||
row.active = False
|
||||
continue
|
||||
layout.prop(props, prop)
|
||||
|
||||
|
||||
def draw_roof_properties(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
|
||||
"""Draw roof properties UI (shared between properties panel and preferences)."""
|
||||
# General properties
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop in general_props:
|
||||
layout.prop(props, prop)
|
||||
|
||||
|
||||
def draw_stair_properties(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
|
||||
"""Draw stair properties UI (shared between properties panel and preferences)."""
|
||||
for prop_name in props.get_props_kwargs():
|
||||
# Skip custom_tread_lock as it's handled with custom_first_last_tread_run
|
||||
if prop_name == "custom_tread_lock":
|
||||
continue
|
||||
|
||||
prop_value = getattr(props, prop_name)
|
||||
|
||||
# Special handling for custom_first_last_tread_run
|
||||
if prop_name == "custom_first_last_tread_run":
|
||||
# Draw the lock toggle
|
||||
row_lock = layout.row(align=True)
|
||||
lock_text = "Lock First/Last Treads" if not props.custom_tread_lock else "Unlock First/Last Treads"
|
||||
row_lock.prop(
|
||||
props,
|
||||
"custom_tread_lock",
|
||||
text=lock_text,
|
||||
icon="LOCKED" if props.custom_tread_lock else "UNLOCKED",
|
||||
)
|
||||
|
||||
# Only show the custom values input if unlocked
|
||||
if not props.custom_tread_lock:
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
layout.label(text=f"{prop_readable_name}:")
|
||||
layout.prop(props, prop_name, text="")
|
||||
elif isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
layout.label(text=f"{prop_readable_name}:")
|
||||
layout.prop(props, prop_name, text="")
|
||||
else:
|
||||
layout.prop(props, prop_name)
|
||||
|
||||
if prop_name == "height": # Weak but we just want to insert this inside props drawing
|
||||
row_length = layout.row(align=True)
|
||||
row_length.prop(props, "total_length_target")
|
||||
row_length.prop(
|
||||
props,
|
||||
"total_length_lock",
|
||||
text="",
|
||||
icon="LOCKED" if props.total_length_lock else "UNLOCKED",
|
||||
)
|
||||
|
||||
|
||||
def add_menu(self: bpy.types.Menu, context: bpy.types.Context) -> None:
|
||||
self.layout.operator_context = "INVOKE_REGION_WIN"
|
||||
self.layout.operator("bim.add_element", icon_value=bonsai.bim.icons["IFC"].icon_id, text="IFC Element")
|
||||
|
||||
@@ -258,6 +258,9 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Model.get_window_props(obj)
|
||||
if not props.is_editing:
|
||||
return
|
||||
|
||||
panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type]
|
||||
accumulated_height = [0] * len(panel_schema[0])
|
||||
built_panels = []
|
||||
@@ -451,6 +454,8 @@ class AddWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert element
|
||||
props = tool.Model.get_window_props(obj)
|
||||
|
||||
tool.Blender.get_addon_preferences().default_parameters.window.copy_to(props)
|
||||
|
||||
window_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator, workspace, gizmo, decorator
|
||||
|
||||
classes = (
|
||||
operator.WM_OT_space_origin_warning,
|
||||
operator.AddProjectLibrary,
|
||||
operator.AppendEntireLibrary,
|
||||
operator.AppendInspectedLinkedElement,
|
||||
@@ -87,6 +88,7 @@ classes = (
|
||||
ui.BIM_UL_filter_categories,
|
||||
ui.BIM_UL_links,
|
||||
gizmo.ClippingPlane,
|
||||
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
@@ -74,6 +74,38 @@ if TYPE_CHECKING:
|
||||
PresetType = Literal["metric_m", "metric_mm", "imperial_ft", "demo", "wizard"]
|
||||
|
||||
|
||||
class WM_OT_space_origin_warning(bpy.types.Operator):
|
||||
"""Warning dialog for spaces at origin"""
|
||||
bl_idname = "wm.space_origin_warning"
|
||||
bl_label = "Space Origin Warning"
|
||||
bl_options = {'INTERNAL'}
|
||||
|
||||
message: bpy.props.StringProperty()
|
||||
space_count: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.alert = True
|
||||
|
||||
box = layout.box()
|
||||
col = box.column(align=True)
|
||||
col.label(text=f"⚠️ WARNING: {self.space_count} IfcSpace(s) at origin (0,0,0)!", icon='ERROR')
|
||||
col.separator()
|
||||
|
||||
for line in self.message.split('\n'):
|
||||
if line.strip():
|
||||
col.label(text=line)
|
||||
|
||||
col.separator()
|
||||
col.label(text="This may indicate incorrect placement.")
|
||||
|
||||
|
||||
class NewProject(bpy.types.Operator):
|
||||
bl_idname = "bim.new_project"
|
||||
bl_label = "New Project"
|
||||
@@ -1679,6 +1711,31 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
settings.json_version = self.json_version
|
||||
settings.json_compact = self.json_compact
|
||||
|
||||
|
||||
# CHECK FOR SPACES AT ORIGIN
|
||||
ifc_file = tool.Ifc.get()
|
||||
spaces_at_origin = []
|
||||
for space in ifc_file.by_type("IfcSpace"):
|
||||
obj = tool.Ifc.get_object(space)
|
||||
if obj and all(abs(coord) < 0.001 for coord in obj.location):
|
||||
spaces_at_origin.append(space.Name or space.GlobalId)
|
||||
|
||||
if spaces_at_origin:
|
||||
# Build message
|
||||
message_lines = [f" • {name}" for name in spaces_at_origin[:5]]
|
||||
if len(spaces_at_origin) > 5:
|
||||
message_lines.append(f" ... and {len(spaces_at_origin) - 5} more")
|
||||
|
||||
message = '\n'.join(message_lines)
|
||||
|
||||
# Show dialog - user MUST click OK
|
||||
bpy.ops.wm.space_origin_warning('INVOKE_DEFAULT',
|
||||
message=message,
|
||||
space_count=len(spaces_at_origin))
|
||||
|
||||
self.report({'WARNING'}, f"⚠️ {len(spaces_at_origin)} IfcSpace(s) at origin!")
|
||||
|
||||
|
||||
ifc_exporter = export_ifc.IfcExporter(settings)
|
||||
print("Starting export")
|
||||
settings.logger.info("Starting export")
|
||||
|
||||
@@ -36,6 +36,7 @@ import bonsai.core.geometry
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute
|
||||
import ifcopenshell.util.unit
|
||||
from bonsai.tool.unit import parse_distance_string
|
||||
from typing import TYPE_CHECKING, Union, Literal
|
||||
|
||||
|
||||
@@ -48,18 +49,46 @@ def get_subelement_class(
|
||||
|
||||
|
||||
def update_elevation(self: "BIMContainer", context: bpy.types.Context) -> None:
|
||||
try:
|
||||
elevation = float(self.elevation)
|
||||
if self.elevation != str(elevation):
|
||||
self.elevation = str(elevation)
|
||||
return
|
||||
except:
|
||||
elevation = 0
|
||||
# Try to parse the input string with unit support first
|
||||
is_valid, parsed_elevation = parse_distance_string(self.elevation, use_project_unit=True)
|
||||
|
||||
if is_valid:
|
||||
elevation = parsed_elevation
|
||||
|
||||
# Format the string if it needs normalization:
|
||||
# - Plain numbers (no units): "4" → "4' - 0""
|
||||
# - Missing symbols: "5'3" → "5' - 3""
|
||||
# - Inconsistent formatting: "5' 3 3/256" → "5' - 3 3/256""
|
||||
input_str = self.elevation.strip()
|
||||
|
||||
# Check if input needs formatting (has feet/inch symbols or is plain number)
|
||||
needs_formatting = (
|
||||
input_str.replace(".", "").replace("-", "").replace(" ", "").replace("/", "").isdigit() # Plain number
|
||||
or "'" in input_str # Has feet symbol
|
||||
or '"' in input_str # Has inch symbol
|
||||
or " " in input_str.replace(" - ", "") # Has spaces (but not the formatted " - ")
|
||||
)
|
||||
|
||||
# Only format if the current string doesn't match our standard format
|
||||
if needs_formatting:
|
||||
formatted = tool.Unit.format_distance(elevation)
|
||||
if self.elevation != formatted:
|
||||
self.elevation = formatted
|
||||
return
|
||||
else:
|
||||
# Fall back to direct float conversion for backward compatibility
|
||||
try:
|
||||
elevation = float(self.elevation)
|
||||
except Exception as e:
|
||||
print(f"Elevation parsing failed for '{self.elevation}': {e}")
|
||||
elevation = 0
|
||||
|
||||
# Update the object's position in the 3D scene
|
||||
if ifc_definition_id := self.ifc_definition_id:
|
||||
entity = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
if obj := tool.Ifc.get_object(entity):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
obj.matrix_world[2][3] = elevation * unit_scale
|
||||
# elevation is already in meters, set it directly
|
||||
obj.matrix_world[2][3] = elevation
|
||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj)
|
||||
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ class BIM_UL_containers_manager(UIList):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
icon = self.icon_by_class.get(item.ifc_class, "META_PLANE")
|
||||
split = row.split(factor=0.85)
|
||||
split = row.split(factor=0.8)
|
||||
if item.long_name:
|
||||
split2 = split.split(factor=0.7)
|
||||
row = split2.row(align=True)
|
||||
|
||||
@@ -102,6 +102,19 @@ class AddOpening(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element_had_openings = tool.Geometry.has_openings(voided_element)
|
||||
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body")
|
||||
if not element2:
|
||||
# Apply scale transformation for vanilla Blender objects before creating IFC representation
|
||||
# This ensures the opening geometry matches the visual scale of the object
|
||||
if tool.Geometry.is_scaled(obj2):
|
||||
if obj2.data:
|
||||
# Make mesh data single-user if it's shared
|
||||
if obj2.data.users > 1:
|
||||
obj2.data = obj2.data.copy()
|
||||
context_override = {}
|
||||
context_override["object"] = context_override["active_object"] = obj2
|
||||
context_override["selected_objects"] = context_override["selected_editable_objects"] = [obj2]
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
|
||||
element2 = bonsai.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
|
||||
@@ -38,6 +38,20 @@ from ifcopenshell.util.file import IfcHeaderExtractor
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDProperty
|
||||
from bonsai.bim.module.pset.prop import IfcProperty
|
||||
from bonsai.bim.module.model.prop import (
|
||||
BIMDoorProperties,
|
||||
BIMWindowProperties,
|
||||
BIMRailingProperties,
|
||||
BIMRoofProperties,
|
||||
BIMStairProperties,
|
||||
)
|
||||
from bonsai.bim.module.model.ui import (
|
||||
draw_door_properties,
|
||||
draw_window_properties,
|
||||
draw_railing_properties,
|
||||
draw_roof_properties,
|
||||
draw_stair_properties,
|
||||
)
|
||||
from typing import Optional, TYPE_CHECKING, Literal
|
||||
from natsort import natsorted
|
||||
import textwrap
|
||||
@@ -228,6 +242,48 @@ class BIM_UL_generic(bpy.types.UIList):
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
|
||||
class GizmoPreferencesDoor(bpy.types.PropertyGroup):
|
||||
"""Property group for door gizmo visibility settings."""
|
||||
|
||||
overall_height: BoolProperty(name="Overall Height", default=True)
|
||||
overall_width: BoolProperty(name="Overall Width", default=True)
|
||||
threshold_thickness: BoolProperty(name="Threshold Thickness", default=True)
|
||||
threshold_depth: BoolProperty(name="Threshold Depth", default=True)
|
||||
lining_depth: BoolProperty(name="Lining Depth", default=True)
|
||||
lining_thickness: BoolProperty(name="Lining Thickness", default=True)
|
||||
transom_offset: BoolProperty(name="Transom Offset", default=True)
|
||||
transom_thickness: BoolProperty(name="Transom Thickness", default=True)
|
||||
swing_arc: BoolProperty(name="Swing Arc", default=True, description="Show door swing direction arc")
|
||||
flip_arc: BoolProperty(name="Flip Arc", default=True, description="Show flip door orientation arc")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
overall_height: bool
|
||||
overall_width: bool
|
||||
threshold_thickness: bool
|
||||
threshold_depth: bool
|
||||
lining_depth: bool
|
||||
lining_thickness: bool
|
||||
transom_offset: bool
|
||||
transom_thickness: bool
|
||||
swing_arc: bool
|
||||
flip_arc: bool
|
||||
|
||||
|
||||
class GizmoPreferences(bpy.types.PropertyGroup):
|
||||
"""Property group for all gizmo visibility settings."""
|
||||
|
||||
draw_gizmos_in_3d_viewport: BoolProperty(
|
||||
name="Draw Gizmos In 3D Viewport",
|
||||
default=True,
|
||||
description="Show interactive gizmos in the 3D viewport for parametric elements",
|
||||
)
|
||||
door: bpy.props.PointerProperty(type=GizmoPreferencesDoor)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
draw_gizmos_in_3d_viewport: bool
|
||||
door: GizmoPreferencesDoor
|
||||
|
||||
|
||||
class DocPreferences(bpy.types.PropertyGroup):
|
||||
sheets_dir: StringProperty(
|
||||
default=os.path.join("sheets") + os.path.sep,
|
||||
@@ -320,6 +376,14 @@ class DocPreferences(bpy.types.PropertyGroup):
|
||||
classes_no_cut: str
|
||||
|
||||
|
||||
class DefaultParameters(bpy.types.PropertyGroup):
|
||||
door: bpy.props.PointerProperty(type=BIMDoorProperties)
|
||||
window: bpy.props.PointerProperty(type=BIMWindowProperties)
|
||||
railing: bpy.props.PointerProperty(type=BIMRailingProperties)
|
||||
roof: bpy.props.PointerProperty(type=BIMRoofProperties)
|
||||
stair: bpy.props.PointerProperty(type=BIMStairProperties)
|
||||
|
||||
|
||||
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
bl_idname = tool.Blender.get_blender_addon_package_name()
|
||||
svg2pdf_command: StringProperty(
|
||||
@@ -493,6 +557,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
name="Occurrence Name Function",
|
||||
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
|
||||
)
|
||||
gizmos: bpy.props.PointerProperty(type=GizmoPreferences)
|
||||
|
||||
def update_data_dir(self, context: bpy.types.Context) -> None:
|
||||
import bonsai.bim.schema
|
||||
@@ -523,6 +588,11 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
doc: bpy.props.PointerProperty(type=DocPreferences)
|
||||
default_parameters: bpy.props.PointerProperty(
|
||||
type=DefaultParameters,
|
||||
name="Default Parameters",
|
||||
description="Default parameters for BIM elements",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
svg2pdf_command: str
|
||||
@@ -553,10 +623,12 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
should_always_cache: bool
|
||||
occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"]
|
||||
occurrence_name_function: str
|
||||
gizmos: GizmoPreferences
|
||||
data_dir: str
|
||||
cache_dir: str
|
||||
pset_dir: str
|
||||
doc: DocPreferences
|
||||
default_parameters: DefaultParameters
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
layout = self.layout
|
||||
@@ -599,10 +671,34 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.prop(self, "should_use_snap")
|
||||
layout.prop(self, "should_play_chaching_sound")
|
||||
|
||||
layout.prop(self.gizmos, "draw_gizmos_in_3d_viewport")
|
||||
if self.gizmos.draw_gizmos_in_3d_viewport:
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
layout,
|
||||
context,
|
||||
"Gizmos Parameters",
|
||||
self.draw_gizmo_parameters,
|
||||
)
|
||||
|
||||
def draw_gizmo_parameters(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
box = layout.box()
|
||||
bonsai.bim.helper.draw_expandable_panel(box, context, "Parametric Door", self.draw_door_gizmo_parameters)
|
||||
|
||||
def draw_door_gizmo_parameters(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
door_gizmos = self.gizmos.door
|
||||
for prop in door_gizmos.__annotations__:
|
||||
layout.prop(door_gizmos, prop)
|
||||
|
||||
def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "occurrence_name_style")
|
||||
if self.occurrence_name_style == "CUSTOM":
|
||||
layout.prop(self, "occurrence_name_function")
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
layout,
|
||||
context,
|
||||
"Default Parameters",
|
||||
self.draw_default_parameters,
|
||||
)
|
||||
|
||||
def draw_directories(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "data_dir")
|
||||
@@ -639,6 +735,39 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
layout.row().prop(self, "decorator_color_error")
|
||||
layout.row().prop(self, "decorator_color_background")
|
||||
|
||||
def draw_default_parameters(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
box = layout.box()
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
box,
|
||||
context,
|
||||
"Door",
|
||||
lambda _layout, _context: draw_door_properties(_layout, self.default_parameters.door),
|
||||
)
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
box,
|
||||
context,
|
||||
"Window",
|
||||
lambda _layout, _context: draw_window_properties(_layout, self.default_parameters.window),
|
||||
)
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
box,
|
||||
context,
|
||||
"Railing",
|
||||
lambda _layout, _context: draw_railing_properties(_layout, self.default_parameters.railing),
|
||||
)
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
box,
|
||||
context,
|
||||
"Roof",
|
||||
lambda _layout, _context: draw_roof_properties(_layout, self.default_parameters.roof),
|
||||
)
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
box,
|
||||
context,
|
||||
"Stair",
|
||||
lambda _layout, _context: draw_stair_properties(_layout, self.default_parameters.stair),
|
||||
)
|
||||
|
||||
def draw_other_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
layout.prop(self, "opening_focus_opacity")
|
||||
layout.prop(self, "should_disable_undo_on_save")
|
||||
|
||||
@@ -1625,7 +1625,12 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
xmin, xmax, ymin, ymax = helper.ortho_view_frame(camera.data)[:4]
|
||||
rl = ifcopenshell.util.placement.get_local_placement(storey.ObjectPlacement)[2][3]
|
||||
y = (camera.matrix_world.inverted() @ Vector((0.0, 0.0, rl))).y
|
||||
|
||||
# Convert RL from project units (feet) to meters for Blender world space
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl_meters = rl * unit_scale
|
||||
|
||||
y = (camera.matrix_world.inverted() @ Vector((0.0, 0.0, rl_meters))).y
|
||||
if y < ymin or y > ymax:
|
||||
return
|
||||
|
||||
|
||||
@@ -2096,6 +2096,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
decomposition_relationships = tool.Root.get_decomposition_relationships(objects_to_duplicate)
|
||||
connection_relationships = tool.Root.get_connection_relationships(objects_to_duplicate)
|
||||
old_to_new: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = {}
|
||||
old_obj_name_to_new_obj_name: dict[str, str] = {}
|
||||
|
||||
for obj in objects_to_duplicate:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -2147,6 +2148,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
old_obj_name_to_new_obj_name[obj.name] = new_obj.name
|
||||
|
||||
if not element:
|
||||
continue
|
||||
@@ -2179,6 +2181,19 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
if new.is_a("IfcRelSpaceBoundary"):
|
||||
tool.Boundary.decorate_boundary(new_obj)
|
||||
|
||||
# Remap Blender parent relationships for duplicated objects
|
||||
for old_obj_name, new_obj_name in old_obj_name_to_new_obj_name.items():
|
||||
new_obj = bpy.data.objects.get(new_obj_name)
|
||||
if new_obj and new_obj.parent and new_obj.parent.name in old_obj_name_to_new_obj_name:
|
||||
# Store world matrix before reparenting to preserve transform
|
||||
world_matrix = new_obj.matrix_world.copy()
|
||||
new_parent_name = old_obj_name_to_new_obj_name[new_obj.parent.name]
|
||||
new_parent = bpy.data.objects.get(new_parent_name)
|
||||
if new_parent:
|
||||
new_obj.parent = new_parent
|
||||
# Restore world transform by setting matrix_world
|
||||
new_obj.matrix_world = world_matrix
|
||||
|
||||
# Recreate aggregate relationship
|
||||
for old in old_to_new.keys():
|
||||
if old.is_a("IfcElementAssembly"):
|
||||
|
||||
@@ -110,12 +110,26 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
oprops = tool.Blender.get_object_bim_props(obj)
|
||||
if not oprops.location_checksum:
|
||||
return True # Let's be conservative
|
||||
loc_check = np.frombuffer(eval(oprops.location_checksum), dtype=np.float32)
|
||||
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||
|
||||
# Handle both old float64 and new float32 checksums for version compatibility
|
||||
loc_checksum_bytes = eval(oprops.location_checksum)
|
||||
if len(loc_checksum_bytes) == 24: # Old format: 3 * 8 bytes (float64)
|
||||
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float64).astype(np.float32)
|
||||
else: # New format: 3 * 4 bytes (float32)
|
||||
loc_check = np.frombuffer(loc_checksum_bytes, dtype=np.float32)
|
||||
|
||||
loc_real = np.array(obj.matrix_world.translation, dtype=np.float32).flatten()
|
||||
if not np.allclose(loc_check, loc_real, atol=1e-4): # 0.1 mm
|
||||
return True
|
||||
rot_check = np.frombuffer(eval(oprops.rotation_checksum), dtype=np.float32).reshape(3, 3)
|
||||
rot_real = np.array(obj.matrix_world.to_3x3())
|
||||
|
||||
# Handle both old float64 and new float32 checksums for version compatibility
|
||||
rot_checksum_bytes = eval(oprops.rotation_checksum)
|
||||
if len(rot_checksum_bytes) == 72: # Old format: 9 * 8 bytes (float64)
|
||||
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float64).astype(np.float32).reshape(3, 3)
|
||||
else: # New format: 9 * 4 bytes (float32)
|
||||
rot_check = np.frombuffer(rot_checksum_bytes, dtype=np.float32).reshape(3, 3)
|
||||
|
||||
rot_real = np.array(obj.matrix_world.to_3x3(), dtype=np.float32)
|
||||
rot_dot = np.dot(rot_check, rot_real.T)
|
||||
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
|
||||
if angle_rad > 0.0017453292519943296: # 0.1 degrees
|
||||
|
||||
@@ -172,11 +172,11 @@ class Polyline(bonsai.core.tool.Polyline):
|
||||
angle = tool.Cad.angle_3_vectors(
|
||||
second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True
|
||||
)
|
||||
angle_round_threshold = 1000 # Avoids rounding when distance is too big
|
||||
angle_round_threshold = 1000 # Avoids rounding when distance is too big
|
||||
|
||||
# Round angle to the nearest 0.05
|
||||
angle = round(angle / 0.05) * 0.05 if distance < angle_round_threshold else angle
|
||||
|
||||
|
||||
orientation_angle = tool.Cad.angle_3_vectors(
|
||||
world_second_to_last_point, last_point, mouse_vector, new_angle=None, degrees=True
|
||||
)
|
||||
|
||||
@@ -181,7 +181,9 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
view_vector = tool.Cad.region_2d_to_vector_3d_np(region, rv3d, mouse_pos)
|
||||
ray_origin = tool.Cad.region_2d_to_origin_3d_np(region, rv3d, mouse_pos, clamp=10) # TODO clamp is hardcoded but might be necessary to adapt
|
||||
ray_origin = tool.Cad.region_2d_to_origin_3d_np(
|
||||
region, rv3d, mouse_pos, clamp=10
|
||||
) # TODO clamp is hardcoded but might be necessary to adapt
|
||||
|
||||
ray_target = ray_origin + view_vector
|
||||
ray_direction = ray_target - ray_origin
|
||||
|
||||
@@ -496,7 +496,9 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
new.long_name = element.LongName or ""
|
||||
if not element.is_a("IfcProject"):
|
||||
elevation = ifcopenshell.util.placement.get_storey_elevation(element)
|
||||
new["elevation"] = tool.Unit.format_value(elevation)
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
elevation_in_meters = elevation * unit_scale
|
||||
new.elevation = tool.Unit.format_distance(elevation_in_meters)
|
||||
new.is_expanded = element.id() not in cls.contracted_containers
|
||||
new.level_index = level_index
|
||||
children = ifcopenshell.util.element.get_parts(element)
|
||||
|
||||
@@ -24,15 +24,274 @@ import ifcopenshell
|
||||
import bonsai.bim.helper
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from lark import Lark, Transformer
|
||||
from typing import Union, Literal, Any, TYPE_CHECKING, assert_never
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.unit.prop import BIMUnitProperties
|
||||
|
||||
|
||||
def parse_distance_string(input_string: str, use_project_unit: bool = True) -> tuple[bool, float]:
|
||||
"""
|
||||
Parse a distance string with optional unit suffixes and convert to meters.
|
||||
|
||||
This function parses distance inputs with units (e.g., "5m", "10ft", "3.5cm")
|
||||
and converts them to meters (SI units) for use in IFC models.
|
||||
|
||||
Supports:
|
||||
- Metric units: mm, cm, dm, m
|
||||
- Imperial units: ft/feet ('), in/inches (")
|
||||
- Arithmetic expressions: +, -, *, /
|
||||
- Fractions for imperial units (e.g., 1/2")
|
||||
- Formula mode: values starting with "="
|
||||
|
||||
:param input_string: The string to parse (e.g., "5m", "10ft", "10'6\"", "3.5cm", "12in")
|
||||
:param use_project_unit: If True, uses project unit scale; if False, uses Blender unit scale
|
||||
:return: Tuple (is_valid, value_in_meters) where is_valid indicates successful parsing
|
||||
and value_in_meters is the converted value in meters
|
||||
|
||||
Examples:
|
||||
>>> parse_distance_string("5m")
|
||||
(True, 5.0)
|
||||
>>> parse_distance_string("30cm")
|
||||
(True, 0.3)
|
||||
>>> parse_distance_string("10ft")
|
||||
(True, 3.048)
|
||||
>>> parse_distance_string("12in")
|
||||
(True, 0.3048)
|
||||
>>> parse_distance_string("5'6\"")
|
||||
(True, 1.6764)
|
||||
>>> parse_distance_string("invalid")
|
||||
(False, 0.0)
|
||||
"""
|
||||
|
||||
grammar_imperial = r"""
|
||||
start: (FORMULA dim expr) | dim
|
||||
dim: imperial
|
||||
|
||||
FORMULA: "="
|
||||
|
||||
imperial: feet_inches | feet_only | inches_only | plain_number
|
||||
feet_only: NUMBER (FEET_SYM | FEET_TEXT)
|
||||
inches_only: inch_value (INCH_SYM | INCH_TEXT)
|
||||
feet_inches: NUMBER (FEET_SYM | FEET_TEXT) DASH? inch_value (INCH_SYM | INCH_TEXT)?
|
||||
plain_number: NUMBER
|
||||
|
||||
inch_value: NUMBER fraction | fraction | NUMBER
|
||||
|
||||
fraction: NUMBER "/" NUMBER
|
||||
|
||||
expr: (ADD | SUB) dim | (MUL | DIV) NUMBER
|
||||
|
||||
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
||||
FEET_SYM: "'"
|
||||
FEET_TEXT: "ft"
|
||||
INCH_SYM: "\""
|
||||
INCH_TEXT: "in"
|
||||
DASH: "-"
|
||||
ADD: "+"
|
||||
SUB: "-"
|
||||
MUL: "*"
|
||||
DIV: "/"
|
||||
|
||||
%ignore " "
|
||||
"""
|
||||
|
||||
grammar_metric = r"""
|
||||
start: FORMULA? dim expr?
|
||||
dim: metric
|
||||
|
||||
FORMULA: "="
|
||||
|
||||
metric: NUMBER (MM | CM | DM | M | DEG)?
|
||||
|
||||
expr: (ADD | SUB | MUL | DIV) dim
|
||||
|
||||
NUMBER: /-?(?:\d+\.?\d*|\.\d+)/
|
||||
MM: "mm"
|
||||
CM: "cm"
|
||||
DM: "dm"
|
||||
M: "m"
|
||||
DEG: "°"
|
||||
ADD: "+"
|
||||
SUB: "-"
|
||||
MUL: "*"
|
||||
DIV: "/"
|
||||
|
||||
%ignore " "
|
||||
"""
|
||||
|
||||
class InputTransform(Transformer):
|
||||
def NUMBER(self, n):
|
||||
return float(n)
|
||||
|
||||
def fraction(self, numbers):
|
||||
return numbers[0] / numbers[1]
|
||||
|
||||
def inch_value(self, args):
|
||||
# Can be: NUMBER fraction, fraction, or NUMBER
|
||||
if len(args) == 2:
|
||||
# NUMBER fraction (e.g., "9 1/64")
|
||||
return args[0] + args[1]
|
||||
else:
|
||||
# Just fraction or just NUMBER
|
||||
return args[0]
|
||||
|
||||
def plain_number(self, args):
|
||||
# A plain number in imperial context is assumed to be feet
|
||||
feet = args[0]
|
||||
# Convert feet to meters (1 foot = 0.3048 meters)
|
||||
return feet * 0.3048
|
||||
|
||||
def feet_only(self, args):
|
||||
# args[0] is the number of feet, args[1] is the unit token (we can ignore it)
|
||||
feet = args[0]
|
||||
# Convert feet to meters (1 foot = 0.3048 meters)
|
||||
return feet * 0.3048
|
||||
|
||||
def inches_only(self, args):
|
||||
# args[0] is the inch_value, args[1] is the unit token
|
||||
inches = args[0]
|
||||
# Convert inches to meters (1 inch = 0.0254 meters)
|
||||
return inches * 0.0254
|
||||
|
||||
def feet_inches(self, args):
|
||||
# Extract feet and inches values
|
||||
feet = args[0]
|
||||
# Find the inch_value (it's a number, not a token)
|
||||
inches = None
|
||||
for arg in args[1:]:
|
||||
if isinstance(arg, (int, float)):
|
||||
inches = arg
|
||||
break
|
||||
if inches is None:
|
||||
inches = 0
|
||||
# Convert to meters
|
||||
total_meters = (feet * 0.3048) + (inches * 0.0254)
|
||||
return total_meters
|
||||
|
||||
def imperial(self, args):
|
||||
# Just return the value from the sub-rule (feet_only, inches_only, or feet_inches)
|
||||
return args[0]
|
||||
|
||||
def metric(self, args):
|
||||
# args[0] is the NUMBER, args[1] if present is the unit
|
||||
value = args[0]
|
||||
if len(args) > 1:
|
||||
unit = str(args[1])
|
||||
# Convert to meters based on unit
|
||||
if unit == "mm":
|
||||
value = value / 1000.0
|
||||
elif unit == "cm":
|
||||
value = value / 100.0
|
||||
elif unit == "dm":
|
||||
value = value / 10.0
|
||||
elif unit == "m":
|
||||
value = value # already in meters
|
||||
elif unit == "°":
|
||||
value = value # degrees, pass through
|
||||
# If no unit specified, assume it's already in the project's unit system
|
||||
return value
|
||||
|
||||
def dim(self, args):
|
||||
return args[0]
|
||||
|
||||
def expr(self, args):
|
||||
op = args[0]
|
||||
value = float(args[1])
|
||||
if op == "+":
|
||||
return lambda x: x + value
|
||||
elif op == "-":
|
||||
return lambda x: x - value
|
||||
elif op == "*":
|
||||
return lambda x: x * value
|
||||
elif op == "/":
|
||||
return lambda x: x / value
|
||||
|
||||
def FORMULA(self, args):
|
||||
return args[0]
|
||||
|
||||
def start(self, args):
|
||||
i = 0
|
||||
if args[0] == "=":
|
||||
i += 1
|
||||
else:
|
||||
if len(args) > 1:
|
||||
raise ValueError("Invalid input.")
|
||||
dimension = args[i]
|
||||
if len(args) > i + 1:
|
||||
expression = args[i + 1]
|
||||
return expression(dimension)
|
||||
else:
|
||||
return dimension
|
||||
|
||||
try:
|
||||
# Determine unit scale
|
||||
if use_project_unit and tool.Ifc.get():
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
else:
|
||||
unit_scale = tool.Blender.get_unit_scale()
|
||||
|
||||
# Try to parse with the project's default grammar first
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
primary_parser = Lark(grammar_imperial)
|
||||
fallback_parser = Lark(grammar_metric)
|
||||
else:
|
||||
primary_parser = Lark(grammar_metric)
|
||||
fallback_parser = Lark(grammar_imperial)
|
||||
|
||||
# Try parsing with primary grammar
|
||||
parse_tree = None
|
||||
try:
|
||||
parse_tree = primary_parser.parse(input_string)
|
||||
except Exception as e:
|
||||
# If primary fails, try fallback grammar (allows metric in imperial projects and vice versa)
|
||||
try:
|
||||
parse_tree = fallback_parser.parse(input_string)
|
||||
except Exception as e2:
|
||||
pass
|
||||
|
||||
if parse_tree is None:
|
||||
return False, 0.0
|
||||
|
||||
# Transform the parse tree to get the numeric result
|
||||
transformer = InputTransform()
|
||||
result = transformer.transform(parse_tree)
|
||||
result = round(result, 6)
|
||||
|
||||
return True, result
|
||||
|
||||
except Exception as e:
|
||||
return False, 0.0
|
||||
|
||||
|
||||
class Unit(bonsai.core.tool.Unit):
|
||||
UNIT_TYPE = Literal["LENGTHUNIT", "AREAUNIT", "VOLUMEUNIT"]
|
||||
|
||||
@staticmethod
|
||||
def format_distance(meters: float, use_imperial: bool = None, **kwargs) -> str:
|
||||
"""
|
||||
Format a distance value in meters to a string in the project's unit system.
|
||||
|
||||
:param meters: The distance value in meters
|
||||
:param use_imperial: If True, format as imperial; if False, format as metric; if None, auto-detect from scene
|
||||
:param kwargs: Additional arguments to pass to the underlying format_distance function
|
||||
(hide_units, precision, decimal_places, etc.)
|
||||
:return: Formatted string with units
|
||||
"""
|
||||
# Import the comprehensive format_distance from the helper module
|
||||
from bonsai.bim.module.drawing import helper
|
||||
|
||||
# The comprehensive function expects value in scene units, not meters
|
||||
# So we pass meters directly since it handles unit conversion internally
|
||||
return helper.format_distance(
|
||||
meters,
|
||||
hide_units=kwargs.get("hide_units", False),
|
||||
precision=kwargs.get("precision"),
|
||||
decimal_places=kwargs.get("decimal_places"),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_unit_props(cls) -> BIMUnitProperties:
|
||||
return bpy.context.scene.BIMUnitProperties
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
from .type_hints import *
|
||||
from typing import Optional, Literal
|
||||
from datetime import datetime
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
from typing import Literal, Annotated
|
||||
from datetime import datetime
|
||||
from pydantic import BeforeValidator
|
||||
from typing import Literal
|
||||
|
||||
|
||||
COUNTRY_CODE = Literal[
|
||||
|
||||
@@ -26,11 +26,11 @@ target_compile_features(IfcParseExamples PUBLIC cxx_std_17)
|
||||
if (WITH_OPENCASCADE)
|
||||
|
||||
ADD_EXECUTABLE(IfcOpenHouse IfcOpenHouse.cpp)
|
||||
TARGET_LINK_LIBRARIES(IfcOpenHouse ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(IfcOpenHouse ${IFCOPENSHELL_LIBRARIES} ${OpenCASCADE_LIBRARIES})
|
||||
set_target_properties(IfcOpenHouse PROPERTIES FOLDER Examples)
|
||||
|
||||
ADD_EXECUTABLE(IfcAdvancedHouse IfcAdvancedHouse.cpp)
|
||||
TARGET_LINK_LIBRARIES(IfcAdvancedHouse ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(IfcAdvancedHouse ${IFCOPENSHELL_LIBRARIES} ${OpenCASCADE_LIBRARIES})
|
||||
set_target_properties(IfcAdvancedHouse PROPERTIES FOLDER Examples)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity
|
||||
1,E.01,Walls,m3,,
|
||||
2,E.01.01,Ground floor walls,m3,100,42
|
||||
2,E.01.02,First floor walls,m3,200,35
|
||||
1,A.02,Paintings,m2,,
|
||||
2,A.03,Paintings with water,m2,,
|
||||
3,B.05,White paintings,m2,25,45
|
||||
3,B.06,Colored paintings,m2,32,33
|
||||
2,C-01,Paintings with machine,m2,17,133
|
||||
2,C-02,Decorated paintings,m2,40,8
|
||||
|
@@ -0,0 +1,7 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity
|
||||
1,A,Group A,,,
|
||||
2,A.02,Paintings,m2,20,1
|
||||
2,A.03,Paintings with water,m2,23,1
|
||||
1,C,Group C,,,
|
||||
2,C-01,Paintings with machine,m2,32,1
|
||||
2,C-02,Decorated paintings,m2,40,2
|
||||
|
@@ -0,0 +1,10 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity,Query,Property
|
||||
1,E.01,Walls,m3,,,,
|
||||
2,E.01.01,Ground floor walls,m3,100,,"IfcWall, location=""Ground Floor""",GrossVolume
|
||||
2,E.01.02,First floor walls,m3,200,,"IfcWall, location=""First Floor""",GrossVolume
|
||||
1,A.02,Paintings,m2,,,,
|
||||
2,A.03,Paintings with water,m2,,,,
|
||||
3,B.05,White paintings,m2,25,45,,
|
||||
3,B.06,Colored paintings,m2,32,33,,
|
||||
2,C-01,Paintings with machine,m2,17,133,,
|
||||
2,C-02,Decorated paintings,m2,40,8,,
|
||||
|
@@ -0,0 +1,10 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity,Description
|
||||
1,E.01,Walls,m3,,,
|
||||
2,E.01.01,Ground floor walls,m3,100,42,"Semi-solid blocks of plain-faced common brick, with an apparent density (excluding holes) of 800 kg/m³; minor drilling 45%; apparent thermal conductivity 0.21 W/mK; characteristic mechanical strength parallel to the holes greater than or equal to 10 N/mm2, perpendicular to the holes greater than or equal to 2N/mm2"
|
||||
2,E.01.02,First floor walls,m3,200,35,"Semi-solid blocks of plain-faced common brick, with an apparent density (excluding holes) of 800 kg/m³; minor drilling 45%; apparent thermal conductivity 0.21 W/mK; characteristic mechanical strength parallel to the holes greater than or equal to 10 N/mm2, perpendicular to the holes greater than or equal to 2N/mm2"
|
||||
1,A.02,Painting,m2,,,"Painting with washable water-based wall paint for indoor/outdoor. The price includes and compensates the costs for the supply of paint, any scaffolding up to a maximum height of 4 m from the support surface, the costs for the protection of furniture, fixed systems or the protection of floors, the cleaning of the surfaces to be treated through the use of rags or net purposes in order to remove residues that can be easily removed. The cost of occasional and partial grouting of surfaces, in order to eliminate any small scratches, including sanding of the grouted parts, is also to be considered included and compensated. For 2 coats with brush or roller."
|
||||
2,A.03,Washable painting,m2,,,"Supply and installation of washable tempera paint for interiors and exteriors. The price includes and compensates for the costs of supplying the paint, any scaffolding up to a maximum height of 4 meters from the support surface, the costs of protecting furnishings, fixed installations, or floors, and cleaning the surfaces to be treated using rags or clean brushes to remove easily removable residues. On previously prepared plaster. Apply two coats with a brush or roller. (Tempera colors from the color chart)."
|
||||
3,B.05,White paintings,m2,25,45,
|
||||
3,B.06,Colored paintings,m2,32,33,
|
||||
2,C-01,External painting,m2,17,133,"Painting with plastic coating. The price includes and compensates for the costs of supplying the paint, any scaffolding up to a maximum height of 4 meters from the support surface, the costs of protecting furnishings, fixed systems, or floors, and cleaning the surfaces to be treated using rags or clean brushes to remove easily removable residues. On already prepared plaster. For 2 coats (interior textured finish)."
|
||||
2,C-02,Decorated paintings,m2,40,8,
|
||||
|
@@ -0,0 +1,7 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity,Description
|
||||
1,A,Group A,,,,
|
||||
2,A.02,Paintings,m2,20,1,Paint made by the best painter in the world
|
||||
2,A.03,Paintings with water,m2,23,1,
|
||||
1,C,Group C,,,,
|
||||
2,C-01,Paintings with machine,m2,32,1,Best painting in the world painted with the best painted machine accordingly with ISO9001
|
||||
2,C-02,Decorated paintings,m2,40,2,
|
||||
|
@@ -0,0 +1,10 @@
|
||||
Index,Identification,Name,Unit,Material,Labor,Quantity
|
||||
1,E.01,Walls,m3,,,
|
||||
2,E.01.01,Ground floor walls,m3,55,45,42
|
||||
2,E.01.02,First floor walls,m3,120,80,35
|
||||
1,A.02,Painting,m2,,,
|
||||
2,A.03,Washable painting,m2,,,
|
||||
3,B.05,White paintings,m2,,,45
|
||||
3,B.06,Colored paintings,m2,,,33
|
||||
2,C-01,External painting,m2,,,133
|
||||
2,C-02,Decorated paintings,m2,,,8
|
||||
|
@@ -0,0 +1,10 @@
|
||||
Index,Identification,Name,Unit,Value,Quantity,RateSchedule,RateID
|
||||
1,E.01,Walls,m3,,,,
|
||||
2,E.01.01,Ground floor walls,m3,100,42,,
|
||||
2,E.01.02,First floor walls,m3,200,35,,
|
||||
1,A.02,Paintings,m2,,,,
|
||||
2,A.03,Paintings with water,m2,,,,
|
||||
3,B.05,White paintings,m2,,45,Ex2 - SoR,A.03
|
||||
3,B.06,Colored paintings,m2,32,33,,
|
||||
2,C-01,Paintings with machine,m2,17,133,,
|
||||
2,C-02,Decorated paintings,m2,40,8,,
|
||||
|
+8
-1
@@ -32,6 +32,13 @@ csv2ifc.execute()
|
||||
```
|
||||
|
||||
See example files as a CSV file format reference:
|
||||
- Ex1 - BoQ without query.csv (a simple Bill of Quantities)
|
||||
- Ex2 - SoR.csv (a simple Schedule of Rates)
|
||||
- Ex3 - BoQ with query.csv (a simple BoQ with the query function)
|
||||
- Ex4 - BoQ with description.csv (a simple BoQ with description column)
|
||||
- Ex5 - SoR_with_description.csv (a simple SoR with description column)
|
||||
- Ex6 - BoQ with categories.csv (a simple BoQ with categories columns)
|
||||
- Ex7 - BoQ with Rates.csv (a simple BoQ that connect to an existing SoR. It needs an already loaded SoR.)
|
||||
- `sample_cost_schedule_house_FR.csv` / `.ods`
|
||||
- `schedule.csv`, `rates.csv` (schedule of rates example)
|
||||
|
||||
@@ -124,4 +131,4 @@ path = "directory/cost_schedule"
|
||||
writer = Ifc5DOdsWriter(file=file, output=path)
|
||||
writer.write()
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# IfcConvert
|
||||
if(WITH_RELATIONSHIP_VALIDATION)
|
||||
file(GLOB IFCCONVERT_CPP_FILES *.cpp)
|
||||
file(GLOB IFCCONVERT_H_FILES *.h)
|
||||
else()
|
||||
file(GLOB IFCCONVERT_CPP_FILES IfcConvert.cpp)
|
||||
file(GLOB IFCCONVERT_H_FILES)
|
||||
endif()
|
||||
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
add_executable(IfcConvert ${IFCCONVERT_FILES})
|
||||
|
||||
target_link_libraries(IfcConvert IfcGeom IfcParse Serializers ${OpenCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
if(WITH_RELATIONSHIP_VALIDATION)
|
||||
set_property(TARGET IfcConvert APPEND_STRING PROPERTY COMPILE_FLAGS " -DWITH_RELATIONSHIP_VALIDATION")
|
||||
endif()
|
||||
|
||||
install(TARGETS IfcConvert)
|
||||
@@ -50,10 +50,6 @@
|
||||
|
||||
#include "../ifcparse/utils.h"
|
||||
|
||||
#ifdef IFOPSH_WITH_CITYJSON
|
||||
#include "./cityjson/geobim.h"
|
||||
#endif
|
||||
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
|
||||
#include <Standard_Version.hxx>
|
||||
@@ -136,9 +132,6 @@ void print_usage(bool suggest_help = true)
|
||||
<< " .svg SVG Scalable Vector Graphics (2D floor plan)\n"
|
||||
#ifdef WITH_HDF5
|
||||
<< " .h5 HDF Hierarchical Data Format storing positions, normals and indices\n"
|
||||
#endif
|
||||
#ifdef IFOPSH_WITH_CITYJSON
|
||||
<< " .cityjson City JSON format for geospatial data\n"
|
||||
#endif
|
||||
<< " .ttl TTL/WKT RDF Turtle with Well-Known-Text geometry\n"
|
||||
<< " .ifc IFC-SPF Industry Foundation Classes\n"
|
||||
@@ -661,7 +654,6 @@ int main(int argc, char** argv) {
|
||||
JSON = IfcUtil::path::from_utf8(".json"),
|
||||
// @todo this is just temporary as it doesn't make sense to require an extension for a DB
|
||||
RDB = IfcUtil::path::from_utf8(".rdb"),
|
||||
CITY_JSON = IfcUtil::path::from_utf8(".cityjson"),
|
||||
IFC = IfcUtil::path::from_utf8(".ifc"),
|
||||
USD = IfcUtil::path::from_utf8(".usd"),
|
||||
USDA = IfcUtil::path::from_utf8(".usda"),
|
||||
@@ -755,84 +747,6 @@ int main(int argc, char** argv) {
|
||||
return exit_code;
|
||||
}
|
||||
#endif
|
||||
#ifdef IFOPSH_WITH_CITYJSON
|
||||
else if (output_extension == CITY_JSON || (output_extension == OBJ || output_extension == DAE || output_extension == GLB) && vmap.count("exterior-only") && exterior_only_algo != "none") {
|
||||
|
||||
// none, convex-decomposition, minkowski-triangles or halfspace-snapping
|
||||
boost::to_lower(exterior_only_algo);
|
||||
|
||||
if (exterior_only_algo == "halfspace-snapping") {
|
||||
cerr_ << "[Error] halfspace-snapping not implemented yet" << std::endl;
|
||||
print_usage();
|
||||
return EXIT_FAILURE;
|
||||
} else if (exterior_only_algo == "minkowski-triangles") {
|
||||
//
|
||||
} else if (exterior_only_algo == "convex-decomposition") {
|
||||
//
|
||||
} else if (exterior_only_algo == "none") {
|
||||
//
|
||||
} else {
|
||||
cerr_ << "[Error] --exterior-only should be convex-decomposition|minkowski-triangles|halfspace-snapping" << std::endl;
|
||||
print_usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
geobim_settings settings;
|
||||
settings.input_filenames = { IfcUtil::path::to_utf8(input_filename) };
|
||||
settings.file = { new IfcParse::IfcFile(IfcUtil::path::to_utf8(input_filename)) };
|
||||
|
||||
/*
|
||||
// No longer set, because we pass to real serializers now, awaiting a proper iterator adaptor
|
||||
if (output_extension == OBJ) {
|
||||
settings.obj_output_filename = IfcUtil::path::to_utf8(output_filename);
|
||||
}
|
||||
*/
|
||||
|
||||
if (output_extension == CITY_JSON) {
|
||||
// we don't have a cityjson serializer though
|
||||
settings.cityjson_output_filename = IfcUtil::path::to_utf8(output_filename);
|
||||
}
|
||||
|
||||
// @todo
|
||||
settings.radii = { "0.05" };
|
||||
settings.apply_openings = false;
|
||||
settings.apply_openings_posthoc = true;
|
||||
settings.debug = false;
|
||||
settings.exact_segmentation = true;
|
||||
settings.minkowski_triangles = exterior_only_algo == "minkowski-triangles";
|
||||
settings.no_erosion = false;
|
||||
settings.spherical_padding = false;
|
||||
if (num_threads != 1) {
|
||||
settings.threads = num_threads;
|
||||
}
|
||||
|
||||
settings.settings.get<ifcopenshell::geometry::settings::UseWorldCoords>().value = false;
|
||||
settings.settings.get<ifcopenshell::geometry::settings::WeldVertices>().value = false;
|
||||
settings.settings.get<ifcopenshell::geometry::settings::ReorientShells>().value = true;
|
||||
settings.settings.get<ifcopenshell::geometry::settings::ConvertBackUnits>().value = true;
|
||||
settings.settings.get<ifcopenshell::geometry::settings::IteratorOutput>().value = ifcopenshell::geometry::settings::NATIVE;
|
||||
settings.settings.get<ifcopenshell::geometry::settings::DisableOpeningSubtractions>().value = !settings.apply_openings;
|
||||
|
||||
if (include_filter.type != geom_filter::UNUSED) {
|
||||
settings.entity_names = include_filter.values;
|
||||
settings.entity_names_included = true;
|
||||
} else if (exclude_filter.type != geom_filter::UNUSED) {
|
||||
settings.entity_names = exclude_filter.values;
|
||||
settings.entity_names_included = false;
|
||||
} else {
|
||||
settings.entity_names = { { "IfcSpace", "IfcOpeningElement" } };
|
||||
settings.entity_names_included = false;
|
||||
}
|
||||
|
||||
elems_from_adaptor.emplace();
|
||||
perform(settings, *elems_from_adaptor);
|
||||
|
||||
if (output_extension == CITY_JSON) {
|
||||
return 0;
|
||||
}
|
||||
// else ... continue on to serialize elems_from_adaptor
|
||||
}
|
||||
#endif
|
||||
|
||||
/// @todo Clean up this filter code further.
|
||||
std::vector<geom_filter> used_filters;
|
||||
|
||||
Submodule src/ifcconvert/cityjson deleted from a9488c8d8f
@@ -0,0 +1,3 @@
|
||||
add_subdirectory(kernels)
|
||||
|
||||
set(kernel_libraries ${kernel_libraries} PARENT_SCOPE)
|
||||
@@ -0,0 +1,42 @@
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
string(TOUPPER ${kernel} KERNEL_UPPER)
|
||||
file(GLOB IFCGEOM_H_FILES ${kernel}/*.h)
|
||||
file(GLOB IFCGEOM_CPP_FILES ${kernel}/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
set(KERNEL_TARGET "geometry_kernel_${kernel}")
|
||||
|
||||
add_library(${KERNEL_TARGET} OBJECT ${IFCGEOM_FILES})
|
||||
set_property(TARGET ${KERNEL_TARGET} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOM_EXPORTS")
|
||||
list(APPEND kernel_libraries ${KERNEL_TARGET})
|
||||
target_link_libraries(${KERNEL_TARGET} ${${KERNEL_UPPER}_LIBRARIES})
|
||||
|
||||
install(TARGETS ${KERNEL_TARGET}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
|
||||
if(${kernel} STREQUAL "cgal")
|
||||
set_property(TARGET ${KERNEL_TARGET} APPEND_STRING PROPERTY COMPILE_FLAGS " -DCGAL_HAS_THREADS")
|
||||
|
||||
set(KERNEL_TARGET_SIMPLE "${KERNEL_TARGET}_simple")
|
||||
add_library(${KERNEL_TARGET_SIMPLE} OBJECT ${IFCGEOM_FILES})
|
||||
set_target_properties(${KERNEL_TARGET_SIMPLE} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
list(APPEND kernel_libraries ${KERNEL_TARGET_SIMPLE})
|
||||
target_link_libraries(${KERNEL_TARGET_SIMPLE} ${${KERNEL_UPPER}_LIBRARIES} Eigen3::Eigen)
|
||||
install(TARGETS ${KERNEL_TARGET_SIMPLE}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
elseif(${kernel} STREQUAL "opencascade")
|
||||
target_link_libraries(${KERNEL_TARGET} ${OpenCASCADE_LIBRARIES})
|
||||
endif()
|
||||
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/kernels/${kernel}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
set(kernel_libraries ${kernel_libraries} PARENT_SCOPE)
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#ifdef IFC_SHARED_BUILD
|
||||
#ifdef _WIN32
|
||||
#ifdef IFC_GEOMLIBRARY_EXPORTS
|
||||
#ifdef IFC_GEOM_EXPORTS
|
||||
#define IFC_GEOMLIBRARY_API __declspec(dllexport)
|
||||
#else
|
||||
#define IFC_GEOMLIBRARY_API __declspec(dllimport)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
find_package(Boost COMPONENTS chrono REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
file(GLOB CPP_FILES *.cpp)
|
||||
file(GLOB H_FILES *.h)
|
||||
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
|
||||
set(SOURCE_FILES ${CPP_FILES})
|
||||
add_executable(IfcGeomServer ${SOURCE_FILES})
|
||||
target_link_libraries(IfcGeomServer IfcGeom ${kernel_libraries} ${OPENCASCADE_LIBRARIES})
|
||||
target_link_libraries(IfcGeomServer IfcGeom ${kernel_libraries} ${OpenCASCADE_LIBRARIES})
|
||||
|
||||
install(TARGETS IfcGeomServer)
|
||||
install(TARGETS IfcGeomServer)
|
||||
|
||||
@@ -35,12 +35,12 @@ if(HAS_MAX)
|
||||
|
||||
message(STATUS "Building IFCMax library for Autodesk 3ds Max SDK ${max_year}")
|
||||
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS} ${ICU_INCLUDE_DIR}
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OPENCOLLADA_INCLUDE_DIRS} ${ICU_INCLUDE_DIR}
|
||||
${Boost_INCLUDE_DIRS} ${max_sdk}/include
|
||||
)
|
||||
|
||||
# All recent versions of 3ds Max (2014 and newer) are 64-bit only so assume lib/x64 directory
|
||||
link_directories(${LINK_DIRECTORIES} ${IfcOpenShell_BINARY_DIR} ${OCC_LIBRARY_DIR} ${OPENCOLLADA_LIBRARY_DIR}
|
||||
link_directories(${LINK_DIRECTORIES} ${IfcOpenShell_BINARY_DIR} ${OPENCOLLADA_LIBRARY_DIR}
|
||||
${ICU_LIBRARY_DIR} ${Boost_LIBRARY_DIRS} ${max_sdk}/lib/x64/Release
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ if(HAS_MAX)
|
||||
tessint.lib
|
||||
viewfile.lib
|
||||
zlibdll.lib
|
||||
${OPENCASCADE_LIBRARIES}
|
||||
${OpenCASCADE_LIBRARIES}
|
||||
)
|
||||
|
||||
set_target_properties(IfcMax_${max_year} PROPERTIES SUFFIX ".dli")
|
||||
|
||||
@@ -103,7 +103,13 @@ autoapi_dirs = [
|
||||
# autoapi_dirs = ['../../bcf/bcf', '../../bsdd', '../../ifccsv', '../../ifcdiff', '../../ifcpatch/ifcpatch', '../../ifctester/ifctester']
|
||||
|
||||
# These are auto-generated based on the IFC schema, so exclude them
|
||||
autoapi_ignore = ["*ifcopenshell/express/rules*"]
|
||||
# Temporarily ignore bsdd files until they are packaged properly in the wheel.
|
||||
autoapi_ignore = [
|
||||
"*ifcopenshell/express/rules*",
|
||||
"*bsdd/bsdd_json.py",
|
||||
"*bsdd/type_hints.py",
|
||||
"*bsdd/yml_to_classes.py",
|
||||
]
|
||||
|
||||
# Custom autoapi templates to make it easier to read our docs
|
||||
autoapi_template_dir = "_autoapi_templates"
|
||||
|
||||
@@ -2,8 +2,7 @@ IfcConvert
|
||||
==========
|
||||
|
||||
IfcConvert is a command-line application for converting IFC geometry into file
|
||||
formats such as OBJ, DAE, GLB, STP, IGS, XML, SVG, H5, CityJSON, TTL/WKT, and
|
||||
IFC-SPF itself.
|
||||
formats such as OBJ, DAE, GLB, STP, IGS, XML, SVG, H5, TTL/WKT, and IFC-SPF itself.
|
||||
|
||||
For other formats, you may use other IfcOpenShell utilities as shown in the
|
||||
table below.
|
||||
@@ -13,7 +12,7 @@ table below.
|
||||
+=========================+=========================+======================+
|
||||
| .ifc | .obj, .dae, .glb, .stp, | IfcConvert |
|
||||
| | .igs, .xml, .svg, .h5, | |
|
||||
| | .cityjson, .ttl, .ifc | |
|
||||
| | .ttl, .ifc | |
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .ifc | .dae, .abc, .usd, .obj, | Bonsai_ |
|
||||
| | .ply, .stl, .fbx, .glb, | |
|
||||
@@ -46,8 +45,6 @@ table below.
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .obj | .ifc | Bonsai_ |
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .json (CityJSON) | .ifc | IfcCityJSON_ |
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .xer (Oracle P6) | .ifc | Ifc4D_ |
|
||||
+-------------------------+-------------------------+----------------------+
|
||||
| .xml (Oracle P6) | .ifc | Ifc4D_ |
|
||||
@@ -63,7 +60,6 @@ table below.
|
||||
.. _IfcCSV: ifccsv.html
|
||||
.. _Ifc4D: ifc4d.html
|
||||
.. _Ifc5D: ifc5d.html
|
||||
.. _IfcCityJSON: ifccityjson.html
|
||||
.. _Ifc2JSON: other.html
|
||||
.. _Bonsai: bonsai.html
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ CLI Manual
|
||||
.xml XML Property definitions and decomposition tree
|
||||
.svg SVG Scalable Vector Graphics (2D floor plan)
|
||||
.h5 HDF Hierarchical Data Format storing positions, normals and indices
|
||||
.cityjson City JSON format for geospatial data
|
||||
.ttl TTL/WKT RDF Turtle with Well-Known-Text geometry
|
||||
.ifc IFC-SPF Industry Foundation Classes
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user