mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-07 00:11:36 +00:00
Compare commits
235 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 8647f9c357 | |||
| 300855276f | |||
| 1a8831012b | |||
| 1bc4e82b7d | |||
| fc197c1d32 | |||
| 33cfa9e2b1 | |||
| 9e9d664511 | |||
| 1b9acadd81 | |||
| c5f6d47c44 | |||
| d19b6d72ef | |||
| 919231b096 | |||
| cead5cc75d | |||
| 9396cb226f | |||
| 56a63cab6e | |||
| 449d13bd5f | |||
| 487a4f345d | |||
| d6daa64c48 | |||
| bf0aa247a0 | |||
| a7c8c908fe | |||
| 64686dde5c | |||
| 29ab446f8c | |||
| 51e2e7cd82 | |||
| e879dac4bd | |||
| 145c629d92 | |||
| 3fe9279bb4 | |||
| 428d88f01c | |||
| 3986d4e85b | |||
| 02d35b6047 | |||
| 3cc5931eae | |||
| db5ec77730 | |||
| bec46562f5 | |||
| 1508c4ab05 | |||
| 99ae768ddf | |||
| fb902a04f5 | |||
| e46a99870b | |||
| 3fbba405d4 | |||
| 989415f423 | |||
| 7a3c7704b5 | |||
| 5d7407e2b9 | |||
| 4a5f1b01a1 | |||
| a038fa62ba | |||
| bb1127d815 | |||
| 40cf322fb4 | |||
| f32ccec154 | |||
| dc0b73f46f | |||
| 36cb625c4e | |||
| 89314733ff | |||
| d4e945222d | |||
| fe80620a5d | |||
| 166ddb4799 | |||
| 8e2a310f92 | |||
| 339a0606c0 | |||
| b6f1119eaa | |||
| 139a024d5f | |||
| 9491096485 | |||
| a34e42a519 | |||
| 6a7ecf7f58 | |||
| 96da78c670 | |||
| 9adc8e484a | |||
| e8eb5e4d0a | |||
| 080222ffe6 | |||
| abef655482 | |||
| 85b3f831dc | |||
| d8ece32d62 | |||
| 0df7117d37 | |||
| 91d67e6371 | |||
| 87d1b0637a | |||
| b8adddc648 | |||
| 25d3edc537 | |||
| 7f8cb80281 | |||
| a2ce2ea23a | |||
| 7f7aef0e6e | |||
| 821b5380ea | |||
| e8bcbc83a4 | |||
| 091af3c1c9 | |||
| 4cc50301dd | |||
| f643a1615e | |||
| 71e78c67f8 | |||
| e7c4a19d9e | |||
| 6c31d43e72 | |||
| 83b56d591b | |||
| e5b26067ab | |||
| a4fae94be8 | |||
| af4851f03a | |||
| 155d4c44ae | |||
| 48119baf97 | |||
| dd1989aebb | |||
| 0806430700 | |||
| 2d97f6638b | |||
| b10e5ad4d6 | |||
| 24f991ceb1 | |||
| ac11baa0c4 | |||
| 1609a77b77 | |||
| dd2f091a52 | |||
| a7bf0e7d5e | |||
| 19bdd3e622 | |||
| e1bf891ec6 | |||
| 4009834b97 | |||
| 77f5e1fbad | |||
| c4eb77d544 | |||
| 8a4ea43239 | |||
| f056e22f60 | |||
| 05e81040ce | |||
| 8d8133e780 | |||
| dceb35782e | |||
| 2c5f0c6cf2 | |||
| abd667684d | |||
| 31b5f88497 | |||
| 77a7026d33 | |||
| 9c5f3f9754 | |||
| 5d56025014 | |||
| ad62c75f4f | |||
| 7a5bd03216 | |||
| 593e0e28a9 | |||
| ed2b2de79a | |||
| 5a511eecc1 | |||
| 83e0fa1287 | |||
| a2dbdbf8cc | |||
| 3d68bbc0ca | |||
| 40f9f261ae | |||
| c08cc58904 | |||
| ebdb122a22 | |||
| 5de2752685 | |||
| d2245a95c7 | |||
| 47075e95bd | |||
| 9d4c980e02 | |||
| 53c1a1537d | |||
| 7db2f66685 | |||
| 6dd34b1fa4 | |||
| 862713cda4 | |||
| 06c1668e42 | |||
| 5f9f0f26eb | |||
| 8db22cdaf7 | |||
| 3bed9262e5 | |||
| 0c171a457b | |||
| b5e8dfd863 | |||
| c9df87fc25 | |||
| 30925a4b3c | |||
| ccf0efffce | |||
| 169c4392a9 | |||
| e543785959 | |||
| 3617d119cb | |||
| 5d1f28c3cb | |||
| 6f04eb12d0 | |||
| b46136e9f8 | |||
| c3fe2d0ee3 | |||
| aeed371888 | |||
| a50c8869a5 | |||
| 04d1fe4052 | |||
| 4e1d0bcf22 | |||
| d2126e5929 | |||
| 3bdfbebc02 | |||
| 1a9bd1891e | |||
| 1599bc14b3 | |||
| 30b74ab09c | |||
| 24bee01f01 | |||
| 7329c485ea | |||
| 9c93b12084 | |||
| b97c7d39ad | |||
| fa0c1c5d2a | |||
| e4c49ce57b | |||
| 3ae77c68e8 | |||
| 8b1cb8e7ae | |||
| ce0348518d | |||
| a660027b93 | |||
| 44ef409335 | |||
| 089edff1db | |||
| bd5a1b5bda | |||
| ba3517f3aa | |||
| 7f144b7b95 | |||
| 19ac0b6c38 | |||
| 3562437293 | |||
| e8ece71d22 |
@@ -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
|
||||
|
||||
@@ -34,6 +34,10 @@ jobs:
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
}
|
||||
- {
|
||||
name: "Linux ARM 64bit",
|
||||
short_name: linuxarm64
|
||||
}
|
||||
- {
|
||||
name: "MacOS Intel 64bit",
|
||||
short_name: macos64
|
||||
@@ -43,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
|
||||
|
||||
@@ -64,13 +64,16 @@ jobs:
|
||||
libboost-regex-dev \
|
||||
libboost-system-dev \
|
||||
libboost-thread-dev \
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
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 }}
|
||||
|
||||
@@ -97,6 +100,41 @@ jobs:
|
||||
..
|
||||
sudo make -j$(nproc) install
|
||||
|
||||
# OpenCOLLADA is ancient, but we still have it in the main build.
|
||||
# So adding it to CI to catch any breakages.
|
||||
- name: build OpenCOLLADA
|
||||
run: |
|
||||
git clone https://github.com/KhronosGroup/OpenCOLLADA
|
||||
cd OpenCOLLADA
|
||||
git checkout v1.6.68
|
||||
patch -p1 --batch --forward -i ../nix/patches/opencollada/pr622_and_disable_subdirs.patch
|
||||
patch -p1 --batch --forward -i ../nix/patches/opencollada/allow_static_libraries_config_on_unix.patch
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_FLAGS_INIT="-fPIC"
|
||||
sudo make -j$(nproc) install
|
||||
|
||||
# Ubuntu has `swig` package, but we build it to match the version we use in the main build.
|
||||
# To avoid failing tests when checking stub generation.
|
||||
- name: build swig
|
||||
run: |
|
||||
# Remove default swig to avoid conflicts.
|
||||
sudo apt remove --purge swig swig4.0
|
||||
sudo apt-get install -y libpcre2-dev bison
|
||||
git clone https://github.com/swig/swig
|
||||
cd swig
|
||||
git checkout v4.1.0
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
sudo make -j$(nproc) install
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
echo $Python3_ROOT_DIR
|
||||
@@ -114,7 +152,6 @@ jobs:
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=${{ env.pythonLocation }}/bin/python \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=${{ env.pythonLocation }}/include/python3.11 \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
-DUSE_MMAP=On \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-5
@@ -17,6 +17,7 @@
|
||||
# General Python residue
|
||||
__pycache__
|
||||
*.py.bak
|
||||
venv
|
||||
|
||||
# Visual Studio Code files
|
||||
.vscode
|
||||
@@ -78,8 +79,6 @@ src/bonsai/bonsai/translations.py
|
||||
|
||||
# bonsai test temp files
|
||||
src/bonsai/test/files/temp
|
||||
src/bonsai/test/files/basic.ifc.cache.blend
|
||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
# bonsai data
|
||||
src/bonsai/bonsai/bim/data/build/
|
||||
@@ -87,9 +86,7 @@ src/bonsai/bonsai/bim/data/gantt/index.html
|
||||
src/bonsai/bonsai/bim/data/gantt/jsgantt.js
|
||||
src/bonsai/bonsai/bim/data/gantt/jsgantt.css
|
||||
src/bonsai/bonsai/bim/data/webui/static/js/jquery.min.js
|
||||
|
||||
src/bonsai/drawings
|
||||
src/bonsai/layouts
|
||||
src/bonsai/bonsai/bim/data/webui/running_pid.json
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper*.so
|
||||
|
||||
@@ -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
|
||||
|
||||
+26
-700
@@ -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)
|
||||
@@ -104,6 +103,9 @@ endif()
|
||||
|
||||
project(IfcOpenShell VERSION ${RELEASE_VERSION})
|
||||
|
||||
# Make sure CMake modules in this project are found first
|
||||
list(PREPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR})
|
||||
|
||||
if(MINIMAL_BUILD)
|
||||
message(STATUS "Setting options for minimal build")
|
||||
set(BUILD_GEOMSERVER OFF)
|
||||
@@ -196,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
|
||||
@@ -230,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}'.")
|
||||
find_package(CGAL REQUIRED)
|
||||
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()
|
||||
|
||||
@@ -286,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)
|
||||
@@ -298,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)
|
||||
@@ -316,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)
|
||||
@@ -440,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)
|
||||
@@ -486,335 +358,18 @@ 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)
|
||||
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No HDF5 include directory specified")
|
||||
else()
|
||||
set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files")
|
||||
endif()
|
||||
|
||||
if("${HDF5_LIBRARY_DIR}" STREQUAL "")
|
||||
message(STATUS "No HDF5 library directory specified")
|
||||
else()
|
||||
set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files")
|
||||
endif()
|
||||
|
||||
if(HDF5_LIBRARY_DIR)
|
||||
# result of the HDF5 ctest package
|
||||
# Find zlib using cmake find_library. How should this be implemented?
|
||||
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
|
||||
if("$ENV{CONDA_BUILD}" STREQUAL "")
|
||||
# result of the HDF5 ctest package
|
||||
if(WIN32)
|
||||
set(zlib_post lib)
|
||||
set(lib_ext lib)
|
||||
else()
|
||||
set(lib_ext a)
|
||||
endif()
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(debug_postfix "_debug")
|
||||
endif()
|
||||
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
|
||||
)
|
||||
|
||||
else()
|
||||
message(STATUS "Packaging hdf5 and zlib for conda distribution")
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
# macOS
|
||||
set(zlib_post libz)
|
||||
set(lib_ext dylib)
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
|
||||
)
|
||||
else()
|
||||
# linux and windows
|
||||
# Find HDF5 package
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
# Find ZLIB package
|
||||
find_package(ZLIB REQUIRED)
|
||||
# Include directories
|
||||
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
# Link libraries
|
||||
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
if(HDF5_DIR)
|
||||
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
|
||||
set(HDF5_LIBRARIES hdf5_cpp-static)
|
||||
else()
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
|
||||
add_definitions(-DWITH_HDF5)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
@@ -931,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}
|
||||
include_directories(${INCLUDE_DIRECTORIES} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${EIGEN_DIR} ${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
${CGAL_INCLUDE_DIR} ${GMP_INCLUDE_DIR} ${MPFR_INCLUDE_DIR} ${USD_INCLUDE_DIR}
|
||||
${TBB_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
@@ -1038,6 +580,7 @@ if(NOT Boost_VERSION LESS 105800)
|
||||
add_definitions(-DBOOST_OPTIONAL_USE_OLD_DEFINITION_OF_NONE)
|
||||
endif()
|
||||
|
||||
add_subdirectory(../src/ifcparse ifcparse)
|
||||
set(IFCOPENSHELL_LIBRARIES IfcParse)
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
@@ -1048,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()
|
||||
|
||||
@@ -1070,62 +608,14 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# IfcParse
|
||||
file(GLOB IFCPARSE_H_FILES_ALL ../src/ifcparse/*.h)
|
||||
file(GLOB IFCPARSE_CPP_FILES_ALL ../src/ifcparse/*.cpp)
|
||||
|
||||
foreach(file ${IFCPARSE_H_FILES_ALL})
|
||||
get_filename_component(filename "${file}" NAME)
|
||||
|
||||
if(NOT "${filename}" MATCHES "[0-9]")
|
||||
list(APPEND IFCPARSE_H_FILES "${file}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(file ${IFCPARSE_CPP_FILES_ALL})
|
||||
get_filename_component(filename "${file}" NAME)
|
||||
|
||||
if(NOT "${filename}" MATCHES "[0-9]")
|
||||
list(APPEND IFCPARSE_CPP_FILES "${file}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
list(APPEND IFCPARSE_H_FILES
|
||||
../src/ifcparse/Ifc${schema}.h
|
||||
../src/ifcparse/Ifc${schema}-definitions.h
|
||||
)
|
||||
list(APPEND IFCPARSE_CPP_FILES
|
||||
../src/ifcparse/Ifc${schema}.cpp
|
||||
../src/ifcparse/Ifc${schema}-schema.cpp
|
||||
)
|
||||
endforeach()
|
||||
|
||||
set(IFCPARSE_FILES ${IFCPARSE_CPP_FILES} ${IFCPARSE_H_FILES})
|
||||
|
||||
add_library(IfcParse ${IFCPARSE_FILES})
|
||||
target_link_libraries(IfcParse ${STDCPPFS})
|
||||
set_target_properties(IfcParse PROPERTIES COMPILE_FLAGS -DIFC_PARSE_EXPORTS VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
|
||||
if(LibXml2_DIR)
|
||||
target_compile_definitions(IfcParse PRIVATE ${LIBXML2_DEFINITIONS})
|
||||
endif()
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(IfcParse ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
else()
|
||||
target_link_libraries(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# CGAL::CGAL target already has dependencies resolved.
|
||||
if(WITH_CGAL AND CGAL_DIR)
|
||||
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()
|
||||
@@ -1137,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)
|
||||
@@ -1192,134 +657,22 @@ 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
|
||||
if(BUILD_GEOMSERVER)
|
||||
|
||||
if(NOT WITH_OPENCASCADE)
|
||||
message(FATAL_ERROR "Open CASCADE is required to build IfcGeomServer.")
|
||||
endif()
|
||||
|
||||
file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp)
|
||||
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
|
||||
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
|
||||
add_executable(IfcGeomServer ${SOURCE_FILES})
|
||||
target_link_libraries(IfcGeomServer IfcGeom IfcParse Serializers ${kernel_libraries} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
|
||||
if((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
SET_INSTALL_RPATHS(IfcGeomServer "${IFCOPENSHELL_LIBRARY_DIR};${OCC_LIBRARY_DIR};${Boost_LIBRARY_DIRS}")
|
||||
endif()
|
||||
|
||||
install(TARGETS IfcGeomServer
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
add_subdirectory(../src/ifcgeomserver ifcgeomserver)
|
||||
endif(BUILD_GEOMSERVER)
|
||||
|
||||
if(ADD_COMMIT_SHA)
|
||||
@@ -1406,17 +759,6 @@ if(BUILD_QTVIEWER)
|
||||
add_subdirectory(../src/qtviewer qtviewer)
|
||||
endif()
|
||||
|
||||
# CMake installation targets
|
||||
install(FILES ${IFCPARSE_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcparse
|
||||
)
|
||||
|
||||
install(TARGETS IfcParse
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# install(FILES ${IFCGEOM_H_FILES}
|
||||
# DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
@@ -1445,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()
|
||||
@@ -0,0 +1,109 @@
|
||||
#
|
||||
# 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 ${CMAKE_SOURCE_DIR})
|
||||
|
||||
if("${HDF5_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No HDF5 include directory specified")
|
||||
else()
|
||||
set(HDF5_INCLUDE_DIR "${HDF5_INCLUDE_DIR}" CACHE FILEPATH "HDF5 header files")
|
||||
endif()
|
||||
|
||||
if("${HDF5_LIBRARY_DIR}" STREQUAL "")
|
||||
message(STATUS "No HDF5 library directory specified")
|
||||
else()
|
||||
set(HDF5_LIBRARY_DIR "${HDF5_LIBRARY_DIR}" CACHE FILEPATH "HDF5 library files")
|
||||
endif()
|
||||
|
||||
if(HDF5_LIBRARY_DIR)
|
||||
# result of the HDF5 ctest package
|
||||
# Find zlib using cmake find_library. How should this be implemented?
|
||||
# FIND_LIBRARY(NAMES z libz libz_debug PATHS ... NO_DEFAULT_PATH)
|
||||
if("$ENV{CONDA_BUILD}" STREQUAL "")
|
||||
# result of the HDF5 ctest package
|
||||
if(WIN32)
|
||||
set(zlib_post lib)
|
||||
set(lib_ext lib)
|
||||
else()
|
||||
set(lib_ext a)
|
||||
endif()
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
set(debug_postfix "_debug")
|
||||
endif()
|
||||
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libz${zlib_post}${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libsz${debug_postfix}.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libaec${debug_postfix}.${lib_ext}"
|
||||
)
|
||||
|
||||
else()
|
||||
message(STATUS "Packaging hdf5 and zlib for conda distribution")
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
# macOS
|
||||
set(zlib_post libz)
|
||||
set(lib_ext dylib)
|
||||
set(HDF5_LIBRARIES
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5_cpp.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/libhdf5.${lib_ext}"
|
||||
"${HDF5_LIBRARY_DIR}/${zlib_post}.${lib_ext}"
|
||||
)
|
||||
else()
|
||||
# linux and windows
|
||||
# Find HDF5 package
|
||||
find_package(HDF5 REQUIRED COMPONENTS C CXX)
|
||||
# Find ZLIB package
|
||||
find_package(ZLIB REQUIRED)
|
||||
# Include directories
|
||||
include_directories(${HDF5_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
|
||||
# Link libraries
|
||||
set(HDF5_LIBRARIES ${HDF5_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
message(STATUS "HDF5 libraries: ${HDF5_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_INCLUDE_DIR OR NOT HDF5_LIBRARY_DIR)
|
||||
# First try to find it as a config.
|
||||
find_package(HDF5 CONFIG)
|
||||
if(HDF5_DIR)
|
||||
message(STATUS "HDF5: found config at '${HDF5_DIR}'.")
|
||||
set(HDF5_LIBRARIES hdf5_cpp-static)
|
||||
else()
|
||||
# If it failed, still try to find as a module.
|
||||
# E.g. on Ubuntu `libhdf5-dev` doesn't provie hdf5-config.cmake.
|
||||
# Will automatically fill HDF5_LIBRARIES and HDF5_INCLUDE_DIR.
|
||||
find_package(HDF5 COMPONENTS CXX)
|
||||
if(NOT HDF5_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"HDF5_INCLUDE_DIR is not provided (current value: '${HDF5_INCLUDE_DIR}'). "
|
||||
"HDF5_LIBRARY_DIR is not provided (current value: '${HDF5_LIBRARY_DIR}'). "
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
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,138 @@
|
||||
#
|
||||
# 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`
|
||||
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
|
||||
|
||||
+16
-14
@@ -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.
|
||||
@@ -382,11 +384,13 @@ if MAC_CROSS_COMPILE_INTEL:
|
||||
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = ["architecture=x86"]
|
||||
MAC_CROSS_COMPILE_INTEL_CXX = "clang++ -arch x86_64"
|
||||
MAC_CROSS_COMPILE_INTEL_CC = "clang -arch x86_64"
|
||||
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = ["--host=x86_64-apple-darwin"]
|
||||
else:
|
||||
MAC_CROSS_COMPILE_INTEL_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_CXX = ""
|
||||
MAC_CROSS_COMPILE_INTEL_CC = ""
|
||||
MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS = []
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
BUILD_STATIC = "shared" not in flags
|
||||
@@ -939,11 +943,9 @@ if "pcre2" in targets:
|
||||
download_name=f"pcre2-{PCRE2_VERSION}.tar.bz2",
|
||||
)
|
||||
|
||||
# An issue exists with swig-1.3 and python >= 3.2
|
||||
# Therefore, build a recent copy from source
|
||||
if "swig" in targets:
|
||||
build_dependency(
|
||||
name="swig",
|
||||
name=f"swig-{SWIG_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-ccache", f"--with-pcre2-prefix={DEPS_DIR}/install/pcre2-{PCRE2_VERSION}"],
|
||||
download_url="https://github.com/swig/swig.git",
|
||||
@@ -1195,7 +1197,9 @@ if "cgal" in targets:
|
||||
OLD_CC = None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC = os.environ.get("CC")
|
||||
# Otherwise it's using arm64 `gcc` and fails to build gmp.
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
gmp_args.extend(MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS)
|
||||
|
||||
build_dependency(
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
@@ -1374,6 +1378,7 @@ if "cgal" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}")
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/gmp-{GMP_VERSION}")
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}")
|
||||
cmake_args.append(f"-DCGAL_WITH_GMPXX=Off")
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/occt-{OCCT_VERSION}")
|
||||
@@ -1426,6 +1431,9 @@ if "rocksdb" in targets:
|
||||
]
|
||||
)
|
||||
|
||||
if "swig" in targets:
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/swig-{SWIG_VERSION}")
|
||||
|
||||
if not WASM and (not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets)):
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
@@ -1441,17 +1449,14 @@ if not WASM and (not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServe
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
|
||||
if "IfcOpenShell-Python" in targets:
|
||||
# On OSX the actual Python library is not linked against.
|
||||
ADDITIONAL_ARGS = ""
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
|
||||
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = "-Wl,-undefined,suppress"
|
||||
ADDITIONAL_ARGS = "-Wl,-undefined,dynamic_lookup"
|
||||
|
||||
# NOTE: We don't use `CXXFLAGS` for wrappers, so wrapper is compiled with different flags
|
||||
# (e.g. ` -fdata-sections` is missing, which is set by default for executables)
|
||||
@@ -1481,9 +1486,6 @@ if "IfcOpenShell-Python" in targets:
|
||||
if os.path.exists(cache_path):
|
||||
os.remove(cache_path)
|
||||
|
||||
prefix_paths: list[str] = []
|
||||
if "swig" in targets:
|
||||
prefix_paths.append(f"{DEPS_DIR}/install/swig")
|
||||
if python_path:
|
||||
# We couldn't just prefix PATH and have to provide all variables explicitly,
|
||||
# see ifcwrap/cmake for the details.
|
||||
@@ -1500,7 +1502,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
run_cmake(
|
||||
"",
|
||||
cmake_args
|
||||
+ get_cmake_args_prefix_path(prefix_paths)
|
||||
+ get_cmake_args_prefix_path()
|
||||
+ [
|
||||
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
|
||||
# Needed because pyodide is expecting setup.py to be in the root.
|
||||
@@ -1516,7 +1518,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "ifcopenshell_wrapper", "VERBOSE=1"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
if python_executable:
|
||||
|
||||
+1
-4
@@ -9,10 +9,8 @@ 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
|
||||
|src/pyodide
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
@@ -21,12 +19,11 @@ disableBytesTypePromotions = true
|
||||
reportUnnecessaryTypeIgnoreComment = true
|
||||
|
||||
# Define here general ruff settings,
|
||||
# then they will be inherited projects .toml files.
|
||||
# then they will be inherited by projects' .toml files.
|
||||
# This allows using assuming different Python version for different projects.
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
# Submodules.
|
||||
"src/ifcconvert/cityjson",
|
||||
"src/ifcopenshell-python/ifcopenshell/express",
|
||||
"src/ifcopenshell-python/ifcopenshell/mvd",
|
||||
"src/ifcopenshell-python/ifcopenshell/simple_spf",
|
||||
|
||||
+4
-1
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=6924012
|
||||
OLD:=e8eb5e4
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -155,6 +155,9 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download six --dest=./wheels
|
||||
# Required by drawing module
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download lxml $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
# Required by drawing module for markdown hyperlinks in annotations
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download markdown-it-py --dest=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download mdurl --dest=./wheels
|
||||
# Required by qto and drawing module
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download shapely $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
# Required by the BIM type manager thumbnail generator
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
@@ -54,11 +48,6 @@
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
@@ -253,12 +242,6 @@
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
@@ -300,11 +283,6 @@
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
@@ -377,7 +355,7 @@
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studio_light": "forest.exr",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
@@ -503,18 +481,12 @@
|
||||
"scene.eevee.shadow_ray_count": 1,
|
||||
"scene.eevee.shadow_step_count": 6,
|
||||
"scene.eevee.shadow_resolution_scale": 1.0,
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_EEVEE_NEXT",
|
||||
"scene.render.engine": "BLENDER_EEVEE",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
@@ -550,11 +522,6 @@
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
@@ -627,7 +594,7 @@
|
||||
0.800000011920929,
|
||||
0.800000011920929
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studio_light": "forest.exr",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
|
||||
@@ -25,6 +25,7 @@ import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -28,6 +28,7 @@ import numpy as np
|
||||
import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.unit
|
||||
@@ -1237,8 +1238,7 @@ class IfcImporter:
|
||||
if not pset:
|
||||
return
|
||||
if "Aggregate_Index" not in pset.keys():
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
self.file,
|
||||
pset=self.file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": aggregate_index, "Name": name},
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.group
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.aggregate as core
|
||||
@@ -321,6 +322,21 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
|
||||
# copy selection query to clipboard
|
||||
result = ""
|
||||
for aggregate in all_parts:
|
||||
aggregate_class = aggregate.is_a()
|
||||
aggregate_name = aggregate.Name or ""
|
||||
query = f'parent = "{aggregate_name}"'
|
||||
if not result:
|
||||
result = query
|
||||
else:
|
||||
result += f" + {query}"
|
||||
|
||||
if result:
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
|
||||
self.one_level_deep = False # <-- forcibly reset
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -235,8 +235,8 @@ class DecoratorData:
|
||||
cut_cache = {}
|
||||
slice_cache = {}
|
||||
fill_cache = {}
|
||||
camera_location_checksum = None
|
||||
camera_rotation_checksum = None
|
||||
camera_location_checksum = ""
|
||||
camera_rotation_checksum = ""
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import gpu
|
||||
import bpy
|
||||
import blf
|
||||
import os
|
||||
import math
|
||||
import bmesh
|
||||
import shapely
|
||||
@@ -41,7 +42,7 @@ from bonsai.bim.module.drawing.helper import format_distance
|
||||
from timeit import default_timer as timer
|
||||
from functools import cache
|
||||
from typing import Optional, Union
|
||||
from collections.abc import Iterator
|
||||
from collections.abc import Generator, Iterator
|
||||
|
||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||
|
||||
@@ -172,15 +173,11 @@ class BaseDecorator:
|
||||
def get_camera_width_mm(self):
|
||||
# Horrific prototype code to ensure bgl draws at drawing scales
|
||||
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
|
||||
def is_landscape(render):
|
||||
return render.resolution_x > render.resolution_y
|
||||
|
||||
camera = bpy.context.scene.camera
|
||||
render = bpy.context.scene.render
|
||||
if is_landscape(render):
|
||||
camera_width_model = camera.data.ortho_scale
|
||||
else:
|
||||
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
|
||||
|
||||
# Always use the camera's ortho_scale as the width in model space
|
||||
camera_width_model = camera.data.ortho_scale
|
||||
|
||||
scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
|
||||
camera_width_mm = scale * camera_width_model
|
||||
@@ -189,29 +186,26 @@ class BaseDecorator:
|
||||
def camera_zoom_to_factor(self, zoom):
|
||||
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
|
||||
|
||||
def get_splines(self, obj):
|
||||
def get_splines(self, obj: bpy.types.Object) -> Generator[list[Vector]]:
|
||||
"""Iterates through splines
|
||||
Args:
|
||||
obj: Blender object with Curve data
|
||||
|
||||
Yields:
|
||||
verts: points of each spline, world coords
|
||||
:param obj: Blender object with Curve data
|
||||
:yield: points of each spline, world coords
|
||||
"""
|
||||
assert type(obj.data) is bpy.types.Curve
|
||||
for spline in obj.data.splines:
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if len(spline_points) < 2:
|
||||
continue
|
||||
yield [obj.matrix_world @ p.co for p in spline_points]
|
||||
|
||||
def get_path_geom(self, obj, topo=True):
|
||||
def get_path_geom(self, obj: bpy.types.Object, topo: bool = True):
|
||||
"""Parses path geometry into line segments
|
||||
|
||||
Args:
|
||||
obj: Blender object with data of type Curve
|
||||
topo: bool; if types of vertices are needed
|
||||
:param obj: Blender object with data of type Curve
|
||||
:param topo: if types of vertices are needed
|
||||
|
||||
Returns:
|
||||
vertices: 3-tuples of coords
|
||||
:return: vertices: 3-tuples of coords
|
||||
indices: 2-tuples of each segment verices' indices
|
||||
topology: types of vertices
|
||||
0: internal
|
||||
@@ -242,11 +236,9 @@ class BaseDecorator:
|
||||
def get_mesh_geom(self, obj, check_mode=True):
|
||||
"""Parses mesh geometry into line segments
|
||||
|
||||
Args:
|
||||
obj: Blender object with data of type Mesh
|
||||
:param obj: Blender object with data of type Mesh
|
||||
|
||||
Returns:
|
||||
vertices: 3-tuples of coords
|
||||
:return: vertices: 3-tuples of coords
|
||||
indices: 2-tuples of each segment verices' indices
|
||||
"""
|
||||
if check_mode and obj.data.is_editmode:
|
||||
@@ -377,11 +369,9 @@ class BaseDecorator:
|
||||
):
|
||||
"""Draw text label
|
||||
|
||||
Args:
|
||||
pos: bottom-center
|
||||
multiline: \n characters will be interpreted as line breaks
|
||||
|
||||
aligned and centered at segment middle
|
||||
:param pos: bottom-center
|
||||
:param multiline: ``\n`` characters will be interpreted as line breaks
|
||||
aligned and centered at segment middle
|
||||
|
||||
NOTE: `blf.draw` seems to ignore the \n character, so we have to split the text ourselves
|
||||
and use the `line_no` argument of `draw_label`
|
||||
@@ -1641,6 +1631,13 @@ class CutDecorator:
|
||||
if not context.scene.camera:
|
||||
return
|
||||
|
||||
# Check if any viewport is in local view - skip decorations if so
|
||||
for area in context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
space = area.spaces.active
|
||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||
return # Don't draw decorations in local view
|
||||
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
self.fallback_colour = (0.3, 0.3, 0.3, 1)
|
||||
@@ -1731,21 +1728,38 @@ class CutDecorator:
|
||||
|
||||
def cache_camera_matrix(self):
|
||||
obj = bpy.context.scene.camera
|
||||
DecoratorData.camera_location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
|
||||
DecoratorData.camera_rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
|
||||
# Explicit `dtype` for Blender <5.0 compatibility.
|
||||
DecoratorData.camera_location_checksum = repr(
|
||||
np.array(obj.matrix_world.translation, dtype=np.float32).tobytes()
|
||||
)
|
||||
DecoratorData.camera_rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
|
||||
|
||||
def is_camera_moved(self):
|
||||
if not DecoratorData.camera_location_checksum:
|
||||
self.cache_camera_matrix()
|
||||
return True # Let's be conservative
|
||||
obj = bpy.context.scene.camera
|
||||
loc_check = np.frombuffer(eval(DecoratorData.camera_location_checksum))
|
||||
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)).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
|
||||
@@ -1974,15 +1988,51 @@ class DecorationsHandler:
|
||||
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
|
||||
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
|
||||
if drawing_font := prefs.doc.drawing_font:
|
||||
# Try Blender addon font folder
|
||||
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
|
||||
if drawing_font_path.is_file():
|
||||
font_id = blf.load(drawing_font_path.__str__())
|
||||
for decorator in self.decorators.values():
|
||||
decorator.font_id = font_id
|
||||
|
||||
if not drawing_font_path.is_file():
|
||||
# 2 — Fallback search: Windows font directories
|
||||
# TODO - Linux
|
||||
win_font_dirs = [
|
||||
Path(os.environ.get("WINDIR", "C:\\Windows")) / "Fonts",
|
||||
# Add any custom enterprise paths here if needed
|
||||
]
|
||||
|
||||
found_font = None
|
||||
for font_dir in win_font_dirs:
|
||||
candidate = font_dir / drawing_font
|
||||
if candidate.is_file():
|
||||
found_font = candidate
|
||||
break
|
||||
|
||||
if found_font:
|
||||
drawing_font_path = found_font
|
||||
else:
|
||||
print(f"[BIM] Font '{drawing_font}' not found in addon or Windows fonts.")
|
||||
return # Bail out without assigning fonts
|
||||
|
||||
font_id = blf.load(str(drawing_font_path))
|
||||
|
||||
for decorator in self.decorators.values():
|
||||
decorator.font_id = font_id
|
||||
|
||||
def __call__(self, context):
|
||||
# Check if any viewport is in local view - skip decorations if so
|
||||
for area in context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
space = area.spaces.active
|
||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||
return # Don't draw decorations in local view
|
||||
|
||||
# disable decorations when not in camera view
|
||||
if context.region_data.view_perspective != "CAMERA":
|
||||
return
|
||||
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import ifcopenshell.util.element
|
||||
import mathutils.geometry
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
@@ -421,8 +422,8 @@ def ortho_view_frame(
|
||||
|
||||
Similar to `bpy.types.Camera.view_frame`
|
||||
|
||||
:arg camera: camera of drawing
|
||||
:arg margin: margins, in scene units
|
||||
:param camera: camera of drawing
|
||||
:param margin: margins, in scene units
|
||||
:return: (xmin, xmax, ymin, ymax, zmin, zmax) in local camera coordinates
|
||||
"""
|
||||
props = tool.Drawing.get_camera_props(camera)
|
||||
@@ -443,8 +444,8 @@ def almost_zero(v):
|
||||
def clip_segment(bounds, segm):
|
||||
"""Clipping line segment to bounds
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:param bounds: (xmin, xmax, ymin, ymax)
|
||||
:param segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
# Liang–Barsky algorithm
|
||||
@@ -493,8 +494,8 @@ def clip_segment(bounds, segm):
|
||||
def elevate_segment(bounds, segm):
|
||||
"""Elevate line xy-perpendicular segment vertically
|
||||
|
||||
:arg bounds: (xmin, xmax, ymin, ymax)
|
||||
:arg segm: 2 vertices of the segment
|
||||
:param bounds: (xmin, xmax, ymin, ymax)
|
||||
:param segm: 2 vertices of the segment
|
||||
:return: 2 new vertices of segment or None if segment outside the bounding box
|
||||
"""
|
||||
_, _, ymin, ymax, zmin, _ = bounds
|
||||
@@ -544,13 +545,11 @@ def add_newline_between_words(text: str, newline_at: int) -> str:
|
||||
def get_relative_z(obj: bpy.types.Object, element, abs_z: float) -> float:
|
||||
"""Return relative Z of an element, accounting for its spatial container.
|
||||
|
||||
Args:
|
||||
obj: The Blender object representing the element.
|
||||
element: The IFC entity for the object.
|
||||
abs_z: The absolute Z value in world coordinates.
|
||||
:param obj: The Blender object representing the element.
|
||||
:param element: The IFC entity for the object.
|
||||
:param abs_z: The absolute Z value in world coordinates.
|
||||
|
||||
Returns:
|
||||
Relative Z value if the element is inside a spatial container,
|
||||
:return: Relative Z value if the element is inside a spatial container,
|
||||
otherwise the absolute Z.
|
||||
"""
|
||||
z = abs_z
|
||||
|
||||
@@ -932,6 +932,31 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
# Add target_view and scale classes to the parent group from IFC data
|
||||
if group is not None:
|
||||
existing_classes = group.get("class", "").split()
|
||||
|
||||
# Add target_view class
|
||||
if hasattr(self, "cprops") and getattr(self.cprops, "target_view", None):
|
||||
target_view_class = tool.Drawing.canonicalise_class_name(str(self.cprops.target_view))
|
||||
target_view_full_class = f"target-view-{target_view_class}"
|
||||
if target_view_full_class not in existing_classes:
|
||||
existing_classes.append(target_view_full_class)
|
||||
|
||||
# Add scale class from EPset_Drawing.Scale
|
||||
drawing_pset = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing")
|
||||
if drawing_pset and drawing_pset.get("Scale"):
|
||||
scale_value = drawing_pset["Scale"]
|
||||
# Remove "1/" prefix if it exists
|
||||
if isinstance(scale_value, str) and scale_value.startswith("1/"):
|
||||
scale_value = scale_value[2:]
|
||||
scale_class = tool.Drawing.canonicalise_class_name(str(scale_value))
|
||||
scale_full_class = f"scale-{scale_class}"
|
||||
if scale_full_class not in existing_classes:
|
||||
existing_classes.append(scale_full_class)
|
||||
|
||||
group.set("class", " ".join(existing_classes))
|
||||
|
||||
if self.cprops.cut_mode == "BISECT":
|
||||
self.remove_cut_linework(root)
|
||||
self.generate_bisect_linework(context, root)
|
||||
@@ -1287,11 +1312,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
|
||||
)
|
||||
|
||||
# ─── Target View ───────────────────────────────────────────
|
||||
if getattr(self.cprops, "target_view", None):
|
||||
target_view_class = tool.Drawing.canonicalise_class_name(str(self.cprops.target_view))
|
||||
classes.append(f"target-view-{target_view_class}")
|
||||
|
||||
return classes
|
||||
|
||||
def is_manifold(self, obj) -> bool:
|
||||
@@ -2701,21 +2721,59 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def set_raster_style(self, context: bpy.types.Context) -> None:
|
||||
scene = context.scene # Do not remove. It is used in exec later
|
||||
assert (space := tool.Blender.get_view3d_space()) # Do not remove. It is used in exec later
|
||||
style = json.loads(self.drawing_style.raster_style)
|
||||
style: dict[str, Any] = json.loads(self.drawing_style.raster_style)
|
||||
|
||||
VIEWPORT_SHADING_TYPE = "scene.display.shading.type"
|
||||
|
||||
def preprocess(path: str, value: Any) -> tuple[str, Any, bool, bool]:
|
||||
warning = False
|
||||
skip = False
|
||||
# @25.05.12
|
||||
if path == "scene.render.engine" and value == "BLENDER_EEVEE":
|
||||
value = "BLENDER_EEVEE_NEXT"
|
||||
|
||||
BLENDER_5_REMOVED = (
|
||||
"scene.render.bake_bias",
|
||||
"scene.render.bake_margin",
|
||||
"scene.render.bake_margin_type",
|
||||
"scene.render.bake_samples",
|
||||
"scene.render.bake_type",
|
||||
"scene.render.bake_user_scale",
|
||||
"scene.render.use_bake_clear",
|
||||
"scene.render.use_bake_lores_mesh",
|
||||
"scene.render.use_bake_multires",
|
||||
"scene.render.use_bake_selected_to_active",
|
||||
"scene.render.use_bake_user_scale",
|
||||
)
|
||||
|
||||
# 25.11.07, Blender 5+
|
||||
if path == "scene.render.engine" and value in ("BLENDER_EEVEE", "BLENDER_EEVEE_NEXT"):
|
||||
if value == "BLENDER_EEVEE_NEXT":
|
||||
print(
|
||||
f"Warning: Value 'BLENDER_EEVEE_NEXT' is outdated for property '{path}' "
|
||||
"since Blender 5.0 and should be replaced with 'BLENDER_EEVEE' in shading_styles.json."
|
||||
)
|
||||
warning = True
|
||||
value = tool.Blender.get_eevee_name()
|
||||
elif tool.Blender.BLENDER_5 and path in BLENDER_5_REMOVED:
|
||||
print(
|
||||
f"Warning: Value 'BLENDER_EEVEE' is outdated for property '{path}' "
|
||||
"since Blender 4.2 and should be replaced with 'BLENDER_EEVEE_NEXT' in shading_styles.json."
|
||||
f"Warning: Property '{path}' is removed "
|
||||
"since Blender 5.0 and should be also removed from shading_styles.json."
|
||||
)
|
||||
warning = True
|
||||
skip = True
|
||||
# @25.11.11
|
||||
elif (
|
||||
path == "scene.display.shading.studio_light"
|
||||
and value == "Default"
|
||||
and style[VIEWPORT_SHADING_TYPE] in ("RENDERED", "MATERIAL")
|
||||
):
|
||||
value = "forest.exr"
|
||||
print(
|
||||
f"Warning: Value 'Default' for property '{path}' and "
|
||||
f"'{VIEWPORT_SHADING_TYPE}' = '{style[VIEWPORT_SHADING_TYPE]}' is outdated "
|
||||
"and should be replaced with 'forest.exr' in shading_styles.json."
|
||||
)
|
||||
warning = True
|
||||
# @25.05.12
|
||||
if path == "scene.display.shading.wireframe_color_type" and value == "MATERIAL":
|
||||
elif path == "scene.display.shading.wireframe_color_type" and value == "MATERIAL":
|
||||
value = "THEME"
|
||||
print(
|
||||
f"Warning: Value 'MATERIAL' is outdated for property '{path}' "
|
||||
@@ -2749,7 +2807,16 @@ class ActivateDrawingStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
return path, value, warning, skip
|
||||
|
||||
for path, value in style.items():
|
||||
paths = list(style.keys())
|
||||
PRIORITY_PATHS = (
|
||||
# `scene.display.shading.studio_light` values depend on `.type`
|
||||
# so we got to set it first.
|
||||
VIEWPORT_SHADING_TYPE,
|
||||
)
|
||||
paths.sort(key=lambda p: p not in PRIORITY_PATHS)
|
||||
|
||||
for path in paths:
|
||||
value = style[path]
|
||||
path, value, warning, skip = preprocess(path, value)
|
||||
self.has_warnings_during_activation |= warning
|
||||
|
||||
@@ -3414,6 +3481,15 @@ class LoadDrawings(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# Operator is not accessible through UI if IFC project is not saved,
|
||||
# but adding poll check to avoid using Drawings UI in scripts with unsaved project.
|
||||
if tool.Ifc.get_path():
|
||||
return True
|
||||
cls.poll_message_set("IFC project is not saved.")
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_drawings(tool.Drawing)
|
||||
|
||||
|
||||
@@ -544,11 +544,10 @@ class Scheduler:
|
||||
"""
|
||||
Adds text to svg.
|
||||
|
||||
Args:
|
||||
p_tags: list of cell's P tags from odt file
|
||||
box_alignment: alignment of text in box
|
||||
wrap_text: if True, text will be wrapped to fit in cell
|
||||
cell_width: width of cell, used for wrapping text
|
||||
:param p_tags: list of cell's P tags from odt file
|
||||
:param box_alignment: alignment of text in box
|
||||
:param wrap_text: if True, text will be wrapped to fit in cell
|
||||
:param cell_width: width of cell, used for wrapping text
|
||||
"""
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
|
||||
@@ -341,17 +341,29 @@ class SheetBuilder:
|
||||
assert style_data is not None
|
||||
text = ""
|
||||
brackets_level = 0
|
||||
selector_buffer = "" # Buffer to accumulate selectors across lines
|
||||
|
||||
for l in style_data:
|
||||
if l == "{":
|
||||
if brackets_level == 0:
|
||||
cur_line = text.splitlines()[-1]
|
||||
text = text[: -len(cur_line)]
|
||||
# Get all accumulated selector text (may span multiple lines)
|
||||
# Find where the last rule ended (after last }) or start of text
|
||||
last_close = text.rfind("}")
|
||||
if last_close == -1:
|
||||
selector_text = text
|
||||
text = ""
|
||||
else:
|
||||
selector_text = text[last_close + 1 :]
|
||||
text = text[: last_close + 1]
|
||||
|
||||
# Process all selectors (split by comma)
|
||||
css_selectors = []
|
||||
# making sure cases like "text, tspan" will be
|
||||
# converted to "text.prefix, tspan.prefix"
|
||||
for css_selector in cur_line.split(","):
|
||||
css_selector = f"{css_selector.strip()}.{prefix}"
|
||||
css_selectors.append(css_selector)
|
||||
for css_selector in selector_text.split(","):
|
||||
css_selector = css_selector.strip()
|
||||
if css_selector: # Only process non-empty selectors
|
||||
css_selector = f"{css_selector}.{prefix}"
|
||||
css_selectors.append(css_selector)
|
||||
|
||||
text += ", ".join(css_selectors) + " "
|
||||
brackets_level += 1
|
||||
elif l == "}":
|
||||
|
||||
@@ -202,6 +202,15 @@ class SvgWriter:
|
||||
) -> Self:
|
||||
self.precision = precision
|
||||
self.decimal_places = decimal_places
|
||||
|
||||
# Sort annotations to ensure FILLAREA types are drawn first (at the bottom)
|
||||
def sort_key(element):
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
# Return 0 for FILLAREA to draw them first, 1 for everything else
|
||||
return 0 if predefined_type == "FILL_AREA" else 1
|
||||
|
||||
annotations = sorted(annotations, key=sort_key)
|
||||
|
||||
for element in annotations:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if (
|
||||
|
||||
@@ -116,7 +116,7 @@ class BIM_PT_camera(Panel):
|
||||
# See #6686.
|
||||
if (
|
||||
props.has_underlay
|
||||
and str(render.engine) == "BLENDER_EEVEE_NEXT"
|
||||
and str(render.engine) == tool.Blender.get_eevee_name()
|
||||
and ((megapixels := (render.resolution_x * render.resolution_y / 10**6)) > MEGAPIXELS_WARNING_THRESHOLD)
|
||||
):
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.AddSweptAreaSolidItem,
|
||||
operator.AssignRepresentationLayer,
|
||||
operator.CopyRepresentation,
|
||||
operator.DirectProfileEdit,
|
||||
operator.DisableEditingRepresentationItemShapeAspect,
|
||||
operator.DisableEditingRepresentationItemStyle,
|
||||
operator.DisableEditingRepresentationItems,
|
||||
@@ -160,6 +161,8 @@ def register():
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.direct_profile_edit", "SPACE", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
# Deletion.
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
@@ -178,6 +181,8 @@ def register():
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
kmi.properties.should_save = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.direct_profile_edit", "SPACE", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("wm.call_menu", "P", "PRESS")
|
||||
kmi.properties.name = ui.BIM_MT_hotkey_separate.bl_idname
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
@@ -42,6 +42,7 @@ import bonsai.core.geometry
|
||||
import bonsai.core.geometry as core
|
||||
import bonsai.core.aggregate
|
||||
import bonsai.core.nest
|
||||
import bonsai.core.spatial
|
||||
import bonsai.core.style
|
||||
import bonsai.core.root
|
||||
import bonsai.core.drawing
|
||||
@@ -809,9 +810,17 @@ class OverrideDelete(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
# Match `object.delete` poll for consistency.
|
||||
# `object.delete` poll just checks for OBJECT mode.
|
||||
poll = bpy.ops.object.delete.poll()
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode")
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
if not context.selected_objects:
|
||||
return {"FINISHED"}
|
||||
# Deep magick from the dawn of time
|
||||
if tool.Ifc.get() is None:
|
||||
bpy.ops.object.delete(use_global=self.use_global, confirm=self.confirm)
|
||||
@@ -823,6 +832,8 @@ class OverrideDelete(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not context.selected_objects:
|
||||
return {"FINISHED"}
|
||||
assert context.window_manager
|
||||
ifc_file = tool.Ifc.get()
|
||||
if ifc_file is None:
|
||||
@@ -1030,9 +1041,18 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context) -> bool:
|
||||
return len(getattr(context, "selected_ids", [])) > 0
|
||||
# Match `outliner.delete` poll for consistency.
|
||||
# `outliner.delete` just checks `area.type` == `OUTLINER`.
|
||||
poll = bpy.ops.outliner.delete.poll()
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available from Outliner.")
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
if len(getattr(context, "selected_ids", [])) == 0:
|
||||
return {"FINISHED"}
|
||||
|
||||
# In this override, we don't check self.hierarchy. This effectively
|
||||
# makes Delete and Delete Hierarchy identical. This is on purpose, since
|
||||
# non-hierarchical deletion may imply a whole bunch of potentially
|
||||
@@ -1051,6 +1071,9 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if len(getattr(context, "selected_ids", [])) == 0:
|
||||
return {"FINISHED"}
|
||||
|
||||
assert context.window_manager
|
||||
ifc_file = tool.Ifc.get()
|
||||
if ifc_file:
|
||||
@@ -1121,10 +1144,18 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
def poll(cls, context) -> bool:
|
||||
# Match `object.duplicate_move` poll for consistency.
|
||||
# `object.duplicate_move` poll checks for OBJECT mode.
|
||||
poll = bpy.ops.object.duplicate_move.poll()
|
||||
if poll:
|
||||
return True
|
||||
cls.poll_message_set("Only available in OBJECT mode")
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
if not context.selected_objects:
|
||||
return {"FINISHED"}
|
||||
return OverrideDuplicateMove.execute_duplicate_operator(self, context, linked=False)
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -1147,7 +1178,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_duplicate_operator(operator: bpy.types.Operator, context: bpy.types.Context, linked: bool = False):
|
||||
objects_to_remove = set()
|
||||
objects_to_remove: set[bpy.types.Object] = set()
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -1205,6 +1236,9 @@ class DuplicateMoveLinkedAggregateMacro(bpy.types.Macro):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
OldToNewType = dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]
|
||||
|
||||
|
||||
class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.object_duplicate_move_linked_aggregate"
|
||||
bl_label = "IFC Duplicate and Move Linked Aggregate"
|
||||
@@ -1227,9 +1261,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
self.new_active_obj = None
|
||||
self.group_name = "BBIM_Linked_Aggregate"
|
||||
self.pset_name = "BBIM_Linked_Aggregate"
|
||||
old_to_new = {}
|
||||
all_old_to_new = {} # Track all duplicates created
|
||||
|
||||
def select_objects_and_add_data(element):
|
||||
def select_objects_and_add_data(element: ifcopenshell.entity_instance) -> None:
|
||||
add_linked_aggregate_group(element)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj.select_set(True)
|
||||
@@ -1243,8 +1277,6 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
select_objects_and_add_data(part)
|
||||
else:
|
||||
index = add_linked_aggregate_pset(part, index)
|
||||
# index += 1
|
||||
|
||||
obj = tool.Ifc.get_object(part)
|
||||
obj.select_set(True)
|
||||
|
||||
@@ -1257,20 +1289,15 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name=self.pset_name)
|
||||
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
pset=pset,
|
||||
properties=properties,
|
||||
)
|
||||
|
||||
index += 1
|
||||
else:
|
||||
pass
|
||||
|
||||
return index
|
||||
|
||||
def add_linked_aggregate_group(element):
|
||||
def add_linked_aggregate_group(element: ifcopenshell.entity_instance) -> None:
|
||||
linked_aggregate_group = None
|
||||
product_groups_name = [
|
||||
r.RelatingGroup.Name
|
||||
@@ -1283,25 +1310,21 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
linked_aggregate_group = ifcopenshell.api.group.add_group(ifc_file, name=self.group_name)
|
||||
ifcopenshell.api.group.assign_group(ifc_file, products=[element], group=linked_aggregate_group)
|
||||
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new: OldToNewType) -> None:
|
||||
for new in old_to_new.values():
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
group_elements: list[ifcopenshell.entity_instance] = next(
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
)
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
new_obj.name = pset["Name"] + "_" + str(pset["Aggregate_Index"])
|
||||
new_obj.name = f"{pset['Name']}_{pset['Aggregate_Index']:02d}"
|
||||
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i["Index"] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
psets = [
|
||||
ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate")
|
||||
for p in parts
|
||||
if ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate")
|
||||
]
|
||||
if psets:
|
||||
index = max([i["Index"] for i in psets if i])
|
||||
return index
|
||||
else:
|
||||
return 0
|
||||
@@ -1315,23 +1338,28 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
ifcopenshell.api.group.assign_group(ifc_file, group=linked_aggregate_group[0], products=new)
|
||||
|
||||
group_elements: list[ifcopenshell.entity_instance] = next(
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
)
|
||||
if linked_aggregate_group:
|
||||
ifcopenshell.api.group.assign_group(ifc_file, group=linked_aggregate_group[0], products=new)
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(old, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
new_pset = ifcopenshell.api.pset.add_pset(ifc_file, product=new[0], name=self.pset_name)
|
||||
if pset["Index"] == 0:
|
||||
group_elements = []
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
group_elements = next(
|
||||
(
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
and "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
),
|
||||
[],
|
||||
)
|
||||
properties = {
|
||||
"Index": pset["Index"],
|
||||
"Name": pset["Name"],
|
||||
"Aggregate_Index": len(group_elements) - 1,
|
||||
"Aggregate_Index": len(group_elements) - 1 if group_elements else 0,
|
||||
}
|
||||
else:
|
||||
properties = {"Index": pset["Index"]}
|
||||
@@ -1351,43 +1379,73 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
location_diff = new_obj.location - base_obj_location
|
||||
new_obj.location = context.scene.cursor.location + location_diff
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
# Process multiple selected aggregates
|
||||
selected_aggregates = []
|
||||
for obj in context.selected_objects:
|
||||
selected_element = tool.Ifc.get_entity(obj)
|
||||
if not selected_element:
|
||||
continue
|
||||
|
||||
# Find the aggregate element
|
||||
if selected_element.is_a("IfcElementAssembly"):
|
||||
selected_aggregates.append(selected_element)
|
||||
elif selected_element.Decomposes:
|
||||
if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
|
||||
selected_aggregates.append(selected_element.Decomposes[0].RelatingObject)
|
||||
|
||||
# Remove duplicates
|
||||
selected_aggregates = list(set(selected_aggregates))
|
||||
|
||||
if not selected_aggregates:
|
||||
self.report({"INFO"}, "No Linked Aggregates selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
selected_obj = context.selected_objects[0]
|
||||
selected_element = tool.Ifc.get_entity(selected_obj)
|
||||
assert selected_element
|
||||
|
||||
if selected_element.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
elif selected_element.Decomposes:
|
||||
if selected_element.Decomposes[0].RelatingObject.is_a("IfcElementAssembly"):
|
||||
selected_element = selected_element.Decomposes[0].RelatingObject
|
||||
selected_obj = tool.Ifc.get_object(selected_element)
|
||||
else:
|
||||
self.report({"INFO"}, "Object is not part of a IfcElementAssembly.")
|
||||
return {"FINISHED"}
|
||||
|
||||
select_objects_and_add_data(selected_element)
|
||||
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
tool.Root.recreate_aggregate(old_to_new)
|
||||
|
||||
copy_linked_aggregate_data(old_to_new)
|
||||
|
||||
custom_incremental_naming_for_element_assembly(old_to_new)
|
||||
|
||||
if location_from_3d_cursor:
|
||||
get_location_from_3d_cursor(old_to_new, selected_element)
|
||||
|
||||
# Deselect all first
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
new_aggregate = old_to_new[selected_element][0]
|
||||
tool.Blender.set_active_object(tool.Ifc.get_object(new_aggregate))
|
||||
|
||||
# Process each selected aggregate
|
||||
for aggregate in selected_aggregates:
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
|
||||
# Select and prepare the aggregate for duplication
|
||||
select_objects_and_add_data(aggregate)
|
||||
|
||||
# Duplicate the aggregate
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
all_old_to_new.update(old_to_new) # Collect all duplicates
|
||||
|
||||
# Recreate aggregate structure
|
||||
tool.Root.recreate_aggregate(old_to_new)
|
||||
|
||||
# Copy linked aggregate data
|
||||
copy_linked_aggregate_data(old_to_new)
|
||||
|
||||
# Apply custom naming
|
||||
custom_incremental_naming_for_element_assembly(old_to_new)
|
||||
|
||||
# Apply 3D cursor location if requested
|
||||
if location_from_3d_cursor:
|
||||
get_location_from_3d_cursor(old_to_new, aggregate)
|
||||
|
||||
# Deselect for next iteration
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
# Select all newly created aggregates
|
||||
for aggregate in selected_aggregates:
|
||||
if aggregate in all_old_to_new:
|
||||
new_aggregate = all_old_to_new[aggregate][0]
|
||||
new_aggregate_obj = tool.Ifc.get_object(new_aggregate)
|
||||
if new_aggregate_obj:
|
||||
new_aggregate_obj.select_set(True)
|
||||
|
||||
# Set active object to the first new aggregate
|
||||
if all_old_to_new:
|
||||
first_new_aggregate = next(iter(all_old_to_new.values()))[0]
|
||||
context.view_layer.objects.active = tool.Ifc.get_object(first_new_aggregate)
|
||||
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
return old_to_new
|
||||
return all_old_to_new
|
||||
|
||||
|
||||
class DuplicateLinkedAggregateTo3dCursor(bpy.types.Operator):
|
||||
@@ -1423,7 +1481,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.pset_name = "BBIM_Linked_Aggregate"
|
||||
refresh_start_time = time()
|
||||
old_to_new = {}
|
||||
original_data: dict[int, dict[int, str]] = {}
|
||||
original_data: dict[int, dict[int, dict[str, Any]]] = {}
|
||||
|
||||
def delete_objects(element: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove IfcElementAssembly and it's parts."""
|
||||
@@ -1469,8 +1527,8 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
def get_original_data(element: ifcopenshell.entity_instance) -> dict[int, dict[int, str]]:
|
||||
group = next(
|
||||
def get_original_data(element: ifcopenshell.entity_instance) -> dict[int, dict[int, dict[str, Any]]]:
|
||||
group: int = next(
|
||||
r.RelatingGroup
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
@@ -1478,8 +1536,8 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
).id()
|
||||
original_data[group] = {}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset["Index"]
|
||||
pset: dict[str, Any] = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index: int = pset["Index"]
|
||||
annotations = get_assignments(element)
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
original_data[group][index] = {
|
||||
@@ -1493,6 +1551,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if parts:
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
# TODO: unused expression.
|
||||
original_data | get_original_data(part)
|
||||
else:
|
||||
try:
|
||||
@@ -2486,6 +2545,264 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.is_changing_mode = False
|
||||
|
||||
|
||||
class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.direct_profile_edit"
|
||||
bl_label = "IFC Direct Profile Edit"
|
||||
bl_description = "Directly enter profile/axis edit mode, or exit back to object mode"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# Only allow in OBJECT or EDIT modes
|
||||
if context.mode not in ("OBJECT", "EDIT_MESH", "EDIT_CURVE"):
|
||||
return False
|
||||
|
||||
# Must have an active object
|
||||
if not context.active_object:
|
||||
return False
|
||||
|
||||
# In edit mode, we're good to go (we'll exit)
|
||||
if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
|
||||
return True
|
||||
|
||||
# In object mode, check if it's an editable object type
|
||||
obj = context.active_object
|
||||
|
||||
# Only mesh and curve objects are editable
|
||||
if obj.type not in ("MESH", "CURVE"):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
# Ensure we're only working with the active object
|
||||
# Deselect all other objects if multiple are selected
|
||||
active_obj = context.active_object
|
||||
if active_obj and context.mode == "OBJECT":
|
||||
if len(context.selected_objects) > 1:
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
"Currently Direct Profile Edit only works on one object at a time. Operating on active object only.",
|
||||
)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
active_obj.select_set(True)
|
||||
context.view_layer.objects.active = active_obj
|
||||
|
||||
# If we're already in edit mode, exit to object mode and SAVE changes
|
||||
if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
|
||||
return self.handle_exit_edit_mode(context)
|
||||
|
||||
# We're in object mode, try to enter profile/axis edit
|
||||
return self.handle_enter_edit_mode(context)
|
||||
|
||||
def handle_exit_edit_mode(self, context):
|
||||
"""Handle exiting from edit mode and saving changes."""
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check if we're editing a representation item
|
||||
if tool.Geometry.is_representation_item(obj):
|
||||
return self.exit_item_edit_mode(context, obj)
|
||||
|
||||
# Check if we're editing an element-level profile or axis
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and tool.Geometry.has_mesh_properties(obj.data):
|
||||
return self.exit_element_edit_mode(context, obj, element)
|
||||
|
||||
# For other edit modes, use standard operator
|
||||
return bpy.ops.bim.override_mode_set_object()
|
||||
|
||||
def exit_item_edit_mode(self, context, obj):
|
||||
"""Exit from SweptAreaSolid item editing."""
|
||||
try:
|
||||
item = tool.Geometry.get_active_representation(obj)
|
||||
if not item or not item.is_a("IfcSweptAreaSolid"):
|
||||
return bpy.ops.bim.override_mode_set_object()
|
||||
|
||||
# Use the standard save workflow for items
|
||||
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
ProfileDecorator.uninstall()
|
||||
tool.Blender.toggle_edit_mode(context)
|
||||
|
||||
profile = tool.Model.export_profile(obj)
|
||||
if not profile:
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="INVALID PROFILE")
|
||||
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
tool.Blender.toggle_edit_mode(context)
|
||||
ProfileDecorator.install(context)
|
||||
return {"CANCELLED"}
|
||||
|
||||
old_profile = item.SweptArea
|
||||
profile.ProfileName = old_profile.ProfileName
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
for inverse in ifc_file.get_inverse(old_profile):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
|
||||
|
||||
tool.Profile.replace_profile_in_profiles_ui(old_profile.id(), profile.id())
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, old_profile)
|
||||
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
if props.representation_obj:
|
||||
tool.Geometry.reload_representation(props.representation_obj)
|
||||
tool.Geometry.disable_item_mode()
|
||||
|
||||
return {"FINISHED"}
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to save item changes: {str(e)}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def exit_element_edit_mode(self, context, obj, element):
|
||||
"""Exit from element-level profile or axis editing."""
|
||||
try:
|
||||
mesh_props = tool.Geometry.get_mesh_props(obj.data)
|
||||
|
||||
# Check if we're editing axis
|
||||
if mesh_props.subshape_type == "AXIS":
|
||||
return bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
# Otherwise, we're editing a profile
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if body:
|
||||
body = ifcopenshell.util.representation.resolve_representation(body)
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
if extrusion:
|
||||
return bpy.ops.bim.edit_extrusion_profile()
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to save profile/axis changes: {str(e)}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Fallback to standard operator
|
||||
return bpy.ops.bim.override_mode_set_object()
|
||||
|
||||
def handle_enter_edit_mode(self, context):
|
||||
"""Handle entering edit mode from object mode."""
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check if object has mesh or curve data
|
||||
if not hasattr(obj, "data") or not obj.data:
|
||||
self.report({"INFO"}, "Object has no editable data")
|
||||
return {"CANCELLED"}
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
self.report({"INFO"}, "Active object is not an IFC element")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check if this is a LAYER2 element (wall, railing, etc.)
|
||||
try:
|
||||
material_usage = tool.Model.get_usage_type(element)
|
||||
except:
|
||||
material_usage = None
|
||||
|
||||
if material_usage == "LAYER2":
|
||||
self.report({"ERROR"}, "LAYER2 elements (walls, railings, etc.) cannot use profile editing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Try to get representation
|
||||
try:
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
except:
|
||||
representation = None
|
||||
|
||||
if not representation:
|
||||
self.report({"INFO"}, "Object has no active representation")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check for PROFILE usage (beams, columns, members) - should edit axis
|
||||
if material_usage == "PROFILE":
|
||||
try:
|
||||
return bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to enable axis editing: {str(e)}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Check if this is an element with an extrusion profile (LAYER3, etc)
|
||||
try:
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if body:
|
||||
body = ifcopenshell.util.representation.resolve_representation(body)
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
if extrusion and not tool.Geometry.is_representation_item(obj):
|
||||
return bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to enable profile editing: {str(e)}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Otherwise, try to enter item mode and edit SweptAreaSolid
|
||||
return self.enter_item_edit_mode(context, obj)
|
||||
|
||||
def enter_item_edit_mode(self, context, obj):
|
||||
"""Enter item mode for SweptAreaSolid editing."""
|
||||
try:
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
if not props.representation_obj:
|
||||
bpy.ops.bim.import_representation_items()
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
|
||||
# Find the SweptAreaSolid item
|
||||
swept_solid_obj = None
|
||||
for item_obj_data in props.item_objs:
|
||||
item_obj = item_obj_data.obj
|
||||
if not item_obj:
|
||||
continue
|
||||
try:
|
||||
item = tool.Geometry.get_active_representation(item_obj)
|
||||
if item and item.is_a("IfcSweptAreaSolid"):
|
||||
swept_solid_obj = item_obj
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not swept_solid_obj:
|
||||
self.report({"INFO"}, "No editable SweptAreaSolid item found")
|
||||
tool.Geometry.disable_item_mode()
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Select and activate the swept solid object
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
swept_solid_obj.select_set(True)
|
||||
context.view_layer.objects.active = swept_solid_obj
|
||||
|
||||
# Import the profile to prepare for editing
|
||||
item = tool.Geometry.get_active_representation(swept_solid_obj)
|
||||
if item and item.is_a("IfcSweptAreaSolid"):
|
||||
profile = item.SweptArea
|
||||
res = tool.Model.import_profile(profile, obj=swept_solid_obj)
|
||||
if res is None:
|
||||
self.report({"ERROR"}, "Couldn't import profile for editing")
|
||||
tool.Geometry.disable_item_mode()
|
||||
return {"CANCELLED"}
|
||||
|
||||
tool.Ifc.link(item, swept_solid_obj.data)
|
||||
tool.Blender.toggle_edit_mode(context)
|
||||
|
||||
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
ProfileDecorator.install(context)
|
||||
|
||||
if not bpy.app.background:
|
||||
tool.Blender.set_viewport_tool("bim.cad_tool")
|
||||
|
||||
return {"FINISHED"}
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to enter item edit mode: {str(e)}")
|
||||
try:
|
||||
tool.Geometry.disable_item_mode()
|
||||
except:
|
||||
pass
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class FlipObject(bpy.types.Operator):
|
||||
bl_idname = "bim.flip_object"
|
||||
bl_label = "Flip Object"
|
||||
|
||||
@@ -96,11 +96,12 @@ def update_shadow_mode(self: "BIMSolarProperties", context: bpy.types.Context) -
|
||||
sun_props = tool.Blender.get_sun_props()
|
||||
assert sun_props
|
||||
if sun_props.sun_object is None:
|
||||
bpy.ops.object.light_add(type="SUN", radius=1, align="WORLD", location=(0, 0, 0), scale=(1, 1, 1))
|
||||
bpy.ops.object.move_to_collection(collection_index=0)
|
||||
sun_props.sun_object = bpy.context.active_object
|
||||
light = bpy.data.lights.new(name="Sun", type="SUN")
|
||||
sun = bpy.data.objects.new("Sun", light)
|
||||
context.scene.collection.objects.link(sun)
|
||||
sun_props.sun_object = sun
|
||||
update_sun_path(self)
|
||||
context.scene.render.engine = "BLENDER_EEVEE_NEXT"
|
||||
context.scene.render.engine = tool.Blender.get_eevee_name()
|
||||
assert context.scene.display
|
||||
assert context.scene.display.shading
|
||||
context.scene.display.shading.light = "FLAT"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import calendar
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -192,24 +193,7 @@ class BIM_PT_solar(bpy.types.Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(props, "year")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(
|
||||
props,
|
||||
"month",
|
||||
text={
|
||||
1: "January",
|
||||
2: "February",
|
||||
3: "March",
|
||||
4: "April",
|
||||
5: "May",
|
||||
6: "June",
|
||||
7: "July",
|
||||
8: "August",
|
||||
9: "September",
|
||||
10: "October",
|
||||
11: "November",
|
||||
12: "December",
|
||||
}[props.month],
|
||||
)
|
||||
row.prop(props, "month", text=calendar.month_name[props.month])
|
||||
row.prop(props, "day")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -478,7 +478,7 @@ def update_door_modifier_bmesh(context: bpy.types.Context) -> None:
|
||||
|
||||
class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_door"
|
||||
bl_label = "Door"
|
||||
bl_label = "Add Door"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -348,24 +348,136 @@ class BIMArrayProperties(PropertyGroup):
|
||||
|
||||
|
||||
def update_total_length_target(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||
"""Update tread_run when total_length_target changes"""
|
||||
# Calculate available length for default treads
|
||||
available_length = self.total_length_target
|
||||
n_default_treads = self.number_of_treads + 1 # number of risers
|
||||
|
||||
# Subtract custom first tread if not locked and not zero
|
||||
if not self.custom_tread_lock and self.custom_first_last_tread_run[0] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
|
||||
# Subtract custom last tread if not locked and not zero
|
||||
if not self.custom_tread_lock and self.custom_first_last_tread_run[1] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
# Calculate tread_run for remaining treads
|
||||
if n_default_treads > 0:
|
||||
self["tread_run"] = available_length / n_default_treads
|
||||
else:
|
||||
# All treads are custom, just use target length
|
||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||
|
||||
|
||||
def update_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||
"""Update either number_of_treads or total_length_target when tread_run changes"""
|
||||
if self.total_length_lock:
|
||||
self["number_of_treads"] = int((self.total_length_target / self.tread_run) - 1)
|
||||
# Calculate how much length custom treads take up
|
||||
custom_length = 0
|
||||
n_custom_treads = 0
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
custom_length += self.custom_first_last_tread_run[0]
|
||||
n_custom_treads += 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
custom_length += self.custom_first_last_tread_run[1]
|
||||
n_custom_treads += 1
|
||||
|
||||
# Calculate how many default treads fit in remaining space
|
||||
available_length = self.total_length_target - custom_length
|
||||
if self.tread_run > 0:
|
||||
n_default_treads = available_length / self.tread_run
|
||||
total_treads = n_default_treads + n_custom_treads
|
||||
# number_of_treads = number_of_risers - 1
|
||||
self["number_of_treads"] = int(total_treads - 1)
|
||||
else:
|
||||
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
|
||||
# Calculate total length from current settings
|
||||
n_default_treads = self.number_of_treads + 1
|
||||
total_length = 0
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
total_length += self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
total_length += self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
total_length += n_default_treads * self.tread_run
|
||||
self["total_length_target"] = total_length
|
||||
|
||||
|
||||
def update_number_of_treads(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||
"""Update either tread_run or total_length_target when number_of_treads changes"""
|
||||
if self.total_length_lock:
|
||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||
# Calculate available length for default treads
|
||||
available_length = self.total_length_target
|
||||
n_default_treads = self.number_of_treads + 1
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
if n_default_treads > 0:
|
||||
self["tread_run"] = available_length / n_default_treads
|
||||
else:
|
||||
self["tread_run"] = self.total_length_target / (self.number_of_treads + 1)
|
||||
else:
|
||||
self["total_length_target"] = (self.number_of_treads + 1) * self.tread_run
|
||||
# Calculate total length from current settings
|
||||
n_default_treads = self.number_of_treads + 1
|
||||
total_length = 0
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
total_length += self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
total_length += self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
total_length += n_default_treads * self.tread_run
|
||||
self["total_length_target"] = total_length
|
||||
|
||||
|
||||
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
|
||||
def update_custom_first_last_tread_run(self: "BIMStairProperties", context: bpy.types.Context) -> None:
|
||||
"""Update tread_run or total_length when custom treads change"""
|
||||
if self.total_length_lock:
|
||||
# Recalculate tread_run to maintain total length
|
||||
available_length = self.total_length_target
|
||||
n_default_treads = self.number_of_treads + 1
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
available_length -= self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
if n_default_treads > 0:
|
||||
self["tread_run"] = available_length / n_default_treads
|
||||
else:
|
||||
# Recalculate total length
|
||||
n_default_treads = self.number_of_treads + 1
|
||||
total_length = 0
|
||||
|
||||
if not self.custom_tread_lock:
|
||||
if self.custom_first_last_tread_run[0] != 0:
|
||||
total_length += self.custom_first_last_tread_run[0]
|
||||
n_default_treads -= 1
|
||||
if self.custom_first_last_tread_run[1] != 0:
|
||||
total_length += self.custom_first_last_tread_run[1]
|
||||
n_default_treads -= 1
|
||||
|
||||
total_length += n_default_treads * self.tread_run
|
||||
self["total_length_target"] = total_length
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
@@ -408,7 +520,7 @@ class BIMStairProperties(PropertyGroup):
|
||||
has_top_nib: bpy.props.BoolProperty(name="Has Top Nib", default=True)
|
||||
stair_type: bpy.props.EnumProperty(
|
||||
name="Stair Type",
|
||||
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(StairType)],
|
||||
items=[(i, i.replace("/", " / ").title(), "") for i in get_args(tool.Model.StairType)],
|
||||
default="CONCRETE",
|
||||
update=validate_nosing_value,
|
||||
)
|
||||
@@ -425,6 +537,7 @@ class BIMStairProperties(PropertyGroup):
|
||||
min=0,
|
||||
unit="LENGTH",
|
||||
size=2,
|
||||
update=update_custom_first_last_tread_run, # Added update callback
|
||||
)
|
||||
nosing_length: bpy.props.FloatProperty(
|
||||
name="Nosing Length",
|
||||
@@ -492,16 +605,18 @@ class BIMStairProperties(PropertyGroup):
|
||||
}
|
||||
stair_kwargs.update(generic_props)
|
||||
|
||||
non_si_units_props = self.non_si_units_props
|
||||
# If locked, use tread_run for both first and last treads
|
||||
if self.custom_tread_lock:
|
||||
stair_kwargs["custom_first_last_tread_run"] = (self.tread_run, self.tread_run)
|
||||
non_si_units_props += ("custom_first_last_tread_run",)
|
||||
stair_kwargs["custom_first_last_tread_run"] = (None, None)
|
||||
else:
|
||||
stair_kwargs["custom_first_last_tread_run"] = self.custom_first_last_tread_run
|
||||
|
||||
if not convert_to_project_units:
|
||||
return stair_kwargs
|
||||
|
||||
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
|
||||
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, non_si_units_props)
|
||||
return stair_kwargs
|
||||
|
||||
def get_props_kwargs_for_ifc_export(self, convert_to_project_units=False, stair_type=None):
|
||||
@@ -513,14 +628,19 @@ class BIMStairProperties(PropertyGroup):
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
tread_run = kwargs.get("tread_run", 0.3)
|
||||
|
||||
# Determine lock state based on whether custom treads match tread_run
|
||||
# If custom_tread_lock wasn't saved (old files), infer it from the data
|
||||
if "custom_tread_lock" not in kwargs:
|
||||
custom_treads = kwargs.get("custom_first_last_tread_run", (0.0, 0.0))
|
||||
tread_run = kwargs.get("tread_run", 0.3)
|
||||
# Lock is off if either custom tread differs from tread_run and is not 0
|
||||
kwargs["custom_tread_lock"] = not any(ct != 0.0 and ct != tread_run for ct in custom_treads)
|
||||
kwargs["custom_tread_lock"] = all(ct not in (0.0, tread_run) for ct in custom_treads)
|
||||
|
||||
if "custom_first_last_tread_run" in kwargs:
|
||||
custom_treads = kwargs["custom_first_last_tread_run"]
|
||||
custom_treads = [tread_run if v is None else v for v in custom_treads]
|
||||
kwargs["custom_first_last_tread_run"] = custom_treads
|
||||
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ def update_ifc_stair_props(obj: bpy.types.Object) -> None:
|
||||
|
||||
class BIM_OT_add_stair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_stair"
|
||||
bl_label = "Stair"
|
||||
bl_label = "Add Stair"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bl_ui_utils.layout
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import Panel, Menu
|
||||
@@ -36,7 +35,12 @@ from bonsai.bim.module.model.stair import regenerate_stair_mesh
|
||||
from bonsai.bim.module.model.railing import update_railing_modifier_bmesh
|
||||
from bonsai.bim.module.model.roof import update_roof_modifier_bmesh
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
|
||||
import _bl_ui_utils.layout as bl_ui_utils_layout
|
||||
else:
|
||||
import bl_ui_utils.layout as bl_ui_utils_layout
|
||||
|
||||
|
||||
class BIM_MT_type_manager_menu(bpy.types.Menu):
|
||||
@@ -56,7 +60,7 @@ class BIM_MT_type_menu(bpy.types.Menu):
|
||||
def draw(self, context):
|
||||
props = tool.Model.get_model_props()
|
||||
layout = self.layout
|
||||
with bl_ui_utils.layout.operator_context(layout, "INVOKE_REGION_WIN"):
|
||||
with bl_ui_utils_layout.operator_context(layout, "INVOKE_REGION_WIN"):
|
||||
op = layout.operator("bim.rename_type", icon="GREASEPENCIL", text="Rename Type")
|
||||
op.element = props.menu_relating_type_id
|
||||
op = layout.operator("bim.select_type", icon="OBJECT_DATA")
|
||||
|
||||
@@ -399,7 +399,7 @@ def update_window_modifier_bmesh(context: bpy.types.Context) -> None:
|
||||
|
||||
class BIM_OT_add_window(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_window"
|
||||
bl_label = "Window"
|
||||
bl_label = "Add Window"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1322,6 +1322,20 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
if len(bpy.context.selected_objects) != 2:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"Exactly two LAYER2 items (walls, railings, etc) must be selected to perform a merge.",
|
||||
)
|
||||
return
|
||||
for item in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(item)
|
||||
if tool.Model.get_usage_type(element) != "LAYER2":
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"Both selected items must be LAYER2 (walls, railings, etc) to perform a merge.",
|
||||
)
|
||||
return
|
||||
bpy.ops.bim.merge_wall()
|
||||
else:
|
||||
if len(bpy.context.selected_objects) == 1:
|
||||
|
||||
@@ -859,8 +859,23 @@ def get_gross_top_area(obj: bpy.types.Object, angle: float = 45) -> float:
|
||||
|
||||
entity = ifc.by_guid(opening_id)
|
||||
open_obj = tool.Ifc.get_object(entity)
|
||||
assert isinstance(open_obj, bpy.types.Object)
|
||||
opening_area += get_net_top_area(open_obj, angle=angle)
|
||||
|
||||
# Check if the opening has a Blender object representation
|
||||
if open_obj is None:
|
||||
# If no Blender object exists, create a temporary mesh from the IFC geometry
|
||||
try:
|
||||
mesh = get_gross_element_mesh(entity)
|
||||
temp_obj = bpy.data.objects.new("TempOpening", mesh)
|
||||
opening_area += get_net_top_area(temp_obj, angle=angle)
|
||||
delete_obj(temp_obj)
|
||||
delete_mesh(mesh)
|
||||
except Exception as e:
|
||||
# If we can't create geometry, skip this opening
|
||||
print(f"Warning: Could not calculate opening area for {opening_id}: {e}")
|
||||
continue
|
||||
else:
|
||||
# Opening has a Blender representation, use it directly
|
||||
opening_area += get_net_top_area(open_obj, angle=angle)
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import ifcopenshell.api
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.qto as core
|
||||
from bonsai.bim.module.qto import helper
|
||||
from ifcopenshell.util.profiler import Profiler
|
||||
|
||||
|
||||
class CalculateCircleRadius(bpy.types.Operator):
|
||||
@@ -148,7 +149,8 @@ class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
}
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
with Profiler("Quantify function time:"):
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
|
||||
not_quantified_elements = elements - set(results.keys())
|
||||
@@ -190,7 +192,8 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
rules = ifc5d.qto.rules[rule]
|
||||
ifc_file = tool.Ifc.get()
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
with Profiler("Quantify function time:"):
|
||||
results = ifc5d.qto.quantify(ifc_file, elements, rules)
|
||||
ifc5d.qto.edit_qtos(ifc_file, results)
|
||||
not_quantified_elements = elements - set(results.keys())
|
||||
return not_quantified_elements
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.spatial as core
|
||||
import bonsai.bim.handler
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -233,7 +234,7 @@ class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""
|
||||
Copies selected 3D elements in the viewport to the selected spatial containers
|
||||
Copies selected 3D elements in the viewport to the container selected in Spatial Manager.
|
||||
|
||||
Example: bulk copy a wall to multiple storeys
|
||||
|
||||
@@ -242,9 +243,13 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
Copying containers to other containers currently is not supported."""
|
||||
|
||||
bl_idname = "bim.copy_to_container"
|
||||
bl_label = "Copy To Container"
|
||||
bl_label = "Copy to Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
container: bpy.props.IntProperty()
|
||||
|
||||
container: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
container: int
|
||||
|
||||
def _execute(self, context):
|
||||
objs = tool.Spatial.get_selected_objects_without_containers()
|
||||
|
||||
@@ -455,7 +455,7 @@ class ActivateExternalStyle(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.copy_material_attributes(db["data_block"], material)
|
||||
if material.use_nodes:
|
||||
if tool.Style.get_use_nodes(material):
|
||||
tool.Blender.copy_node_graph(material, db["data_block"])
|
||||
bpy.data.materials.remove(db["data_block"])
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -99,7 +99,8 @@ class BIMSystemProperties(PropertyGroup):
|
||||
systems: CollectionProperty(name="Systems", type=System)
|
||||
expanded_groups_json: StringProperty(name="Expanded Systems JSON", default="[]")
|
||||
"""See `expanded_groups_json`. We name it "groups" for compatibility with Group UI code."""
|
||||
active_system_index: IntProperty(name="Active System Index")
|
||||
# Named not as `active_system_index` to match `BIMGroupProperties`.
|
||||
active_group_index: IntProperty(name="Active System Index")
|
||||
active_system_id: IntProperty(name="Active System Id")
|
||||
edited_system_id: IntProperty(name="Edited System Id")
|
||||
system_class: EnumProperty(items=get_system_class, name="Class")
|
||||
@@ -113,7 +114,7 @@ class BIMSystemProperties(PropertyGroup):
|
||||
is_adding: bool
|
||||
systems: bpy.types.bpy_prop_collection_idprop[System]
|
||||
expanded_groups_json: str
|
||||
active_system_index: int
|
||||
active_group_index: int
|
||||
active_system_id: int
|
||||
edited_system_id: int
|
||||
system_class: str
|
||||
@@ -121,7 +122,7 @@ class BIMSystemProperties(PropertyGroup):
|
||||
|
||||
@property
|
||||
def active_system_ui_item(self) -> Union[System, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.systems, self.active_system_index)
|
||||
return tool.Blender.get_active_uilist_element(self.systems, self.active_group_index)
|
||||
|
||||
|
||||
def update_active_zone_index(self: "BIMZoneProperties", context: object) -> None:
|
||||
|
||||
@@ -114,7 +114,7 @@ class BIM_PT_systems(Panel):
|
||||
self.props,
|
||||
"systems",
|
||||
self.props,
|
||||
"active_system_index",
|
||||
"active_group_index",
|
||||
)
|
||||
|
||||
if self.props.edited_system_id:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -786,7 +786,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
material = bpy.data.materials.get("Section Override")
|
||||
if not material:
|
||||
material = bpy.data.materials.new("Section Override")
|
||||
material.use_nodes = True
|
||||
tool.Style.set_use_nodes(material, True)
|
||||
|
||||
props = tool.Blender.get_bim_props()
|
||||
if props.should_section_selected_objects:
|
||||
@@ -809,7 +809,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator):
|
||||
def override_materials(self):
|
||||
override = bpy.data.node_groups.get("Section Override")
|
||||
for material in bpy.data.materials:
|
||||
material.use_nodes = True
|
||||
tool.Style.set_use_nodes(material, True)
|
||||
if material.node_tree.nodes.get("Section Override"):
|
||||
continue
|
||||
# In EEVEE rendering engine, `blend_mode` is deprecated and replaced by `surface_render_method`
|
||||
|
||||
@@ -20,7 +20,12 @@ import ifcopenshell
|
||||
import ifcopenshell.util.pset
|
||||
import bonsai.tool as tool
|
||||
import bpy
|
||||
import bpy_restrict_state
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING or bpy.app.version >= (5, 0, 0):
|
||||
import _bpy_restrict_state as bpy_restrict_state
|
||||
else:
|
||||
import bpy_restrict_state
|
||||
|
||||
|
||||
class IfcSchema:
|
||||
|
||||
+48
-10
@@ -324,25 +324,51 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
bl_idname = tool.Blender.get_blender_addon_package_name()
|
||||
svg2pdf_command: StringProperty(
|
||||
name="SVG to PDF Command",
|
||||
description='print sheet to pdf together with svg. Leave blank svg is just created, E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "svg", "-o", "pdf"]]',
|
||||
description=(
|
||||
"print sheet to pdf together with svg. Leave blank svg is just created, "
|
||||
'E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "svg", "-o", "pdf"]]'
|
||||
'\n `"svg"`, `"pdf"` will be replaced automatically.'
|
||||
),
|
||||
)
|
||||
svg2dxf_command: StringProperty(
|
||||
name="SVG to DXF Command",
|
||||
description='E.g. [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]',
|
||||
description=(
|
||||
'E.g. `[["inkscape", "svg", "-o", "eps"], '
|
||||
'["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]`'
|
||||
'\n `"svg"`, `"eps"`, `"dxf"` will be replaced automatically.'
|
||||
),
|
||||
)
|
||||
svg_command: StringProperty(
|
||||
name="SVG Command",
|
||||
description='Software to open generated drawing and sheets. Leave blank system default for .svg is used E.g. [["path to application eg. /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "path"]]',
|
||||
description=(
|
||||
"Software to open generated drawing and sheets. Leave blank system default for .svg is used "
|
||||
'E.g. [["path to application eg. /Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", "path"]]'
|
||||
'\n `"path"` will be replaced with the path to the generated .svg file.'
|
||||
),
|
||||
)
|
||||
layout_svg_command: StringProperty(
|
||||
name="Layout SVG Command",
|
||||
description='Software to open layouts, before generated. Leave blank system default for .svg is used E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]',
|
||||
description=(
|
||||
"Software to open layouts, before generated. Leave blank system default for .svg is used. "
|
||||
'E.g. `[["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]`'
|
||||
'\n `"path"` will be replaced with the path to the generated .svg file.'
|
||||
),
|
||||
)
|
||||
pdf_command: StringProperty(
|
||||
name="PDF Command",
|
||||
description='Software to open .pdf, leave blank uses system default. E.g. [["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]',
|
||||
description=(
|
||||
"Software to open .pdf, leave blank uses system default. "
|
||||
'E.g. `[["path to application eg. /Applications/Inkscape.app/Contents/MacOS/inkscape", "path"]]`'
|
||||
'\n `"path"` will be replaced with the path to the generated .pdf file.'
|
||||
),
|
||||
)
|
||||
spreadsheet_command: StringProperty(
|
||||
name="Spreadsheet Command",
|
||||
description=(
|
||||
'E.g. [["libreoffice", "path"]]'
|
||||
'\n `"path"` will be replaced with the path to the generated spreadsheet file.'
|
||||
),
|
||||
)
|
||||
spreadsheet_command: StringProperty(name="Spreadsheet Command", description='E.g. [["libreoffice", "path"]]')
|
||||
should_hide_empty_props: BoolProperty(
|
||||
name="Hide Empty Properties",
|
||||
default=True,
|
||||
@@ -351,12 +377,18 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
should_setup_workspace: BoolProperty(
|
||||
name="Setup Workspace Layout for BIM",
|
||||
default=True,
|
||||
description="If enabled, this will add a default workspace dedicated to working with BIM models.\nIt is recommended to keep this `Enabled`",
|
||||
description=(
|
||||
"If enabled, this will add a default workspace dedicated to working with BIM models.\n"
|
||||
"It is recommended to keep this `Enabled`"
|
||||
),
|
||||
)
|
||||
activate_workspace: BoolProperty(
|
||||
name="Activate BIM Workspace on Startup",
|
||||
default=True,
|
||||
description="If enabled, this will automatically activate the BIM workspace when opening a project.\nIt is recommended to keep this `Enabled`",
|
||||
description=(
|
||||
"If enabled, this will automatically activate the BIM workspace when opening a project.\n"
|
||||
"It is recommended to keep this `Enabled`"
|
||||
),
|
||||
)
|
||||
should_setup_toolbar: BoolProperty(
|
||||
name="Always Show Toolbar In 3D Viewport",
|
||||
@@ -366,7 +398,9 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
should_use_snap: BoolProperty(
|
||||
name="Enable Snapping on Startup",
|
||||
default=True,
|
||||
description="If enabled, snapping will be enabled on new sessions.\nIt is recommended to keep this `Enabled`",
|
||||
description=(
|
||||
"If enabled, snapping will be enabled on new sessions.\n" "It is recommended to keep this `Enabled`"
|
||||
),
|
||||
)
|
||||
should_play_chaching_sound: BoolProperty(name="Play A Cha-Ching Sound When Project Costs Updates", default=False)
|
||||
tmp_dir: StringProperty(
|
||||
@@ -428,7 +462,10 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
max=100,
|
||||
subtype="PERCENTAGE",
|
||||
name="Non-Openings Opacity",
|
||||
description="When modifying openings, other elements of the model will display with some transparency.\n0 is fully transparent and 100 is fully opaque",
|
||||
description=(
|
||||
"When modifying openings, other elements of the model will display with some transparency.\n"
|
||||
"0 is fully transparent and 100 is fully opaque"
|
||||
),
|
||||
)
|
||||
|
||||
bsdd_load_preview_dictionaries: BoolProperty(
|
||||
@@ -498,6 +535,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
|
||||
should_setup_workspace: bool
|
||||
activate_workspace: bool
|
||||
should_setup_toolbar: bool
|
||||
should_use_snap: bool
|
||||
should_play_chaching_sound: bool
|
||||
tmp_dir: str
|
||||
decorations_colour: tuple[float, float, float, float]
|
||||
|
||||
@@ -60,10 +60,18 @@ def edit_assigned_product(
|
||||
) -> None:
|
||||
element = ifc.get_entity(obj)
|
||||
assert element
|
||||
existing_product = drawing.get_assigned_product(element)
|
||||
if existing_product != product:
|
||||
if existing_product:
|
||||
|
||||
# Temporarily accomodate existing bug and check for multiple products.
|
||||
existing_products = drawing.get_assigned_product_workaround(element)
|
||||
if existing_products != [product]:
|
||||
|
||||
if product in existing_products:
|
||||
assert product is not None
|
||||
existing_products.remove(product)
|
||||
|
||||
for existing_product in existing_products:
|
||||
ifc.run("drawing.unassign_product", relating_product=existing_product, related_object=element)
|
||||
|
||||
if product:
|
||||
ifc.run("drawing.assign_product", relating_product=product, related_object=element)
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ def remove_representation(
|
||||
assert element
|
||||
element_type = geometry.get_element_type(element)
|
||||
data = None
|
||||
has_switched_from_data = False
|
||||
data_removed_by_switch_representation = False
|
||||
if element_type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
|
||||
representation = geometry.resolve_mapped_representation(representation)
|
||||
data = geometry.get_representation_data(representation)
|
||||
@@ -155,7 +155,7 @@ def remove_representation(
|
||||
for element in geometry.get_elements_of_type(element_type):
|
||||
obj = ifc.get_object(element)
|
||||
if obj:
|
||||
has_switched_from_data = True
|
||||
data_removed_by_switch_representation = True
|
||||
geometry.switch_from_representation(obj, representation)
|
||||
obj = ifc.get_object(element_type)
|
||||
if obj:
|
||||
@@ -164,12 +164,13 @@ def remove_representation(
|
||||
else:
|
||||
data = geometry.get_representation_data(representation)
|
||||
if data and geometry.has_data_users(data):
|
||||
has_switched_from_data = True
|
||||
data_removed_by_switch_representation = True
|
||||
geometry.switch_from_representation(obj, representation)
|
||||
ifc.run("geometry.unassign_representation", product=element, representation=representation)
|
||||
|
||||
ifc.run("geometry.remove_representation", representation=representation)
|
||||
if data and not has_switched_from_data:
|
||||
|
||||
if data and not data_removed_by_switch_representation:
|
||||
geometry.delete_data(data)
|
||||
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ def assign_class(
|
||||
ifc_representation_class: Optional[str] = None,
|
||||
) -> Optional[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Args:
|
||||
context: is not optional if `should_add_representation` is True
|
||||
:param context: is not optional if ``should_add_representation`` is True
|
||||
|
||||
TODO: Do NOT use should_add_representation. Because it internally calls
|
||||
geometry.add_representation which is 1,000 lines of Blender -> IFC magic.
|
||||
|
||||
@@ -308,6 +308,7 @@ class Document:
|
||||
class Drawing:
|
||||
def activate_drawing(cls, camera): pass
|
||||
def add_literal(cls, **attributes): pass
|
||||
def clear_annotation_relationships(cls, drawing): pass
|
||||
def copy_representation(cls, source, dest): pass
|
||||
def create_annotation_context(cls, target_view, object_type=None): pass
|
||||
def create_annotation_object(cls, drawing, object_type): pass
|
||||
@@ -342,6 +343,7 @@ class Drawing:
|
||||
def get_annotation_context(cls, target_view, object_type=None): pass
|
||||
def get_annotation_representation(cls, element_type): pass
|
||||
def get_assigned_product(cls, element): pass
|
||||
def get_assigned_product_workaround(cls, element): pass
|
||||
def get_body_context(cls): pass
|
||||
def get_default_drawing_path(cls, name): pass
|
||||
def get_default_drawing_resource_path(cls, resource): pass
|
||||
@@ -485,6 +487,19 @@ class Georeference:
|
||||
def xyz2enh(cls, coordinates): pass
|
||||
|
||||
|
||||
@interface
|
||||
class Group:
|
||||
def get_group_props(cls): pass
|
||||
def get_groups_data(cls, group_type): pass
|
||||
def import_groups(cls, group_type): pass
|
||||
def enable_group_editing_ui(cls) : pass
|
||||
def disable_group_editing_ui(cls): pass
|
||||
def disable_editing_group(cls): pass
|
||||
def set_active_group_to_edit(cls, group): pass
|
||||
def toggle_group(cls, group, group_type, option): pass
|
||||
def update_uilist_index(cls, group_type): pass
|
||||
|
||||
|
||||
@interface
|
||||
class Ifc:
|
||||
def get(cls): pass
|
||||
@@ -854,8 +869,6 @@ class Sequence:
|
||||
def get_animation_product_frames(cls, work_schedule, settings): pass
|
||||
def get_animation_settings(cls): pass
|
||||
def get_checked_tasks(cls): pass
|
||||
def get_direct_nested_tasks(cls, task):pass
|
||||
def get_direct_task_outputs(cls, task): pass
|
||||
def get_finish_date(cls): pass
|
||||
def get_highlighted_task(cls): pass
|
||||
def get_lag_time_attributes(cls): pass
|
||||
|
||||
@@ -100,6 +100,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
- (identifier, name, description, icon, number)
|
||||
"""
|
||||
BLENDER_ENUM_ITEMS = Iterable[BLENDER_ENUM_ITEM]
|
||||
BLENDER_5 = bpy.app.version >= (5, 0, 0)
|
||||
|
||||
@classmethod
|
||||
def activate_camera(cls, obj: bpy.types.Object) -> None:
|
||||
@@ -416,7 +417,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {}
|
||||
) -> Union[bpy.types.ShaderNode, None]:
|
||||
"""returns first node from the `blender_material` shader graph with type `node_type`"""
|
||||
if not blender_material.use_nodes:
|
||||
if not tool.Style.get_use_nodes(blender_material):
|
||||
return
|
||||
nodes = blender_material.node_tree.nodes
|
||||
for node in nodes:
|
||||
@@ -709,7 +710,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
|
||||
new_selected_objects = [o for o in selected_objects if cls.is_valid_data_block(o) and o in view_layer_objects]
|
||||
|
||||
if active_object and (not cls.is_valid_data_block(active_object) or active_object not in view_layer_objects):
|
||||
if active_object and not cls.is_valid_data_block(active_object):
|
||||
active_object = None
|
||||
|
||||
return context, active_object, new_selected_objects
|
||||
@@ -1064,7 +1065,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
"""Tries to validate the current BIM modifier parameters for the active object
|
||||
Goes into path editing mode if the modifier supports it
|
||||
|
||||
Returns True if an action was taken, False otherwise
|
||||
:return: True if an action was taken, False otherwise
|
||||
"""
|
||||
if cls.is_roof(element):
|
||||
if cls.is_editing_roof_parameters(obj):
|
||||
@@ -1088,7 +1089,7 @@ class Blender(bonsai.core.tool.Blender):
|
||||
def try_canceling_editing_modifier_parameters_or_path(cls, obj: bpy.types.Object) -> bool:
|
||||
"""Tries to cancel the current BIM modifier parameters or path edition for the active object
|
||||
|
||||
Returns True if an action was taken, False otherwise
|
||||
:return: True if an action was taken, False otherwise
|
||||
"""
|
||||
if cls.is_editing_railing_path(obj):
|
||||
bpy.ops.bim.cancel_editing_railing_path()
|
||||
@@ -1671,12 +1672,10 @@ class Blender(bonsai.core.tool.Blender):
|
||||
- "user_interface.wcol_menu.text" (Menu Text)
|
||||
- "user_interface.wcol_menu.text_sel" (Menu Text Selected)
|
||||
|
||||
Args:
|
||||
color_path (str, optional): The attribute path relative to bpy.context.preferences.themes[0].
|
||||
threshold (float, optional): The RGB sum threshold for determining dark mode. Default is 1.671.
|
||||
:param color_path: The attribute path relative to bpy.context.preferences.themes[0].
|
||||
:param threshold: The RGB sum threshold for determining dark mode. Default is 1.671.
|
||||
|
||||
Returns:
|
||||
str: 'dm' (dark mode) if the RGB sum is > threshold, otherwise 'lm' (light mode).
|
||||
:return: 'dm' (dark mode) if the RGB sum is > threshold, otherwise 'lm' (light mode).
|
||||
"""
|
||||
full_path = f"bpy.context.preferences.themes[0].{color_path}"
|
||||
|
||||
@@ -2083,3 +2082,14 @@ class Blender(bonsai.core.tool.Blender):
|
||||
assert bpy.context.preferences
|
||||
if props_updated and bpy.context.preferences.use_preferences_save:
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
@classmethod
|
||||
def get_eevee_name(cls) -> Literal["BLENDER_EEVEE"] | Literal["BLENDER_EEVEE_NEXT"]:
|
||||
"""Convenience method to get correct eevee render engine name in multiple Blender versions.
|
||||
|
||||
In Blender 4.2 eevee was renamed to "eevee next".
|
||||
In Blender 5 eevee next is now just "eevee" again.
|
||||
"""
|
||||
if cls.BLENDER_5:
|
||||
return "BLENDER_EEVEE"
|
||||
return "BLENDER_EEVEE_NEXT"
|
||||
|
||||
+208
-20
@@ -33,6 +33,7 @@ import sys
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import numpy as np
|
||||
import mathutils.geometry
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
import itertools
|
||||
@@ -188,6 +189,33 @@ class Cad:
|
||||
"""
|
||||
return geometry.intersect_line_plane(v1, v2, plane_co, plane_no)
|
||||
|
||||
def intersect_edge_plane_v2(v1, v2, plane_co, plane_no, eps=1e-9):
|
||||
"""
|
||||
Numpy version of intersect_edge_plane
|
||||
> takes an edges as two vector, and a plane as origin point and normal
|
||||
< return the intersection point or None
|
||||
"""
|
||||
|
||||
# References: https://rosettacode.org/wiki/Find_the_intersection_of_a_line_with_a_plane#Python,
|
||||
# https://stackoverflow.com/a/18543221
|
||||
|
||||
p0 = np.array((v1.x, v1.y, v1.z), dtype=np.float64)
|
||||
p1 = np.array((v2.x, v2.y, v2.z), dtype=np.float64)
|
||||
pc = np.array((plane_co.x, plane_co.y, plane_co.z), dtype=np.float64)
|
||||
n = np.array((plane_no.x, plane_no.y, plane_no.z), dtype=np.float64)
|
||||
|
||||
u = p1 - p0
|
||||
dot = np.dot(u, n)
|
||||
if abs(dot) < eps:
|
||||
# Line is parallel to plane (no intersection or lies in plane)
|
||||
return None
|
||||
|
||||
w = pc - p0
|
||||
fac = np.dot(w, n) / dot
|
||||
p = p0 + fac * u
|
||||
|
||||
return Vector(p)
|
||||
|
||||
@classmethod
|
||||
def intersect_edges(
|
||||
cls, edge1: tuple[Vector, Vector], edge2: tuple[Vector, Vector]
|
||||
@@ -215,47 +243,50 @@ class Cad:
|
||||
def intersect_edges_v2(cls, edge1, edge2):
|
||||
"""
|
||||
Calculate the closest points on two line segments.
|
||||
Note: This function doesn't use intersect_line_line
|
||||
Note: This function doesn't use intersect_line_line and uses Numpy for calculations
|
||||
|
||||
> edge1: tuple of two vectors (v1, v2) representing the first segment
|
||||
> edge2: tuple of two vectors (v3, v4) representing the second segment
|
||||
< returns: tuple of two vectors (C1, C2) or (None, None) if lines are parallel
|
||||
"""
|
||||
# This function seems to work better then intersect_line_line
|
||||
# This function seems to work better than intersect_line_line
|
||||
# in orthogonal view
|
||||
# https://en.wikipedia.org/wiki/Skew_lines#Nearest_points
|
||||
|
||||
is_2d = False
|
||||
# Starting and ending points
|
||||
P1, P1_end = edge1
|
||||
P2, P2_end = edge2
|
||||
if len(P1) == 2:
|
||||
p1, p1_end = edge1
|
||||
p2, p2_end = edge2
|
||||
if len(p1) == 2:
|
||||
is_2d = True
|
||||
P1, P1_end = P1.to_3d(), P1_end.to_3d()
|
||||
P2, P2_end = P2.to_3d(), P2_end.to_3d()
|
||||
p1, p1_end = p1.to_3d(), p1_end.to_3d()
|
||||
p2, p2_end = p2.to_3d(), p2_end.to_3d()
|
||||
|
||||
p1 = np.array((p1.x, p1.y, p1.z), dtype=np.float64)
|
||||
p1_end = np.array((p1_end.x, p1_end.y, p1_end.z), dtype=np.float64)
|
||||
p2 = np.array((p2.x, p2.y, p2.z), dtype=np.float64)
|
||||
p2_end = np.array((p2_end.x, p2_end.y, p2_end.z), dtype=np.float64)
|
||||
# Directions
|
||||
d1 = (P1_end - P1).normalized()
|
||||
d2 = (P2_end - P2).normalized()
|
||||
d1 = p1_end - p1
|
||||
d2 = p2_end - p2
|
||||
d1 /= np.linalg.norm(d1) or 1 # equivalent of Vector.normalized() or Vector / Vector.length
|
||||
d2 /= np.linalg.norm(d2) or 1
|
||||
|
||||
n = d1.cross(d2)
|
||||
n = np.cross(d1, d2)
|
||||
|
||||
# if n is zero, lines are parallel
|
||||
if abs(n.length) < 1e-6:
|
||||
if abs(np.linalg.norm(n)) < 1e-6:
|
||||
return None, None
|
||||
|
||||
n2 = d2.cross(n)
|
||||
|
||||
C1 = P1 + ((P2 - P1).dot(n2) / (d1.dot(n2))) * d1
|
||||
|
||||
n1 = d1.cross(n)
|
||||
|
||||
C2 = P2 + ((P1 - P2).dot(n1) / (d2.dot(n1))) * d2
|
||||
n2 = np.cross(d2, n)
|
||||
c1 = p1 + (np.dot((p2 - p1), n2) / (np.dot(d1, n2))) * d1
|
||||
n1 = np.cross(d1, n)
|
||||
c2 = p2 + (np.dot((p1 - p2), n1) / (np.dot(d2, n1))) * d2
|
||||
|
||||
if is_2d:
|
||||
return C1.to_2d(), C2.to_2d()
|
||||
return Vector(c1[:2].copy()), Vector(c2[:2].copy())
|
||||
else:
|
||||
return C1, C2
|
||||
return Vector(c1), Vector(c2)
|
||||
|
||||
@classmethod
|
||||
def get_intersection(cls, edge1, edge2):
|
||||
@@ -807,3 +838,160 @@ class Cad:
|
||||
v1 = v2
|
||||
|
||||
return new_verts
|
||||
|
||||
@classmethod
|
||||
def region_2d_to_vector_3d_np(cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector) -> Vector:
|
||||
"""
|
||||
Numpy version of view3d_utils.region_2d_to_vector_3d
|
||||
Return a direction vector from the viewport at the specific 2d region
|
||||
coordinate.
|
||||
|
||||
> region: region of the 3D viewport, typically bpy.context.region.
|
||||
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
> coord: 2d coordinates relative to the region:
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
< returns a normalized 3d vector.
|
||||
"""
|
||||
view_m = np.array(rv3d.view_matrix)
|
||||
window_m = np.array(rv3d.window_matrix)
|
||||
viewinv = np.linalg.inv(view_m)
|
||||
|
||||
if rv3d.is_perspective:
|
||||
# For better precision with large numbers, avoid using rv3d.perspective_matrix. See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046
|
||||
# Calculate it with view_matrix and window_matrix as numpy arrays.
|
||||
pers_m = window_m @ view_m
|
||||
persinv = np.linalg.inv(pers_m)
|
||||
|
||||
out = np.array(
|
||||
[(2.0 * coord[0] / region.width) - 1.0, (2.0 * coord[1] / region.height) - 1.0, -0.5, 1.0],
|
||||
dtype=np.float64,
|
||||
)
|
||||
|
||||
w = out[:3].dot(persinv[3, :3]) + persinv[3, 3]
|
||||
|
||||
viewinv_translation = viewinv[:3, 3]
|
||||
view_vector = (persinv.dot(out)[:3] / w) - viewinv_translation
|
||||
else:
|
||||
view_vector = -viewinv[:3, 2].copy() # -Z column
|
||||
|
||||
view_vector /= np.linalg.norm(view_vector) or 1 # equivalent to Vector.normalized()
|
||||
return Vector(view_vector)
|
||||
|
||||
@classmethod
|
||||
def region_2d_to_location_3d_np(
|
||||
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, depth_location: Vector
|
||||
) -> Vector:
|
||||
"""
|
||||
Numpy version of view3d_utils.region_2d_to_location_3d
|
||||
Return a 3d location from the region relative 2d coords, aligned with
|
||||
*depth_location*.
|
||||
|
||||
> region: region of the 3D viewport, typically bpy.context.region.
|
||||
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
> coord: 2d coordinates relative to the region:
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
< returns a normalized 3d vector.
|
||||
"""
|
||||
coord_vec = cls.region_2d_to_vector_3d_np(region, rv3d, coord)
|
||||
depth_location = np.array([depth_location[0], depth_location[1], depth_location[2]], dtype=np.float64)
|
||||
|
||||
origin_start = cls.region_2d_to_origin_3d_np(region, rv3d, coord)
|
||||
origin_end = origin_start + coord_vec
|
||||
|
||||
if rv3d.is_perspective:
|
||||
viewinv = np.linalg.inv(rv3d.view_matrix)
|
||||
view_vec = viewinv[:3, 2].copy()
|
||||
return cls.intersect_edge_plane_v2(
|
||||
Vector(origin_start), Vector(origin_end), Vector(depth_location), Vector(view_vec)
|
||||
)
|
||||
else:
|
||||
return cls.point_on_edge(
|
||||
Vector(depth_location),
|
||||
(Vector(origin_start), Vector(origin_end)),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def region_2d_to_origin_3d_np(
|
||||
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, clamp: float = None
|
||||
) -> Vector:
|
||||
"""
|
||||
Numpy version of view3d_utils.region_2d_to_origin_3d
|
||||
Return the 3d view origin from the region relative 2d coords.
|
||||
|
||||
.. note::
|
||||
|
||||
Orthographic views have a less obvious origin,
|
||||
the far clip is used to define the viewport near/far extents.
|
||||
Since far clip can be a very large value,
|
||||
the result may give with numeric precision issues.
|
||||
|
||||
To avoid this problem, you can optionally clamp the far clip to a
|
||||
smaller value based on the data you're operating on.
|
||||
|
||||
|
||||
> region: region of the 3D viewport, typically bpy.context.region.
|
||||
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
> coord: 2d coordinates relative to the region:
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
> clamp: clamp: Clamp the maximum far-clip value used.
|
||||
(negative value will move the offset away from the view_location)
|
||||
< returns the origin of the viewpoint in 3d space.
|
||||
"""
|
||||
|
||||
view_m = np.array(rv3d.view_matrix)
|
||||
window_m = np.array(rv3d.window_matrix)
|
||||
viewinv = np.linalg.inv(view_m)
|
||||
|
||||
if rv3d.is_perspective:
|
||||
origin_start = viewinv[:3, 3].copy()
|
||||
else:
|
||||
pers_m = window_m @ view_m # See https://github.com/IfcOpenShell/IfcOpenShell/issues/7046
|
||||
persinv = np.linalg.inv(pers_m)
|
||||
|
||||
dx = (2.0 * coord[0] / region.width) - 1.0
|
||||
dy = (2.0 * coord[1] / region.height) - 1.0
|
||||
|
||||
origin_start = (persinv[:3, 0] * dx) + (persinv[:3, 1] * dy) + persinv[:3, 3]
|
||||
|
||||
if clamp != 0.0:
|
||||
if rv3d.view_perspective != "CAMERA":
|
||||
origin_offset = persinv[:3, 2].copy() # column 2
|
||||
if clamp is not None:
|
||||
c = float(clamp)
|
||||
if c < 0.0:
|
||||
origin_offset = -origin_offset
|
||||
c = -c
|
||||
length = np.linalg.norm(origin_offset)
|
||||
if length > c and length > 0.0:
|
||||
origin_offset = (origin_offset / length) * c
|
||||
origin_start = origin_start - origin_offset
|
||||
|
||||
return Vector(origin_start)
|
||||
|
||||
@classmethod
|
||||
def location_3d_to_region_2d_np(
|
||||
cls, region: bpy.types.Region, rv3d: bpy.types.RegionView3d, coord: Vector, *, default=None
|
||||
) -> Vector:
|
||||
"""
|
||||
Numpy version of view3d_utils.location_3d_to_region_2d
|
||||
Return the *region* relative 2d location of a 3d position.
|
||||
|
||||
> region: region of the 3D viewport, typically bpy.context.region.
|
||||
> rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
> coord: 2d coordinates relative to the region:
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
< returns a 2d location.
|
||||
"""
|
||||
pt = np.array((coord[0], coord[1], coord[2], 1.0), dtype=np.float64)
|
||||
view_m = np.array(rv3d.view_matrix)
|
||||
window_m = np.array(rv3d.window_matrix)
|
||||
pers_m = window_m @ view_m
|
||||
prj = pers_m.dot(pt) # 4-vector
|
||||
w = prj[3]
|
||||
if w > 0.0:
|
||||
width_half = region.width / 2.0
|
||||
height_half = region.height / 2.0
|
||||
x = width_half + width_half * (prj[0] / w)
|
||||
y = height_half + height_half * (prj[1] / w)
|
||||
return Vector((float(x), float(y)))
|
||||
return default
|
||||
|
||||
@@ -143,12 +143,11 @@ class Debug(bonsai.core.tool.Debug):
|
||||
|
||||
Note that Styles UI (or other UI) should be updated manually after using this method.
|
||||
|
||||
Args:
|
||||
object_type: The type of object to merge
|
||||
by_name_or_identification_only: If True, merge based only on Name attribute (or equivalent identifier).
|
||||
Strips .XXX suffix patterns (e.g., 'foo.001' matches 'foo', 'foo.002').
|
||||
For PERSON, uses Identification. For APPLICATION, uses ApplicationFullName.
|
||||
For PERSON_AND_ORGANIZATION, uses combination of person and organization identifiers.
|
||||
:param object_type: The type of object to merge
|
||||
:param by_name_or_identification_only: If True, merge based only on Name attribute (or equivalent identifier).
|
||||
Strips .XXX suffix patterns (e.g., 'foo.001' matches 'foo', 'foo.002').
|
||||
For PERSON, uses Identification. For APPLICATION, uses ApplicationFullName.
|
||||
For PERSON_AND_ORGANIZATION, uses combination of person and organization identifiers.
|
||||
"""
|
||||
|
||||
def normalize_name(name: str) -> str:
|
||||
|
||||
@@ -38,12 +38,15 @@ import bonsai.core.geometry
|
||||
import bonsai.core.type
|
||||
import bonsai.tool as tool
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.drawing
|
||||
import ifcopenshell.api.document
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
@@ -220,6 +223,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
def exclude_annotation_from_drawing(
|
||||
cls, element: ifcopenshell.entity_instance, drawing: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=drawing, name="EPset_Drawing")
|
||||
@@ -893,6 +897,33 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
return axis
|
||||
return product
|
||||
|
||||
@classmethod
|
||||
def get_assigned_product_workaround(
|
||||
cls, element: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""Get all products assigned to the element.
|
||||
|
||||
A workaround allowing to unassign accumulated products until we properly resolve #4014.
|
||||
In theory annotations should have more than one product assigned,
|
||||
but there's still undefined bug causing that in some cases.
|
||||
"""
|
||||
|
||||
assigned_products: list[ifcopenshell.entity_instance] = []
|
||||
for rel in element.HasAssignments:
|
||||
if not rel.is_a("IfcRelAssignsToProduct"):
|
||||
continue
|
||||
assigned_products.append(rel.RelatingProduct)
|
||||
|
||||
if len(assigned_products) > 1:
|
||||
print(
|
||||
f"WARNING. Detected multiple assigned products ({len(assigned_products)}) for annotation '{element}'."
|
||||
"\nIf you can reproduce this, please report it to Bonsai developers "
|
||||
"at https://github.com/IfcOpenShell/IfcOpenShell/issues/4014."
|
||||
"\nAssigned products:\n" + "\n".join([str(p) for p in assigned_products])
|
||||
)
|
||||
|
||||
return assigned_products
|
||||
|
||||
@classmethod
|
||||
def import_annotations_in_group(cls, group: ifcopenshell.entity_instance) -> None:
|
||||
elements = set(
|
||||
@@ -1705,11 +1736,22 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
im = camera.matrix_world.inverted()
|
||||
v1, v2 = [im @ Vector((m @ np.append(v, 1.0))[:3]) for v in [v1, v2]]
|
||||
|
||||
target_view = cls.get_drawing_target_view(drawing)
|
||||
bounds = helper.ortho_view_frame(camera.data)
|
||||
if not (points := helper.clip_segment(bounds, [v1, v2])):
|
||||
|
||||
if target_view in ("PLAN_VIEW", "REFLECTED_PLAN_VIEW"):
|
||||
# For plan views, clip to XY bounds and set Z=0
|
||||
if not (points := helper.clip_segment(bounds, [v1, v2])):
|
||||
return
|
||||
for v in points:
|
||||
v.z = 0
|
||||
elif target_view in ("ELEVATION_VIEW", "SECTION_VIEW"):
|
||||
# For section/elevation views, elevate the segment vertically
|
||||
if not (points := helper.elevate_segment(bounds, [v1, v2])):
|
||||
return
|
||||
else:
|
||||
return
|
||||
for v in points:
|
||||
v.z = 0
|
||||
|
||||
return points
|
||||
|
||||
@classmethod
|
||||
@@ -1969,9 +2011,18 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
@classmethod
|
||||
def get_default_annotation_matrix(cls, camera, matrix_world=None):
|
||||
if matrix_world is None:
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
annotation_offset = Vector((0, 0, -camera.data.clip_start - 0.05))
|
||||
annotation_offset = camera.matrix_world.to_quaternion() @ annotation_offset
|
||||
# Use normalized camera matrix (without scale) for RCP compatibility
|
||||
matrix_world = cls.get_camera_matrix(camera)
|
||||
|
||||
# Check if this is an RCP (reflected ceiling plan) by checking for negative scale
|
||||
camera_scale = camera.matrix_world.to_scale()
|
||||
is_rcp = camera_scale.x < 0 or camera_scale.y < 0 or camera_scale.z < 0
|
||||
|
||||
# For RCP, the offset needs to be positive instead of negative
|
||||
# because the Z axis is flipped 180° in get_camera_matrix
|
||||
offset_direction = 1 if is_rcp else -1
|
||||
annotation_offset = Vector((0, 0, offset_direction * (camera.data.clip_start + 0.05)))
|
||||
annotation_offset = matrix_world.to_quaternion() @ annotation_offset
|
||||
matrix_world.translation += annotation_offset
|
||||
return matrix_world
|
||||
|
||||
|
||||
@@ -1146,8 +1146,9 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
def record_object_position(cls, obj: bpy.types.Object) -> None:
|
||||
# These are recorded separately because they have different numerical tolerances
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes())
|
||||
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes())
|
||||
# Explicit dtype for Blender <5.0 compatibility.
|
||||
props.location_checksum = repr(np.array(obj.matrix_world.translation, dtype=np.float32).tobytes())
|
||||
props.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3(), dtype=np.float32).tobytes())
|
||||
|
||||
@classmethod
|
||||
def remove_connection(cls, connection: ifcopenshell.entity_instance) -> None:
|
||||
@@ -1261,7 +1262,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
return False
|
||||
for slot in obj.material_slots:
|
||||
if slot.material and slot.material.use_nodes:
|
||||
if slot.material and tool.Style.get_use_nodes(slot.material):
|
||||
for node in slot.material.node_tree.nodes:
|
||||
if node.type == "TEX_COORD" and node.outputs["UV"].links:
|
||||
return True
|
||||
@@ -1359,6 +1360,8 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
|
||||
If no other representation present, will replace object with an empty.
|
||||
Method assumes that `obj` does have a current representation (it could be not `representation`).
|
||||
|
||||
Will clean up old ``obj.data`` if no other users exist.
|
||||
"""
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
@@ -1377,7 +1380,11 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
|
||||
# `representation` is the only representation for object.
|
||||
if new_representation is None:
|
||||
old_data = obj.data
|
||||
assert old_data is not None
|
||||
cls.recreate_object_with_data(obj, None)
|
||||
if not cls.has_data_users(old_data):
|
||||
cls.delete_data(old_data)
|
||||
return
|
||||
|
||||
bonsai.core.geometry.switch_representation(
|
||||
@@ -2020,7 +2027,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
continue
|
||||
if not (style := tool.Ifc.get_entity(material)):
|
||||
style = ifcopenshell.api.style.add_style(ifc_file, name=material.name)
|
||||
if material.use_nodes:
|
||||
if tool.Style.get_use_nodes(material):
|
||||
ifc_class = "IfcSurfaceStyleRendering"
|
||||
attributes = tool.Style.get_surface_rendering_attributes(material)
|
||||
else:
|
||||
|
||||
@@ -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))
|
||||
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)).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
|
||||
|
||||
@@ -116,7 +116,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
if transparency := surface_style.get("Transparency", None):
|
||||
alpha = 1 - transparency
|
||||
blender_material.diffuse_color = surface_style["SurfaceColour"] + (alpha,)
|
||||
blender_material.use_nodes = False
|
||||
tool.Style.set_use_nodes(blender_material, False)
|
||||
|
||||
@classmethod
|
||||
def restart_material_node_tree(cls, blender_material: bpy.types.Material) -> None:
|
||||
@@ -204,7 +204,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
|
||||
# TODO: reset pins to default values if no values passed
|
||||
if reflectance_method in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
tool.Style.set_use_nodes(blender_material, True)
|
||||
cls.restart_material_node_tree(blender_material)
|
||||
bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED")
|
||||
assert bsdf
|
||||
@@ -231,7 +231,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
blender_material.blend_method = "BLEND"
|
||||
|
||||
elif reflectance_method == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
tool.Style.set_use_nodes(blender_material, True)
|
||||
cls.restart_material_node_tree(blender_material)
|
||||
|
||||
output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL")
|
||||
|
||||
@@ -44,11 +44,8 @@ import mathutils
|
||||
from math import atan, cos, degrees, pi, radians
|
||||
from mathutils import Matrix, Vector
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from bonsai.bim import import_ifc
|
||||
|
||||
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData
|
||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
|
||||
from typing import Optional, Union, TypeVar, Any, Literal, TYPE_CHECKING, TypedDict, assert_never
|
||||
from collections.abc import Iterable, Sequence
|
||||
@@ -145,9 +142,10 @@ class Model(bonsai.core.tool.Model):
|
||||
for prop_name in data:
|
||||
if prop_name in non_si_props:
|
||||
continue
|
||||
prop_value = data[prop_name]
|
||||
# `None` is used by `custom_first_last_tread_run`.
|
||||
prop_value: Iterable[float | None] | float = data[prop_name]
|
||||
if isinstance(prop_value, collections.abc.Iterable):
|
||||
data[prop_name] = [v * si_conversion for v in prop_value]
|
||||
data[prop_name] = [v if v is None else v * si_conversion for v in prop_value]
|
||||
else:
|
||||
data[prop_name] = prop_value * si_conversion
|
||||
return data
|
||||
@@ -1103,6 +1101,7 @@ class Model(bonsai.core.tool.Model):
|
||||
return
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
@@ -1208,6 +1207,8 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
Allows postponing the thumbnail update until it is actually needed by the user.
|
||||
"""
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
|
||||
element_id = element.id()
|
||||
if element_id not in AuthoringData.type_thumbnails:
|
||||
return
|
||||
@@ -1311,23 +1312,34 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
@classmethod
|
||||
def is_parametric_roof_active(cls) -> bool:
|
||||
from bonsai.bim.module.model.data import RoofData
|
||||
|
||||
return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"])
|
||||
|
||||
@classmethod
|
||||
def is_parametric_railing_active(cls) -> bool:
|
||||
from bonsai.bim.module.model.data import RailingData
|
||||
|
||||
return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"])
|
||||
|
||||
@classmethod
|
||||
def is_parametric_window_active(cls) -> bool:
|
||||
from bonsai.bim.module.model.data import WindowData
|
||||
|
||||
return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"])
|
||||
|
||||
@classmethod
|
||||
def is_parametric_door_active(cls) -> bool:
|
||||
from bonsai.bim.module.model.data import DoorData
|
||||
|
||||
return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"])
|
||||
|
||||
CustomTreadRunType = Union[tuple[float, float], tuple[None, None]]
|
||||
|
||||
@classmethod
|
||||
def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
||||
props = bpy.context.active_object.BIMStairProperties
|
||||
assert (obj := bpy.context.active_object)
|
||||
props = tool.Model.get_stair_props(obj)
|
||||
|
||||
if props.is_editing:
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
@@ -1338,38 +1350,48 @@ class Model(bonsai.core.tool.Model):
|
||||
last_tread_run = props.custom_first_last_tread_run[1] / si_conversion
|
||||
nosing_length = props.nosing_length / si_conversion
|
||||
else:
|
||||
number_of_treads = pset_data["number_of_treads"]
|
||||
height = pset_data["height"]
|
||||
tread_run = pset_data["tread_run"]
|
||||
assert pset_data
|
||||
number_of_treads: int = pset_data["number_of_treads"]
|
||||
height: float = pset_data["height"]
|
||||
tread_run: float = pset_data["tread_run"]
|
||||
# use .get to not break the old .ifc models
|
||||
custom_first_last_tread_run = pset_data.get("custom_first_last_tread_run", (0, 0))
|
||||
custom_first_last_tread_run: tool.Model.CustomTreadRunType = pset_data.get(
|
||||
"custom_first_last_tread_run", (0, 0)
|
||||
)
|
||||
first_tread_run, last_tread_run = custom_first_last_tread_run
|
||||
nosing_length = pset_data.get("nosing_length", 0)
|
||||
|
||||
calculated_params = {}
|
||||
calculated_params: dict[str, Any] = {}
|
||||
number_of_rises = number_of_treads + 1
|
||||
calculated_params["Number of Risers"] = number_of_rises
|
||||
calculated_params["Tread Rise"] = round(height / number_of_rises, 5)
|
||||
|
||||
# calculate stair length
|
||||
# Start with all treads using default tread_run
|
||||
n_default_tread_runs = number_of_rises
|
||||
length = 0
|
||||
if first_tread_run != 0:
|
||||
length = 0.0
|
||||
|
||||
# If first tread has custom width (non-None), use it instead of default.
|
||||
if first_tread_run is not None:
|
||||
n_default_tread_runs -= 1
|
||||
length += first_tread_run
|
||||
if last_tread_run != 0:
|
||||
|
||||
# If last tread has custom width (non-None), use it instead of default
|
||||
if last_tread_run is not None:
|
||||
n_default_tread_runs -= 1
|
||||
if n_default_tread_runs >= 0:
|
||||
length += last_tread_run
|
||||
length += tread_run * max(n_default_tread_runs, 0)
|
||||
# nosing overlaps
|
||||
# are not part of the tread run
|
||||
# so they don't affect the stair length
|
||||
# except the first tread's nosing
|
||||
length += last_tread_run
|
||||
|
||||
# Add remaining default tread runs
|
||||
length += tread_run * n_default_tread_runs
|
||||
|
||||
# Handle nosing length effects on total length
|
||||
# Nosing overlaps don't affect tread run spacing,
|
||||
# but the first tread's nosing extends the total length
|
||||
if nosing_length > 0: # nosing overlaps
|
||||
length += nosing_length
|
||||
if nosing_length < 0: # tread gaps
|
||||
if nosing_length < 0: # tread gaps between treads
|
||||
length += abs(nosing_length) * number_of_treads
|
||||
|
||||
calculated_params["Length"] = round(length, 5)
|
||||
pitch = height / length
|
||||
pitch_formatted = str(round(pitch * 100, 1)) + " % / " + str(round(degrees(atan(pitch)), 1)) + " deg"
|
||||
@@ -1377,35 +1399,38 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
return calculated_params
|
||||
|
||||
StairType = Literal["CONCRETE", "WOOD/STEEL", "GENERIC"]
|
||||
|
||||
@classmethod
|
||||
def generate_stair_2d_profile(
|
||||
cls,
|
||||
number_of_treads,
|
||||
height,
|
||||
width,
|
||||
tread_run,
|
||||
stair_type,
|
||||
number_of_treads: int,
|
||||
height: float,
|
||||
width: float,
|
||||
tread_run: float,
|
||||
stair_type: StairType,
|
||||
# WOOD/STEEL CONCRETE STAIR ARGUMENTS
|
||||
tread_depth=None,
|
||||
tread_depth: Union[float, None] = None,
|
||||
# CONCRETE STAIR ARGUMENTS
|
||||
has_top_nib=None,
|
||||
top_slab_depth=None,
|
||||
base_slab_depth=None,
|
||||
custom_first_last_tread_run=(0, 0),
|
||||
nosing_length=0,
|
||||
has_top_nib: Union[bool, None] = None,
|
||||
top_slab_depth: Union[float, None] = None,
|
||||
base_slab_depth: Union[float, None] = None,
|
||||
custom_first_last_tread_run: Union[tuple[float, float], tuple[None, None]] = (None, None),
|
||||
nosing_length: float = 0.0,
|
||||
# CONCRETE GENERIC STAIR ARGUMENTS
|
||||
nosing_depth=0,
|
||||
):
|
||||
nosing_depth: float = 0.0,
|
||||
) -> tuple[list[Vector], list[tuple[int, ...]], list[[list[int]]]]:
|
||||
"""returns a tuple of stair profile data: (vertices, edges, faces)"""
|
||||
vertices = []
|
||||
edges = []
|
||||
faces = []
|
||||
vertices: list[Vector] = []
|
||||
edges: list[tuple[int, ...]] = []
|
||||
faces: list[[list[int]]] = []
|
||||
|
||||
number_of_risers = number_of_treads + 1
|
||||
tread_rise = height / number_of_risers
|
||||
nosing_overlap = max(nosing_length, 0)
|
||||
nosing_tread_gap = -min(nosing_length, 0)
|
||||
nosing_overlap_offset = -V_(nosing_overlap, 0)
|
||||
first_tread_run = custom_first_last_tread_run[0] if custom_first_last_tread_run[0] is not None else tread_run
|
||||
|
||||
def define_generic_stair_treads():
|
||||
vertices.append(Vector([0, 0]))
|
||||
@@ -1421,7 +1446,12 @@ class Model(bonsai.core.tool.Model):
|
||||
elif nosing_depth == 0:
|
||||
default_tread_verts = (V_(-nosing_overlap, tread_rise), V_(tread_run, tread_rise))
|
||||
else: # nosing_overlap > 0 nosing_depth > 0
|
||||
# kind of L shape
|
||||
# kind of L shape:
|
||||
# (2)●───────────────────────────●(3)
|
||||
# |
|
||||
# |
|
||||
# ●──────────────●
|
||||
# (1) (0)
|
||||
default_tread_verts = (
|
||||
V_(0, tread_rise - nosing_depth),
|
||||
V_(-nosing_overlap, tread_rise - nosing_depth),
|
||||
@@ -1470,12 +1500,12 @@ class Model(bonsai.core.tool.Model):
|
||||
current_offset += tread_offset
|
||||
|
||||
if stair_type == "WOOD/STEEL":
|
||||
builder = ShapeBuilder(None)
|
||||
assert tread_depth is not None
|
||||
|
||||
# full tread rectangle
|
||||
def get_tread_verts(*args, **kwargs):
|
||||
fn = partial(builder.get_rectangle_coords, position=V_(0, -(tread_depth - tread_rise)))
|
||||
return [Vector(x) for x in fn(*args, **kwargs)]
|
||||
def get_tread_verts(size: Vector) -> list[Vector]:
|
||||
coords = ShapeBuilder.get_rectangle_coords(position=V_(0, -(tread_depth - tread_rise)), size=size)
|
||||
return [Vector(x) for x in coords]
|
||||
|
||||
default_tread_verts = get_tread_verts(size=V_(tread_run + nosing_overlap, tread_depth))
|
||||
default_tread_offset = V_(tread_run + nosing_tread_gap, tread_rise)
|
||||
@@ -1504,6 +1534,7 @@ class Model(bonsai.core.tool.Model):
|
||||
# each tread is a separate shape
|
||||
cur_offset = V_(0, 0)
|
||||
tread_index = 0
|
||||
|
||||
for i in range(number_of_risers):
|
||||
tread_offset, tread_verts = get_tread_data(i)
|
||||
|
||||
@@ -1538,6 +1569,11 @@ class Model(bonsai.core.tool.Model):
|
||||
elif stair_type == "CONCRETE":
|
||||
define_generic_stair_treads()
|
||||
|
||||
assert has_top_nib is not None
|
||||
assert top_slab_depth is not None
|
||||
assert base_slab_depth is not None
|
||||
assert tread_depth is not None
|
||||
|
||||
# add the nibs
|
||||
# basically we define stair bottom line as a line at `tread_depth` distance
|
||||
# from the tread diagonal line
|
||||
@@ -1551,15 +1587,20 @@ class Model(bonsai.core.tool.Model):
|
||||
# s0 is just a sampled point from the bottom line
|
||||
# we stick to the third point as the first point
|
||||
# is affected by customized tread run
|
||||
s0 = V_(custom_first_last_tread_run[0] or tread_run, tread_rise) + td_vector
|
||||
s0 = V_(first_tread_run, tread_rise) + td_vector
|
||||
# comes from y = stair_tan * x + b
|
||||
b = s0.y - stair_tan * s0.x
|
||||
|
||||
def get_point_on_2d_line(x=None, y=None):
|
||||
if y is None:
|
||||
def get_point_on_2d_line(
|
||||
x: Union[float, None] = None,
|
||||
y: Union[float, None] = None,
|
||||
) -> Vector:
|
||||
if x is not None and y is None:
|
||||
y = stair_tan * x + b
|
||||
elif x is None:
|
||||
elif x is None and y is not None:
|
||||
x = (y - b) / stair_tan
|
||||
else:
|
||||
assert False
|
||||
return V_(x, y)
|
||||
|
||||
# top nib
|
||||
@@ -1603,11 +1644,13 @@ class Model(bonsai.core.tool.Model):
|
||||
else:
|
||||
raise Exception(f"Unsupported stair type: {stair_type}")
|
||||
|
||||
vertices = (v.to_3d().xzy for v in vertices)
|
||||
vertices = [v.to_3d().xzy for v in vertices]
|
||||
return (vertices, edges, faces)
|
||||
|
||||
@classmethod
|
||||
def update_simple_openings(cls, element: ifcopenshell.entity_instance) -> None:
|
||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
fillings = {e: tool.Ifc.get_object(e) for e in tool.Ifc.get_all_element_occurrences(element)}
|
||||
|
||||
@@ -2170,6 +2213,8 @@ class Model(bonsai.core.tool.Model):
|
||||
|
||||
@classmethod
|
||||
def add_filled_opening(cls, voided_obj: bpy.types.Object, filling_obj: bpy.types.Object) -> None:
|
||||
from bonsai.bim.module.model.opening import FilledOpeningGenerator
|
||||
|
||||
FilledOpeningGenerator().generate(filling_obj, voided_obj)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -172,23 +172,24 @@ 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
|
||||
|
||||
# Round angle to the nearest 0.05
|
||||
angle = round(angle / 0.05) * 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
|
||||
)
|
||||
|
||||
# Round angle to the nearest 0.05
|
||||
orientation_angle = round(orientation_angle / 0.05) * 0.05
|
||||
orientation_angle = round(orientation_angle / 0.05) * 0.05 if distance < angle_round_threshold else angle
|
||||
|
||||
if distance == 0:
|
||||
angle = 0
|
||||
orientation_angle = 0
|
||||
if input_ui:
|
||||
if should_round:
|
||||
angle = 5 * round(angle / 5)
|
||||
angle = 5 * round(angle / 5) if distance < angle_round_threshold else angle
|
||||
factor = tool.Snap.get_increment_snap_value(context)
|
||||
distance = factor * round(distance / factor)
|
||||
input_ui.set_value("X", mouse_vector.x)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import shutil
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.document
|
||||
@@ -502,3 +503,13 @@ class Project(bonsai.core.tool.Project):
|
||||
d2 = v3 - v2
|
||||
normals.append((v1, d1.cross(d2).normalized()))
|
||||
return normals
|
||||
|
||||
TEMP_PROJECT_PATH = Path.cwd() / "test/files/temp/test_project.ifc"
|
||||
|
||||
@classmethod
|
||||
def save_test_project(cls) -> None:
|
||||
tmp = cls.TEMP_PROJECT_PATH.parent
|
||||
# Clean up previous test project to avoid conflicts with non-updated assets.
|
||||
if tmp.exists():
|
||||
shutil.rmtree(tmp)
|
||||
bpy.ops.bim.save_project(filepath=cls.TEMP_PROJECT_PATH.__str__(), should_save_as=True)
|
||||
|
||||
@@ -93,7 +93,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
return None
|
||||
|
||||
for v in bbox:
|
||||
coord_2d = view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, v)
|
||||
coord_2d = tool.Cad.location_3d_to_region_2d_np(context.region, context.space_data.region_3d, v)
|
||||
if coord_2d is not None:
|
||||
transposed_bbox.append(coord_2d)
|
||||
|
||||
@@ -180,8 +180,11 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
if not mouse_pos:
|
||||
mouse_pos = event.mouse_region_x, event.mouse_region_y
|
||||
|
||||
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_pos)
|
||||
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_pos)
|
||||
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_target = ray_origin + view_vector
|
||||
ray_direction = ray_target - ray_origin
|
||||
|
||||
@@ -247,7 +250,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
|
||||
except:
|
||||
loc = Vector((0, 0, 0))
|
||||
|
||||
@@ -267,7 +270,9 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
points.append(snap_point)
|
||||
return points
|
||||
if obj and obj.type == "CURVE":
|
||||
mw = obj.matrix_world.copy()
|
||||
obj = bpy.data.objects.new("new_object", obj.to_mesh().copy())
|
||||
obj.matrix_world = mw @ obj.matrix_world
|
||||
|
||||
if not custom_bmesh:
|
||||
bm = bmesh.new()
|
||||
@@ -340,7 +345,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
|
||||
except:
|
||||
loc = Vector((0, 0, 0))
|
||||
|
||||
@@ -399,8 +404,8 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
default_container_elevation = 0.0
|
||||
intersection = Vector((0, 0, default_container_elevation))
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
intersection = mathutils.geometry.intersect_line_plane(ray_target, loc, plane_origin, plane_normal)
|
||||
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
|
||||
intersection = tool.Cad.intersect_edge_plane_v2(ray_target, loc, plane_origin, plane_normal)
|
||||
except:
|
||||
intersection = Vector((0, 0, default_container_elevation))
|
||||
|
||||
@@ -418,7 +423,7 @@ class Raycast(bonsai.core.tool.Raycast):
|
||||
snap_threshold = cls.calculate_snap_threshold(rv3d.view_distance)
|
||||
|
||||
try:
|
||||
loc = view3d_utils.region_2d_to_location_3d(region, rv3d, mouse_pos, ray_direction)
|
||||
loc = tool.Cad.region_2d_to_location_3d_np(region, rv3d, mouse_pos, ray_direction)
|
||||
except:
|
||||
loc = Vector((0, 0, 0))
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user