mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Compare commits
559 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dac137801 | |||
| 8ffa6fcf47 | |||
| 8824e6e2c2 | |||
| 5dbfe539ac | |||
| d1453e1139 | |||
| 5b5dcb3c6d | |||
| 4f467d199e | |||
| a6d20c0cc4 | |||
| d5d942bb2e | |||
| 9e2c7e653f | |||
| 200c0bf773 | |||
| 381b0d379a | |||
| f48b5b9d92 | |||
| 1f47aadc94 | |||
| bb3cd7e49c | |||
| edd01d44de | |||
| 52b899d070 | |||
| 008292df0f | |||
| 87bfd182d7 | |||
| 8fcc622b8a | |||
| 9415878f57 | |||
| 919ac41a74 | |||
| 80093f3777 | |||
| 28f5d6e91c | |||
| 550779deee | |||
| e2cf60f0c1 | |||
| 546a042d40 | |||
| 86b89f2153 | |||
| 34f4aa430a | |||
| 67678cb7f1 | |||
| f05b0d946d | |||
| 486651e0b7 | |||
| 5885924bb7 | |||
| ead849625d | |||
| f52aafdd6a | |||
| b0ae0a2c82 | |||
| 4d688170e0 | |||
| 322a2179e5 | |||
| 8014980b88 | |||
| 65237cab65 | |||
| 155827528a | |||
| fb15435296 | |||
| 6bba3ebeeb | |||
| 3343f3fb35 | |||
| 1b451d45cf | |||
| f5f676cf51 | |||
| 1dd477d382 | |||
| 96d05300bb | |||
| 0073a4c0a2 | |||
| d3a5071d9d | |||
| 471bd34fc6 | |||
| 44cc12763b | |||
| b68aca04ae | |||
| dd22264877 | |||
| cc6ef0268f | |||
| feecb89c8e | |||
| cb5c488cfd | |||
| b68e8dedd2 | |||
| 0975eb455e | |||
| df0e8bba8c | |||
| 71d5239823 | |||
| 92ae79f64e | |||
| 9c1f636cd4 | |||
| 23c60b38fd | |||
| 89c039252b | |||
| 60d85b4259 | |||
| e8a382b02a | |||
| 2b3ec66017 | |||
| 97dd37b345 | |||
| e4877e8246 | |||
| e216d345c1 | |||
| 14c1b70768 | |||
| e15f222a19 | |||
| 4ab2664529 | |||
| bd0cfa5366 | |||
| 2e4b0e83a2 | |||
| cf61111386 | |||
| 3aa5ddb070 | |||
| 25dacc8fed | |||
| 7cba63af9c | |||
| 3c1d6abc7e | |||
| 8da4b9f23f | |||
| 99fa77edee | |||
| 839c5ad932 | |||
| b68ce08811 | |||
| e662308665 | |||
| a2ccff67ca | |||
| 8e045d772c | |||
| 16fd614886 | |||
| f31df307c6 | |||
| 11873a7c51 | |||
| ccccbe23f4 | |||
| 2173a562be | |||
| a11f81f2ce | |||
| d3b553c4fb | |||
| 5eb11c3516 | |||
| c195799801 | |||
| ac5a44a962 | |||
| 86880e9e62 | |||
| f8ccdf1170 | |||
| e3d8473d47 | |||
| bd5d4cecbf | |||
| 2ede8a2206 | |||
| cb77f9bd26 | |||
| 0bb21fedae | |||
| 478f90c2d4 | |||
| a016f6e5c8 | |||
| 1b2fa4f994 | |||
| 2d8383efc6 | |||
| add5132dec | |||
| fe333051df | |||
| 8d1b533f91 | |||
| fb304e141a | |||
| bd094f930e | |||
| b61000d2fe | |||
| c72cadb234 | |||
| 10642177a9 | |||
| 0b8ea28463 | |||
| 9b24f54f0d | |||
| 289645dacc | |||
| c365acb517 | |||
| 785cf56621 | |||
| 60617eecce | |||
| 26c68c00b7 | |||
| ab0a602f9f | |||
| 066326ab3b | |||
| 02713dbd39 | |||
| d58dae5d2f | |||
| ca3685900f | |||
| 5e10e61aca | |||
| 92e25ce3cb | |||
| 804a670454 | |||
| efa8382fc5 | |||
| 92f9d5f506 | |||
| 8800c14105 | |||
| ec2d0bfa4a | |||
| d684d47dc8 | |||
| babf95785c | |||
| f6ebf80fab | |||
| ea2cff3311 | |||
| bf4aa8cbb1 | |||
| 8b751577f4 | |||
| 6fbe982623 | |||
| 0b93694097 | |||
| 2027c71d99 | |||
| 6da51c2a6e | |||
| 53def55dc9 | |||
| 8722350c0b | |||
| 34c92d4d9c | |||
| 725cb40ef2 | |||
| 0c81b41ac7 | |||
| a8a82450a5 | |||
| 1b5b9fdcf6 | |||
| af1e542064 | |||
| 78154d17bf | |||
| 9bc8012aa4 | |||
| 407962ec93 | |||
| 21fd09b424 | |||
| 2264e69a1e | |||
| e02d6c3f4b | |||
| ec0abb7b37 | |||
| 477becab0b | |||
| 60bf424461 | |||
| 923e8423f9 | |||
| e228095391 | |||
| 1492a660c8 | |||
| 7a931ea6f2 | |||
| 2d59bb9cb8 | |||
| 06f5ded038 | |||
| 91f371a46e | |||
| 573e53ebfd | |||
| a492be5971 | |||
| ceac85ca02 | |||
| bfa981709a | |||
| c64b477e53 | |||
| 4f7501e1e8 | |||
| a1f20ec7bb | |||
| e2ee6c6e56 | |||
| 6940a05f2b | |||
| ff8f4ecd3c | |||
| de2bd3ff1a | |||
| cfa21b3cbf | |||
| db7c3e3b01 | |||
| 0532ceb029 | |||
| 0fe869c169 | |||
| bcde68d851 | |||
| 960a90e36c | |||
| aea0395fd6 | |||
| 416d51f7c7 | |||
| a9b13fd056 | |||
| 080056806e | |||
| 7a33a9eb02 | |||
| 07ba483a13 | |||
| 4925741060 | |||
| 7c4505b2fd | |||
| f9ee838cd2 | |||
| cce0c39ab2 | |||
| cd57fd4b1d | |||
| 6b705f0448 | |||
| 9cff6c87f0 | |||
| cac41064e0 | |||
| 350cf0406d | |||
| 47fd52b7c1 | |||
| 8a33e2fea1 | |||
| d4c02a312a | |||
| c20294b993 | |||
| 7500631b81 | |||
| 7cc1a849a6 | |||
| 4df93af1fd | |||
| fc1c8f2071 | |||
| deedffa84e | |||
| e8d924426f | |||
| 5a3f809d03 | |||
| 58ffd8bf62 | |||
| b22452aaac | |||
| 6c05e2ea8c | |||
| a1773b6f46 | |||
| b3583d4191 | |||
| 50e8d9f67e | |||
| 0d4819d6e7 | |||
| ce91d296b6 | |||
| 21137b9268 | |||
| 0dc319e510 | |||
| 798beb0030 | |||
| a717b348e0 | |||
| 14ed4d1d08 | |||
| bd3250aff6 | |||
| 1ee5bb73fa | |||
| 9a6dc54f6c | |||
| ab59173135 | |||
| b6abb24858 | |||
| 2b24d5ebe5 | |||
| 10a99fff05 | |||
| 64aa1d07fb | |||
| 730eafa74f | |||
| c077727374 | |||
| 2e4241e435 | |||
| 8a03a1379e | |||
| 8bb7ef5e25 | |||
| da4508ef9a | |||
| f3dedba320 | |||
| f659b9bb7b | |||
| 488dee82d7 | |||
| 3c4020be8d | |||
| ede4e49c0c | |||
| 3d2f361585 | |||
| 4505ed1c98 | |||
| 4cbad8804f | |||
| 3ec7b71565 | |||
| c0ec2e7e2b | |||
| 048a94ddc5 | |||
| ec43415822 | |||
| 235eb3c83c | |||
| 4d0737c49b | |||
| b499686c7c | |||
| 1857d06245 | |||
| 663d233e3c | |||
| fa1936c10a | |||
| 8c89ba497b | |||
| 2284ddd68c | |||
| 8daf6b0b04 | |||
| e7a6f86299 | |||
| cae711b393 | |||
| 566c9afa77 | |||
| 867852688e | |||
| 94851016d7 | |||
| 1dd3c298df | |||
| c384ac26cd | |||
| a57080da74 | |||
| 71e59fe4c2 | |||
| aedae0b3c1 | |||
| 658f9b87a5 | |||
| fa9105e174 | |||
| d0d5f972df | |||
| f5220fba56 | |||
| 365f4f114b | |||
| b5b64d6c4a | |||
| 632e45af18 | |||
| 559636ef0b | |||
| 5a2ed576c4 | |||
| cc7732ecbf | |||
| a3c6e295c8 | |||
| 87069f3ac3 | |||
| 6469f79343 | |||
| a3b04997ef | |||
| 4c1af9e2f5 | |||
| 646f2f2e32 | |||
| 629d6c85ae | |||
| be66d4884d | |||
| 5de96c9f95 | |||
| c661bb62ca | |||
| b1b95ec2db | |||
| 5f50e6fcf1 | |||
| 23b48c43a6 | |||
| 75e1d0e3c7 | |||
| 0d942f968a | |||
| b5ccb93618 | |||
| 41fb8ca583 | |||
| a86904dd41 | |||
| 8a82343e27 | |||
| 8a841b449a | |||
| 24bbe9fb69 | |||
| 9a38455465 | |||
| ced52862a1 | |||
| f0cd8c59c7 | |||
| e795cec56b | |||
| b1248be1b8 | |||
| 79672f795e | |||
| 6ec12fe7c7 | |||
| 4ee6863d43 | |||
| 6497d5f78e | |||
| 5b96df89ea | |||
| ce4ad3c3a7 | |||
| 8d714ed3bc | |||
| 9d99de2340 | |||
| 41f9b1522e | |||
| 42b1cf039d | |||
| db832946e6 | |||
| 05e8680b48 | |||
| e8b1b6f783 | |||
| 9367da61a0 | |||
| c7e53e7762 | |||
| 2260f9983c | |||
| e68cc7201c | |||
| e2b39620a0 | |||
| 36da945e2d | |||
| 371aeed3f6 | |||
| 86902c159b | |||
| f4be8812e2 | |||
| 12bcf1d2c7 | |||
| 10e805b3f1 | |||
| 1e9239b352 | |||
| 7bea3d9d10 | |||
| e9bad17502 | |||
| be79d3712d | |||
| e13dbd4767 | |||
| c61d4bb70a | |||
| ee632b7c37 | |||
| f86e5abb85 | |||
| b5275f5ab2 | |||
| a925226cbc | |||
| 06a89f9b17 | |||
| 231d047ce9 | |||
| a50fc3184b | |||
| de8c8d3bed | |||
| f8e523daff | |||
| 8ca9bd63dc | |||
| 761b69ebfe | |||
| e28979ceb7 | |||
| d552f247fa | |||
| 8ef083e3af | |||
| 49bbf0cb9e | |||
| a2f0bc8a17 | |||
| 62295beab2 | |||
| a0b93265c2 | |||
| 338d3cf089 | |||
| 39702cc543 | |||
| a9c0c1f4ba | |||
| abfb56350d | |||
| fcb263f59a | |||
| 679fa6d876 | |||
| 876c5ac2e6 | |||
| 29a86c6748 | |||
| a4a04e6941 | |||
| 9aafa32008 | |||
| 4f8e37d733 | |||
| 311f279b61 | |||
| 3ad7c52cc4 | |||
| a5d9fe551a | |||
| 1672a7da32 | |||
| 10dcd9a923 | |||
| be72887216 | |||
| 5400712a7a | |||
| 1dea8ca37b | |||
| f945a94fed | |||
| 14ae8680e3 | |||
| 9ec0732a84 | |||
| 7b490bbc69 | |||
| 0cf3bb3da9 | |||
| 062bf8e60b | |||
| f457a7d831 | |||
| 1861baaaa9 | |||
| 39253c6a6d | |||
| 21137de8a0 | |||
| a53017d9dc | |||
| 70286f2bcb | |||
| 26467a1d0e | |||
| 09f9b23153 | |||
| 6ac95e1ed3 | |||
| 25c0ed6759 | |||
| f75d629990 | |||
| b492110652 | |||
| 942cb586b2 | |||
| 49b9f99557 | |||
| 15b69bfa98 | |||
| 5f964bd33c | |||
| 274a5cbfad | |||
| b6fa1759ef | |||
| f016d72787 | |||
| 8c12bd0322 | |||
| 90e6838ddb | |||
| 5304072892 | |||
| 59a5bf2344 | |||
| d4918edcd9 | |||
| d901506029 | |||
| aaf980b6b3 | |||
| d419e50ece | |||
| d5ff48fbc9 | |||
| 99ca1c7eb4 | |||
| df4f565176 | |||
| ed37ac3066 | |||
| 1433240ae6 | |||
| d9fe751163 | |||
| 555621ceae | |||
| d7ddbd2b99 | |||
| eba3ced5fe | |||
| d36205ea8b | |||
| a831884ee6 | |||
| 01b64674ab | |||
| 78958567cf | |||
| e6ea58cd72 | |||
| 6c607241a2 | |||
| 18ad13cb8b | |||
| 55e54f0b37 | |||
| 453949ee01 | |||
| e4d08ce6b7 | |||
| ce02e7a2a0 | |||
| f43ddf2cd5 | |||
| 9d0eb4f994 | |||
| 67fb569669 | |||
| da9be7cff0 | |||
| a839183ac2 | |||
| 34bf374047 | |||
| 43c35eaf24 | |||
| d91362a6f7 | |||
| 76e3b8dd55 | |||
| d004410ccb | |||
| abbc5c2159 | |||
| ae771cb37d | |||
| b1a0b7a7d0 | |||
| 556eb128d6 | |||
| dfaa8d510c | |||
| 93c2a4fe66 | |||
| df726a2d61 | |||
| 2cf5011813 | |||
| 9330e73a70 | |||
| a73c60a467 | |||
| f29b0d776a | |||
| e6a7c5334b | |||
| 54b448cdb4 | |||
| 6bbff22a45 | |||
| 51ae3f4c5e | |||
| df1b748b39 | |||
| 01ea1f627d | |||
| 0d3545c621 | |||
| 12cfa21d9b | |||
| c8a60cc4d5 | |||
| 6702e912e7 | |||
| b5aeab6404 | |||
| 7eea995b88 | |||
| d9265765c5 | |||
| d2a560d144 | |||
| 974e62290c | |||
| 1a1583405c | |||
| ef10c23842 | |||
| 77acfd7069 | |||
| 79546807d9 | |||
| e14974a1b5 | |||
| 4b5aa1221c | |||
| b448bea279 | |||
| a219d335ea | |||
| 47f7afb33b | |||
| 3245d0af5c | |||
| b6712602e2 | |||
| 2c6956ec02 | |||
| 322fbe27d6 | |||
| a65fa1e606 | |||
| 71a6bf25bc | |||
| 43fbdf57c8 | |||
| 39100b7c97 | |||
| 1cd32be2fc | |||
| c090073858 | |||
| c97a1faecd | |||
| 9dd7b488d6 | |||
| 4dca91c56e | |||
| b92871818a | |||
| d01bf9690d | |||
| 604e0b0d94 | |||
| 8420b98f3f | |||
| 46693baacf | |||
| c665c9d7e5 | |||
| b05c83e612 | |||
| a958f1373c | |||
| b2c20b15b4 | |||
| b9b12520f7 | |||
| 11a048bf18 | |||
| 5a06715843 | |||
| 6d9650e826 | |||
| 2369f72798 | |||
| 234cb76cd7 | |||
| 0cd5c673c3 | |||
| 1de5940294 | |||
| a0cc94a614 | |||
| 4835829700 | |||
| e5fe2df552 | |||
| 5719c3db2b | |||
| 9109fcec12 | |||
| 4e71ae7d85 | |||
| 96336d6f68 | |||
| 115d3d4498 | |||
| 6095c523d7 | |||
| 71f1a7d0a4 | |||
| a0c92638d2 | |||
| ff3fa48332 | |||
| 8888e45115 | |||
| 92d4a89a43 | |||
| ea53c96134 | |||
| 2f62064814 | |||
| 722e819e11 | |||
| 862aa71a77 | |||
| e920379ca3 | |||
| 4011666162 | |||
| 9d9e9ed4cb | |||
| 9331e89d15 | |||
| 8903d6a85c | |||
| cb551e1248 | |||
| 915c38947d | |||
| 190d9f8e3f | |||
| d2c7c1532c | |||
| f7e8643f30 | |||
| dccd9d4139 | |||
| d31aafac5b | |||
| 2949a86f09 | |||
| 5a597d1a84 | |||
| 61c4797b8f | |||
| c935b2fc12 | |||
| 425de7b857 | |||
| 8c466eab00 | |||
| 929611f384 | |||
| 8af4afa781 | |||
| 8a3c979172 | |||
| 15bbb3b779 | |||
| 6cefb2e991 | |||
| 10682fb226 | |||
| df6b7a34f7 | |||
| 67932a22c3 | |||
| 9f49ad5f71 | |||
| ce4407ff3c | |||
| c58c8269af | |||
| c0f148cd3c | |||
| 1d9dcb3ab7 | |||
| a420c4b707 | |||
| 048f77cfed | |||
| b4d0cfe0c8 | |||
| 69139dee5c | |||
| e82cfda6a3 | |||
| 5c9ff09517 | |||
| fb1d78b311 | |||
| da4e86a1d5 |
@@ -11,7 +11,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos
|
||||
runner: macos-13
|
||||
runner: macos-14
|
||||
arch: x64
|
||||
oldarch:
|
||||
- os: macos
|
||||
@@ -42,7 +42,8 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install git bison autoconf automake libffi cmake findutils
|
||||
# preinstalled: xz, cmake
|
||||
brew install git bison autoconf automake libffi findutils
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install aws cli
|
||||
@@ -51,20 +52,44 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}-${{ matrix.os }}
|
||||
key: mac-${{ matrix.arch }}
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "macos" ]; then
|
||||
DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE
|
||||
fi
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
if [ "${{ matrix.arch }}" == "x64" ]; then
|
||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
MAC_INTEL=-mac-cross-compile-intel
|
||||
# We don't use gmpcxx, but it comes preinstalled on macos runner as arm64 bottle
|
||||
# and CGAL detects it and breaks cross compilation.
|
||||
brew uninstall --ignore-dependencies gmp
|
||||
# Required by Python.
|
||||
/usr/local/bin/brew install gettext openssl
|
||||
fi
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release \
|
||||
python3 ./nix/build-all.py -v --diskcleanup ${MAC_INTEL} \
|
||||
| tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-osx-${{ matrix.arch }}
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/*/logs/*.log
|
||||
build/*/*/*/build/ifcopenshell/**/CMakeCache.txt
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
@@ -85,7 +110,7 @@ jobs:
|
||||
- name: Package .zip archives
|
||||
run: |
|
||||
VERSION=v`cat VERSION`
|
||||
cd ./build/`uname`/*/10.9/install/ifcopenshell
|
||||
cd ./build/`uname`/*/10.15/install/ifcopenshell
|
||||
mkdir ~/output
|
||||
|
||||
ls -d python-* | while read py_version; do
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils xz
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
@@ -44,18 +44,30 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils xz
|
||||
findutils xz byacc
|
||||
python3 -m pip install typing_extensions
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
@@ -44,18 +44,30 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
install_root=$(find ./build -maxdepth 4 -type d -name install 2>/dev/null | head -n 1 || true)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
- name: ccache
|
||||
# Using fork to support Rocky.
|
||||
uses: Andrej730/ccache-action@main
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux8
|
||||
|
||||
- name: Run Build Script
|
||||
shell: bash
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
set -o pipefail
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py -v --diskcleanup 2>&1 | tee build.log
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-logs-rocky-arm64
|
||||
path: |
|
||||
build.log
|
||||
build/*/*/logs/*.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
|
||||
@@ -47,6 +47,8 @@ jobs:
|
||||
id: ruff
|
||||
run: |
|
||||
uvx ruff check
|
||||
# It's actually Python 3.6, but ruff only supports 3.7+, but it should do.
|
||||
uvx ruff check nix/build-all.py --target-version py37
|
||||
continue-on-error: true
|
||||
|
||||
- name: Final check
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: ci-ifctester-org
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- src/ifctester/**
|
||||
|
||||
jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
path: ifctester_org_static_html
|
||||
- name: Build webapp
|
||||
working-directory: ./src/ifctester
|
||||
run: |
|
||||
sudo apt update && sudo apt install -y nodejs
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
make webapp-build
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cp -r src/ifctester/webapp/dist/* ifctester_org_static_html/
|
||||
git -C ifctester_org_static_html add .
|
||||
git -C ifctester_org_static_html commit --allow-empty -m "$(git log --oneline -1)"
|
||||
git -C ifctester_org_static_html push
|
||||
+27
-13
@@ -49,7 +49,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
pip install git+https://github.com/zdhoward/aud
|
||||
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
@@ -69,9 +69,30 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
# rocksdb on debian distros misses RTTI?
|
||||
- name: build rocksdb
|
||||
run: |
|
||||
git clone https://github.com/facebook/rocksdb --branch v9.11.2
|
||||
cd rocksdb
|
||||
mkdir build && cd build
|
||||
# rocksdb is using ccache automatically.
|
||||
cmake -DFAIL_ON_WARNINGS=Off \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr/local \
|
||||
-DWITH_TESTS=OFF \
|
||||
-DWITH_TOOLS=OFF \
|
||||
-DWITH_GFLAGS=OFF \
|
||||
-DWITH_BENCHMARK_TOOLS=OFF \
|
||||
-DWITH_CORE_TOOLS=OFF \
|
||||
-DROCKSDB_BUILD_SHARED=Off \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=On \
|
||||
-DUSE_RTTI=On \
|
||||
..
|
||||
sudo make -j$(nproc) install
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
@@ -79,9 +100,9 @@ jobs:
|
||||
echo ${{ env.pythonLocation }}
|
||||
|
||||
mkdir build && cd build
|
||||
# Ubuntu 22.04's libocct-foundation-dev package doesn't have Config.cmake, so we provide OCC paths directly.
|
||||
# In later versions of Ubuntu, this can be simplified and the OCC paths can be removed.
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
@@ -94,14 +115,7 @@ jobs:
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3_add2" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_INCLUDE_DIR=/usr/include/hdf5/serial \
|
||||
-DEIGEN_DIR=/usr/include/eigen3 \
|
||||
-DWITH_ROCKSDB=On \
|
||||
../cmake
|
||||
sudo make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
@@ -107,4 +107,6 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
bonsaiDecoratorForLoads.code-workspace
|
||||
dev_environment.bat
|
||||
|
||||
.pixi/
|
||||
@@ -37,25 +37,25 @@ Contents
|
||||
|
||||
| Name | Description | License | Service |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ------------------- | ------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) [](https://anaconda.org/conda-forge/bcf-client) |
|
||||
| bonsai | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
| ifc5d | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
|
||||
| ifcbimtester | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
|
||||
| [bcf](https://docs.ifcopenshell.org/bcf.html) | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) [](https://anaconda.org/conda-forge/bcf-client) |
|
||||
| [bonsai](https://docs.ifcopenshell.org/bonsai.html) | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=bonsai&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| [bsdd](https://docs.ifcopenshell.org/bsdd.html) | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| [ifc2ca](https://docs.ifcopenshell.org/ifc2ca.html) | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| [ifc4d](https://docs.ifcopenshell.org/ifc4d.html) | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
| [ifc5d](https://docs.ifcopenshell.org/ifc5d.html) | Report and optimise cost information from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifc5d/) |
|
||||
| [ifcbimtester](https://docs.ifcopenshell.org/bimtester.html) | Wrapper for Gherkin based unit testing for IFC models | LGPL-3.0-or-later |
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
| [ifccityjson](https://docs.ifcopenshell.org/ifccityjson.html) | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| [ifcclash](https://docs.ifcopenshell.org/ifcclash.html) | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| [ifcconvert](https://docs.ifcopenshell.org/ifcconvert.html) | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| [ifccsv](https://docs.ifcopenshell.org/ifccsv.html) | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| [ifcdiff](https://docs.ifcopenshell.org/ifcdiff.html) | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| [ifcfm](https://docs.ifcopenshell.org/ifcfm.html) | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| [ifcmax](https://docs.ifcopenshell.org/ifcmax.html) | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| [ifcopenshell-python](https://docs.ifcopenshell.org/ifcopenshell-python.html) | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| [ifcpatch](https://docs.ifcopenshell.org/ifcpatch.html) | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| [ifcsverchok](https://docs.ifcopenshell.org/ifcsverchok.html) | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| [ifctester](https://docs.ifcopenshell.org/ifctester.html) | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
|
||||
The IfcOpenShell C++ codebase is split into multiple interal libraries:
|
||||
|
||||
|
||||
+229
-107
@@ -30,10 +30,14 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
message(STATUS "`ccache` is found, using it as a compiler launcher.")
|
||||
endif()
|
||||
|
||||
add_definitions(-D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
|
||||
|
||||
if(POLICY CMP0141) # 3.25+
|
||||
cmake_policy(SET CMP0141 NEW) # Support for `CMAKE_MSVC_DEBUG_INFORMATION_FORMAT`.
|
||||
endif()
|
||||
if(POLICY CMP0144) # 3.27
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
endif()
|
||||
@@ -83,10 +87,12 @@ option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." O
|
||||
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)
|
||||
|
||||
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in IfcParse.h with the file VERSION in the repository root" OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in buildinfo.cpp with the file VERSION in the repository root" OFF)
|
||||
|
||||
set(
|
||||
PYTHON_MODULE_INSTALL_DIR
|
||||
@@ -121,6 +127,26 @@ if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM)
|
||||
set(BUILD_IFCGEOM ON)
|
||||
endif()
|
||||
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
if(MSVC)
|
||||
# By default Visual Studio generators will use /Zi which is not compatible
|
||||
# with ccache, so tell Visual Studio to use /Z7 instead.
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
# Not needed for Ninja.
|
||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
file(COPY_FILE
|
||||
${CCACHE_FOUND} ${CMAKE_BINARY_DIR}/cl.exe
|
||||
ONLY_IF_DIFFERENT)
|
||||
set(CMAKE_VS_GLOBALS
|
||||
"CLToolExe=cl.exe"
|
||||
"CLToolPath=${CMAKE_BINARY_DIR}"
|
||||
"UseMultiToolTask=true"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
@@ -227,8 +253,23 @@ set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT EXISTS "${CGAL_INCLUDE_DIR}")
|
||||
message(FATAL_ERROR "CGAL_INCLUDE_DIR ('${CGAL_INCLUDE_DIR}') is not provided or doesn't exist.")
|
||||
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}'.")
|
||||
link_libraries(CGAL::CGAL)
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_CGAL)
|
||||
@@ -249,15 +290,26 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_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(NOT JSON_INCLUDE_DIR)
|
||||
find_package(nlohmann_json CONFIG)
|
||||
if(nlohmann_json_DIR)
|
||||
link_libraries(nlohmann_json::nlohmann_json)
|
||||
else()
|
||||
message(STATUS "Unable to find nlohmann_json package, trying to find it as a header-only library.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(json_header_path)
|
||||
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
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)
|
||||
message(STATUS "JSON for Modern C++ header file found in '${JSON_INCLUDE_DIR}'.")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file / package, aborting")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_GLTF)
|
||||
@@ -311,6 +363,38 @@ if(USD_SUPPORT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
endif(USD_SUPPORT)
|
||||
|
||||
if (WITH_ROCKSDB)
|
||||
# Temporaily mess with CMAKE_FIND_PACKAGE_PREFER_CONFIG to help RocksDB
|
||||
# find it's zstd dependency on Windows.
|
||||
# Only do it on Windows, otherwise it might create problems as
|
||||
# findzstd and zstd-config target names do not match.
|
||||
if(WIN32)
|
||||
set(TEMP CMAKE_FIND_PACKAGE_PREFER_CONFIG)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
endif()
|
||||
find_package(RocksDB CONFIG REQUIRED)
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ${TEMP})
|
||||
endif()
|
||||
|
||||
message(STATUS "RocksDB: found at '${RocksDB_DIR}'.")
|
||||
add_definitions(-DIFOPSH_WITH_ROCKSDB)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB)
|
||||
link_libraries(RocksDB::rocksdb)
|
||||
|
||||
if (WITH_ZSTD)
|
||||
# @todo do we actually need the zstd include dir or rather just pass
|
||||
# the libzstd.a along with the rocksdb library when needed and feature
|
||||
# detect based on rocksdb API?
|
||||
find_package(zstd CONFIG REQUIRED)
|
||||
message(STATUS "zstd: found at '${zstd_DIR}'.")
|
||||
link_libraries(zstd::libzstd_static)
|
||||
|
||||
add_definitions(-DIFOPSH_WITH_ROCKSDB_ZSTD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DIFOPSH_WITH_ROCKSDB_ZSTD)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find Boost: On win32 the (hardcoded) default is to use static libraries and
|
||||
# runtime, when doing running conda-build we pick what conda prepared for us.
|
||||
if(WIN32 AND("$ENV{CONDA_BUILD}" STREQUAL ""))
|
||||
@@ -368,7 +452,29 @@ if(NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
clear_wasm_sysroot()
|
||||
if(IFCXML_SUPPORT)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
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?)
|
||||
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()
|
||||
endif()
|
||||
restore_wasm_sysroot()
|
||||
endif()
|
||||
@@ -379,43 +485,18 @@ if(IFCXML_SUPPORT)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(MSVC)
|
||||
if(MSVC AND NOT LibXml2_DIR)
|
||||
add_debug_variants(LIBXML2_LIBRARIES "${LIBXML2_LIBRARIES}" d)
|
||||
endif()
|
||||
|
||||
# Open CASCADE
|
||||
if(WITH_OPENCASCADE)
|
||||
|
||||
# Fallback for missing OCC_INCLUDE_DIR.
|
||||
if(NOT OCC_INCLUDE_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_path(OCC_INCLUDE_DIR Standard_Version.hxx
|
||||
PATHS
|
||||
/usr/include/occt
|
||||
/usr/include/oce
|
||||
/usr/include/opencascade
|
||||
)
|
||||
restore_wasm_sysroot()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
message(STATUS "Found Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
else()
|
||||
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}" STREQUAL "")
|
||||
find_library(OCC_LIBRARY TKernel
|
||||
PATHS
|
||||
/usr/lib
|
||||
)
|
||||
|
||||
if(OCC_LIBRARY)
|
||||
GET_FILENAME_COMPONENT(OCC_LIBRARY_DIR ${OCC_LIBRARY} PATH)
|
||||
message(STATUS "Found Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
else()
|
||||
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()
|
||||
@@ -424,14 +505,34 @@ if(BUILD_IFCGEOM)
|
||||
if(OCC_INCLUDE_DIR AND OCC_LIBRARY_DIR)
|
||||
message(
|
||||
STATUS
|
||||
"Using provided OCCT_INCLUDE_DIR ('${OCC_INCLUDE_DIR}') "
|
||||
"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)
|
||||
get_target_property(OCC_INCLUDE_DIR TKernel INTERFACE_INCLUDE_DIRECTORIES)
|
||||
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}', "
|
||||
@@ -443,25 +544,12 @@ if(BUILD_IFCGEOM)
|
||||
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."
|
||||
"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()
|
||||
|
||||
# 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}")
|
||||
|
||||
set(
|
||||
OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
@@ -484,7 +572,10 @@ if(BUILD_IFCGEOM)
|
||||
if(libTKernel)
|
||||
message(STATUS "Required Open Cascade Library files found")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find Open Cascade library files, aborting")
|
||||
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
|
||||
@@ -538,7 +629,18 @@ if(BUILD_IFCGEOM)
|
||||
endif(WITH_OPENCASCADE)
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if(COLLADA_SUPPORT)
|
||||
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()
|
||||
endif()
|
||||
if(COLLADA_SUPPORT AND NOT OpenCOLLADA_DIR)
|
||||
# Find OpenCOLLADA
|
||||
if("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
|
||||
message(STATUS "No OpenCOLLADA include directory specified")
|
||||
@@ -571,8 +673,7 @@ if(COLLADA_SUPPORT)
|
||||
|
||||
if(COLLADASWStreamWriter_h)
|
||||
message(STATUS "OpenCOLLADA header files found")
|
||||
add_definitions(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
set(OPENCOLLADA_FOUND TRUE)
|
||||
|
||||
set(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
@@ -620,7 +721,12 @@ if(COLLADA_SUPPORT)
|
||||
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)
|
||||
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 "")
|
||||
@@ -687,27 +793,25 @@ if(HDF5_SUPPORT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT HDF5_LIBRARIES)
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
# debian default
|
||||
set(HDF5_LIBRARIES
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so
|
||||
/usr/lib/x86_64-linux-gnu/libsz.so
|
||||
/usr/lib/x86_64-linux-gnu/libaec.so
|
||||
z dl
|
||||
)
|
||||
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()
|
||||
find_package(HDF5 CONFIG)
|
||||
if(NOT HDF5_DIR)
|
||||
# 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."
|
||||
"Also could not find HDF5 package (neither module or config)."
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Found HDF5 package: '${HDF5_DIR}'.")
|
||||
set(HDF5_LIBRARIES hdf5_cpp-static)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -829,7 +933,9 @@ endif(MSVC)
|
||||
# Ensure other dependencies are provided.
|
||||
if(NOT EXISTS "${EIGEN_DIR}")
|
||||
find_package(Eigen3 CONFIG)
|
||||
if(NOT Eigen3_DIR)
|
||||
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}'). "
|
||||
@@ -849,7 +955,7 @@ include_directories(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCL
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
if(WASM_BUILD)
|
||||
# super arbitrarily try to keep size down at least a little bit
|
||||
set(SCHEMA_VERSIONS "2x3" "4")
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x3_add2")
|
||||
else()
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3" "4x3_tc1" "4x3_add1" "4x3_add2")
|
||||
endif()
|
||||
@@ -937,11 +1043,7 @@ if(BUILD_IFCGEOM)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
set(IFCGEOM_SCHEMA_LIBRARIES ${IFCGEOM_SCHEMA_LIBRARIES} geometry_mapping_ifc${schema})
|
||||
endforeach()
|
||||
if(WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
@@ -955,12 +1057,14 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
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_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
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})
|
||||
list(APPEND geometry_serializer_libraries geometry_serializer_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries})
|
||||
add_library(geometry_serializer STATIC ../src/ifcgeom/Serialization/Serialization.h ../src/ifcgeom/Serialization/Serialization.cpp)
|
||||
set_target_properties(geometry_serializer PROPERTIES COMPILE_FLAGS "-DIFC_GEOMSERIALIZATION_EXPORTS")
|
||||
target_link_libraries(geometry_serializer ${geometry_serializer_libraries} IfcParse)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} geometry_serializer ${geometry_serializer_libraries})
|
||||
endif()
|
||||
endif()
|
||||
@@ -999,8 +1103,12 @@ 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()
|
||||
@@ -1008,7 +1116,11 @@ else()
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(WITH_CGAL)
|
||||
# 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)
|
||||
@@ -1030,23 +1142,23 @@ if(BUILD_IFCGEOM)
|
||||
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})
|
||||
set_property(TARGET geometry_kernel_${kernel} APPEND PROPERTY COMPILE_FLAGS "-DIFC_GEOM_EXPORTS")
|
||||
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})
|
||||
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_GEOM_EXPORTS -DIFOPSH_SIMPLE_KERNEL -DCGAL_HAS_THREADS")
|
||||
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})
|
||||
target_link_libraries(geometry_kernel_${kernel}_simple ${${KERNEL_UPPER}_LIBRARIES} IfcGeom IfcParse)
|
||||
list(APPEND kernel_libraries geometry_kernel_${kernel}_simple)
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -1061,6 +1173,9 @@ if(BUILD_IFCGEOM)
|
||||
add_library(geometry_mapping_ifc${schema} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(geometry_mapping_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcParse)
|
||||
if (NOT BUILD_SHARED_LIBS)
|
||||
target_link_libraries(geometry_mapping_ifc${schema} IfcGeom)
|
||||
endif()
|
||||
list(APPEND mapping_libraries geometry_mapping_ifc${schema})
|
||||
endforeach()
|
||||
|
||||
@@ -1076,11 +1191,7 @@ if(BUILD_IFCGEOM)
|
||||
find_package(Threads)
|
||||
endif()
|
||||
|
||||
if(WASM_BUILD)
|
||||
target_link_libraries(IfcGeom ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
target_link_libraries(IfcGeom IfcParse ${kernel_libraries} ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
target_link_libraries(IfcGeom IfcParse ${mapping_libraries} ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
@@ -1095,7 +1206,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
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})
|
||||
@@ -1105,7 +1216,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
endforeach()
|
||||
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS" VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
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")
|
||||
@@ -1116,7 +1227,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
target_link_libraries(Serializers ${PROJ_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
target_link_libraries(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES} IfcGeom ${OPENCASCADE_LIBRARIES} ${kernel_libraries} IfcParse)
|
||||
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
@@ -1163,7 +1274,7 @@ if(BUILD_CONVERT)
|
||||
set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
add_executable(IfcConvert ${IFCCONVERT_FILES})
|
||||
|
||||
target_link_libraries(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
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()
|
||||
@@ -1197,7 +1308,7 @@ if(BUILD_GEOMSERVER)
|
||||
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
|
||||
set(SOURCE_FILES ${CPP_FILES} ${H_FILES})
|
||||
add_executable(IfcGeomServer ${SOURCE_FILES})
|
||||
target_link_libraries(IfcGeomServer ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES})
|
||||
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}")
|
||||
@@ -1213,6 +1324,10 @@ endif(BUILD_GEOMSERVER)
|
||||
if(ADD_COMMIT_SHA)
|
||||
find_package(Git)
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
message(FATAL_ERROR "Failed to find Git for ADD_COMMIT_SHA option.")
|
||||
endif()
|
||||
|
||||
if(GIT_FOUND)
|
||||
if (VERSION_OVERRIDE)
|
||||
set (git_branch ${RELEASE_VERSION})
|
||||
@@ -1250,8 +1365,10 @@ if(ADD_COMMIT_SHA)
|
||||
message(FATAL_ERROR "Unable to determine commit sha and/or branch")
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
target_compile_definitions(IfcParse PRIVATE
|
||||
-DIFCOPENSHELL_BRANCH=${git_branch}
|
||||
-DIFCOPENSHELL_COMMIT=${git_sha}
|
||||
)
|
||||
endif()
|
||||
endif(ADD_COMMIT_SHA)
|
||||
|
||||
@@ -1308,6 +1425,11 @@ if(BUILD_IFCGEOM)
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
file(GLOB SERIALIZATION_H_FILES ../src/ifcgeom/serialization/*.h)
|
||||
install(FILES ${SERIALIZATION_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/serialization
|
||||
)
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"version": 6,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "shared",
|
||||
"generator": "Ninja",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"BUILD_IFCPYTHON": "ON",
|
||||
"BUILD_IFCGEOM": "ON",
|
||||
"COLLADA_SUPPORT": "OFF",
|
||||
"BUILD_EXAMPLES": "OFF",
|
||||
"BUILD_GEOMSERVER": "OFF",
|
||||
"GLTF_SUPPORT": "ON",
|
||||
"BUILD_CONVERT": "ON",
|
||||
"BUILD_IFCMAX": "OFF",
|
||||
"IFCXML_SUPPORT": "ON",
|
||||
"HDF5_SUPPORT": "ON",
|
||||
"SCHEMA_VERSIONS": "4x3_add2",
|
||||
"CITYJSON_SUPPORT": "OFF",
|
||||
"CMAKE_GENERATOR_PLATFORM": "",
|
||||
"CMAKE_GENERATOR_TOOLSET": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-shared",
|
||||
"inherits": [
|
||||
"shared"
|
||||
],
|
||||
"hidden": true,
|
||||
"environment": {
|
||||
"CONDA_BUILD": "ON",
|
||||
"CONDA_PY": "312"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"Boost_USE_STATIC_LIBS": "OFF",
|
||||
"CMAKE_INSTALL_PREFIX": "$env{LIBRARY_PREFIX}",
|
||||
"PYTHON_EXECUTABLE": "$env{PREFIX}/python.exe",
|
||||
"PYTHON_INCLUDE_DIR": "$env{PREFIX}/include",
|
||||
"PYTHON_LIBRARY": "$env{PREFIX}/libs/python$env{CONDA_PY}.lib",
|
||||
"CMAKE_FIND_ROOT_PATH": "$env{LIBRARY_PREFIX}",
|
||||
"CMAKE_PREFIX_PATH": "$env{LIBRARY_PREFIX}",
|
||||
"LIBXML2_LIBRARIES": "$env{LIBRARY_PREFIX}/lib/libxml2.lib",
|
||||
"OCC_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include/opencascade",
|
||||
"OCC_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"CGAL_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"EIGEN_DIR": "$env{LIBRARY_PREFIX}/include/eigen3",
|
||||
"JSON_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"GMP_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"GMP_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"MPFR_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"Boost_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include",
|
||||
"HDF5_LIBRARY_DIR": "$env{LIBRARY_PREFIX}/lib",
|
||||
"ZLIB_INCLUDE_DIR": "$env{LIBRARY_PREFIX}/include"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-release",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/prod",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/prod/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-debug",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"binaryDir": "${sourceDir}/../build/win-debug",
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/dev",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/dev/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-test",
|
||||
"inherits": [
|
||||
"win-shared"
|
||||
],
|
||||
"hidden": false,
|
||||
"binaryDir": "${sourceDir}/../build/win-test",
|
||||
"warnings": {
|
||||
"dev": false
|
||||
},
|
||||
"environment": {
|
||||
"PREFIX": "${sourceDir}/../.pixi/envs/tests",
|
||||
"LIBRARY_PREFIX": "${sourceDir}/../.pixi/envs/tests/Library"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"SCHEMA_VERSIONS": "2x3;4;4x3_add2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+409
-126
@@ -31,6 +31,7 @@ Available arguments:
|
||||
``-py-313`` - build for specific Python version
|
||||
(building for all supported Python version by default).
|
||||
``-wasm`` - compile for wasm
|
||||
``-mac-cross-compile-intel`` - cross compile for Intel Mac on Apple Silicon host
|
||||
``-shared`` - build shared libraries. By default will build static.
|
||||
``-diskcleanup`` - clean up build directories after finishing building dependencies
|
||||
``-v`` - enable verbose logs
|
||||
@@ -41,11 +42,17 @@ Used environment variables:
|
||||
- ``BUILD_DIR`` - build directory. By default will use "build" folder in IfcOpenShell repository.
|
||||
- ``DEPS_DIR`` - dependencies directory. By default will create automatic folder in build directory.
|
||||
- ``BUILD_CFG`` - build configuration, 'RelWithDebInfo' by default.
|
||||
"""
|
||||
- ``USE_CURRENT_PYTHON_VERSION`` - use current python config instead of compile from source
|
||||
- ``IFCOS_NUM_BUILD_PROCS`` - number of concurrent processes defaults to available cores + 1
|
||||
- ``NO_CLEAN`` - do not clean `ifcopenshell` build directories but continue working on current build
|
||||
(installed dependencies are never cleared).
|
||||
By default option is disabled, to enable pass any value from `1`, `on`, `true`.
|
||||
- ``IFCOS_SCHEMAS`` - schemas to be built; defaults to cmake default (IFC2X3; IFC4; IFC4X3_ADD2) - to be supplied as `2x3;4`
|
||||
- ``USE_OCCT`` - whether to use official Open CASCADE instead of Community Edition
|
||||
(`true` by default, any other value is considered `false`)
|
||||
- ``ADD_COMMIT_SHA`` - if defined with any non-empty value then
|
||||
`ADD_COMMIT_SHA` and `VERSION_OVERRIDE` will be set to `ON` while configuring IfcOpenShell
|
||||
|
||||
|
||||
###############################################################################
|
||||
# #
|
||||
# This script builds IfcOpenShell and its dependencies #
|
||||
# #
|
||||
# Prerequisites for this script to function correctly: #
|
||||
@@ -62,14 +69,19 @@ Used environment variables:
|
||||
# #
|
||||
# for python37 to install correctly additionally: #
|
||||
# * libffi(-dev[el]) #
|
||||
# for Python build we also needs ssl #
|
||||
# (since we do `pip install numpy` at the end) #
|
||||
# * libssl-dev #
|
||||
# #
|
||||
# on debian 7.8 these can be obtained with: #
|
||||
# $ apt-get install git gcc g++ autoconf bison bzip2 cmake #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# libssl-dev xz #
|
||||
# #
|
||||
# on ubuntu 14.04: #
|
||||
# $ apt-get install git gcc g++ autoconf bison make cmake #
|
||||
# mesa-common-dev libffi-dev libfontconfig1-dev #
|
||||
# libssl-dev xz-utils #
|
||||
# #
|
||||
# on OS X El Capitan with homebrew: #
|
||||
# $ brew install git bison autoconf automake libffi cmake #
|
||||
@@ -77,8 +89,10 @@ Used environment variables:
|
||||
# on RHEL-related distros: #
|
||||
# $ yum install git gcc gcc-c++ autoconf bison make cmake #
|
||||
# mesa-libGL-devel libffi-devel fontconfig-devel bzip2 #
|
||||
# automake patch #
|
||||
###############################################################################
|
||||
# automake patch byacc xz #
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -89,7 +103,9 @@ import shutil
|
||||
import tarfile
|
||||
import multiprocessing
|
||||
import platform
|
||||
import threading
|
||||
import sysconfig
|
||||
from datetime import datetime
|
||||
|
||||
# @todo temporary for expired mpfr.org certificate on 2023-04-08
|
||||
import ssl
|
||||
@@ -117,23 +133,26 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.0"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.6"]
|
||||
JSON_VERSION = "3.11.3"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.86.0"
|
||||
EIGEN_VERSION = "3.4.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
PCRE2_VERSION = "10.32"
|
||||
LIBXML2_VERSION = "2.13.8"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
SWIG_VERSION = "4.1.0"
|
||||
OPENCOLLADA_VERSION = "v1.6.68"
|
||||
HDF5_VERSION = "1.12.1"
|
||||
HDF5_VERSION = "1.13.1"
|
||||
|
||||
GMP_VERSION = "6.2.1"
|
||||
GMP_VERSION = "6.3.0"
|
||||
MPFR_VERSION = "3.1.6" # latest is 4.1.0
|
||||
CGAL_VERSION = "5.6.x-branch"
|
||||
CGAL_VERSION = "v5.6.3"
|
||||
USD_VERSION = "23.05"
|
||||
TBB_VERSION = "2021.9.0"
|
||||
|
||||
ROCKSDB_VERSION = "9.11.2"
|
||||
ZSTD_VERSION = "1.5.7"
|
||||
# binaries
|
||||
cp = "cp"
|
||||
bash = "bash"
|
||||
@@ -149,6 +168,8 @@ date = "date"
|
||||
curl = "curl"
|
||||
wget = "wget"
|
||||
strip = "strip"
|
||||
xz = "xz" # Used implicitly for `tar -xf *.tar.xz`.
|
||||
brew = "brew"
|
||||
|
||||
explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
|
||||
"""Targets provided by CLI."""
|
||||
@@ -172,13 +193,20 @@ def cecho(message, color=NO_COLOR):
|
||||
logger.info(f"{color}{message}\033[0m")
|
||||
|
||||
|
||||
def which(cmd):
|
||||
for path in os.getenv("PATH").split(":"):
|
||||
def which(cmd: str) -> Union[str, None]:
|
||||
PATH = os.getenv("PATH")
|
||||
assert PATH
|
||||
for path in PATH.split(":"):
|
||||
if os.path.exists(path) and cmd in os.listdir(path):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
|
||||
# Flags.
|
||||
MAC_CROSS_COMPILE_INTEL = "mac-cross-compile-intel" in flags
|
||||
assert platform.system() == "Darwin" or not MAC_CROSS_COMPILE_INTEL
|
||||
WASM = "wasm" in flags
|
||||
|
||||
# Set defaults for missing empty environment variables
|
||||
|
||||
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
|
||||
@@ -186,8 +214,10 @@ USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
|
||||
TOOLSET = None
|
||||
if platform.system() == "Darwin":
|
||||
# C++11 features used in OCCT 7+ need a more recent stdlib
|
||||
TOOLSET = "10.9" if USE_OCCT else "10.6"
|
||||
|
||||
# TOOLSET = "10.9" if USE_OCCT else "10.6"
|
||||
# /Users/runner/work/IfcOpenShell/IfcOpenShell/build/Darwin/arm64/10.9/build/rocksdb/cache/clock_cache.cc:732:14: error: aligned allocation function of type 'void *(std::size_t, std::align_val_t)' is only available on macOS 10.13 or newer
|
||||
# /Users/runner/work/IfcOpenShell/IfcOpenShell/src/ifcparse/IfcFile.cpp:539:14: error: 'exists' is unavailable: introduced in macOS 10.15
|
||||
TOOLSET = "10.15"
|
||||
|
||||
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
|
||||
|
||||
@@ -195,7 +225,15 @@ CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmak
|
||||
|
||||
build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))
|
||||
|
||||
DEFAULT_DEPS_DIR = Path(build_dir) / platform.system() / ("wasm" if "wasm" in flags else platform.machine())
|
||||
|
||||
if WASM:
|
||||
arch = "wasm"
|
||||
elif MAC_CROSS_COMPILE_INTEL:
|
||||
arch = "x86_64"
|
||||
else:
|
||||
arch = platform.machine()
|
||||
DEFAULT_DEPS_DIR = Path(build_dir) / platform.system() / arch
|
||||
|
||||
if TOOLSET:
|
||||
DEFAULT_DEPS_DIR = DEFAULT_DEPS_DIR / TOOLSET
|
||||
DEFAULT_DEPS_DIR = os.path.realpath(DEFAULT_DEPS_DIR)
|
||||
@@ -244,23 +282,26 @@ cecho(
|
||||
)
|
||||
|
||||
dependency_tree: "dict[str, tuple[str, ...]]" = {
|
||||
"IfcParse": ("boost", "libxml2", "hdf5"),
|
||||
"IfcGeom": ("IfcParse", "occ", "json", "cgal", "eigen"),
|
||||
"IfcParse": ("boost", "libxml2", "hdf5", "rocksdb"),
|
||||
"IfcGeom": ("IfcParse", "occ", "json", "cgal", "eigen", "OpenCOLLADA"),
|
||||
"IfcConvert": ("IfcGeom",),
|
||||
"OpenCOLLADA": ("libxml2", "pcre"),
|
||||
"IfcGeomServer": ("IfcGeom",),
|
||||
"IfcOpenShell-Python": ("python", "swig", "IfcGeom"),
|
||||
"swig": ("pcre",),
|
||||
"swig": ("pcre2",),
|
||||
"boost": (),
|
||||
"libxml2": (),
|
||||
"python": (),
|
||||
"occ": ("freetype",),
|
||||
"pcre": (),
|
||||
"pcre2": (),
|
||||
"json": (),
|
||||
"hdf5": (),
|
||||
"cgal": (),
|
||||
"eigen": (),
|
||||
"freetype": (),
|
||||
"rocksdb": ("zstd",),
|
||||
"zstd": (),
|
||||
# 'usd': ('boost', 'oneTBB')
|
||||
}
|
||||
|
||||
@@ -268,8 +309,9 @@ dependency_tree: "dict[str, tuple[str, ...]]" = {
|
||||
def gather_dependencies(dep: str) -> "Generator[str]":
|
||||
yield dep
|
||||
for d in dependency_tree[dep]:
|
||||
for x in gather_dependencies(d):
|
||||
yield x
|
||||
if f"without-{d.lower()}" not in flags:
|
||||
for x in gather_dependencies(d):
|
||||
yield x
|
||||
|
||||
|
||||
if "v" in flags:
|
||||
@@ -279,6 +321,17 @@ if "v" in flags:
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
MAC_CROSS_COMPILE_INTEL_ARGS = ["-DCMAKE_OSX_ARCHITECTURES=x86_64"]
|
||||
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"
|
||||
else:
|
||||
MAC_CROSS_COMPILE_INTEL_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_BJAM_ARGS = []
|
||||
MAC_CROSS_COMPILE_INTEL_CXX = ""
|
||||
MAC_CROSS_COMPILE_INTEL_CC = ""
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
BUILD_STATIC = "shared" not in flags
|
||||
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
|
||||
@@ -291,6 +344,11 @@ PIC = "-fPIC" if BUILD_STATIC else ""
|
||||
if any(f.startswith("py-") for f in flags):
|
||||
PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if f"py-{''.join(pyv.split('.')[:2])}" in flags]
|
||||
|
||||
if any(f.startswith("occt-") for f in flags):
|
||||
OCCT_VERSION = next(f.split("-", 1)[1] for f in flags if f.startswith("occt-"))
|
||||
|
||||
print(OCCT_VERSION)
|
||||
|
||||
if explicit_targets:
|
||||
targets = {dep for target in explicit_targets for dep in gather_dependencies(target)}
|
||||
else:
|
||||
@@ -301,10 +359,21 @@ targets = set(t for t in targets if "without-%s" % t.lower() not in flags)
|
||||
print("Building:", *sorted(targets, key=lambda t: len(list(gather_dependencies(t)))))
|
||||
|
||||
# Check that required tools are in PATH
|
||||
|
||||
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
|
||||
yacc = "yacc" # Used during swig building process, installed with `bison` on Debian / `byacc` on Red Hat.
|
||||
missing_commands: "list[str]" = []
|
||||
required_commands = [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake", yacc, xz]
|
||||
if "wasm" in flags:
|
||||
required_commands.remove(yacc) # yacc not needed for wasm builds
|
||||
for cmd in required_commands:
|
||||
if which(cmd) is None:
|
||||
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
|
||||
missing_commands.append(cmd)
|
||||
|
||||
if missing_commands:
|
||||
raise ValueError(f"Required tools not installed or not added to PATH: {', '.join(missing_commands)}")
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
brew = "/usr/local/bin/brew"
|
||||
assert os.path.exists(brew), f"For intel cross compilation the brew path is expected to be '{brew}'."
|
||||
|
||||
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
|
||||
download_tool_default = download_tool_py = "py"
|
||||
@@ -334,22 +403,43 @@ def run(cmds: "Sequence[str]", cwd: "Union[str, None]" = None, can_fail: bool =
|
||||
with leading and trailing whitespace removed.
|
||||
"""
|
||||
|
||||
def timestamp() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3] # same format as logging
|
||||
|
||||
def stream_reader(pipe, collector: "list[str]", log_file) -> None:
|
||||
for line in iter(pipe.readline, ""):
|
||||
log_file.write(f"{timestamp()} {line}")
|
||||
log_file.flush()
|
||||
collector.append(line)
|
||||
pipe.close()
|
||||
|
||||
logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
|
||||
log_file_handle = open(LOG_FILE, "a")
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
|
||||
stdout, stderr = proc.communicate()
|
||||
log_file_handle.write(stdout)
|
||||
log_file_handle.write(stderr)
|
||||
log_file_handle.close()
|
||||
stdout: list[str] = []
|
||||
stderr: list[str] = []
|
||||
|
||||
# Ensure both live logs available in the log file
|
||||
# and the putput.
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log_file_handle:
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
|
||||
assert proc.stdout and proc.stderr
|
||||
|
||||
t_out = threading.Thread(target=stream_reader, args=(proc.stdout, stdout, log_file_handle))
|
||||
t_err = threading.Thread(target=stream_reader, args=(proc.stderr, stderr, log_file_handle))
|
||||
t_out.start()
|
||||
t_err.start()
|
||||
t_out.join()
|
||||
t_err.join()
|
||||
proc.wait()
|
||||
|
||||
logger.debug(f"command returned {proc.returncode}")
|
||||
|
||||
if proc.returncode != 0 and not can_fail:
|
||||
print("-" * 70)
|
||||
print(stderr)
|
||||
print("".join(stderr))
|
||||
print("-" * 70)
|
||||
raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
|
||||
|
||||
return stdout.strip()
|
||||
return "".join(stdout).strip()
|
||||
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
@@ -462,6 +552,7 @@ def build_dependency(
|
||||
pre_compile_subs: "Sequence[tuple[str, str, str]]" = (),
|
||||
additional_files: "Union[dict[str, str], None]" = None,
|
||||
no_append_name=False,
|
||||
cmake_dir=None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Handles building of dependencies with different tools (which are
|
||||
@@ -556,17 +647,23 @@ def build_dependency(
|
||||
sp.run(shell, shell=True, check=True, cwd=extract_dir)
|
||||
|
||||
if mode == "ctest":
|
||||
run(
|
||||
["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"],
|
||||
cwd=extract_dir,
|
||||
)
|
||||
try:
|
||||
run(
|
||||
["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"],
|
||||
cwd=extract_dir,
|
||||
)
|
||||
except Exception as e:
|
||||
print("-" * 70)
|
||||
print(open(os.path.join(extract_dir, "hdf5.log")))
|
||||
print("-" * 70)
|
||||
raise e
|
||||
run([tar, "-xf", kwargs["ctest_result"] + ".tar.gz"], cwd=os.path.join(extract_dir, "build"))
|
||||
shutil.copytree(
|
||||
os.path.join(extract_dir, "build", kwargs["ctest_result"], kwargs["ctest_result_path"]),
|
||||
os.path.join(DEPS_DIR, "install", name),
|
||||
)
|
||||
elif mode != "bjam":
|
||||
extract_build_dir = os.path.join(extract_dir, "build")
|
||||
extract_build_dir = os.path.join(extract_dir, *([cmake_dir] if cmake_dir else []), "build")
|
||||
if os.path.exists(extract_build_dir):
|
||||
shutil.rmtree(extract_build_dir)
|
||||
os.makedirs(extract_build_dir)
|
||||
@@ -668,15 +765,25 @@ if "hdf5" in targets:
|
||||
for f in compiler_flags:
|
||||
os.environ[f] = re.sub(r"-flto(=\w+)?", "", os.environ[f])
|
||||
|
||||
HDF5_UNDERSCORE = "_".join(HDF5_VERSION.split("."))
|
||||
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
|
||||
dependency_name = f"hdf5-{HDF5_VERSION}"
|
||||
build_dependency(
|
||||
name=f"hdf5-{HDF5_VERSION}",
|
||||
mode="ctest",
|
||||
build_tool_args=[],
|
||||
download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
|
||||
download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
|
||||
ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
|
||||
ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}",
|
||||
name=dependency_name,
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
|
||||
"-DHDF5_ENABLE_Z_LIB_SUPPORT=OFF",
|
||||
"-DBUILD_TESTING=OFF",
|
||||
"-DHDF5_BUILD_TOOLS=OFF",
|
||||
"-DHDF5_BUILD_EXAMPLES=OFF",
|
||||
"-DBUILD_SHARED_LIBS=OFF",
|
||||
"-DHDF5_BUILD_UTILS=OFF",
|
||||
"-DHDF5_BUILD_CPP_LIB=ON",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url=f"https://github.com/HDFGroup/hdf5/archive/refs/tags/",
|
||||
download_name=f"hdf5-{HDF5_UNDERSCORE}.tar.gz",
|
||||
)
|
||||
|
||||
for f, o in zip(compiler_flags, orig):
|
||||
@@ -684,19 +791,39 @@ if "hdf5" in targets:
|
||||
|
||||
|
||||
if "json" in targets:
|
||||
json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
|
||||
json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
|
||||
if not os.path.exists(os.path.dirname(json_install_path)):
|
||||
os.makedirs(os.path.dirname(json_install_path))
|
||||
if not os.path.exists(json_install_path):
|
||||
urlretrieve(json_url, json_install_path)
|
||||
dependency_name = f"json-{JSON_VERSION}"
|
||||
build_dependency(
|
||||
name=dependency_name,
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
|
||||
"-DJSON_BuildTests=OFF",
|
||||
],
|
||||
download_url=f"https://github.com/nlohmann/json/releases/download/v{JSON_VERSION}",
|
||||
download_name="json.tar.xz",
|
||||
)
|
||||
|
||||
if "eigen" in targets:
|
||||
git_clone_or_pull_repository(
|
||||
"https://gitlab.com/libeigen/eigen.git", f"{DEPS_DIR}/install/eigen-3.3.9", revision="3.3.9"
|
||||
dependency_name = f"eigen-install-{EIGEN_VERSION}"
|
||||
build_dependency(
|
||||
name=f"{dependency_name}",
|
||||
mode="cmake",
|
||||
# We add '-install-' in the middle, so it won't be confused with git repo we used previously.
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/{dependency_name}",
|
||||
],
|
||||
download_url=f"https://gitlab.com/libeigen/eigen/-/archive/{EIGEN_VERSION}/",
|
||||
download_name=f"eigen-{EIGEN_VERSION}.tar.gz",
|
||||
)
|
||||
|
||||
if "pcre" in targets:
|
||||
OLD_CC, OLD_CCXX = None, None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC, OLD_CCXX = os.environ.get("CC"), os.environ.get("CXX")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
os.environ["CXX"] = MAC_CROSS_COMPILE_INTEL_CXX
|
||||
# Keep it autoconf as OpenCOLLADA is pretty old and might break
|
||||
# if we update it's dependencies for mmore modern cmake.
|
||||
build_dependency(
|
||||
name=f"pcre-{PCRE_VERSION}",
|
||||
mode="autoconf",
|
||||
@@ -704,6 +831,24 @@ if "pcre" in targets:
|
||||
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
|
||||
download_name=f"pcre-{PCRE_VERSION}.tar.bz2",
|
||||
)
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
if OLD_CCXX is None:
|
||||
del os.environ["CXX"]
|
||||
else:
|
||||
os.environ["CXX"] = OLD_CCXX
|
||||
|
||||
if "pcre2" in targets:
|
||||
build_dependency(
|
||||
name=f"pcre2-{PCRE2_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=[DISABLE_FLAG],
|
||||
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre2/{PCRE2_VERSION}/",
|
||||
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
|
||||
@@ -711,11 +856,11 @@ if "swig" in targets:
|
||||
build_dependency(
|
||||
name="swig",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
|
||||
build_tool_args=["--disable-ccache", f"--with-pcre2-prefix={DEPS_DIR}/install/pcre2-{PCRE2_VERSION}"],
|
||||
download_url="https://github.com/swig/swig.git",
|
||||
download_name="swig",
|
||||
download_tool=download_tool_git,
|
||||
revision=f"rel-{SWIG_VERSION}",
|
||||
revision=f"v{SWIG_VERSION}",
|
||||
)
|
||||
|
||||
if "freetype" in targets:
|
||||
@@ -743,6 +888,9 @@ if USE_OCCT and "occ" in targets:
|
||||
if OCCT_VERSION == "7.8.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_8_1.patch")
|
||||
|
||||
if OCCT_VERSION == "7.9.1":
|
||||
patches.append("./patches/occt/no_ExpToCasExe_7_9_1.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
patches.append("./patches/occt/no_em_js.patch")
|
||||
|
||||
@@ -752,9 +900,17 @@ if USE_OCCT and "occ" in targets:
|
||||
build_tool_args=[
|
||||
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
|
||||
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
|
||||
"-DBUILD_MODULE_Draw=0",
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
|
||||
f"-DBUILD_MODULE_Draw=0",
|
||||
f"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
|
||||
# Disable xlib explicitly, as it tries to use it on Desktop Ubuntu, adding unnecessary dependency.
|
||||
f"-DUSE_XLIB=OFF",
|
||||
# Avoid building 3D Viewer.
|
||||
f"-DUSE_FREETYPE=OFF",
|
||||
f"-DUSE_OPENGL=OFF",
|
||||
f"-DUSE_GLES2=OFF",
|
||||
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype",
|
||||
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/Open-Cascade-SAS/OCCT",
|
||||
download_name="occt",
|
||||
@@ -767,12 +923,12 @@ elif "occ" in targets:
|
||||
name=f"oce-{OCE_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DOCE_DISABLE_TKSERVICE_FONT=ON",
|
||||
"-DOCE_TESTING=OFF",
|
||||
"-DOCE_BUILD_SHARED_LIB=OFF",
|
||||
"-DOCE_DISABLE_X11=ON",
|
||||
"-DOCE_VISUALISATION=OFF",
|
||||
"-DOCE_OCAF=OFF",
|
||||
f"-DOCE_DISABLE_TKSERVICE_FONT=ON",
|
||||
f"-DOCE_TESTING=OFF",
|
||||
f"-DOCE_BUILD_SHARED_LIB=OFF",
|
||||
f"-DOCE_DISABLE_X11=ON",
|
||||
f"-DOCE_VISUALISATION=OFF",
|
||||
f"-DOCE_OCAF=OFF",
|
||||
f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}",
|
||||
],
|
||||
download_url="https://github.com/tpaviot/oce/archive/",
|
||||
@@ -780,24 +936,40 @@ elif "occ" in targets:
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
OLD_CC = ""
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC = os.environ.get("CC")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
build_tool_args = [
|
||||
"--without-python",
|
||||
ENABLE_FLAG,
|
||||
DISABLE_FLAG,
|
||||
"--without-zlib",
|
||||
"--without-iconv",
|
||||
"--without-lzma",
|
||||
]
|
||||
if "wasm" in flags:
|
||||
build_tool_args.append("--without-threads")
|
||||
build_dependency(
|
||||
f"libxml2-{LIBXML2_VERSION}",
|
||||
"autoconf",
|
||||
build_tool_args=[
|
||||
"--without-python",
|
||||
ENABLE_FLAG,
|
||||
DISABLE_FLAG,
|
||||
"--without-zlib",
|
||||
"--without-iconv",
|
||||
"--without-lzma",
|
||||
],
|
||||
build_tool_args=build_tool_args,
|
||||
download_url=f"https://download.gnome.org/sources/libxml2/{'.'.join(LIBXML2_VERSION.split('.')[0:2])}/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.xz",
|
||||
)
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]
|
||||
|
||||
# This patch allows static libraries config on Unix,
|
||||
# because the config is weird and doesn't allow non-shared libraries on Unix.
|
||||
patches.append("./patches/opencollada/allow_static_libraries_config_on_unix.patch")
|
||||
|
||||
if "wasm" in flags:
|
||||
# This is necessary for the WASM build, because recent versions of
|
||||
# clang don't have the tr1:: namespace anymore. However, it breaks
|
||||
@@ -815,6 +987,9 @@ if "OpenCOLLADA" in targets:
|
||||
f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
|
||||
f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/",
|
||||
# OpenCOLLADA is ancient at this point and allows cmake 2.6+, which results in error in cmake 4.
|
||||
f"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
download_name="OpenCOLLADA",
|
||||
@@ -837,6 +1012,13 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
|
||||
PYTHON_CONFIGURE_ARGS: "list[str]" = []
|
||||
if platform.system() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS = ["--enable-shared"]
|
||||
open_ssl_prefix = run([brew, "--prefix", "openssl@3"]).strip()
|
||||
# I'm not sure why, but if I do `"{open_ssl_prefix}"` (keep the quotes),
|
||||
# autconf fails to find ssl.
|
||||
PYTHON_CONFIGURE_ARGS.append(f"--with-openssl={open_ssl_prefix}")
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
PYTHON_CONFIGURE_ARGS.extend(["--with-universal-archs=intel-64", "--enable-universalsdk"])
|
||||
|
||||
for PYTHON_VERSION in PYTHON_VERSIONS:
|
||||
try:
|
||||
@@ -885,6 +1067,7 @@ if "boost" in targets:
|
||||
"stage",
|
||||
"-s",
|
||||
"NO_BZIP2=1",
|
||||
*MAC_CROSS_COMPILE_INTEL_BJAM_ARGS,
|
||||
],
|
||||
download_url=BOOST_LOCATION,
|
||||
# don't remember what this is, but fail on 1.86
|
||||
@@ -905,6 +1088,11 @@ if "cgal" in targets:
|
||||
gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
|
||||
mpfr_args.extend(("--host", "none"))
|
||||
|
||||
OLD_CC = None
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
OLD_CC = os.environ.get("CC")
|
||||
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
|
||||
|
||||
build_dependency(
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
@@ -924,6 +1112,12 @@ if "cgal" in targets:
|
||||
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2",
|
||||
)
|
||||
|
||||
if MAC_CROSS_COMPILE_INTEL:
|
||||
if OLD_CC is None:
|
||||
del os.environ["CC"]
|
||||
else:
|
||||
os.environ["CC"] = OLD_CC
|
||||
|
||||
build_dependency(
|
||||
name=f"cgal-{CGAL_VERSION}",
|
||||
mode="cmake",
|
||||
@@ -934,8 +1128,8 @@ if "cgal" in targets:
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
|
||||
"-DCGAL_HEADER_ONLY=On",
|
||||
"-DBUILD_SHARED_LIBS=Off",
|
||||
f"-DCGAL_HEADER_ONLY=On",
|
||||
f"-DBUILD_SHARED_LIBS=Off",
|
||||
],
|
||||
download_url="https://github.com/CGAL/cgal.git",
|
||||
download_name="cgal",
|
||||
@@ -975,6 +1169,50 @@ if "usd" in targets:
|
||||
revision=f"v{USD_VERSION}",
|
||||
)
|
||||
|
||||
if "zstd" in targets:
|
||||
build_dependency(
|
||||
name=f"zstd-{ZSTD_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/zstd-{ZSTD_VERSION}",
|
||||
f"-DZSTD_BUILD_STATIC=ON",
|
||||
f"-DZSTD_BUILD_SHARED=OFF",
|
||||
f"-DCMAKE_INSTALL_LIBDIR=lib",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
cmake_dir="build/cmake/",
|
||||
download_url="https://github.com/facebook/zstd",
|
||||
download_name="zstd",
|
||||
download_tool=download_tool_git,
|
||||
revision=f"v{ZSTD_VERSION}",
|
||||
)
|
||||
|
||||
if "rocksdb" in targets:
|
||||
build_dependency(
|
||||
name=f"rocksdb-{ROCKSDB_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/rocksdb-{ROCKSDB_VERSION}",
|
||||
f"-DFAIL_ON_WARNINGS=Off",
|
||||
f"-DWITH_TESTS=OFF",
|
||||
f"-DWITH_TOOLS=OFF",
|
||||
f"-DWITH_GFLAGS=OFF",
|
||||
f"-DWITH_BENCHMARK_TOOLS=OFF",
|
||||
f"-DWITH_CORE_TOOLS=OFF",
|
||||
f"-DROCKSDB_BUILD_SHARED=Off",
|
||||
f"-DCMAKE_POSITION_INDEPENDENT_CODE=On",
|
||||
f"-DUSE_RTTI=On",
|
||||
f"-DWITH_ZSTD=On",
|
||||
f"-DPORTABLE=1",
|
||||
f"-DCMAKE_PREFIX_PATH={DEPS_DIR}/install/zstd-{ZSTD_VERSION}",
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
],
|
||||
download_url="https://github.com/facebook/rocksdb",
|
||||
download_name="rocksdb",
|
||||
download_tool=download_tool_git,
|
||||
revision=f"v{ROCKSDB_VERSION}",
|
||||
)
|
||||
|
||||
cecho("Building IfcOpenShell:", GREEN)
|
||||
|
||||
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
@@ -991,83 +1229,115 @@ cmake_args = [
|
||||
"-DUSE_MMAP=OFF",
|
||||
"-DBUILD_EXAMPLES=OFF",
|
||||
"-DBUILD_SHARED_LIBS=" + OFF_ON[not BUILD_STATIC],
|
||||
f"-DBOOST_ROOT={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
"-DGLTF_SUPPORT=ON",
|
||||
f"-DJSON_INCLUDE_DIR={DEPS_DIR}/install/json",
|
||||
f"-DEIGEN_DIR={DEPS_DIR}/install/eigen-3.3.9",
|
||||
"-DBoost_NO_BOOST_CMAKE=On",
|
||||
"-DADD_COMMIT_SHA=" + ("On" if ADD_COMMIT_SHA else "Off"),
|
||||
"-DVERSION_OVERRIDE=" + ("On" if ADD_COMMIT_SHA else "Off"),
|
||||
*MAC_CROSS_COMPILE_INTEL_ARGS,
|
||||
]
|
||||
"""Default CMake args to use for all CMake configs."""
|
||||
cmake_args_prefix_path: "list[str]" = [
|
||||
f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"{DEPS_DIR}/install/eigen-install-{EIGEN_VERSION}",
|
||||
f"{DEPS_DIR}/install/json-{JSON_VERSION}",
|
||||
]
|
||||
|
||||
|
||||
def get_cmake_args_prefix_path(additional_paths: "Sequence[str]" = ()) -> "list[str]":
|
||||
args_prefix_path = cmake_args_prefix_path.copy()
|
||||
args_prefix_path.extend(additional_paths)
|
||||
prefix_path = ";".join(args_prefix_path)
|
||||
return [f"-DCMAKE_PREFIX_PATH={prefix_path}"]
|
||||
|
||||
|
||||
if "wasm" in flags:
|
||||
# Boost is built by the build script so should not be found
|
||||
# inside of the sysroot set by the emscriptem toolchain
|
||||
cmake_args.append("-DWASM_BUILD=On")
|
||||
# set Eigen3 path for WASM to avoid find_package issues
|
||||
cmake_args.append(f"-DEIGEN_DIR={DEPS_DIR}/install/eigen-install-{EIGEN_VERSION}/include/eigen3")
|
||||
|
||||
schemas = os.environ.get("IFCOS_SCHEMAS")
|
||||
if schemas:
|
||||
cmake_args.append(f"-DSCHEMA_VERSIONS={schemas}")
|
||||
|
||||
if "cgal" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
"-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
"-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
|
||||
"-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
"-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
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}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DCGAL_INCLUDE_DIR={DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
f"-DGMP_LIBRARY_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DMPFR_LIBRARY_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
|
||||
cmake_args.extend(["-DOCC_INCLUDE_DIR=" + occ_include_dir, "-DOCC_LIBRARY_DIR=" + occ_library_dir])
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/occt-{OCCT_VERSION}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DOCC_INCLUDE_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade",
|
||||
f"-DOCC_LIBRARY_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
|
||||
elif "occ" in targets:
|
||||
# We don't support find_package for OCE.
|
||||
occ_include_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
cmake_args.extend(["-DOCC_INCLUDE_DIR=" + occ_include_dir, "-DOCC_LIBRARY_DIR=" + occ_library_dir])
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
|
||||
"-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada",
|
||||
]
|
||||
)
|
||||
# pcre is a dependency of OpenCOLLADA, but since we `find_package`,
|
||||
# we don't need to add it explicitly here as cmake will find it from the config.
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/OpenCOLLADA")
|
||||
else:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DCOLLADA_SUPPORT=" "Off",
|
||||
f"-DCOLLADA_SUPPORT=Off",
|
||||
]
|
||||
)
|
||||
|
||||
if "pcre" in targets:
|
||||
cmake_args.append("-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib")
|
||||
|
||||
if "libxml2" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
"-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
|
||||
]
|
||||
)
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}")
|
||||
if "wasm" in flags:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
|
||||
]
|
||||
)
|
||||
|
||||
if "hdf5" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
|
||||
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
cmake_args_prefix_path.append(f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}")
|
||||
else:
|
||||
cmake_args.append("-DHDF5_SUPPORT=Off")
|
||||
|
||||
if "usd" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
"-DUSD_SUPPORT=" "On",
|
||||
"-DUSD_INCLUDE_DIR=" f"{DEPS_DIR}/install/usd-{USD_VERSION}/include",
|
||||
"-DUSD_LIBRARY_DIR=" f"{DEPS_DIR}/install/usd-{USD_VERSION}/lib",
|
||||
f"-DUSD_SUPPORT=" "On",
|
||||
f"-DUSD_INCLUDE_DIR={DEPS_DIR}/install/usd-{USD_VERSION}/include",
|
||||
f"-DUSD_LIBRARY_DIR={DEPS_DIR}/install/usd-{USD_VERSION}/lib",
|
||||
]
|
||||
)
|
||||
|
||||
if "rocksdb" in targets:
|
||||
cmake_args.extend(
|
||||
[
|
||||
f"-DWITH_ROCKSDB=On",
|
||||
f"-DWITH_ZSTD=On",
|
||||
]
|
||||
)
|
||||
cmake_args_prefix_path.extend(
|
||||
[
|
||||
f"{DEPS_DIR}/install/rocksdb-{ROCKSDB_VERSION}",
|
||||
f"{DEPS_DIR}/install/zstd-{ZSTD_VERSION}",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1075,14 +1345,14 @@ if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(expl
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
exec_args = [
|
||||
"-DBUILD_IFCGEOM=" + OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" + OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" + OFF_ON["IfcConvert" in targets],
|
||||
"-DBUILD_IFCPYTHON=" "OFF",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
|
||||
f"-DBUILD_IFCGEOM={OFF_ON['IfcGeom' in targets]}",
|
||||
f"-DBUILD_GEOMSERVER={OFF_ON['IfcGeomServer' in targets]}",
|
||||
f"-DBUILD_CONVERT={OFF_ON['IfcConvert' in targets]}",
|
||||
f"-DBUILD_IFCPYTHON=OFF",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
run_cmake("", exec_args + cmake_args + get_cmake_args_prefix_path(), cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
@@ -1098,6 +1368,9 @@ if "IfcOpenShell-Python" in targets:
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
|
||||
|
||||
# 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)
|
||||
# So cache doesn't match and running build-all.py builds most of ifcopenshell libraries twice.
|
||||
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
@@ -1120,13 +1393,14 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
|
||||
|
||||
swig_when_built = []
|
||||
swig_prefix_paths: list[str] = []
|
||||
if "swig" in targets:
|
||||
swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")
|
||||
swig_prefix_paths.append(f"{DEPS_DIR}/install/swig")
|
||||
|
||||
run_cmake(
|
||||
"",
|
||||
cmake_args
|
||||
+ get_cmake_args_prefix_path(swig_prefix_paths)
|
||||
+ [
|
||||
"-DPYTHON_LIBRARY=" + python_library,
|
||||
*([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
|
||||
@@ -1140,10 +1414,9 @@ if "IfcOpenShell-Python" in targets:
|
||||
else []
|
||||
),
|
||||
"-DPYTHON_INCLUDE_DIR=" + python_include,
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX="
|
||||
+ ["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
|
||||
*swig_when_built,
|
||||
],
|
||||
cmake_dir=CMAKE_DIR,
|
||||
cwd=python_dir,
|
||||
@@ -1155,9 +1428,15 @@ if "IfcOpenShell-Python" in targets:
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
if python_executable:
|
||||
module_dir = os.path.dirname(
|
||||
run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"])
|
||||
run([python_executable, "-m", "ensurepip"])
|
||||
run([python_executable, "-m", "pip", "install", "--user", "numpy", "typing_extensions"])
|
||||
module_dir = run(
|
||||
[python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]
|
||||
)
|
||||
# Use just the last line is used,
|
||||
# because output might contain warning like `No stream support: No module named 'lark'`.
|
||||
module_dir = module_dir.strip().splitlines()[-1]
|
||||
module_dir = os.path.dirname(module_dir)
|
||||
|
||||
if platform.system() != "Darwin":
|
||||
if BUILD_CFG == "Release":
|
||||
@@ -1199,6 +1478,10 @@ if "IfcOpenShell-Python" in targets:
|
||||
|
||||
module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
|
||||
assert module_dir
|
||||
# Not sure why, but added after reading this in the logs
|
||||
# cp: /Users/runner/work/IfcOpenShell/IfcOpenShell/build/Darwin/x86_64/10.15/install/ifcopenshell/python-3.9.11: No such file or directory
|
||||
# D'oh this was just due to a missing f-string f but doesn't hurt to keep it in.
|
||||
run(["mkdir", "-p", os.path.join(DEPS_DIR, "install", "ifcopenshell")])
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
|
||||
|
||||
logger.info("\rBuilt IfcOpenShell...\n\n")
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 34300d41ad..09b2e0d45f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -721,6 +721,8 @@ else()
|
||||
OCCT_CHECK_AND_UNSET ("3RDPARTY_DOT_EXECUTABLE")
|
||||
endif()
|
||||
|
||||
+list (REMOVE_ITEM BUILD_TOOLKITS ExpToCasExe)
|
||||
+
|
||||
# bison
|
||||
if (BUILD_YACCLEX)
|
||||
list (APPEND OCCT_3RDPARTY_CMAKE_LIST "adm/cmake/bison")
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/OpenCOLLADAConfig.cmake.in b/OpenCOLLADAConfig.cmake.in
|
||||
index d765b108..6800c2a7 100644
|
||||
--- a/OpenCOLLADAConfig.cmake.in
|
||||
+++ b/OpenCOLLADAConfig.cmake.in
|
||||
@@ -31,7 +31,7 @@ set(OPENCOLLADA_INCLUDE_DIRS
|
||||
include("@PACKAGE_OPENCOLLADA_INST_CMAKECONFIG@/OpenCOLLADATargets.cmake")
|
||||
|
||||
# Set the library variable
|
||||
-if(UNIX)
|
||||
+if(0)
|
||||
set(OPENCOLLADA_LIBRARIES
|
||||
ftoa_shared
|
||||
buffer_shared
|
||||
@@ -0,0 +1,84 @@
|
||||
[project]
|
||||
name = "IfcOpenShell"
|
||||
version = "0.8.4"
|
||||
description = "IfcOpenShell is a library to support the IFC file format"
|
||||
channels = ["conda-forge"]
|
||||
platforms = ["win-64", "linux-64", "osx-64"]
|
||||
|
||||
[environments]
|
||||
common = { features = ["common"], no-default-feature = true }
|
||||
prod = { features = ["prod", "common"], no-default-feature = true }
|
||||
dev = { features = ["dev", "common"], no-default-feature = true }
|
||||
lint = { features = ["lint"], no-default-feature = true }
|
||||
tests = { features = ["tests", "common"], no-default-feature = true }
|
||||
|
||||
[feature.lint.dependencies]
|
||||
ruff = "*"
|
||||
black = "*"
|
||||
|
||||
[feature.common.dependencies]
|
||||
# Build deps
|
||||
cmake = "==3.30.5"
|
||||
ninja = "*"
|
||||
swig = "*"
|
||||
c-compiler = "*"
|
||||
cxx-compiler = "*"
|
||||
|
||||
# Runtime deps
|
||||
python = "3.12.*"
|
||||
libboost-devel = "*"
|
||||
occt = { version = "*", build = "*novtk*" }
|
||||
cgal-cpp = "*"
|
||||
numpy = "*"
|
||||
lark = "*"
|
||||
hdf5 = "*"
|
||||
eigen = "*"
|
||||
mpfr = "*"
|
||||
gmp = "*"
|
||||
nlohmann_json = "*"
|
||||
zlib = "*"
|
||||
# Not strictly necessary
|
||||
pythonocc-core = "*"
|
||||
|
||||
[feature.tests.dependencies]
|
||||
pytest = "*"
|
||||
shapely = "*"
|
||||
tabulate = "*"
|
||||
isodate = "*"
|
||||
python-dateutil = "*"
|
||||
xmlschema = "*"
|
||||
xsdata = "*"
|
||||
lxml = "*"
|
||||
networkx = "*"
|
||||
|
||||
[feature.common.target.win-64.dependencies]
|
||||
vs2022_win-64 = "*"
|
||||
#vs_win-64 = "*" # Visual Studio
|
||||
#vswhere = "*" # Visual Studio
|
||||
|
||||
[feature.common.tasks]
|
||||
init-submodules = { cmd = "git submodule update --init --recursive", outputs =["$PIXI_PROJECT_ROOT/src/svgfill/3rdparty/svgpp/*"]}
|
||||
|
||||
[feature.prod.target.win-64.tasks]
|
||||
configure-release = { cmd = ["cmake", "--preset", "win-release", "-B", "build/win-release", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }] }
|
||||
build-release = { cmd = ["cmake", "--build", "build/win-release", "--config", "Release"], description = "Build the project" }
|
||||
|
||||
[feature.dev.target.win-64.tasks]
|
||||
configure-debug = { cmd = ["cmake", "--preset", "win-debug", "-B", "build/win-debug", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }], outputs=["build/win-debug/CMakeCache.txt"] }
|
||||
build-debug = { cmd = ["cmake", "--build", "build/win-debug", "--config", "Debug"], description = "Build the project", depends-on = ["configure-debug"] }
|
||||
install-debug = { cmd = ["cmake", "--install", "build/win-debug", "--config", "Debug"], description = "Install the project" } # Optionally Install files to your desired env using --prefix
|
||||
|
||||
vsdebug = { cmd = ["python"], description = "Run a python script with vs debugger attached" }
|
||||
|
||||
[feature.tests.tasks]
|
||||
configure-test = { cmd = ["cmake", "--preset", "win-test", "-B", "build/win-test", "cmake"], description = "Configure the project", depends-on=[{ task="init-submodules", environment = "common" }], outputs=["build/win-test/CMakeCache.txt"] }
|
||||
build-test = { cmd = ["cmake", "--build", "build/win-test", "--config", "Release"], description = "Build the project", depends-on = ["configure-test"], outputs=["build/win-test/IfcGeom.lib"] }
|
||||
install-test = { cmd = ["cmake", "--install", "build/win-test", "--config", "Release"], description = "Install the project", depends-on = ["build-test"] } # Optionally Install files to your desired env using --prefix
|
||||
|
||||
test = { cmd = "pytest .", cwd="src/ifcopenshell-python/test", depends-on=["install-test"], env = {PYTHONPATH="$PIXI_PROJECT_ROOT/src/ifcpatch"}}
|
||||
|
||||
[feature.lint.tasks]
|
||||
lint = { cmd = "ruff check && black --diff --check ."}
|
||||
|
||||
[feature.common.target.win-64.activation]
|
||||
#scripts = ["cmake/activate.bat"]
|
||||
+1
-1
@@ -7,7 +7,7 @@ source:
|
||||
|
||||
build:
|
||||
script: |
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
|
||||
BUILD_CFG=Release python nix/build-all.py --without-rocksdb --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
|
||||
mv package/ifcopenshell .
|
||||
cp pyodide/setup.py .
|
||||
|
||||
|
||||
+1
-1
@@ -7,5 +7,5 @@ setup(name='ifcopenshell',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
url='http://ifcopenshell.org',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['*.so', '*.json']},
|
||||
package_data={'ifcopenshell': ['util/schema/*.json', 'util/schema/*.ifc'], '': ['*.so', '*.json', '*.ifc']},
|
||||
)
|
||||
|
||||
+1
-3
@@ -38,6 +38,7 @@ exclude = [
|
||||
"src/ifc2ca/templates",
|
||||
]
|
||||
[tool.ruff.lint]
|
||||
preview = true
|
||||
select = [
|
||||
# Default Ruff rules.
|
||||
# "E4", # imports
|
||||
@@ -60,7 +61,4 @@ ignore = [
|
||||
"UP030", # implicit references for positional format fields
|
||||
"UP031", # Replace % with .format
|
||||
"UP032", # Replace .format with f-string
|
||||
|
||||
# Deprecated Ruff rules.
|
||||
"UP038", # Use X | Y in isinstance
|
||||
]
|
||||
|
||||
+3
-12
@@ -6,7 +6,7 @@ from numpy.typing import NDArray
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float64],
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
@@ -22,7 +22,7 @@ def camera_vectors_from_element_placement(
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
) -> tuple[list[float], list[float], list[float]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
@@ -38,16 +38,7 @@ def camera_vectors_from_target_position(
|
||||
camera_direction = unit_vector(-camera_offset) # pylint: disable=invalid-unary-operand-type
|
||||
camera_right = unit_vector(np.cross(np.array([0.0, 0.0, 1.0]), camera_direction))
|
||||
camera_up = unit_vector(np.cross(camera_direction, camera_right))
|
||||
return camera_position, camera_direction, camera_up
|
||||
# rotation_transform = np.eye(4)
|
||||
# rotation_transform[0, :3] = camera_right
|
||||
# rotation_transform[1, :3] = camera_up
|
||||
# rotation_transform[2, :3] = camera_direction
|
||||
# translation_transform = np.eye(4)
|
||||
# translation_transform[:3, -1] = -camera_position
|
||||
# look_at_transform = np.matmul(rotation_transform, translation_transform)
|
||||
# mat = np.linalg.inv(look_at_transform)
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
return camera_position.tolist(), camera_direction.tolist(), camera_up.tolist()
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||
|
||||
@@ -192,6 +192,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -179,6 +179,8 @@ class TopicHandler:
|
||||
bcf_zip: The BCF zip file to save to.
|
||||
"""
|
||||
topic_dir = self.guid
|
||||
# simulating directory creation (ZipFile in python < 3.11 doesn't have mkdir)
|
||||
destination_zip.writestr(f"{topic_dir}/", "")
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
|
||||
@@ -319,9 +319,7 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
elem_placement[:3, 3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -381,7 +379,7 @@ def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
camera_position: list[float], camera_dir: list[float], camera_up: list[float]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
+4
-2
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=358395c
|
||||
OLD:=6924012
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -127,7 +127,7 @@ endif
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download GitPython --dest=./wheels
|
||||
# Provides audio playback for costing
|
||||
# This is a REALLY IMPORTANT feature
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/Andrej730/aud.git --wheel-dir=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel git+https://github.com/zdhoward/aud --wheel-dir=./wheels
|
||||
# IfcOpenShell dependency - support for new typing features
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download typing_extensions --dest=./wheels
|
||||
# Required by IfcCSV
|
||||
@@ -192,6 +192,8 @@ else ifeq ($(PLATFORM), macos)
|
||||
else
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
endif
|
||||
# Required by ifctester web ui.
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download flask $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
|
||||
# jQuery is required for web UI functionality
|
||||
cd build/bonsai/bim/data/webui/static/js/ && wget https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
Binary file not shown.
|
After Width: | Height: | Size: 217 B |
@@ -13,10 +13,10 @@ DATA;
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','Whether or not this drawing can be referenced in other drawings.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','Comma separated list of selector expressions to evaluate for each drawing elementand add results to their ''class'' attribute.\X2\000A\X0\E.g. ''Name, id'' would add to ''class'' value similar to ''Name-Wall id-1220''.\X2\000A\X0\Then it can be used to applied css styles based on the resulting class.\X2\000A\X0\If attribute is not present on the element, then it won''t be added to it''s ''class''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
@@ -5,10 +5,10 @@ FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Anno
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#4,#33,#29,#32,#3,#2));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCPROPERTYSETTEMPLATE('0iKwujnQL9IevVQato8f7Z',$,'BBIM_Batting','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/BATTING,IfcTypeProduct',(#6,#7));
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0t2LEesGT1QRQtrIZUAR8L',$,'Thickness','Batting thickness',.P_SINGLEVALUE.,'IfcPositiveLengthMeasure',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('082PndS6v2kBOiJoSboMnh',$,'Reverse pattern direction','Reverse batting pattern (swap starting and ending points)',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
@@ -36,5 +36,7 @@ DATA;
|
||||
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
|
||||
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
|
||||
#32=IFCSIMPLEPROPERTYTEMPLATE('0gjJzDYBX8P85qn1xcAOOo',$,'Reverse_List','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#33=IFCSIMPLEPROPERTYTEMPLATE('22TrcxF8jFNB4buSmzjGEF',$,'List_Separator','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -36,6 +36,7 @@ from bonsai.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
from typing import Union
|
||||
from logging import Logger
|
||||
from math import radians
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -74,12 +75,12 @@ class IfcExporter:
|
||||
json.dump(jsonData, outfile, indent=None if self.ifc_export_settings.json_compact else 4)
|
||||
|
||||
def set_header(self):
|
||||
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.wrapped_data.header.file_name.time_stamp = (
|
||||
self.file.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.header.file_name.time_stamp = (
|
||||
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.file.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), tool.Blender.get_bonsai_version()
|
||||
)
|
||||
|
||||
@@ -104,7 +105,16 @@ class IfcExporter:
|
||||
|
||||
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = self.file.by_id(tool.Blender.get_object_bim_props(obj).ifc_definition_id)
|
||||
if tool.Geometry.is_scaled(obj):
|
||||
# Handle camera scales specially
|
||||
if obj.type == "CAMERA":
|
||||
# Check if this is a reflected ceiling plan camera
|
||||
camera = tool.Ifc.get_entity(obj)
|
||||
if ifcopenshell.util.element.get_pset(camera, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW":
|
||||
# Ensure reflected ceiling cameras have the correct scale
|
||||
if obj.scale != (-1, -1, -1):
|
||||
obj.scale = (-1, -1, -1)
|
||||
# Skip all other scale handling for cameras
|
||||
elif tool.Geometry.is_scaled(obj):
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
# update_representation might not apply scale if the object has openings
|
||||
# reset it, so let user know that the scale wasn't saved.
|
||||
|
||||
@@ -222,8 +222,6 @@ def refresh_ui_data():
|
||||
if isinstance(ifc_file := tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
ifc_file.clear_cache()
|
||||
|
||||
props = tool.Drawing.get_document_props()
|
||||
props.should_draw_decorations = props.should_draw_decorations
|
||||
if tool.Web.get_web_props().is_connected:
|
||||
tool.Web.send_webui_data()
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, ge
|
||||
import bonsai.tool as tool
|
||||
from types import EllipsisType
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bonsai.bim.prop
|
||||
@@ -51,7 +51,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def draw_attributes(
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: Union[bpy.types.bpy_prop_collection_idprop[Attribute], Sequence[Attribute]],
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
@@ -296,7 +296,13 @@ def add_attribute_enum_items_descriptions(
|
||||
new_enum_description.name = description
|
||||
|
||||
|
||||
def add_attribute_description(attribute_blender: bonsai.bim.prop.Attribute, attribute_ifc=None):
|
||||
def add_attribute_description(
|
||||
attribute_blender: bonsai.bim.prop.Attribute,
|
||||
attribute_ifc: Union[ifcopenshell.entity_instance, None] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param attribute_ifc: IFC Entity to use as a fallback source of description (using "Description" attribute).
|
||||
"""
|
||||
if not attribute_blender.name:
|
||||
return
|
||||
version = tool.Ifc.get_schema()
|
||||
@@ -550,6 +556,7 @@ def draw_filter(
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "comparison", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
|
||||
@@ -132,7 +132,7 @@ class IfcStore:
|
||||
def generate_cache_path() -> str:
|
||||
"""Generate cache path based on the active file and it's path."""
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_key = IfcStore.path + IfcStore.file.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
cache_path = os.path.join(prefs.cache_dir, f"{ifc_hash}.h5")
|
||||
|
||||
@@ -219,9 +219,7 @@ class IfcImporter:
|
||||
self.gross_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.element_types: set[ifcopenshell.entity_instance] = set()
|
||||
self.spatial_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.type_products = {}
|
||||
self.meshes: dict[str, OBJECT_DATA_TYPE] = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
self.unit_scale = 1.0
|
||||
# ifc definition ids to blender elements mapping
|
||||
@@ -285,6 +283,7 @@ class IfcImporter:
|
||||
self.place_objects_in_collections()
|
||||
self.profile_code("Place objects in collections")
|
||||
self.setup_arrays()
|
||||
self.update_linked_aggregates()
|
||||
self.profile_code("Setup arrays")
|
||||
tool.Project.load_linked_models_from_ifc()
|
||||
self.profile_code("Load linked models")
|
||||
@@ -463,6 +462,10 @@ class IfcImporter:
|
||||
return False
|
||||
|
||||
def calculate_model_offset(self) -> None:
|
||||
# TODO:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
print("WARNING. Calculating model offset for IFCSQLite is not supported.")
|
||||
return
|
||||
props = tool.Georeference.get_georeference_props()
|
||||
if self.ifc_import_settings.false_origin_mode == "MANUAL":
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
@@ -516,11 +519,52 @@ class IfcImporter:
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement))
|
||||
|
||||
def create_element_types(self):
|
||||
# TODO:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
self.create_element_types_sqlite(self.element_types)
|
||||
return
|
||||
for element_type in self.element_types:
|
||||
if not element_type:
|
||||
continue
|
||||
self.create_element_type(element_type)
|
||||
|
||||
def create_element_types_sqlite(self, element_types: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sqlite)
|
||||
geometry_cache = self.file.get_geometry([e.id() for e in element_types])
|
||||
geometry_meshes: dict[str, bpy.types.Mesh] = {}
|
||||
for geometry_id, geometry in geometry_cache["geometry"].items():
|
||||
verts = geometry["verts"]
|
||||
fake_geometry = type("Geometry", (), {"id": geometry_id})
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(fake_geometry) # pyright: ignore[reportArgumentType]
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
if geometry["faces"].size:
|
||||
mesh = tool.Loader.create_mesh_from_shape(
|
||||
mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3)
|
||||
)
|
||||
else:
|
||||
vertices = verts.reshape(-1, 3).tolist()
|
||||
edges = geometry["edges"].reshape(-1, 2).tolist()
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
tool.Loader.link_mesh(fake_geometry, mesh) # pyright: ignore[reportArgumentType]
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
mesh["ios_material_ids"] = geometry["material_ids"]
|
||||
self.meshes[mesh_name] = mesh
|
||||
geometry_meshes[geometry_id] = mesh
|
||||
|
||||
shapes = geometry_cache["shapes"]
|
||||
for element in element_types:
|
||||
# Allow missing element types to accomodate older ifcsqlite files
|
||||
# that didn't store element types geometry.
|
||||
shape = shapes.get(element.id())
|
||||
if shape:
|
||||
geometry_id = shapes[element.id()]["geometry"]
|
||||
else:
|
||||
geometry_id = None
|
||||
mesh = None if geometry_id is None else geometry_meshes[geometry_id]
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
|
||||
def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
mesh = None
|
||||
@@ -547,7 +591,6 @@ class IfcImporter:
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh, False)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
@@ -582,18 +625,13 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self) -> None:
|
||||
if tool.Blender.get_addon_preferences().spatial_elements_unselectable:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
self.create_generic_elements(self.gross_elements, is_gross=True)
|
||||
|
||||
def create_generic_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance], unselectable=False, is_gross=False
|
||||
) -> None:
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], is_gross=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -617,10 +655,6 @@ class IfcImporter:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sql.sqlite)
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
@@ -631,15 +665,13 @@ class IfcImporter:
|
||||
verts = geometry["verts"]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
if geometry["faces"].size:
|
||||
mesh = tool.Loader.create_mesh_from_shape(
|
||||
mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3)
|
||||
)
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
vertices = verts.reshape(-1, 3).tolist()
|
||||
edges = geometry["edges"].reshape(-1, 2).tolist()
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
@@ -920,6 +952,9 @@ class IfcImporter:
|
||||
if not props.ifc_file:
|
||||
props.ifc_file = self.ifc_import_settings.input_file
|
||||
self.file = tool.Ifc.get()
|
||||
# IFC4 Reference View shall have no booleans https://github.com/BuildingSMART/IFC4-CV/issues/14
|
||||
if self.file.schema == "IFC4" and "ReferenceView" in str(self.file.header.file_description.description):
|
||||
self.ifc_import_settings.void_limit = 0
|
||||
|
||||
def calculate_unit_scale(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
@@ -977,6 +1012,7 @@ class IfcImporter:
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
obj.hide_viewport = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
@@ -1178,6 +1214,36 @@ class IfcImporter:
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
def update_linked_aggregates(self):
|
||||
# TODO Remove this after a while. See commit 17d6b8a
|
||||
# https://github.com/IfcOpenShell/IfcOpenShell/commit/17d6b8af61c8dd7cd82f6e72b2fb3831d851a33d#commitcomment-163151609
|
||||
|
||||
groups = self.file.by_type("IfcGroup")
|
||||
target_groups = [group for group in groups if group.Name == "BBIM_Linked_Aggregate"]
|
||||
if not target_groups:
|
||||
return
|
||||
|
||||
for i, group in enumerate(target_groups):
|
||||
name = "Default"
|
||||
elements = ifcopenshell.util.element.get_grouped_by(self.file.by_id(group.id()), is_recursive=True)
|
||||
for j, element in enumerate(elements):
|
||||
split = element.Name.rsplit("_")
|
||||
name = split[0]
|
||||
try:
|
||||
aggregate_index = int(split[1])
|
||||
except:
|
||||
aggregate_index = 0
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate", should_inherit=False)
|
||||
if not pset:
|
||||
return
|
||||
if "Aggregate_Index" not in pset.keys():
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
self.file,
|
||||
pset=self.file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": aggregate_index, "Name": name},
|
||||
)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
"""
|
||||
|
||||
@@ -94,6 +94,9 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
aggregates_to_check = set()
|
||||
|
||||
# First pass: unassign all parts and track their aggregates
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -101,6 +104,10 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
continue
|
||||
|
||||
# Track this aggregate for later checking
|
||||
aggregates_to_check.add(aggregate)
|
||||
|
||||
core.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
@@ -116,6 +123,29 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
# Second pass: delete aggregates that now have no parts
|
||||
deleted_aggregates = []
|
||||
for aggregate in aggregates_to_check:
|
||||
related_objects = ifcopenshell.util.element.get_parts(aggregate)
|
||||
if len(related_objects) == 0:
|
||||
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
|
||||
deleted_aggregates.append(aggregate_name)
|
||||
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj:
|
||||
ifcopenshell.api.root.remove_product(tool.Ifc.get(), product=aggregate)
|
||||
bpy.data.objects.remove(aggregate_obj, do_unlink=True)
|
||||
|
||||
# Show info message if aggregates were deleted
|
||||
if deleted_aggregates:
|
||||
if len(deleted_aggregates) == 1:
|
||||
self.report(
|
||||
{"INFO"}, f"Aggregate '{deleted_aggregates[0]}' was deleted because it had no remaining parts"
|
||||
)
|
||||
else:
|
||||
aggregate_list = ", ".join(f"'{name}'" for name in deleted_aggregates)
|
||||
self.report({"INFO"}, f"Aggregates {aggregate_list} were deleted because they had no remaining parts")
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
|
||||
@@ -1215,7 +1215,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
assert blender_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if self.viewpoint_guid:
|
||||
viewpoint_guid = self.viewpoint_guid
|
||||
viewpoint_guid = self.viewpoint_guid + ".bcfv"
|
||||
if viewpoint_guid not in topic.viewpoints:
|
||||
self.report({"ERROR"}, f"No such viewpoint in the active topic: '{viewpoint_guid}'.")
|
||||
return {"CANCELLED"}
|
||||
@@ -1313,7 +1313,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
rotation = Matrix((x_axis, y_axis, z_axis))
|
||||
rotation.invert()
|
||||
matrix = np.matrix(
|
||||
matrix = np.array(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], camera.camera_view_point.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], camera.camera_view_point.y],
|
||||
|
||||
@@ -40,6 +40,7 @@ from mathutils import Vector, Matrix
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
from bonsai.bim.module.boundary.decorator import BoundaryDecorator
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
import bonsai.core
|
||||
import bonsai.core.geometry
|
||||
from typing import Union, Optional
|
||||
@@ -1058,15 +1059,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
tool.Model.unit_scale = self.unit_scale
|
||||
builder = ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
surface = tool.Ifc.get().createIfcCurveBoundedPlane()
|
||||
surface.BasisSurface = tool.Ifc.get().createIfcPlane(
|
||||
tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([o / self.unit_scale for o in p1]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
|
||||
)
|
||||
)
|
||||
placement = builder.create_axis2_placement_3d([o / self.unit_scale for o in p1], z_axis, x_axis)
|
||||
surface.BasisSurface = tool.Ifc.get().create_entity("IfcPlane", placement)
|
||||
|
||||
if tool.Ifc.get().schema != "IFC2X3":
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
|
||||
|
||||
@@ -56,6 +56,7 @@ class ClassificationsData:
|
||||
data = element.get_info()
|
||||
if tool.Ifc.get().schema == "IFC2X3" and element.EditionDate:
|
||||
data["EditionDate"] = ifcopenshell.util.date.ifc2datetime(data["EditionDate"])
|
||||
data["Name"] = data["Name"] or "Unnamed"
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
@@ -192,21 +191,11 @@ class EnableEditingClassification(bpy.types.Operator):
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
def callback(name, prop, data):
|
||||
if name == "ReferenceTokens":
|
||||
geo_props = tool.Georeference.get_georeference_props()
|
||||
new = geo_props.projected_crs.add()
|
||||
new.name = name
|
||||
new.data_type = "string"
|
||||
new.is_null = data[name] is None
|
||||
new.is_optional = True
|
||||
new.string_value = "" if new.is_null else json.dumps(data[name])
|
||||
return True
|
||||
|
||||
props = tool.Classification.get_classification_props()
|
||||
props.classification_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes(
|
||||
tool.Ifc.get().by_id(self.classification), props.classification_attributes, callback
|
||||
tool.Ifc.get().by_id(self.classification),
|
||||
props.classification_attributes,
|
||||
)
|
||||
props.active_classification_id = self.classification
|
||||
return {"FINISHED"}
|
||||
@@ -248,13 +237,7 @@ class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Classification.get_classification_props()
|
||||
|
||||
def callback(attributes, prop):
|
||||
if prop.name == "ReferenceTokens":
|
||||
attributes[prop.name] = json.loads(prop.string_value)
|
||||
return True
|
||||
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes, callback=callback)
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.classification.edit_classification(
|
||||
ifc_file,
|
||||
|
||||
@@ -73,6 +73,7 @@ class BIM_PT_classifications(Panel):
|
||||
self.draw_ui(classification)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
assert self.layout
|
||||
if self.props.is_adding:
|
||||
bonsai.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
|
||||
row = self.layout.row(align=True)
|
||||
@@ -83,10 +84,12 @@ class BIM_PT_classifications(Panel):
|
||||
row.operator("bim.enable_adding_manual_classification", text="Add Classification", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
assert self.layout
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_classification_from_bsdd", icon="ADD")
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
assert self.layout
|
||||
if ClassificationsData.data["has_classification_file"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "available_classifications", text="")
|
||||
@@ -97,13 +100,15 @@ class BIM_PT_classifications(Panel):
|
||||
row.label(text="No Active Classification Library")
|
||||
row.operator("bim.load_classification_library", text="", icon="IMPORT")
|
||||
|
||||
def draw_editable_ui(self):
|
||||
def draw_editable_ui(self) -> None:
|
||||
assert self.layout
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_classification", text="Save changes", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_classification", text="", icon="CANCEL")
|
||||
bonsai.bim.helper.draw_attributes(self.props.classification_attributes, self.layout)
|
||||
|
||||
def draw_ui(self, classification):
|
||||
def draw_ui(self, classification: dict[str, Any]) -> None:
|
||||
assert self.layout
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=classification["Name"], icon="ASSET_MANAGER")
|
||||
if not self.props.active_classification_id:
|
||||
@@ -277,7 +282,11 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool((obj := context.active_object) and tool.Ifc.get_entity(obj))
|
||||
return (
|
||||
(obj := tool.Blender.get_active_object())
|
||||
and (element := tool.Ifc.get_entity(obj))
|
||||
and element.is_a("IfcObjectDefinition")
|
||||
)
|
||||
|
||||
def get_object_name(self, context: bpy.types.Context) -> str:
|
||||
assert (obj := context.active_object)
|
||||
|
||||
@@ -28,6 +28,7 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
@@ -60,8 +61,8 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
{
|
||||
"ifc": os.path.basename(tool.Ifc.get_path()) if os.path.isfile(tool.Ifc.get_path()) else "Unsaved",
|
||||
"schema": tool.Ifc.get().schema,
|
||||
"preprocessor_version": tool.Ifc.get().wrapped_data.header.file_name.preprocessor_version,
|
||||
"originating_system": tool.Ifc.get().wrapped_data.header.file_name.originating_system,
|
||||
"preprocessor_version": tool.Ifc.get().header.file_name.preprocessor_version,
|
||||
"originating_system": tool.Ifc.get().header.file_name.originating_system,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -253,13 +254,17 @@ class ProfileImportIFC(bpy.types.Operator):
|
||||
class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Test All Shapes"
|
||||
bl_description = "Look for errors in all the shapes contained in the file"
|
||||
bl_description = (
|
||||
"Look for errors in all the shapes contained in the file.\n\nSee system console for the detailed results."
|
||||
)
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
geometry_library: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Geometry Library",
|
||||
description="Geometry library to use for testing shape creation.",
|
||||
items=[(i, i, "") for i in get_args(ifcopenshell.geom.GEOMETRY_LIBRARY)],
|
||||
default="opencascade",
|
||||
# By default use the same library as used for importing ifc project.
|
||||
default="hybrid-cgal-simple-opencascade",
|
||||
)
|
||||
custom_geometry_library: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Custom Geometry Library",
|
||||
@@ -272,19 +277,23 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
if not tool.Ifc.get():
|
||||
cls.poll_message_set("No IFC file is loaded.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
geometry_library = self.custom_geometry_library or self.geometry_library
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||
print(f"Testing geometry library '{geometry_library}'.")
|
||||
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("keep-bounding-boxes", True)
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
failures = []
|
||||
failures: list[ifcopenshell.entity_instance] = []
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements, 1):
|
||||
if element.GlobalId in excludes:
|
||||
@@ -303,12 +312,11 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
if shape:
|
||||
assert isinstance(shape, W.TriangulationElement)
|
||||
geom = shape.geometry
|
||||
print(
|
||||
"Success",
|
||||
time.time() - start,
|
||||
len(shape.geometry.verts),
|
||||
len(shape.geometry.edges),
|
||||
len(shape.geometry.faces),
|
||||
f"Success {time.time() - start:.3f}s "
|
||||
f"V:{(len(geom.verts)//3)} E:{(len(geom.edges)//2)} F:{(len(geom.faces)//3)}"
|
||||
)
|
||||
self.report({"INFO"}, f"Failed shapes: {len(failures)}, check the system console for details.")
|
||||
for failure in failures:
|
||||
@@ -810,7 +818,12 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.merge_identical_objects"
|
||||
bl_label = "Merge Identical Objects"
|
||||
bl_description = "For materials currently only IfcMaterials are supported"
|
||||
bl_description = (
|
||||
"Merge identical IFC objects (that match all attributes).\n"
|
||||
"\n"
|
||||
"SHIFT + CLICK to merge by name/identification attribute only.\n"
|
||||
"Merges names with number suffix, as well (ex: foo, foo.001, foo.002)\n"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
@@ -818,18 +831,36 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
items=((s, s.capitalize(), "") for s in get_args(tool.Debug.PurgeMergeObjectType)),
|
||||
)
|
||||
|
||||
by_name_or_identification_only: bpy.props.BoolProperty(
|
||||
name="By Name/Identification Only",
|
||||
description="Merge based only on Name or Identification attribute, ignoring other properties",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
object_type: tool.Debug.PurgeMergeObjectType
|
||||
|
||||
def invoke(self, context, event):
|
||||
# Check if shift key is pressed
|
||||
if event.shift:
|
||||
self.by_name_or_identification_only = True
|
||||
else:
|
||||
self.by_name_or_identification_only = False
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
object_type: str = self.object_type
|
||||
if object_type in ("PROFILE", "TYPE"):
|
||||
self.report({"ERROR"}, f"Unsupported object type {object_type}.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
merged_data = tool.Debug.merge_identical_objects(object_type)
|
||||
merged_data = tool.Debug.merge_identical_objects(
|
||||
object_type, by_name_or_identification_only=self.by_name_or_identification_only
|
||||
)
|
||||
plural_object_type = f"{object_type.lower().replace('_', ' ')}s"
|
||||
if merged_data:
|
||||
merge_mode = " by name/identification" if self.by_name_or_identification_only else ""
|
||||
for element_type, element_names in merged_data.items():
|
||||
print(f"- {element_type}:")
|
||||
for name in element_names:
|
||||
@@ -838,7 +869,8 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
merged = sum(len(v) for v in merged_data.values())
|
||||
|
||||
msg = " See system console for details." if merged else ""
|
||||
self.report({"INFO"}, f"{merged} identical {plural_object_type} were merged.{msg}")
|
||||
merge_mode = " (by name/identification)" if self.by_name_or_identification_only else ""
|
||||
self.report({"INFO"}, f"{merged} identical {plural_object_type} were merged{merge_mode}.{msg}")
|
||||
|
||||
if merged == 0:
|
||||
return
|
||||
|
||||
@@ -72,7 +72,10 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.convert_to_blender")
|
||||
|
||||
row = layout.row()
|
||||
row = layout.row(align=True)
|
||||
op_props = wm.operator_properties_last(bpy.ops.bim.create_all_shapes.idname())
|
||||
row.prop(op_props, "geometry_library", text="")
|
||||
row.prop(op_props, "custom_geometry_library", text="", placeholder="Custom Geometry Library")
|
||||
row.operator("bim.create_all_shapes")
|
||||
|
||||
row = layout.row()
|
||||
|
||||
@@ -61,6 +61,7 @@ classes = (
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingElementFilter,
|
||||
operator.EnableEditingText,
|
||||
operator.ExcludeAnnotation,
|
||||
operator.ExpandSheet,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadReferences,
|
||||
|
||||
@@ -25,6 +25,7 @@ import ifcopenshell.util.unit
|
||||
import bonsai.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
from natsort import natsorted
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -234,12 +235,18 @@ class DecoratorData:
|
||||
cut_cache = {}
|
||||
slice_cache = {}
|
||||
fill_cache = {}
|
||||
camera_location_checksum = None
|
||||
camera_rotation_checksum = None
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
cls.cut_cache = {}
|
||||
cls.layerset_cache = {}
|
||||
cls.fill_cache = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, handler):
|
||||
cls.is_loaded = True
|
||||
cls.cut_cache = {}
|
||||
cls.layerset_cache = {}
|
||||
|
||||
text = {}
|
||||
dimension = {}
|
||||
@@ -332,7 +339,7 @@ class DecoratorData:
|
||||
returns font size in mm for current ifc text object"""
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
|
||||
# getting font size
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
|
||||
# use `regular` as default
|
||||
@@ -345,31 +352,35 @@ class DecoratorData:
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
|
||||
# get newline_at
|
||||
newline_at = pset_data.get("Newline_At", 0)
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
# other attributes
|
||||
props_literals = props.literals
|
||||
props_literals_n = len(props.literals)
|
||||
literals = tool.Drawing.get_text_literal(obj, return_list=True)
|
||||
literals_data = []
|
||||
for i, literal in enumerate(literals):
|
||||
assert isinstance(literals, list)
|
||||
literals_data: list[dict[str, Any]] = []
|
||||
product = tool.Drawing.get_assigned_product(element) or element
|
||||
for literal in literals:
|
||||
literal_value = literal.Literal
|
||||
literal_data = {
|
||||
"Literal": literal.Literal,
|
||||
"Literal": literal_value,
|
||||
"BoxAlignment": literal.BoxAlignment,
|
||||
"CurrentValue": tool.Drawing.replace_text_literal_variables(
|
||||
literal_value, product, reverse_list, list_separator
|
||||
),
|
||||
}
|
||||
if i < props_literals_n:
|
||||
literal_data["CurrentValue"] = props_literals[i].value
|
||||
else:
|
||||
literal_data["CurrentValue"] = literal.Literal
|
||||
|
||||
literals_data.append(literal_data)
|
||||
|
||||
return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
|
||||
return {
|
||||
"Literals": literals_data,
|
||||
"FontSize": font_size,
|
||||
"Symbol": symbol,
|
||||
"Newline_At": newline_at,
|
||||
"Reverse_List": reverse_list,
|
||||
"List_Separator": list_separator,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
@@ -483,9 +494,9 @@ class AnnotationData:
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append(relating_type)
|
||||
|
||||
results = [("0", "Untyped", "")]
|
||||
results.extend([(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types])
|
||||
results.sort(key=lambda x: x[1].lower())
|
||||
results = [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types]
|
||||
results = natsorted(results, key=lambda x: x[1])
|
||||
results = [("0", "Untyped", "")] + results
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -22,6 +22,7 @@ import blf
|
||||
import math
|
||||
import bmesh
|
||||
import shapely
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
@@ -601,7 +602,7 @@ class BaseDecorator:
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
text_data = DecoratorData.data["text"].get(obj.name, None)
|
||||
if props.is_editing:
|
||||
text_data = text_data | props.get_text_edited_data()
|
||||
text_data = props.get_text_edited_data()
|
||||
literals_data = text_data["Literals"]
|
||||
symbol = text_data["Symbol"]
|
||||
newline_at = text_data["Newline_At"]
|
||||
@@ -1205,17 +1206,7 @@ class PlanLevelDecorator(BaseDecorator):
|
||||
|
||||
def get_text():
|
||||
abs_z = verts[0].z
|
||||
z = abs_z
|
||||
|
||||
if element:
|
||||
spatial_container = ifcopenshell.util.element.get_container(element)
|
||||
|
||||
if spatial_container:
|
||||
container_obj = tool.Ifc.get_object(spatial_container)
|
||||
|
||||
if container_obj:
|
||||
spatial_container_z = container_obj.matrix_world.translation.z
|
||||
z = abs_z - spatial_container_z
|
||||
z = helper.get_relative_z(obj, element, abs_z)
|
||||
|
||||
rl = self.format_value(context, z)
|
||||
text = "{}{}".format("" if z < 0 else "+", rl)
|
||||
@@ -1635,6 +1626,7 @@ class CutDecorator:
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
handler.cache_camera_matrix()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW")
|
||||
|
||||
@classmethod
|
||||
@@ -1737,6 +1729,30 @@ class CutDecorator:
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
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())
|
||||
|
||||
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()
|
||||
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())
|
||||
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
|
||||
self.cache_camera_matrix()
|
||||
return True
|
||||
return False
|
||||
|
||||
def decorate(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
|
||||
has_cut_cache = element.id() in DecoratorData.cut_cache
|
||||
has_fill_cache = element.id() in DecoratorData.fill_cache
|
||||
@@ -1744,9 +1760,9 @@ class CutDecorator:
|
||||
# Currently selected objects must be recalculated as they may be being moved / edited.
|
||||
# If the camera is selected, we also recalculate as the user may be moving the camera.
|
||||
|
||||
if not has_cut_cache or obj.select_get() or context.scene.camera.select_get():
|
||||
if not has_cut_cache or obj.select_get() or self.is_camera_moved():
|
||||
self.recalculate_cut(context, obj, element)
|
||||
if not has_fill_cache or obj.select_get() or context.scene.camera.select_get():
|
||||
if not has_fill_cache or obj.select_get() or self.is_camera_moved():
|
||||
self.recalculate_fill(context, obj, element)
|
||||
|
||||
def recalculate_cut(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
|
||||
@@ -1943,8 +1959,7 @@ class DecorationsHandler:
|
||||
# NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR
|
||||
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
|
||||
if not DecoratorData.is_loaded:
|
||||
DecoratorData.load(handler)
|
||||
DecoratorData.clear_cache()
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
@@ -1971,5 +1986,8 @@ class DecorationsHandler:
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
|
||||
if not DecoratorData.is_loaded:
|
||||
DecoratorData.load(self)
|
||||
|
||||
for obj, decorator in DecoratorData.data["object_decorators"]:
|
||||
decorator.decorate(context, obj)
|
||||
|
||||
@@ -539,3 +539,26 @@ def add_newline_between_words(text: str, newline_at: int) -> str:
|
||||
start = space_index + 1 # Skip the space itself
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
Relative Z value if the element is inside a spatial container,
|
||||
otherwise the absolute Z.
|
||||
"""
|
||||
z = abs_z
|
||||
if element:
|
||||
spatial_container = ifcopenshell.util.element.get_container(element)
|
||||
if spatial_container:
|
||||
container_obj = tool.Ifc.get_object(spatial_container)
|
||||
if container_obj:
|
||||
spatial_container_z = container_obj.matrix_world.translation.z
|
||||
z = abs_z - spatial_container_z
|
||||
return z
|
||||
|
||||
@@ -106,14 +106,17 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = props.type_name
|
||||
ifc_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW")
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=has_representation,
|
||||
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
if representation := tool.Drawing.get_representation(element, ifc_context):
|
||||
tool.Drawing.reload_representation(obj=obj, representation=representation)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
if props.create_representation_for_type and object_type == "IMAGE":
|
||||
@@ -207,12 +210,6 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
should_duplicate_annotations=self.should_duplicate_annotations,
|
||||
)
|
||||
|
||||
# TODO: Why need to resync active drawing, if it wasn't changed.
|
||||
drawing = props.get_active_drawing()
|
||||
if drawing is None:
|
||||
return
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
class CreateDrawing(bpy.types.Operator):
|
||||
"""Creates/refreshes a .svg drawing
|
||||
@@ -435,21 +432,19 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
|
||||
return svg_path
|
||||
|
||||
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
obj.hide_render = obj.name not in visible_object_names
|
||||
|
||||
assert context.scene and context.view_layer and context.screen
|
||||
context.scene.render.filepath = str(Path(svg_path).with_suffix(".png"))
|
||||
assert (drawing_style := self.cprops.get_active_drawing_style())
|
||||
|
||||
tool.Blender.sync_render_visibility()
|
||||
|
||||
if drawing_style.render_type == "DEFAULT":
|
||||
bpy.ops.render.render(write_still=True)
|
||||
else:
|
||||
previous_visibility: dict[str, bool] = {}
|
||||
collection = tool.Blender.get_object_bim_props(self.camera).collection
|
||||
assert collection
|
||||
# Hie annotations.
|
||||
# Hide annotations.
|
||||
for obj in collection.objects:
|
||||
if context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
@@ -626,7 +621,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
path.attrib["d"] = d
|
||||
group.append(g)
|
||||
|
||||
def generate_wall_layers(self, context: bpy.types.Context, root) -> None:
|
||||
def generate_material_layers(self, context: bpy.types.Context, root) -> None:
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
if "projection" in el.get("class", "").split():
|
||||
continue
|
||||
@@ -795,8 +790,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
edge_bm.to_mesh(edge_mesh)
|
||||
edge_bm.free()
|
||||
|
||||
actual_path = svg_path[0:-4] + "0001.svg"
|
||||
freestyle_svg_exporter = tool.Blender.get_addon("freestyle_svg_exporter")
|
||||
context.scene.render.filepath = svg_path[0:-4]
|
||||
actual_path = freestyle_svg_exporter.create_path(bpy.context.scene)
|
||||
bpy.ops.render.render(write_still=False)
|
||||
|
||||
os.replace(actual_path, svg_path)
|
||||
@@ -840,6 +836,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
self.generate_bisect_linework(context, root)
|
||||
if self.cprops.generate_material_layers:
|
||||
self.generate_material_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
self.move_elements_to_top(root)
|
||||
|
||||
@@ -937,12 +935,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if self.cprops.cut_mode == "BISECT":
|
||||
self.remove_cut_linework(root)
|
||||
self.generate_bisect_linework(context, root)
|
||||
self.generate_wall_layers(context, root)
|
||||
if self.cprops.generate_material_layers:
|
||||
self.generate_material_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
self.move_elements_to_top(root)
|
||||
elif self.cprops.cut_mode == "OPENCASCADE":
|
||||
self.move_projection_to_bottom(root)
|
||||
self.generate_wall_layers(context, root)
|
||||
if self.cprops.generate_material_layers:
|
||||
self.generate_material_layers(context, root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
self.move_elements_to_top(root)
|
||||
|
||||
@@ -1252,6 +1252,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
def get_svg_classes(self, element, layer=None):
|
||||
classes = [element.is_a()]
|
||||
|
||||
# ─── Material ──────────────────────────────────────────────
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
material_name = ""
|
||||
if material:
|
||||
@@ -1264,20 +1266,36 @@ class CreateDrawing(bpy.types.Operator):
|
||||
else:
|
||||
classes.append("material-null")
|
||||
|
||||
# ─── Layer ─────────────────────────────────────────────────
|
||||
if layer:
|
||||
classes.append(layer.is_a())
|
||||
material_name = layer.Material.Name or "null"
|
||||
material_name = tool.Drawing.canonicalise_class_name(material_name)
|
||||
classes.append(f"layer-material-{material_name}")
|
||||
layer_material = layer.Material
|
||||
layer_material_name = layer_material.Name or "null"
|
||||
layer_material_name = tool.Drawing.canonicalise_class_name(layer_material_name)
|
||||
classes.append(f"layer-material-{layer_material_name}")
|
||||
|
||||
# Add material category if available
|
||||
if hasattr(layer_material, "Category") and layer_material.Category:
|
||||
layer_material_category = tool.Drawing.canonicalise_class_name(layer_material.Category)
|
||||
classes.append(f"layer-material-category-{layer_material_category}")
|
||||
|
||||
# ─── Metadata ──────────────────────────────────────────────
|
||||
for key in self.metadata:
|
||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||
if value:
|
||||
classes.append(
|
||||
tool.Drawing.canonicalise_class_name(key) + "-" + tool.Drawing.canonicalise_class_name(str(value))
|
||||
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:
|
||||
result = self.is_manifold_cache.get(obj.data.name, None)
|
||||
if result is not None:
|
||||
@@ -1342,7 +1360,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
join_criteria = join_criteria.split(",")
|
||||
else:
|
||||
# Drawing convention states that same objects classes with the same material are merged when cut.
|
||||
join_criteria = ["class", "material.Name", "/Pset_.*Common/.Status", "EPset_Status.Status", "Material.Name"]
|
||||
join_criteria = ["class", "material.Name", "/Pset_.*Common/.Status", "EPset_Status.Status", "EPset_Status.UserDefinedStatus"]
|
||||
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
joined_paths = {}
|
||||
@@ -1471,11 +1489,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
joined_paths.setdefault(hash_keys, []).append(el)
|
||||
|
||||
for key, els in joined_paths.items():
|
||||
polygons = []
|
||||
classes = set()
|
||||
queue = []
|
||||
|
||||
for el in els:
|
||||
classes.update(el.attrib["class"].split())
|
||||
classes = set(el.attrib["class"].split())
|
||||
classes.add(el.attrib["{http://www.ifcopenshell.org/ns}guid"])
|
||||
is_closed_polygon = False
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
@@ -1490,31 +1507,30 @@ class CreateDrawing(bpy.types.Operator):
|
||||
coords.append(coords[0])
|
||||
if len(coords) > 2 and coords[0] == coords[-1]:
|
||||
is_closed_polygon = True
|
||||
polygons.append(shapely.Polygon(coords))
|
||||
queue.append((shapely.Polygon(coords), classes))
|
||||
if is_closed_polygon:
|
||||
el.getparent().remove(el)
|
||||
|
||||
try:
|
||||
merged_polygons = shapely.ops.unary_union(polygons)
|
||||
except:
|
||||
print("Warning. Portions of the merge failed. Please report a bug!", polygons)
|
||||
merged_polygons = polygons
|
||||
while queue:
|
||||
polygon, polygon_classes = queue.pop()
|
||||
for polygon2, polygon2_classes in queue[:]:
|
||||
try:
|
||||
merged_polygon = shapely.union(polygon, polygon2)
|
||||
except:
|
||||
print("Warning. Portions of the merge failed. Please report a bug!", polygon, polygon2)
|
||||
continue
|
||||
if type(merged_polygon) == shapely.Polygon:
|
||||
polygon = merged_polygon
|
||||
polygon_classes.update(polygon2_classes)
|
||||
queue.remove((polygon2, polygon2_classes))
|
||||
|
||||
if type(merged_polygons) == shapely.MultiPolygon:
|
||||
merged_polygons = merged_polygons.geoms
|
||||
elif type(merged_polygons) == shapely.Polygon:
|
||||
merged_polygons = [merged_polygons]
|
||||
else:
|
||||
merged_polygons = []
|
||||
|
||||
for polygon in merged_polygons:
|
||||
g = etree.Element("g")
|
||||
path = etree.SubElement(g, "path")
|
||||
d = "M" + " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]) + " Z"
|
||||
for interior in polygon.interiors:
|
||||
d += " M" + " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]]) + " Z"
|
||||
path.attrib["d"] = d
|
||||
g.set("class", " ".join(list(classes)))
|
||||
g.set("class", " ".join(list(polygon_classes)))
|
||||
group.append(g)
|
||||
|
||||
def drawing_to_model_co(self, x: float, y: float) -> Vector:
|
||||
@@ -1856,13 +1872,15 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
# Won't be visible in UI anyway.
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
if not props.sheets or not prefs.data_dir:
|
||||
return False
|
||||
if not tool.Drawing.get_active_drawing_item():
|
||||
cls.poll_message_set("No drawing selected.")
|
||||
return False
|
||||
if not props.sheets:
|
||||
cls.poll_message_set("No sheets available.")
|
||||
return False
|
||||
if not tool.Blender.get_user_data_dir():
|
||||
cls.poll_message_set("BIM data directory not set.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -2165,27 +2183,10 @@ class ActivateModel(bpy.types.Operator):
|
||||
if model_props.show_cut_decorator:
|
||||
CutDecorator.uninstall()
|
||||
|
||||
# Preserve current visibility statuses for:
|
||||
# - non-ifc objects
|
||||
# - type product
|
||||
# - annotations (so we won't unhide other drawings)
|
||||
ifc_file = tool.Ifc.get()
|
||||
visibility_status: dict[bpy.types.Object, bool] = {}
|
||||
for obj in bpy.data.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
hide = obj.hide_get()
|
||||
elif element.is_a("IfcAnnotation"):
|
||||
hide = True
|
||||
elif element.is_a("IfcTypeProduct"):
|
||||
hide = obj.hide_get()
|
||||
else:
|
||||
continue
|
||||
visibility_status[obj] = hide
|
||||
|
||||
if not bpy.app.background:
|
||||
with context.temp_override(**tool.Blender.get_viewport_context()):
|
||||
bpy.ops.object.hide_view_clear(select=False)
|
||||
bpy.ops.bim.activate_status_filters(only_if_enabled=True)
|
||||
|
||||
elements = {e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))}
|
||||
@@ -2230,15 +2231,10 @@ class ActivateModel(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=model,
|
||||
should_reload=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# restore visibility after hide_view_clear()
|
||||
for obj, hide_status in visibility_status.items():
|
||||
obj.hide_set(hide_status)
|
||||
|
||||
tool.Blender.reset_object_visibility()
|
||||
tool.Drawing.hide_all_drawing_collections()
|
||||
tool.Blender.update_viewport()
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -2251,6 +2247,32 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
# Ifc Operator is necessary, because sync_references may create or remove IFC elements.
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Activates the selected drawing view.\n\n"
|
||||
+ "ALT+CLICK to keep the viewport position.\n\n"
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view"
|
||||
)
|
||||
|
||||
drawing: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
should_view_from_camera: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Should View From Camera",
|
||||
description="Move view to the activated drawing's camera position.",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
use_quick_preview: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Use Quick Preview",
|
||||
description="Just move the camera to the drawing view, without loading anything else.",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
drawing: int
|
||||
should_view_from_camera: bool
|
||||
use_quick_preview: bool
|
||||
|
||||
def invoke(self, context, event) -> set["rna_enums.OperatorReturnItems"]:
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
@@ -2273,20 +2295,44 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
viewport_position = None
|
||||
if not self.should_view_from_camera:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
v3d_space = tool.Blender.get_view3d_space()
|
||||
assert v3d_space
|
||||
if self.should_view_from_camera:
|
||||
# Since we must switch to drawing camera, undo local camera in viewport.
|
||||
# The code is needed to undo `else` branch from activating other cameras.
|
||||
v3d_space.use_local_camera = False
|
||||
else:
|
||||
# Since we use `scene.camera` to store active drawing,
|
||||
# the only way to preserve previous drawing camera position is making it local.
|
||||
r3d = v3d_space.region_3d
|
||||
assert r3d
|
||||
if r3d.view_perspective == "CAMERA":
|
||||
if v3d_space.use_local_camera:
|
||||
# Nothing to do, local camera is already set by user.
|
||||
pass
|
||||
else:
|
||||
previous_camera = context.scene.camera
|
||||
if previous_camera:
|
||||
v3d_space.camera = previous_camera
|
||||
v3d_space.use_local_camera = True
|
||||
else:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
|
||||
if not self.should_view_from_camera:
|
||||
assert viewport_position
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
if viewport_position is None:
|
||||
# Local camera takes priority over active scene camera,
|
||||
# so nothing to do after drawing view activation.
|
||||
pass
|
||||
else:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
dprops.drawing_styles.clear()
|
||||
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
@@ -2299,10 +2345,21 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
camera = context.scene.camera
|
||||
assert camera
|
||||
camera_props = tool.Drawing.get_camera_props(camera)
|
||||
# Check if this is a reflected ceiling camera and preserve its scale
|
||||
camera_element = tool.Ifc.get_entity(camera)
|
||||
is_reflected = False
|
||||
if camera_element:
|
||||
is_reflected = ifcopenshell.util.element.get_pset(camera_element, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW"
|
||||
if is_reflected and camera.scale != (-1, -1, -1):
|
||||
camera.scale = (-1, -1, -1)
|
||||
|
||||
|
||||
if camera_props.update_representation(camera.matrix_world):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
# See 6452 and 6478.
|
||||
# bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT")
|
||||
# Restore the scale after update if needed
|
||||
if is_reflected:
|
||||
camera.scale = (-1, -1, -1)
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -2317,10 +2374,6 @@ class ActivateDrawing(bpy.types.Operator, ActivateDrawingBase):
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view"
|
||||
)
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
should_view_from_camera: bpy.props.BoolProperty(name="Should View From Camera", default=True, options={"SKIP_SAVE"})
|
||||
use_quick_preview: bpy.props.BoolProperty(name="Use Quick Preview", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
|
||||
class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
|
||||
bl_idname = "bim.activate_drawing_from_sheet"
|
||||
@@ -2332,10 +2385,6 @@ class ActivateDrawingFromSheet(bpy.types.Operator, ActivateDrawingBase):
|
||||
+ "SHIFT+CLICK to load a quick preview of the drawing view"
|
||||
)
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
should_view_from_camera: bpy.props.BoolProperty(name="Should View From Camera", default=True, options={"SKIP_SAVE"})
|
||||
use_quick_preview: bpy.props.BoolProperty(name="Use Quick Preview", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
@@ -2786,8 +2835,13 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not props.schedules:
|
||||
cls.poll_message_set("No schedule selected.")
|
||||
return False
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
return props.schedules and props.sheets and prefs.data_dir
|
||||
if not props.sheets:
|
||||
cls.poll_message_set("No sheets available.")
|
||||
return False
|
||||
if not tool.Blender.get_user_data_dir():
|
||||
cls.poll_message_set("BIM data directory not set.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
@@ -2854,8 +2908,13 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not props.references:
|
||||
cls.poll_message_set("No reference selected.")
|
||||
return False
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
return props.references and props.sheets and bim_props.data_dir
|
||||
if not props.sheets:
|
||||
cls.poll_message_set("No sheets available.")
|
||||
return False
|
||||
if not tool.Blender.get_user_data_dir():
|
||||
cls.poll_message_set("BIM data directory not set.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
@@ -2971,47 +3030,9 @@ class EditTextPopup(bpy.types.Operator):
|
||||
first_run: bpy.props.BoolProperty(default=True)
|
||||
|
||||
def draw(self, context):
|
||||
# shares most of the code with BIM_PT_text.draw()
|
||||
# need to keep them in sync or move to some common function
|
||||
# NOTE: that `popup_active_attribute` is used here when it's not used in `BIM_PT_text.draw()`
|
||||
from bonsai.bim.module.drawing.ui import BIM_PT_text
|
||||
|
||||
assert self.layout
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="Add Literal")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "font_size")
|
||||
|
||||
for i, literal_props in enumerate(props.literals):
|
||||
box = self.layout.box()
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
attributes = [a for a in literal_props.attributes if a.name != "BoxAlignment"]
|
||||
bonsai.bim.helper.draw_attributes(attributes, box, popup_active_attribute=attributes[0])
|
||||
|
||||
row = box.row(align=True)
|
||||
cols = [row.column(align=True) for i in range(3)]
|
||||
for i in range(9):
|
||||
cols[i % 3].prop(
|
||||
literal_props,
|
||||
"box_alignment",
|
||||
text="",
|
||||
index=i,
|
||||
icon="RADIOBUT_ON" if literal_props.box_alignment[i] else "RADIOBUT_OFF",
|
||||
)
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text=" Text box alignment:")
|
||||
col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}')
|
||||
BIM_PT_text.draw_text_editing_ui(self, context, popup_mode=True)
|
||||
|
||||
def cancel(self, context):
|
||||
# disable editing when dialog is closed
|
||||
@@ -3066,7 +3087,6 @@ class DisableEditingText(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
# force update this object's font size for viewport display
|
||||
DecoratorData.data.pop(obj.name, None)
|
||||
tool.Drawing.update_text_value(obj)
|
||||
tool.Blender.update_viewport()
|
||||
|
||||
|
||||
@@ -3123,6 +3143,7 @@ class RemoveTextLiteral(bpy.types.Operator):
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
props.literals.remove(self.literal_prop_id)
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -3140,6 +3161,7 @@ class OrderTextLiteralUp(bpy.types.Operator):
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
props.literals.move(self.literal_prop_id, self.literal_prop_id - 1)
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -3157,6 +3179,7 @@ class OrderTextLiteralDown(bpy.types.Operator):
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
props.literals.move(self.literal_prop_id, self.literal_prop_id + 1)
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -3740,3 +3763,19 @@ class OpenDocumentationWebUi(bpy.types.Operator):
|
||||
else:
|
||||
bpy.ops.bim.open_web_browser(page="documentation")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExcludeAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.exclude_annotation"
|
||||
bl_label = "Exclude Annotation"
|
||||
bl_description = "Excludes the automatic annotation reference from the drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
if not (obj := bpy.context.scene.camera) or not (drawing := tool.Ifc.get_entity(obj)):
|
||||
return
|
||||
for obj in tool.Blender.get_selected_objects(include_active=False):
|
||||
if (element := tool.Ifc.get_entity(obj)) and tool.Drawing.is_auto_annotation(element):
|
||||
if (referenced_element := tool.Drawing.get_annotation_element(element)):
|
||||
tool.Drawing.exclude_annotation_from_drawing(referenced_element, drawing)
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
|
||||
|
||||
@@ -267,12 +267,6 @@ def update_titleblocks(self, context):
|
||||
def update_should_draw_decorations(self, context: bpy.types.Context) -> None:
|
||||
if self.should_draw_decorations:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = tool.Blender.get_object_bim_props(context.scene.camera).collection
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
continue
|
||||
tool.Drawing.update_text_value(obj)
|
||||
refresh_drawing_data()
|
||||
if bpy.app.background:
|
||||
return
|
||||
@@ -471,6 +465,12 @@ class DocProperties(PropertyGroup):
|
||||
return None
|
||||
return tool.Ifc.get().by_id(drawing_id)
|
||||
|
||||
def get_active_target_view(self) -> Union[str, None]:
|
||||
active_drawing = self.get_active_drawing()
|
||||
if not active_drawing:
|
||||
return None
|
||||
return tool.Drawing.get_drawing_target_view(active_drawing)
|
||||
|
||||
|
||||
def update_width_height(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||
self.update_camera_resolution()
|
||||
@@ -498,6 +498,11 @@ class BIMCameraProperties(PropertyGroup):
|
||||
name="Linework Mode",
|
||||
update=get_update_layer_callback("linework_mode", "LineworkMode"),
|
||||
)
|
||||
generate_material_layers: bpy.props.BoolProperty(
|
||||
name="Generate Material Layers",
|
||||
description="Generate material layer linework in drawings",
|
||||
default=True
|
||||
)
|
||||
fill_mode: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "Disable filling areas seen in projection"),
|
||||
@@ -709,16 +714,12 @@ class LiteralProps(PropertyGroup):
|
||||
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
|
||||
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
# Current text value with evaluated expressions stored in `value`.
|
||||
# The original (Literal) value stored in `attributes['Literal']`
|
||||
# and can be accessed with `get_text()`
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
box_alignment: BoolVectorProperty(
|
||||
name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT
|
||||
)
|
||||
ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
|
||||
|
||||
def get_literal_edited_data(self):
|
||||
def get_literal_edited_data(self) -> dict[str, str]:
|
||||
text_data = {
|
||||
"CurrentValue": self.attributes["Literal"].string_value,
|
||||
"Literal": self.attributes["Literal"].string_value,
|
||||
@@ -729,7 +730,7 @@ class LiteralProps(PropertyGroup):
|
||||
if TYPE_CHECKING:
|
||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
value: str
|
||||
box_alignment: str
|
||||
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
|
||||
ifc_definition_id: int
|
||||
|
||||
|
||||
@@ -748,12 +749,50 @@ class BIMTextProperties(PropertyGroup):
|
||||
name="Font Size",
|
||||
)
|
||||
newline_at: IntProperty(name="Newline At")
|
||||
reverse_list: BoolProperty(name="Reverse List", description="Reverses the order of any list.", default=False)
|
||||
list_separator: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="List Separator",
|
||||
description="Text used to separate lists. Uses a comma (, ) if empty.",
|
||||
)
|
||||
symbol: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Symbol",
|
||||
description="Symbol from symbols.svg to use for this text.",
|
||||
items=[(s, s, "") for s in ["NO SYMBOL", "CUSTOM SYMBOL"] + tool.Drawing.DEFAULT_SYMBOLS],
|
||||
default="NO SYMBOL",
|
||||
)
|
||||
custom_symbol: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Custom Symbol",
|
||||
description="Non-default symbol to use for this text.",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
literals: bpy.types.bpy_prop_collection_idprop[LiteralProps]
|
||||
font_size: str
|
||||
newline_at: int
|
||||
reverse_list: bool
|
||||
list_separator: str
|
||||
symbol: Union[str, Literal["NO SYMBOL", "CUSTOM SYMBOL"]]
|
||||
custom_symbol: str
|
||||
|
||||
def get_symbol(self) -> Union[str, None]:
|
||||
if self.symbol == "NO SYMBOL":
|
||||
return None
|
||||
elif self.symbol == "CUSTOM SYMBOL":
|
||||
return self.custom_symbol or None
|
||||
else:
|
||||
return self.symbol
|
||||
|
||||
def set_symbol(self, symbol: Union[str, None]):
|
||||
if not symbol:
|
||||
self.property_unset("symbol")
|
||||
self.property_unset("custom_symbol")
|
||||
elif symbol in tool.Drawing.DEFAULT_SYMBOLS:
|
||||
self.symbol = symbol
|
||||
self.property_unset("custom_symbol")
|
||||
else:
|
||||
self.symbol = "CUSTOM SYMBOL"
|
||||
self.custom_symbol = symbol
|
||||
|
||||
def get_text_edited_data(self) -> dict[str, Any]:
|
||||
"""should be called only if `is_editing`
|
||||
@@ -768,6 +807,9 @@ class BIMTextProperties(PropertyGroup):
|
||||
"Literals": literals_data,
|
||||
"FontSize": float(self.font_size),
|
||||
"Newline_At": int(self.newline_at),
|
||||
"Symbol": self.get_symbol(),
|
||||
"Reverse_List": self.reverse_list,
|
||||
"List_Separator": self.list_separator or ", ",
|
||||
}
|
||||
return text_data
|
||||
|
||||
@@ -822,6 +864,19 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
)
|
||||
is_adding_type: bpy.props.BoolProperty(default=False)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
tag_rotation_mode: bpy.props.EnumProperty(
|
||||
name="Tag Rotation Mode",
|
||||
description="How to orient the tag relative to the tagged object",
|
||||
items=[
|
||||
("NONE", "No Rotation", "Keep tag in default orientation"),
|
||||
("LOCAL_X", "Local X Axis", "Align tag with object's local X axis"),
|
||||
("LOCAL_Y", "Local Y Axis", "Align tag with object's local Y axis"),
|
||||
("LOCAL_Z", "Local Z Axis", "Align tag with object's local Z axis"),
|
||||
("CAMERA_Horizontal", "Camera Horizontal", "Align tag with camera X axis"),
|
||||
("CAMERA_Vertical", "Camera Vertical", "Align tag with camera Y axis"),
|
||||
],
|
||||
default="NONE",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
object_type: str
|
||||
|
||||
@@ -46,6 +46,7 @@ class SheetBuilder:
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
root.attrib["xmlns:sodipodi"] = "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
root.attrib["id"] = "root"
|
||||
root.attrib["version"] = "1.1"
|
||||
|
||||
@@ -65,6 +66,7 @@ class SheetBuilder:
|
||||
view_height = self.convert_to_mm(view_root.attrib["height"])
|
||||
view = ET.SubElement(root, "g")
|
||||
view.attrib["data-type"] = "titleblock"
|
||||
view.attrib["sodipodi:insensitive"] = "true"
|
||||
titleblock = ET.SubElement(view, "image")
|
||||
titleblock.attrib["xlink:href"] = Path(os.path.relpath(titleblock_path, sheet_dir)).as_posix()
|
||||
titleblock.attrib["x"] = "0"
|
||||
|
||||
@@ -823,6 +823,7 @@ class SvgWriter:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
element = tool.Ifc.get_entity(text_obj)
|
||||
assert element
|
||||
text_literals = tool.Drawing.get_text_literal(text_obj, return_list=True)
|
||||
product = tool.Drawing.get_assigned_product(element)
|
||||
|
||||
@@ -838,6 +839,12 @@ class SvgWriter:
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
newline_at = tool.Drawing.get_newline_at(element)
|
||||
|
||||
# Get reverse_list and list_separator from EPset_Annotation
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
|
||||
reverse_list = pset_data.get("Reverse_List", False)
|
||||
list_separator = pset_data.get("List_Separator") or ", "
|
||||
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
|
||||
@@ -854,7 +861,7 @@ class SvgWriter:
|
||||
# NOTE: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
field.attrib["class"] = classes_str
|
||||
|
||||
@@ -874,7 +881,9 @@ class SvgWriter:
|
||||
line_number = 0
|
||||
|
||||
for text_literal in text_literals:
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
|
||||
text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element, reverse_list, list_separator
|
||||
)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
@@ -1011,7 +1020,9 @@ class SvgWriter:
|
||||
|
||||
# TODO: allow metric to be configurable
|
||||
def get_text():
|
||||
z = (matrix_world @ points[0].co.xyz).z
|
||||
abs_z = (matrix_world @ points[0].co.xyz).z
|
||||
z = helper.get_relative_z(obj, element, abs_z)
|
||||
|
||||
rl = helper.format_distance(
|
||||
z,
|
||||
precision=self.precision,
|
||||
|
||||
@@ -29,7 +29,7 @@ from bonsai.bim.module.drawing.data import (
|
||||
ElementFiltersData,
|
||||
DecoratorData,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.drawing.prop import DocProperties, Drawing, Sheet
|
||||
@@ -99,12 +99,13 @@ class BIM_PT_camera(Panel):
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "linework_mode")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "generate_material_layers")
|
||||
if props.linework_mode == "OPENCASCADE":
|
||||
row = self.layout.row()
|
||||
row.prop(props, "fill_mode")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "cut_mode")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "width")
|
||||
row = self.layout.row()
|
||||
@@ -193,6 +194,7 @@ class BIM_PT_element_filters(Panel):
|
||||
text = "Exclude Filter" if ElementFiltersData.data["has_exclude_filter"] else "No Exclude Filter Found"
|
||||
icon = "GREASEPENCIL" if ElementFiltersData.data["has_exclude_filter"] else "ADD"
|
||||
row.label(text=text, icon="FILTER")
|
||||
row.operator("bim.exclude_annotation", icon="REMOVE", text="")
|
||||
row.operator("bim.enable_editing_element_filter", icon=icon, text="").filter_mode = "EXCLUDE"
|
||||
|
||||
|
||||
@@ -565,56 +567,81 @@ class BIM_PT_text(Panel):
|
||||
return
|
||||
return tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"])
|
||||
|
||||
def draw_text_editing_ui(
|
||||
self: Union[bpy.types.Panel, bpy.types.Operator],
|
||||
context: bpy.types.Context,
|
||||
*,
|
||||
popup_mode: bool = False,
|
||||
) -> None:
|
||||
# The method is also used in EditTextPopup.draw().
|
||||
assert self.layout
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
if popup_mode:
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="Add Literal")
|
||||
else:
|
||||
row.operator("bim.edit_text", icon="CHECKMARK")
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="")
|
||||
row.operator("bim.disable_editing_text", icon="CANCEL", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "font_size")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "newline_at")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "reverse_list")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "list_separator")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "symbol")
|
||||
if props.symbol == "CUSTOM SYMBOL":
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "custom_symbol", text="")
|
||||
|
||||
for i, literal_props in enumerate(props.literals):
|
||||
box = self.layout.box()
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
if i > 0:
|
||||
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
|
||||
if i < len(props.literals) - 1:
|
||||
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
attributes = [a for a in literal_props.attributes if a.name != "BoxAlignment"]
|
||||
popup_active_attribute = attributes[0] if popup_mode else None
|
||||
bonsai.bim.helper.draw_attributes(attributes, box, popup_active_attribute=popup_active_attribute)
|
||||
|
||||
row = box.row(align=True)
|
||||
cols = [row.column(align=True) for i in range(3)]
|
||||
for i in range(9):
|
||||
cols[i % 3].prop(
|
||||
literal_props,
|
||||
"box_alignment",
|
||||
text="",
|
||||
index=i,
|
||||
icon="RADIOBUT_ON" if literal_props.box_alignment[i] else "RADIOBUT_OFF",
|
||||
)
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text=" Text box alignment:")
|
||||
col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}')
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
|
||||
if props.is_editing:
|
||||
# shares most of the code with EditTextPopup.draw()
|
||||
# need to keep them in sync or move to some common function
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_text", icon="CHECKMARK")
|
||||
row.operator("bim.add_text_literal", icon="ADD", text="")
|
||||
row.operator("bim.disable_editing_text", icon="CANCEL", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "font_size")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "newline_at")
|
||||
|
||||
for i, literal_props in enumerate(props.literals):
|
||||
box = self.layout.box()
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text=f"Literal[{i}]:")
|
||||
if i > 0:
|
||||
row.operator("bim.order_text_literal_up", icon="TRIA_UP", text="").literal_prop_id = i
|
||||
if i < len(props.literals) - 1:
|
||||
row.operator("bim.order_text_literal_down", icon="TRIA_DOWN", text="").literal_prop_id = i
|
||||
row.operator("bim.remove_text_literal", icon="X", text="").literal_prop_id = i
|
||||
|
||||
# skip BoxAlignment since we're going to format it ourselves
|
||||
attributes = [a for a in literal_props.attributes if a.name != "BoxAlignment"]
|
||||
bonsai.bim.helper.draw_attributes(attributes, box)
|
||||
|
||||
row = box.row(align=True)
|
||||
cols = [row.column(align=True) for i in range(3)]
|
||||
for i in range(9):
|
||||
cols[i % 3].prop(
|
||||
literal_props,
|
||||
"box_alignment",
|
||||
text="",
|
||||
index=i,
|
||||
icon="RADIOBUT_ON" if literal_props.box_alignment[i] else "RADIOBUT_OFF",
|
||||
)
|
||||
|
||||
col = row.column(align=True)
|
||||
col.label(text=" Text box alignment:")
|
||||
col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}')
|
||||
|
||||
self.draw_text_editing_ui(context)
|
||||
else:
|
||||
text_data = DecoratorData.get_text_data(obj)
|
||||
|
||||
@@ -627,6 +654,12 @@ class BIM_PT_text(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Newline_At")
|
||||
row.label(text=str(text_data["Newline_At"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Reverse_List")
|
||||
row.label(text=str(text_data["Reverse_List"]))
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="List_Separator")
|
||||
row.label(text=str(text_data["List_Separator"]))
|
||||
|
||||
for literal_data in text_data["Literals"]:
|
||||
box = self.layout.box()
|
||||
|
||||
@@ -180,9 +180,6 @@ def create_annotation_occurrence(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
if obj.data and not relating_type_rep:
|
||||
@@ -231,7 +228,6 @@ class AnnotationToolUI:
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
object_type = cls.props.object_type
|
||||
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, cls.props, "object_type", text="")
|
||||
@@ -251,6 +247,9 @@ class AnnotationToolUI:
|
||||
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
|
||||
|
||||
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
row.prop(cls.props, "tag_rotation_mode", text="")
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout,
|
||||
"Bulk Tag",
|
||||
@@ -302,6 +301,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
|
||||
related_objects = bpy.context.selected_objects
|
||||
created_objects = []
|
||||
|
||||
for related_object in related_objects:
|
||||
obj = core.add_annotation(
|
||||
tool.Ifc,
|
||||
@@ -314,7 +315,16 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
),
|
||||
enable_editing=False,
|
||||
)
|
||||
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
|
||||
tool.Drawing.setup_annotation_object(obj, object_type, related_object, props.tag_rotation_mode)
|
||||
created_objects.append(obj)
|
||||
|
||||
# Select the created annotation objects
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for obj in created_objects:
|
||||
obj.select_set(True)
|
||||
if created_objects:
|
||||
bpy.context.view_layer.objects.active = created_objects[-1]
|
||||
|
||||
|
||||
def hotkey_S_A(self):
|
||||
if bpy.ops.bim.add_annotation.poll():
|
||||
@@ -344,4 +354,5 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
related_object = tool.Ifc.get_object(related_product)
|
||||
|
||||
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
|
||||
rotation_mode = tool.Drawing.get_annotation_props().tag_rotation_mode
|
||||
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object, rotation_mode)
|
||||
|
||||
@@ -109,8 +109,9 @@ def block_scale(scene: bpy.types.Scene) -> None:
|
||||
if obj.type == "CAMERA":
|
||||
camera = tool.Ifc.get_entity(obj)
|
||||
if ifcopenshell.util.element.get_pset(camera, "EPset_Drawing", "TargetView") == "REFLECTED_PLAN_VIEW":
|
||||
obj.scale = (-1, -1, -1)
|
||||
obj.rotation_euler = (0.0, 0.0, math.radians(180))
|
||||
# Only update if scale isn't already (-1, -1, -1)
|
||||
if obj.scale != (-1, -1, -1):
|
||||
obj.scale = (-1, -1, -1)
|
||||
else:
|
||||
if obj.scale != (1, 1, 1):
|
||||
obj.scale = (1, 1, 1)
|
||||
|
||||
@@ -20,7 +20,7 @@ import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.tool as tool
|
||||
from typing import Any, Union
|
||||
|
||||
@@ -28,6 +28,7 @@ import bonsai.tool as tool
|
||||
from math import pi, pow
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
from typing import Union, Any, TypeVar, Optional
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -35,6 +36,7 @@ T = TypeVar("T")
|
||||
class Helper:
|
||||
def __init__(self, file: ifcopenshell.file):
|
||||
self.file = file
|
||||
self.builder = ShapeBuilder(file)
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
# We can detect a rectangular extrusion by picking any face, then find an
|
||||
@@ -340,8 +342,10 @@ class Helper:
|
||||
def create_extruded_area_solid(
|
||||
self, mesh: bpy.types.Mesh, extrusion_indices: list[int], profile_def: dict[str, Any]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
position = self.create_ifc_axis_2_placement_3d(
|
||||
profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"]
|
||||
position = self.builder.create_axis2_placement_3d(
|
||||
self.convert_si_to_unit(profile_def["curve_ucs"]["center"]),
|
||||
profile_def["curve_ucs"]["z_axis"],
|
||||
profile_def["curve_ucs"]["x_axis"],
|
||||
)
|
||||
direction = self.get_extrusion_direction(mesh, extrusion_indices, profile_def["curve_ucs"])
|
||||
unit_direction = direction.normalized()
|
||||
@@ -505,12 +509,3 @@ class Helper:
|
||||
return self.file.createIfcAxis2Placement2D(
|
||||
self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y))
|
||||
)
|
||||
|
||||
def create_ifc_axis_2_placement_3d(
|
||||
self, point: Vector, up: Vector, forward: Vector
|
||||
) -> ifcopenshell.entity_instance:
|
||||
return self.file.createIfcAxis2Placement3D(
|
||||
self.create_cartesian_point(point.x, point.y, point.z),
|
||||
self.file.createIfcDirection((up.x, up.y, up.z)),
|
||||
self.file.createIfcDirection((forward.x, forward.y, forward.z)),
|
||||
)
|
||||
|
||||
@@ -427,9 +427,7 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
should_reload: bpy.props.BoolProperty()
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -465,9 +463,6 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=self.should_reload,
|
||||
is_global=self.should_switch_all_meshes,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -721,9 +716,6 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if show_representation_parameters:
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
@@ -866,6 +858,10 @@ class OverrideDelete(bpy.types.Operator):
|
||||
objects_to_remove = context.selected_objects
|
||||
|
||||
self.process_arrays(context)
|
||||
|
||||
# Track aggregates before deleting their parts
|
||||
aggregates_to_check = self.track_aggregates(objects_to_remove)
|
||||
|
||||
clear_active_object = True
|
||||
|
||||
for i, obj in enumerate(objects_to_remove, 1):
|
||||
@@ -890,13 +886,28 @@ class OverrideDelete(bpy.types.Operator):
|
||||
continue
|
||||
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
|
||||
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
|
||||
return {"FINISHED"}
|
||||
continue
|
||||
if element.is_a("IfcGridAxis"):
|
||||
# Deleting the last W axis is OK
|
||||
if ((grid := element.PartOfU) and len(grid[0].UAxes) == 1) or (
|
||||
(grid := element.PartOfV) and len(grid[0].VAxes) == 1
|
||||
):
|
||||
self.report(
|
||||
{"INFO"}, "The last grid axis of a grid cannot be deleted. Delete the grid instead."
|
||||
)
|
||||
continue
|
||||
if tool.Drawing.is_auto_annotation(element):
|
||||
self.report({"INFO"}, "References cannot be deleted. Exclude the referenced element instead.")
|
||||
continue
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
elif tool.Geometry.is_representation_item(obj):
|
||||
tool.Geometry.delete_ifc_item(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
# Delete empty aggregates after deleting their parts
|
||||
self.delete_empty_aggregates(aggregates_to_check)
|
||||
|
||||
for opening in tool.Model.get_model_props().openings:
|
||||
if opening.obj is not None and not tool.Ifc.get_entity(opening.obj):
|
||||
bpy.data.objects.remove(opening.obj)
|
||||
@@ -929,6 +940,49 @@ class OverrideDelete(bpy.types.Operator):
|
||||
data["old_file"].redo()
|
||||
tool.Ifc.set(data["new_file"])
|
||||
|
||||
def track_aggregates(self, objects_to_remove):
|
||||
"""Track aggregates that contain objects being deleted"""
|
||||
aggregates_to_check = set()
|
||||
for obj in objects_to_remove:
|
||||
if not tool.Blender.is_valid_data_block(obj):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
aggregates_to_check.add(aggregate)
|
||||
return aggregates_to_check
|
||||
|
||||
def delete_empty_aggregates(self, aggregates_to_check):
|
||||
"""Delete aggregates that now have no parts"""
|
||||
deleted_aggregates = []
|
||||
for aggregate in aggregates_to_check:
|
||||
# Check if aggregate still exists (might have been deleted already)
|
||||
try:
|
||||
aggregate.id()
|
||||
except:
|
||||
continue
|
||||
|
||||
related_objects = ifcopenshell.util.element.get_parts(aggregate)
|
||||
if len(related_objects) == 0:
|
||||
aggregate_name = aggregate.Name or f"{aggregate.is_a()} #{aggregate.id()}"
|
||||
deleted_aggregates.append(aggregate_name)
|
||||
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj and tool.Blender.is_valid_data_block(aggregate_obj):
|
||||
tool.Geometry.delete_ifc_object(aggregate_obj)
|
||||
|
||||
# Show info message if aggregates were deleted
|
||||
if deleted_aggregates:
|
||||
if len(deleted_aggregates) == 1:
|
||||
self.report(
|
||||
{"INFO"}, f"Aggregate '{deleted_aggregates[0]}' was deleted because it had no remaining parts"
|
||||
)
|
||||
else:
|
||||
aggregate_list = ", ".join(f"'{name}'" for name in deleted_aggregates)
|
||||
self.report({"INFO"}, f"Aggregates {aggregate_list} were deleted because they had no remaining parts")
|
||||
|
||||
def process_arrays(self, context: bpy.types.Context) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
selected_objects = set(context.selected_objects)
|
||||
@@ -1427,7 +1481,13 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset["Index"]
|
||||
annotations = get_assignments(element)
|
||||
original_data[group][index] = {"Name": str(pset["Aggregate_Index"]), "Assignment": annotations}
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
original_data[group][index] = {
|
||||
"Name": pset["Name"],
|
||||
"Aggregate_Index": pset["Aggregate_Index"],
|
||||
"Assignment": annotations,
|
||||
"Container": container,
|
||||
}
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
@@ -1470,13 +1530,22 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
index = pset["Index"]
|
||||
|
||||
if index == 0:
|
||||
obj.name = pset["Name"]
|
||||
obj.name = pset["Name"] + "_" + str(original_data[group][index]["Aggregate_Index"])
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
ifc_file,
|
||||
ifc_file.by_id(pset["id"]),
|
||||
properties={"Aggregate_Index": int(original_data[group][index]["Name"])},
|
||||
properties={"Aggregate_Index": int(original_data[group][index]["Aggregate_Index"])},
|
||||
)
|
||||
bonsai.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
container=original_data[group][index]["Container"],
|
||||
element_obj=obj,
|
||||
)
|
||||
for part in ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj)):
|
||||
tool.Collector.assign(tool.Ifc.get_object(part))
|
||||
assignments = original_data[group][index]["Assignment"]
|
||||
if assignments:
|
||||
assign_to_annotations(obj, assignments)
|
||||
@@ -1889,9 +1958,6 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=self.target,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@@ -2969,6 +3035,7 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
item_mesh = bpy.data.meshes.new("tmp")
|
||||
tool.Ifc.link(item, item_mesh)
|
||||
item_obj = bpy.data.objects.new("tmp", item_mesh)
|
||||
tool.Geometry.lock_scale(item_obj)
|
||||
tool.Geometry.name_item_object(item_obj, item)
|
||||
item_obj.matrix_world = obj.matrix_world
|
||||
bpy.context.collection.objects.link(item_obj)
|
||||
|
||||
@@ -157,8 +157,6 @@ class BIM_PT_representations(Panel):
|
||||
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
|
||||
text="",
|
||||
)
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
|
||||
@@ -22,8 +22,7 @@ import ifcopenshell.api.group
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
import json
|
||||
from natsort import natsorted
|
||||
from typing import TYPE_CHECKING, Literal, get_args
|
||||
|
||||
|
||||
class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -31,67 +30,34 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Load Groups"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
expanded_groups: list[int]
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = tool.Blender.get_group_props()
|
||||
self.expanded_groups = json.loads(self.props.expanded_groups_json)
|
||||
self.props.groups.clear()
|
||||
|
||||
groups = [
|
||||
group for group in tool.Ifc.get().by_type("IfcGroup", include_subtypes=False) if not group.HasAssignments
|
||||
]
|
||||
sorted_groups = natsorted(groups, key=lambda group: group.Name or "Unnamed")
|
||||
|
||||
for group in sorted_groups:
|
||||
self.load_group(group)
|
||||
|
||||
self.props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_group()
|
||||
tool.Group.import_groups("IfcGroup")
|
||||
tool.Group.enable_group_editing_ui()
|
||||
tool.Group.disable_editing_group()
|
||||
return {"FINISHED"}
|
||||
|
||||
def load_group(self, group: ifcopenshell.entity_instance, tree_depth: int = 0) -> None:
|
||||
new = self.props.groups.add()
|
||||
new.ifc_definition_id = group.id()
|
||||
new["name"] = group.Name or "Unnamed"
|
||||
new.tree_depth = tree_depth
|
||||
new.has_children = False
|
||||
new.is_expanded = group.id() in self.expanded_groups
|
||||
|
||||
related_groups: list[ifcopenshell.entity_instance]
|
||||
related_groups = [
|
||||
related_object
|
||||
for rel in group.IsGroupedBy or []
|
||||
for related_object in rel.RelatedObjects
|
||||
if related_object.is_a("IfcGroup")
|
||||
]
|
||||
sorted_related_groups = natsorted(related_groups, key=lambda group: group.Name or "Unnamed")
|
||||
|
||||
if sorted_related_groups:
|
||||
new.has_children = True
|
||||
if new.is_expanded:
|
||||
for related_group in sorted_related_groups:
|
||||
self.load_group(related_group, tree_depth=tree_depth + 1)
|
||||
|
||||
|
||||
class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_group"
|
||||
bl_label = "Toggle Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
ifc_definition_id: bpy.props.IntProperty()
|
||||
index: bpy.props.IntProperty()
|
||||
option: bpy.props.StringProperty(name="Expand or Collapse")
|
||||
|
||||
ifc_definition_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
group_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(tool.Group.GroupType)],
|
||||
)
|
||||
option: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(tool.Group.ToggleOption)],
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifc_definition_id: int
|
||||
group_type: tool.Group.GroupType
|
||||
option: tool.Group.ToggleOption
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
expanded_groups: set[int]
|
||||
expanded_groups = set(json.loads(props.expanded_groups_json))
|
||||
if self.option == "Expand":
|
||||
expanded_groups.add(self.ifc_definition_id)
|
||||
elif self.ifc_definition_id in expanded_groups:
|
||||
expanded_groups.remove(self.ifc_definition_id)
|
||||
props.expanded_groups_json = json.dumps(list(expanded_groups))
|
||||
bpy.ops.bim.load_groups()
|
||||
group = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
tool.Group.toggle_group(group, self.group_type, self.option)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -101,9 +67,8 @@ class DisableGroupEditingUI(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props.is_editing = False
|
||||
props.active_group_id = 0
|
||||
tool.Group.disable_group_editing_ui()
|
||||
tool.Group.disable_editing_group()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -122,10 +87,10 @@ class AddGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.group.add_group(tool.Ifc.get())
|
||||
if self.group:
|
||||
ifcopenshell.api.group.assign_group(
|
||||
tool.Ifc.get(), products=[result], group=tool.Ifc.get().by_id(self.group)
|
||||
)
|
||||
bpy.ops.bim.load_groups()
|
||||
group = tool.Ifc.get().by_id(self.group)
|
||||
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=[result], group=group)
|
||||
tool.Group.toggle_group(group, "IfcGroup", "EXPAND")
|
||||
tool.Group.import_groups("IfcGroup")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -135,11 +100,11 @@ class EditGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.group_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.group.edit_group(ifc_file, group=ifc_file.by_id(props.active_group_id), attributes=attributes)
|
||||
bpy.ops.bim.load_groups()
|
||||
tool.Group.import_groups("IfcGroup")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -152,7 +117,8 @@ class RemoveGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.group.remove_group(self.file, group=self.file.by_id(self.group))
|
||||
bpy.ops.bim.load_groups()
|
||||
tool.Group.import_groups("IfcGroup")
|
||||
tool.Group.update_uilist_index("IfcGroup")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -163,10 +129,11 @@ class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
props.group_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes(tool.Ifc.get().by_id(self.group), props.group_attributes)
|
||||
props.active_group_id = self.group
|
||||
ifc_file = tool.Ifc.get()
|
||||
tool.Group.set_active_group_to_edit(ifc_file.by_id(self.group))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -176,8 +143,7 @@ class DisableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props.active_group_id = 0
|
||||
tool.Group.disable_editing_group()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -202,6 +168,7 @@ class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if (element := tool.Ifc.get_entity(o))
|
||||
]
|
||||
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=products, group=tool.Ifc.get().by_id(self.group))
|
||||
self.report({"INFO"}, f"Assigned {len(products)} objects to group.")
|
||||
|
||||
|
||||
class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -213,13 +180,14 @@ class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
products = [
|
||||
tool.Ifc.get_entity(o)
|
||||
element
|
||||
for o in tool.Blender.get_selected_objects(include_active=False)
|
||||
if tool.Ifc.get_entity(o)
|
||||
if (element := tool.Ifc.get_entity(o))
|
||||
]
|
||||
if not products:
|
||||
return
|
||||
ifcopenshell.api.group.unassign_group(tool.Ifc.get(), products=products, group=tool.Ifc.get().by_id(self.group))
|
||||
self.report({"INFO"}, f"Unassigned {len(products)} objects from group.")
|
||||
|
||||
|
||||
class SelectGroupElements(bpy.types.Operator):
|
||||
|
||||
@@ -44,7 +44,8 @@ class BIM_PT_groups(Panel):
|
||||
def draw(self, context):
|
||||
if not GroupsData.is_loaded:
|
||||
GroupsData.load()
|
||||
self.props = tool.Blender.get_group_props()
|
||||
self.props = tool.Group.get_group_props()
|
||||
assert self.layout
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{GroupsData.data['total_groups']} Groups Found", icon="OUTLINER")
|
||||
@@ -105,7 +106,7 @@ class BIM_PT_object_groups(Panel):
|
||||
if not ObjectGroupsData.is_loaded:
|
||||
ObjectGroupsData.load()
|
||||
assert self.layout
|
||||
self.props = tool.Blender.get_group_props()
|
||||
self.props = tool.Group.get_group_props()
|
||||
|
||||
for group in ObjectGroupsData.data["groups"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -138,7 +139,7 @@ class BIM_UL_groups(UIList):
|
||||
op = row.operator(
|
||||
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
|
||||
)
|
||||
op.group_type = "IfcGroup"
|
||||
op.ifc_definition_id = item.ifc_definition_id
|
||||
op.index = index
|
||||
op.option = "Collapse" if item.is_expanded else "Expand"
|
||||
op.option = "COLLAPSE" if item.is_expanded else "EXPAND"
|
||||
row.prop(item, "name", text="", emboss=False)
|
||||
|
||||
@@ -41,7 +41,7 @@ class SolarData:
|
||||
|
||||
@classmethod
|
||||
def sun_position(cls):
|
||||
return tool.Blender.get_sun_position_addon()
|
||||
return tool.Blender.get_addon("sun_position")
|
||||
|
||||
@classmethod
|
||||
def sites(cls):
|
||||
|
||||
@@ -202,7 +202,7 @@ class RadianceRender(bpy.types.Operator):
|
||||
print(f"Camera position: {camera_position}")
|
||||
print(f"Camera direction: {camera_direction}")
|
||||
|
||||
# sun_position = tool.Blender.get_sun_position_addon()
|
||||
# sun_position = tool.Blender.get_addon("sun_position")
|
||||
# azimuth, elevation = sun_position.sun_calc.get_sun_coordinates(
|
||||
# sun_pos_props.time,
|
||||
# sun_pos_props.latitude,
|
||||
|
||||
@@ -40,7 +40,7 @@ from bpy.types import PropertyGroup
|
||||
from bonsai.bim.module.light.data import SolarData
|
||||
from bonsai.bim.module.light.decorator import SolarDecorator
|
||||
|
||||
sun_position = tool.Blender.get_sun_position_addon()
|
||||
sun_position = tool.Blender.get_addon("sun_position")
|
||||
now = datetime.datetime.now()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "spectraldb.json"), "r") as f:
|
||||
|
||||
@@ -323,20 +323,23 @@ class ObjectMaterialData:
|
||||
|
||||
@classmethod
|
||||
def total_thickness(cls):
|
||||
if cls.material:
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
if not cls.material:
|
||||
return
|
||||
layers = []
|
||||
if cls.material.is_a("IfcMaterialLayerSetUsage"):
|
||||
layers = cls.material.ForLayerSet.MaterialLayers
|
||||
elif cls.material.is_a("IfcMaterialLayerSet"):
|
||||
layers = cls.material.MaterialLayers
|
||||
if not layers:
|
||||
return
|
||||
thickness = sum([l.LayerThickness for l in layers or []])
|
||||
prefs = tool.Blender.get_addon_preferences()
|
||||
assert bpy.context.scene
|
||||
unit_system = bpy.context.scene.unit_settings.system
|
||||
precision = None
|
||||
if unit_system == "IMPERIAL":
|
||||
precision = prefs.doc.imperial_precision
|
||||
return format_distance(thickness, precision=precision, suppress_zero_inches=True, in_unit_length=True)
|
||||
|
||||
@classmethod
|
||||
def set_item_name(cls) -> Union[str, None]:
|
||||
@@ -404,7 +407,7 @@ class ObjectMaterialData:
|
||||
material = cls.material
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
return [m.Name for m in material.MaterialConstituents or [] if m.Name]
|
||||
|
||||
@classmethod
|
||||
def is_type_material_overridden(cls) -> bool:
|
||||
|
||||
@@ -208,6 +208,24 @@ class BIMObjectMaterialProperties(PropertyGroup):
|
||||
parameterized_profile_classes: EnumProperty(
|
||||
items=get_parameterized_profile_classes, name="Parameterized Profile Classes"
|
||||
)
|
||||
use_custom_offset: BoolProperty(name="Use Custom Usage Settings")
|
||||
custom_offset: FloatProperty(name="Use Custom Usage Settings", subtype="DISTANCE")
|
||||
custom_slab_reference: EnumProperty(
|
||||
items=[
|
||||
("TOP", "TOP", ""),
|
||||
("MIDDLE", "MIDDLE", ""),
|
||||
("BOTTOM", "BOTTOM", ""),
|
||||
],
|
||||
name="Custom Offset Reference",
|
||||
)
|
||||
custom_wall_reference: EnumProperty(
|
||||
items=[
|
||||
("EXTERIOR", "EXTERIOR", ""),
|
||||
("CENTER", "CENTER", ""),
|
||||
("INTERIOR", "INTERIOR", ""),
|
||||
],
|
||||
name="Custom Offset Reference",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
material_type: str
|
||||
@@ -221,3 +239,7 @@ class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_set_item_material: str
|
||||
profile_classes: str
|
||||
parameterized_profile_classes: str
|
||||
use_custom_offset: bool
|
||||
custom_offset: float
|
||||
custom_slab_reference: str
|
||||
custom_wall_reference: str
|
||||
|
||||
@@ -231,6 +231,7 @@ class BIM_PT_object_material(Panel):
|
||||
bonsai.bim.helper.draw_attributes(self.props.material_set_attributes, self.layout)
|
||||
bonsai.bim.helper.draw_attributes(self.props.material_set_usage_attributes, self.layout)
|
||||
|
||||
self.draw_custom_offset()
|
||||
if ObjectMaterialData.data["set_item_name"] == "profile" and not self.mprops.profiles:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Profiles Available")
|
||||
@@ -253,6 +254,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Exterior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
if (
|
||||
len(self.props.material_set_item_profile_attributes)
|
||||
@@ -356,6 +361,10 @@ class BIM_PT_object_material(Panel):
|
||||
active_object = bpy.context.active_object
|
||||
self.layerset_bounds(box, active_object, location="Top_Interior")
|
||||
|
||||
if not ObjectMaterialData.data["set_items"]:
|
||||
row = box.row()
|
||||
row.label(text="No Materials Found")
|
||||
|
||||
for set_item in ObjectMaterialData.data["set_items"]:
|
||||
material_name = set_item["material"]
|
||||
material_id = set_item["material_id"]
|
||||
@@ -390,6 +399,25 @@ class BIM_PT_object_material(Panel):
|
||||
else:
|
||||
row.label(text="Exterior")
|
||||
|
||||
def draw_custom_offset(self):
|
||||
set_usage = ObjectMaterialData.data.get("set_usage", {})
|
||||
layer_set_direction = set_usage.get("layer_set_direction")
|
||||
if layer_set_direction:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "use_custom_offset", text="Use Custom Offset")
|
||||
row = box.row(align=True)
|
||||
if layer_set_direction == "AXIS2":
|
||||
row.prop(self.props, "custom_wall_reference", text="Reference")
|
||||
row.enabled = self.props.use_custom_offset
|
||||
if layer_set_direction == "AXIS3":
|
||||
row.prop(self.props, "custom_slab_reference", text="Reference")
|
||||
row.enabled = self.props.use_custom_offset
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "custom_offset", text="Custom Offset")
|
||||
row.enabled = self.props.use_custom_offset
|
||||
|
||||
|
||||
class BIM_UL_materials(UIList):
|
||||
def draw_item(
|
||||
|
||||
@@ -193,9 +193,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
|
||||
@@ -224,9 +221,6 @@ class SplitAlongEdge(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1226,7 +1226,7 @@ class BoundingBoxDecorator:
|
||||
def find_closest_trihedron(corners, edges, region, rv3d):
|
||||
|
||||
min_y = float("inf")
|
||||
best_origin = None
|
||||
best_origin = 0
|
||||
for idx, corner in enumerate(corners):
|
||||
screen_co = location_3d_to_region_2d(region, rv3d, corner)
|
||||
if screen_co is not None and screen_co.y < min_y:
|
||||
|
||||
@@ -95,15 +95,17 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
representation_data["part_of_product"] = None
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
if fallback_material := (int(props.lining_material) or int(props.framing_material) or int(props.glazing_material)):
|
||||
materials = {
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
}
|
||||
if props.transom_thickness:
|
||||
materials["Glazing"] = tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material)
|
||||
ifcopenshell.api.material.set_shape_aspect_constituents(
|
||||
ifc_file,
|
||||
element=element,
|
||||
context=body,
|
||||
materials={
|
||||
"Lining": tool.Ifc.get().by_id(int(props.lining_material) or fallback_material),
|
||||
"Framing": tool.Ifc.get().by_id(int(props.framing_material) or fallback_material),
|
||||
"Glazing": tool.Ifc.get().by_id(int(props.glazing_material) or fallback_material),
|
||||
},
|
||||
materials=materials,
|
||||
)
|
||||
elif material := ifcopenshell.util.element.get_material(element):
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
@@ -143,9 +145,6 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -579,9 +578,6 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -186,9 +186,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
@@ -249,9 +246,6 @@ class FilledOpeningGenerator:
|
||||
tool.Geometry,
|
||||
obj=voided_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def generate_opening_from_filling(
|
||||
@@ -420,9 +414,6 @@ class RecalculateFill(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=building_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -798,9 +789,6 @@ class CloneOpening(Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -557,7 +557,33 @@ def get_generic_product_preview_data(context, relating_type):
|
||||
|
||||
mouse_point.z = snap_obj.matrix_world.translation.z
|
||||
|
||||
if snap_element and (container := ifcopenshell.util.element.get_container(snap_element)):
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
mouse_point.z = container_obj.location.z
|
||||
|
||||
obj_type = tool.Ifc.get_object(relating_type)
|
||||
|
||||
subcontexts = tool.Drawing.get_active_drawing_subcontexts()
|
||||
if not subcontexts:
|
||||
subcontexts = [("Model", "Body", "MODEL_VIEW")]
|
||||
|
||||
active_context = tool.Geometry.get_active_representation_context(obj_type)
|
||||
active_context_params = tool.Geometry.get_subcontext_parameters(active_context)
|
||||
for subcontext in subcontexts:
|
||||
if subcontext == active_context_params:
|
||||
break
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(relating_type, *subcontext)
|
||||
if representation:
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj_type,
|
||||
representation,
|
||||
)
|
||||
context.view_layer.update()
|
||||
break
|
||||
|
||||
if obj_type.data:
|
||||
data = ItemDecorator.get_obj_data(obj_type)
|
||||
data["verts"] = [tuple(obj_type.matrix_world.inverted() @ Vector(v)) for v in data["verts"]]
|
||||
|
||||
@@ -327,9 +327,21 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
|
||||
props.relating_type_id = str(self.relating_type_id)
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
building_obj = context.active_object
|
||||
building_element = tool.Ifc.get_entity(building_obj)
|
||||
|
||||
self.container = None
|
||||
self.container_obj = None
|
||||
if container := tool.Root.get_default_container():
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and (container := ifcopenshell.util.element.get_container(building_element))
|
||||
):
|
||||
self.container = container
|
||||
self.container_obj = tool.Ifc.get_object(container)
|
||||
elif container := tool.Root.get_default_container():
|
||||
self.container = container
|
||||
self.container_obj = tool.Ifc.get_object(container)
|
||||
|
||||
@@ -394,9 +406,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif self.representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -410,17 +419,9 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return
|
||||
|
||||
building_obj = None
|
||||
if len(context.selected_objects) == 1 and context.active_object:
|
||||
building_obj = context.active_object
|
||||
building_element = tool.Ifc.get_entity(building_obj)
|
||||
|
||||
mesh = bpy.data.meshes.new(name="Instance")
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
|
||||
@@ -446,9 +447,6 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
@@ -487,12 +485,15 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering", "IfcElementAssembly"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
tool.Model.add_filled_opening(building_obj, obj)
|
||||
# TODO For now we are hardcoding windows and doors as a prototype
|
||||
tool.Model.add_filled_opening(building_obj, obj)
|
||||
else:
|
||||
if self.container_obj:
|
||||
bonsai.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, container=self.container, element_obj=obj
|
||||
)
|
||||
if props.rl_mode == "BOTTOM":
|
||||
obj.location.z = self.container_obj.location.z - tool.Blender.get_object_bounding_box(obj)["min_z"]
|
||||
elif props.rl_mode == "CONTAINER":
|
||||
@@ -728,7 +729,4 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -98,7 +98,8 @@ class DumbProfileGenerator:
|
||||
vec1 = Vector((polyline_points[i].x, polyline_points[i].y, polyline_points[i].z))
|
||||
vec2 = Vector((polyline_points[i + 1].x, polyline_points[i + 1].y, polyline_points[i + 1].z))
|
||||
coords = (vec1, vec2)
|
||||
profiles.append(self.create_profile_from_2_points(coords))
|
||||
if profile := self.create_profile_from_2_points(coords):
|
||||
profiles.append(profile)
|
||||
return profiles, is_polyline_closed
|
||||
|
||||
def derive_from_cursor(self) -> bpy.types.Object:
|
||||
@@ -166,9 +167,6 @@ class DumbProfileGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
@@ -183,7 +181,7 @@ class DumbProfileGenerator:
|
||||
) -> ProfileFrom2PointsReturn:
|
||||
self.direction = coords[1] - coords[0]
|
||||
length = self.direction.length
|
||||
if round(length, 4) < 0.1:
|
||||
if round(length, 4) < 0.001:
|
||||
return
|
||||
data: dict[str, Any] = {"coords": coords}
|
||||
|
||||
@@ -567,9 +565,6 @@ class DumbProfileJoiner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
if element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting"):
|
||||
@@ -1036,9 +1031,6 @@ def disable_editing_extrusion_axis(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1100,9 +1092,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
@@ -373,7 +373,12 @@ class BIMStairProperties(PropertyGroup):
|
||||
if self.stair_type != "WOOD/STEEL" and self.nosing_length < 0:
|
||||
self["nosing_length"] = 0
|
||||
|
||||
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
|
||||
def update_custom_tread_lock(self, context: bpy.types.Context) -> None:
|
||||
"""When lock is enabled, sync custom treads with tread_run"""
|
||||
if self.custom_tread_lock:
|
||||
self["custom_first_last_tread_run"] = (self.tread_run, self.tread_run)
|
||||
|
||||
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type", "custom_tread_lock")
|
||||
|
||||
is_editing: bpy.props.BoolProperty(default=False)
|
||||
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
|
||||
@@ -407,6 +412,12 @@ class BIMStairProperties(PropertyGroup):
|
||||
default="CONCRETE",
|
||||
update=validate_nosing_value,
|
||||
)
|
||||
custom_tread_lock: bpy.props.BoolProperty(
|
||||
name="Lock First/Last Treads to Tread Run",
|
||||
description="When enabled, first and last treads automatically use the Tread Run value",
|
||||
default=True,
|
||||
update=update_custom_tread_lock,
|
||||
)
|
||||
custom_first_last_tread_run: bpy.props.FloatVectorProperty(
|
||||
name="Custom First / Last Treads Widths",
|
||||
description='Specify custom first / last treads widths, different from the general "Tread Run". Leave 0 to disable.',
|
||||
@@ -442,6 +453,7 @@ class BIMStairProperties(PropertyGroup):
|
||||
top_slab_depth: float
|
||||
has_top_nib: bool
|
||||
stair_type: str
|
||||
custom_tread_lock: bool
|
||||
custom_first_last_tread_run: tuple[float, float]
|
||||
nosing_length: float
|
||||
nosing_depth: float
|
||||
@@ -480,17 +492,38 @@ class BIMStairProperties(PropertyGroup):
|
||||
}
|
||||
stair_kwargs.update(generic_props)
|
||||
|
||||
# defined here to appear last in UI
|
||||
stair_kwargs["custom_first_last_tread_run"] = self.custom_first_last_tread_run
|
||||
# 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)
|
||||
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)
|
||||
return stair_kwargs
|
||||
|
||||
def get_props_kwargs_for_ifc_export(self, convert_to_project_units=False, stair_type=None):
|
||||
"""Get props including custom_tread_lock for saving to IFC"""
|
||||
stair_kwargs = self.get_props_kwargs(convert_to_project_units, stair_type)
|
||||
# Add the lock state for IFC storage (after getting base kwargs to avoid passing to generate function)
|
||||
stair_kwargs["custom_tread_lock"] = self.custom_tread_lock
|
||||
return stair_kwargs
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
@@ -538,9 +538,6 @@ def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -167,9 +167,6 @@ class DumbSlabGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
obj.matrix_world = obj.matrix_world @ Matrix.Rotation(self.x_angle, 4, "X")
|
||||
|
||||
@@ -292,6 +289,9 @@ class DumbSlabPlaner:
|
||||
if thickness == 0:
|
||||
return
|
||||
|
||||
custom_offset = tool.Model.get_material_layer_custom_offset(element, obj)
|
||||
layer_offset = (custom_offset * self.unit_scale) if custom_offset is not None else layer_params["offset"]
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if representation:
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
@@ -307,7 +307,7 @@ class DumbSlabPlaner:
|
||||
direction_ratios = Vector(extrusion.ExtrudedDirection.DirectionRatios)
|
||||
offset_direction = direction_ratios.copy()
|
||||
perpendicular_depth = thickness * abs(1 / cos(existing_x_angle))
|
||||
perpendicular_offset = layer_params["offset"] * abs(1 / cos(existing_x_angle)) / self.unit_scale
|
||||
perpendicular_offset = layer_offset * abs(1 / cos(existing_x_angle)) / self.unit_scale
|
||||
|
||||
# Check angle and z direction to determine whether the extrusion direction is positive or negative
|
||||
if (abs(existing_x_angle) < (pi / 2) and direction_ratios.z > 0) or (
|
||||
@@ -331,15 +331,15 @@ class DumbSlabPlaner:
|
||||
extrusion.Depth = perpendicular_depth
|
||||
|
||||
ifc_position = extrusion.Position
|
||||
if perpendicular_offset == 0.0:
|
||||
# Clean up possible previous offset.
|
||||
tool.Model.reset_extrusion_position(extrusion)
|
||||
position = offset_direction * perpendicular_offset
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSetUsage"):
|
||||
material.OffsetFromReferenceLine = position.z
|
||||
if ifc_position:
|
||||
ifc_position.Location.Coordinates = position
|
||||
else:
|
||||
position = offset_direction * perpendicular_offset
|
||||
if ifc_position:
|
||||
ifc_position.Location.Coordinates = position
|
||||
else:
|
||||
tool.Model.add_extrusion_position(extrusion, position)
|
||||
tool.Model.add_extrusion_position(extrusion, position)
|
||||
|
||||
else:
|
||||
props = tool.Model.get_model_props()
|
||||
@@ -357,9 +357,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_rep,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bonsai.core.geometry.remove_representation(
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=representation
|
||||
@@ -383,9 +380,6 @@ class DumbSlabPlaner:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -585,9 +579,6 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -629,9 +620,6 @@ def disable_editing_extrusion_profile(context):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -764,9 +752,6 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Only certain classes should have a footprint
|
||||
|
||||
@@ -188,7 +188,8 @@ class AddStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Model.get_stair_props(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
stair_data = props.get_props_kwargs(convert_to_project_units=True)
|
||||
# Use the special method that includes custom_tread_lock for IFC storage
|
||||
stair_data = props.get_props_kwargs_for_ifc_export(convert_to_project_units=True)
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.pset.add_pset(ifc_file, product=element, name="BBIM_Stair")
|
||||
@@ -241,7 +242,8 @@ class FinishEditingStair(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert element
|
||||
props = tool.Model.get_stair_props(obj)
|
||||
|
||||
data = props.get_props_kwargs(convert_to_project_units=True)
|
||||
# Use the special method that includes custom_tread_lock for IFC storage
|
||||
data = props.get_props_kwargs_for_ifc_export(convert_to_project_units=True)
|
||||
props.is_editing = False
|
||||
regenerate_stair_mesh(obj)
|
||||
tool.Model.add_body_representation(obj)
|
||||
|
||||
@@ -302,13 +302,36 @@ class BIM_PT_stair(bpy.types.Panel):
|
||||
row.operator("bim.cancel_editing_stair", icon="CANCEL", text="")
|
||||
row = self.layout.row(align=True)
|
||||
for prop_name in props.get_props_kwargs():
|
||||
# Skip custom_tread_lock as it's handled with custom_first_last_tread_run
|
||||
if prop_name == "custom_tread_lock":
|
||||
continue
|
||||
|
||||
prop_value = getattr(props, prop_name)
|
||||
if isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
|
||||
|
||||
# Special handling for custom_first_last_tread_run
|
||||
if prop_name == "custom_first_last_tread_run":
|
||||
# Draw the lock toggle
|
||||
row_lock = self.layout.row(align=True)
|
||||
lock_text = "Lock First/Last Treads" if not props.custom_tread_lock else "Unlock First/Last Treads"
|
||||
row_lock.prop(
|
||||
props,
|
||||
"custom_tread_lock",
|
||||
text=lock_text,
|
||||
icon="LOCKED" if props.custom_tread_lock else "UNLOCKED",
|
||||
)
|
||||
|
||||
# Only show the custom values input if unlocked
|
||||
if not props.custom_tread_lock:
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
self.layout.label(text=f"{prop_readable_name}:")
|
||||
self.layout.prop(props, prop_name, text="")
|
||||
elif isinstance(prop_value, Iterable) and not isinstance(prop_value, str):
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
self.layout.label(text=f"{prop_readable_name}:")
|
||||
self.layout.prop(props, prop_name, text="")
|
||||
else:
|
||||
self.layout.prop(props, prop_name)
|
||||
|
||||
if prop_name == "height": # Weak but we just want to insert this inside props drawing
|
||||
row_length = self.layout.row(align=True)
|
||||
row_length.prop(props, "total_length_target")
|
||||
@@ -550,7 +573,7 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
|
||||
row = prop_with_search(self.layout, props, "lining_material")
|
||||
tool.Model.draw_material_ui_select(row, props.lining_material)
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Material")
|
||||
row = prop_with_search(self.layout, props, "framing_material", text="Panel Framing Material")
|
||||
tool.Model.draw_material_ui_select(row, props.framing_material)
|
||||
if props.transom_thickness:
|
||||
row = prop_with_search(self.layout, props, "glazing_material")
|
||||
|
||||
@@ -441,6 +441,7 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
x_angle = 0 if tool.Cad.is_x(self.x_angle, pi, tolerance=0.001) else self.x_angle
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
selected_objs = tool.Model.get_selected_mesh_ifc_objects()
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
|
||||
for obj in selected_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -466,16 +467,15 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
existing_x_angle = obj.rotation_euler.x
|
||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, 0, tolerance=0.001) else existing_x_angle
|
||||
existing_x_angle = 0 if tool.Cad.is_x(existing_x_angle, pi, tolerance=0.001) else existing_x_angle
|
||||
# Reset the transformation and returns to the original points with 0 degrees
|
||||
extrusion.SweptArea.OuterCurve.Points.CoordList = [
|
||||
(p[0], p[1] * abs(cos(existing_x_angle)))
|
||||
for p in extrusion.SweptArea.OuterCurve.Points.CoordList
|
||||
]
|
||||
|
||||
# Apply the transformation for the new x_angle
|
||||
extrusion.SweptArea.OuterCurve.Points.CoordList = [
|
||||
(p[0], p[1] * abs(1 / cos(x_angle))) for p in extrusion.SweptArea.OuterCurve.Points.CoordList
|
||||
]
|
||||
coord_list = builder.get_polyline_coords(extrusion.SweptArea.OuterCurve)
|
||||
coord_list = [
|
||||
(p[0], p[1] * abs(cos(existing_x_angle))) for p in coord_list
|
||||
] # Reset the transformation and returns to the original points with 0 degrees
|
||||
coord_list = [
|
||||
(p[0], p[1] * abs(1 / cos(x_angle))) for p in coord_list
|
||||
] # Apply the transformation for the new x_angle
|
||||
builder.set_polyline_coords(extrusion.SweptArea.OuterCurve, coord_list)
|
||||
|
||||
# The extrusion direction calculated previously default to the positive direction
|
||||
# Here we set the extrusion direction to negative if that's the case
|
||||
@@ -517,9 +517,6 @@ class ChangeExtrusionXAngle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
# Object rotation
|
||||
@@ -895,7 +892,8 @@ class DumbWallGenerator:
|
||||
elevation = self.container_obj.location.z
|
||||
representation = ifcopenshell.util.representation.get_representation(slab, "Model", "Body", "MODEL_VIEW")
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
polyline_points = extrusion.SweptArea.OuterCurve.Points.CoordList
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
polyline_points = builder.get_polyline_coords(extrusion.SweptArea.OuterCurve)
|
||||
polyline_points = [[(v * self.unit_scale) for v in p] for p in polyline_points]
|
||||
polyline_points = [slab_obj.matrix_world @ Vector((p[0], p[1], elevation)) for p in polyline_points]
|
||||
if not tool.Cad.is_counter_clockwise_order(polyline_points[0], polyline_points[1], polyline_points[2]):
|
||||
@@ -1023,9 +1021,6 @@ class DumbWallGenerator:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties={"Engine": "Bonsai.DumbLayer2"})
|
||||
|
||||
@@ -123,9 +123,6 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None:
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=ifcopenshell.util.representation.get_representation(element, active_context),
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
# type attributes
|
||||
@@ -495,9 +492,6 @@ class CancelEditingWindow(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
props.is_editing = False
|
||||
|
||||
@@ -1452,16 +1452,20 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "No LAYER2 objects selected")
|
||||
return
|
||||
|
||||
if layer2_bases and len(set(layer2_bases)) > 1:
|
||||
# --- tolerance check ---
|
||||
tolerance = 1e-5 # to provide a little wiggle room
|
||||
if layer2_bases and (max(layer2_bases) - min(layer2_bases)) > tolerance:
|
||||
min_base = min(layer2_bases)
|
||||
max_base = max(layer2_bases)
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Selected LAYER2 objects have different base heights ({min_base:.3f}m to {max_base:.3f}m). All objects must be at the exact same base level.",
|
||||
f"Selected LAYER2 objects have different base heights ({min_base:.3f}m to {max_base:.3f}m). "
|
||||
f"All objects must be at the exact same base level (tolerance {tolerance}).",
|
||||
)
|
||||
return
|
||||
|
||||
common_base = layer2_bases[0]
|
||||
# use the mean base as the "common" one to avoid floating-point mismatches
|
||||
common_base = sum(layer2_bases) / len(layer2_bases)
|
||||
new_height = cursor_z - common_base
|
||||
|
||||
if new_height > 0:
|
||||
|
||||
@@ -55,6 +55,7 @@ classes = (
|
||||
operator.RefreshClippingPlanes,
|
||||
operator.RefreshLibrary,
|
||||
operator.ReloadLink,
|
||||
operator.RemoveProjectLibrary,
|
||||
operator.RevertProject,
|
||||
operator.RewindLibrary,
|
||||
operator.SaveLibraryFile,
|
||||
|
||||
@@ -29,6 +29,7 @@ from typing import Union, Any
|
||||
def refresh():
|
||||
ProjectData.is_loaded = False
|
||||
LinksData.is_loaded = False
|
||||
ProjectLibraryData.is_loaded = False
|
||||
|
||||
|
||||
class ProjectData:
|
||||
@@ -43,6 +44,7 @@ class ProjectData:
|
||||
"library_file": cls.library_file(),
|
||||
"last_saved": cls.last_saved(),
|
||||
"total_elements": cls.total_elements(),
|
||||
"header_info": cls.header_info(),
|
||||
}
|
||||
# After export_schema.
|
||||
cls.data["template_file"] = cls.template_file()
|
||||
@@ -95,7 +97,7 @@ class ProjectData:
|
||||
if not ifc:
|
||||
return ""
|
||||
try:
|
||||
save_datetime = ifc.wrapped_data.header.file_name.time_stamp
|
||||
save_datetime = ifc.header.file_name.time_stamp
|
||||
save_date, save_time = save_datetime.split("T")
|
||||
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
|
||||
except:
|
||||
@@ -107,6 +109,12 @@ class ProjectData:
|
||||
return len(ifc.by_type("IfcElement"))
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def header_info(cls) -> Union[tool.Project.HeaderData, None]:
|
||||
if not tool.Ifc.get():
|
||||
return None
|
||||
return tool.Project.get_header_data()
|
||||
|
||||
|
||||
class ProjectLibraryData:
|
||||
data: dict[str, Any] = {}
|
||||
|
||||
@@ -155,6 +155,7 @@ class CreateProject(bpy.types.Operator):
|
||||
tool.Ifc, tool.Georeference, tool.Project, tool.Spatial, schema=props.export_schema, template=template
|
||||
)
|
||||
tool.Blender.register_toolbar()
|
||||
tool.Loader.set_unit_scale(ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()))
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.file = None
|
||||
@@ -167,11 +168,27 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
bl_idname = "bim.select_library_file"
|
||||
bl_label = "Select Library File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC file that can be used as a library"
|
||||
bl_description = (
|
||||
"Select an IFC file that can be used as a library.\n\nALT+click to reload the current loaded library file."
|
||||
)
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
append_all: bpy.props.BoolProperty(default=False)
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
reload_previous_file = False
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.alt:
|
||||
old_filepath = IfcStore.library_path
|
||||
if not old_filepath:
|
||||
self.report({"ERROR"}, "No library file loaded to reload.")
|
||||
return {"CANCELLED"}
|
||||
self.filepath = old_filepath
|
||||
self.reload_previous_file = True
|
||||
return self.execute(context)
|
||||
|
||||
return ImportHelper.invoke(self, context, event)
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
old_filepath = IfcStore.library_path
|
||||
@@ -201,6 +218,7 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
if self.append_all:
|
||||
bpy.ops.bim.append_entire_library()
|
||||
ProjectLibraryData.load()
|
||||
self.report({"INFO"}, f"Loaded library from {filepath}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -223,6 +241,7 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
class RefreshLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_library"
|
||||
bl_label = "Refresh Library"
|
||||
bl_description = "Refresh the library browser"
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -506,6 +525,7 @@ class SaveLibraryFile(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.library_file.write(IfcStore.library_path)
|
||||
self.report({"INFO"}, f"Library saved to {IfcStore.library_path}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -601,8 +621,11 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.import_type_from_ifc(element_type, context)
|
||||
elif element.is_a("IfcMaterial"):
|
||||
self.import_material_from_ifc(element, context)
|
||||
elif element.is_a("IfcPresentationStyle"):
|
||||
elif element.is_a("IfcSurfaceStyle"):
|
||||
self.import_presentation_style_from_ifc(element, context)
|
||||
else:
|
||||
# E.g. other IfcPresentationStyles.
|
||||
pass
|
||||
|
||||
try:
|
||||
props = tool.Project.get_project_props()
|
||||
@@ -774,6 +797,45 @@ class AddProjectLibrary(bpy.types.Operator):
|
||||
IfcStore.library_file.redo()
|
||||
|
||||
|
||||
class RemoveProjectLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_project_library"
|
||||
bl_label = "Remove Project Library"
|
||||
bl_description = "Remove the currently selected IfcProjectLibrary."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
library_file.begin_transaction()
|
||||
result = self._execute(context)
|
||||
library_file.end_transaction()
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Project.get_project_props()
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
|
||||
project_library = library_file.by_id(int(props.selected_project_library))
|
||||
tool.Project.remove_project_library(project_library)
|
||||
|
||||
ProjectLibraryData.load() # Update enum.
|
||||
enum_len = len(ProjectLibraryData.data["project_libraries_enum"])
|
||||
if props.is_property_set("selected_project_library"):
|
||||
props["selected_project_library"] = min(enum_len - 1, props["selected_project_library"])
|
||||
bpy.ops.bim.refresh_library()
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.library_file.undo()
|
||||
|
||||
def commit(self, data):
|
||||
IfcStore.library_file.redo()
|
||||
|
||||
|
||||
class EnableEditingHeader(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_header"
|
||||
bl_label = "Enable Editing Header"
|
||||
@@ -788,26 +850,8 @@ class EnableEditingHeader(bpy.types.Operator):
|
||||
self.file = tool.Ifc.get()
|
||||
props = tool.Project.get_project_props()
|
||||
props.is_editing = True
|
||||
|
||||
mvd = "".join(tool.Ifc.get().wrapped_data.header.file_description.description)
|
||||
if "[" in mvd:
|
||||
props.mvd = mvd.split("[")[1][0:-1]
|
||||
else:
|
||||
props.mvd = ""
|
||||
|
||||
author = self.file.wrapped_data.header.file_name.author
|
||||
if author:
|
||||
props.author_name = author[0]
|
||||
if len(author) > 1:
|
||||
props.author_email = author[1]
|
||||
|
||||
organisation = self.file.wrapped_data.header.file_name.organization
|
||||
if organisation:
|
||||
props.organisation_name = organisation[0]
|
||||
if len(organisation) > 1:
|
||||
props.organisation_email = organisation[1]
|
||||
|
||||
props.authorisation = self.file.wrapped_data.header.file_name.authorization or ""
|
||||
header_data = tool.Project.get_header_data()
|
||||
props.load_header_data(header_data)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -822,6 +866,9 @@ class EditHeader(bpy.types.Operator):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def execute(self, context):
|
||||
# NOTE: Though header entities are now generic `entity_instance`
|
||||
# we still have a special undo system in place for this operator
|
||||
# since general undo system tracks only elements with ids != 0.
|
||||
IfcStore.begin_transaction(self)
|
||||
self.transaction_data = {}
|
||||
self.transaction_data["old"] = self.record_state()
|
||||
@@ -829,6 +876,7 @@ class EditHeader(bpy.types.Operator):
|
||||
self.transaction_data["new"] = self.record_state()
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -836,35 +884,35 @@ class EditHeader(bpy.types.Operator):
|
||||
props = tool.Project.get_project_props()
|
||||
props.is_editing = True
|
||||
|
||||
self.file.wrapped_data.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
|
||||
self.file.wrapped_data.header.file_name.author = (props.author_name, props.author_email)
|
||||
self.file.wrapped_data.header.file_name.organization = (props.organisation_name, props.organisation_email)
|
||||
self.file.wrapped_data.header.file_name.authorization = props.authorisation
|
||||
self.file.header.file_description.description = (f"ViewDefinition[{props.mvd}]",)
|
||||
self.file.header.file_name.author = (props.author_name, props.author_email)
|
||||
self.file.header.file_name.organization = (props.organisation_name, props.organisation_email)
|
||||
self.file.header.file_name.authorization = props.authorisation
|
||||
bpy.ops.bim.disable_editing_header()
|
||||
return {"FINISHED"}
|
||||
|
||||
def record_state(self):
|
||||
self.file = tool.Ifc.get()
|
||||
return {
|
||||
"description": self.file.wrapped_data.header.file_description.description,
|
||||
"author": self.file.wrapped_data.header.file_name.author,
|
||||
"organisation": self.file.wrapped_data.header.file_name.organization,
|
||||
"authorisation": self.file.wrapped_data.header.file_name.authorization,
|
||||
"description": self.file.header.file_description.description,
|
||||
"author": self.file.header.file_name.author,
|
||||
"organisation": self.file.header.file_name.organization,
|
||||
"authorisation": self.file.header.file_name.authorization,
|
||||
}
|
||||
|
||||
def rollback(self, data):
|
||||
file = tool.Ifc.get()
|
||||
file.wrapped_data.header.file_description.description = data["old"]["description"]
|
||||
file.wrapped_data.header.file_name.author = data["old"]["author"]
|
||||
file.wrapped_data.header.file_name.organization = data["old"]["organisation"]
|
||||
file.wrapped_data.header.file_name.authorization = data["old"]["authorisation"]
|
||||
file.header.file_description.description = data["old"]["description"]
|
||||
file.header.file_name.author = data["old"]["author"]
|
||||
file.header.file_name.organization = data["old"]["organisation"]
|
||||
file.header.file_name.authorization = data["old"]["authorisation"]
|
||||
|
||||
def commit(self, data):
|
||||
file = tool.Ifc.get()
|
||||
file.wrapped_data.header.file_description.description = data["new"]["description"]
|
||||
file.wrapped_data.header.file_name.author = data["new"]["author"]
|
||||
file.wrapped_data.header.file_name.organization = data["new"]["organisation"]
|
||||
file.wrapped_data.header.file_name.authorization = data["new"]["authorisation"]
|
||||
file.header.file_description.description = data["new"]["description"]
|
||||
file.header.file_name.author = data["new"]["author"]
|
||||
file.header.file_name.organization = data["new"]["organisation"]
|
||||
file.header.file_name.authorization = data["new"]["authorisation"]
|
||||
|
||||
|
||||
class DisableEditingHeader(bpy.types.Operator):
|
||||
@@ -2474,6 +2522,7 @@ class FlipClippingPlane(bpy.types.Operator):
|
||||
obj = context.active_object
|
||||
if obj in tool.Project.get_project_props().clipping_planes_objs:
|
||||
obj.rotation_euler[0] += radians(180)
|
||||
obj.rotation_euler[0] %= radians(360)
|
||||
context.view_layer.update()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -446,6 +446,14 @@ class BIMProjectProperties(PropertyGroup):
|
||||
def get_library_element_index(self, lib_element: LibraryElement) -> int:
|
||||
return next(i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)
|
||||
|
||||
def load_header_data(self, header_data: tool.Project.HeaderData) -> None:
|
||||
self.mvd = header_data.mvd
|
||||
self.author_name = header_data.author_name
|
||||
self.author_email = header_data.author_email
|
||||
self.organisation_name = header_data.organization_name
|
||||
self.organisation_email = header_data.organization_email
|
||||
self.authorisation = header_data.authorization
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
is_loading: bool
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import os
|
||||
import ifcopenshell
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.helper import prop_with_search, draw_attributes
|
||||
@@ -258,36 +259,37 @@ class BIM_PT_project(Panel):
|
||||
|
||||
def draw_editable_file_info(self, context):
|
||||
pprops = self.props
|
||||
assert self.layout
|
||||
|
||||
if tool.Ifc.get():
|
||||
if ifc_file := tool.Ifc.get():
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC Schema", icon="FILE_CACHE")
|
||||
row.label(text=tool.Ifc.get().schema)
|
||||
row.label(text=ifc_file.schema)
|
||||
|
||||
if pprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "mvd")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "author_name")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "author_email")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "organisation_name")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "organisation_email")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "authorisation")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="IFC MVD", icon="FILE_HIDDEN")
|
||||
mvd = "".join(tool.Ifc.get().wrapped_data.header.file_description.description)
|
||||
if "[" in mvd:
|
||||
mvd = mvd.split("[")[1][0:-1]
|
||||
row.label(text=mvd)
|
||||
if not ProjectData.is_loaded:
|
||||
ProjectData.load()
|
||||
|
||||
headers_props_and_icons = [
|
||||
("mvd", "FILE_HIDDEN"),
|
||||
("author_name", None),
|
||||
("author_email", None),
|
||||
("organisation_name", None),
|
||||
("organisation_email", None),
|
||||
("authorisation", None),
|
||||
]
|
||||
rna_props = pprops.bl_rna.properties
|
||||
for prop_name, prop_icon in headers_props_and_icons:
|
||||
if pprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, prop_name)
|
||||
else:
|
||||
value = getattr(ProjectData.data["header_info"], prop_name)
|
||||
if not value:
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
icon = {} if prop_icon is None else {"icon": prop_icon}
|
||||
row.label(text=rna_props[prop_name].name, **icon)
|
||||
row.label(text=value)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="File Not Loaded", icon="ERROR")
|
||||
@@ -406,6 +408,7 @@ class BIM_PT_project_library(Panel):
|
||||
|
||||
if library_is_selected and not props.is_editing_project_library:
|
||||
row.prop(props, "is_editing_project_library", text="", icon="GREASEPENCIL")
|
||||
row.operator("bim.remove_project_library", text="", icon="X")
|
||||
|
||||
row.prop(self.props, "show_library_tree", text="", icon="OUTLINER")
|
||||
|
||||
@@ -650,4 +653,5 @@ class BIM_PT_purge(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{object_type.replace('_', ' ').capitalize()}:")
|
||||
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = object_type
|
||||
row.operator("bim.merge_identical_objects", text="Merge Identical").object_type = object_type
|
||||
merge_op = row.operator("bim.merge_identical_objects", text="Merge Identical")
|
||||
merge_op.object_type = object_type
|
||||
|
||||
@@ -269,7 +269,7 @@ class GroupQtosData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
assert (active_group := props.active_group)
|
||||
ifc_definition_id = active_group.ifc_definition_id
|
||||
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get_entity_by_id(ifc_definition_id), qtos_only=True)}
|
||||
@@ -282,7 +282,7 @@ class GroupPsetData(Data):
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
assert (active_group := props.active_group)
|
||||
ifc_definition_id = active_group.ifc_definition_id
|
||||
cls.data = {"psets": cls.psetqtos(tool.Ifc.get_entity_by_id(ifc_definition_id), psets_only=True)}
|
||||
|
||||
@@ -242,14 +242,11 @@ class BIM_PT_object_psets(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not (obj := context.active_object):
|
||||
return False
|
||||
ifc_id = tool.Blender.get_ifc_definition_id(obj)
|
||||
if not ifc_id:
|
||||
return False
|
||||
if not tool.Ifc.get_object_by_identifier(ifc_id):
|
||||
return False
|
||||
return True
|
||||
return (
|
||||
(obj := tool.Blender.get_active_object())
|
||||
and (element := tool.Ifc.get_entity(obj))
|
||||
and element.is_a("IfcObjectDefinition")
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectPsetsData.is_loaded:
|
||||
@@ -388,14 +385,11 @@ class BIM_PT_object_qtos(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not (obj := context.active_object):
|
||||
return False
|
||||
ifc_id = tool.Blender.get_ifc_definition_id(obj)
|
||||
if not ifc_id:
|
||||
return False
|
||||
if not tool.Ifc.get_object_by_identifier(ifc_id):
|
||||
return False
|
||||
return True
|
||||
return (
|
||||
(obj := tool.Blender.get_active_object())
|
||||
and (element := tool.Ifc.get_entity(obj))
|
||||
and element.is_a("IfcObjectDefinition")
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectQtosData.is_loaded:
|
||||
@@ -667,7 +661,7 @@ class BIM_PT_group_qtos(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
return bool(props.active_group)
|
||||
|
||||
def draw(self, context):
|
||||
@@ -699,7 +693,7 @@ class BIM_PT_group_psets(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Blender.get_group_props()
|
||||
props = tool.Group.get_group_props()
|
||||
return bool(props.active_group)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -203,6 +203,7 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
predefined_type: str
|
||||
userdefined_type: str
|
||||
context_id: int
|
||||
props_to_pset: bool
|
||||
should_add_representation: bool
|
||||
ifc_representation_class: str
|
||||
|
||||
@@ -316,6 +317,7 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
f"Mesh '{obj.data.name}' has loose geometry, loose geometry will be ignored to save mesh to IFC as a tessellation.",
|
||||
)
|
||||
|
||||
representation = tool.Geometry.export_mesh_to_tessellation(obj, ifc_context)
|
||||
element = core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
@@ -325,16 +327,9 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=False,
|
||||
)
|
||||
representation = tool.Geometry.export_mesh_to_tessellation(obj, ifc_context)
|
||||
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=representation
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -385,13 +380,8 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# TODO: reload representation might lead to the object being replaced by object of the other type.
|
||||
# We probably should track it somehow and keep the original selection.
|
||||
|
||||
# Validation selection.
|
||||
new_selected_objects = list(filter(tool.Blender.is_valid_data_block, current_selection[2]))
|
||||
active_object = current_selection[1]
|
||||
if active_object and not tool.Blender.is_valid_data_block(active_object):
|
||||
active_object = None
|
||||
current_selection = (current_selection[0], active_object, new_selected_objects)
|
||||
|
||||
# Validate selection and reapply it.
|
||||
current_selection = tool.Blender.validate_object_selection(*current_selection)
|
||||
tool.Blender.set_objects_selection(*current_selection)
|
||||
|
||||
|
||||
@@ -583,9 +573,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if not tool.Ifc.get_entity(props.representation_obj):
|
||||
bpy.data.objects.remove(props.representation_obj)
|
||||
@@ -605,9 +592,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EXTRUSION":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -654,9 +638,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
@@ -767,9 +748,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "EDGE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -782,9 +760,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
elif representation_template == "FACE":
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||
@@ -797,9 +772,6 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
bpy.context.view_layer.update() # Ensures obj.matrix_world is correct
|
||||
|
||||
@@ -101,7 +101,7 @@ class ColourByPropertyData:
|
||||
elif pset.endswith("BaseQuantities"):
|
||||
keys.extend([f'/.*BaseQuantities/."{name}"' for name in properties.keys() if name != "id"])
|
||||
else:
|
||||
keys.extend([f"{pset}.{name}" for name in properties.keys() if name != "id"])
|
||||
keys.extend([f'"{pset}"."{name}"' for name in properties.keys() if name != "id"])
|
||||
results = [(k, k, "") for k in keys]
|
||||
return default + results
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ classes = (
|
||||
operator.RemoveWorkTime,
|
||||
operator.ReorderTask,
|
||||
operator.SaveAnimationColorScheme,
|
||||
operator.SelectTaskElements,
|
||||
operator.SelectTaskRelatedInputs,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectUnassignedWorkScheduleProducts,
|
||||
|
||||
@@ -243,6 +243,9 @@ class SequenceData:
|
||||
data["NestingIndex"] = None
|
||||
for rel in task.Nests or []:
|
||||
data["NestingIndex"] = rel.RelatedObjects.index(task)
|
||||
data["TotalInputs"] = len(tool.Sequence.get_task_inputs(task))
|
||||
data["TotalOutputs"] = len(tool.Sequence.get_task_outputs(task))
|
||||
data["TotalElements"] = data["TotalInputs"] + data["TotalOutputs"]
|
||||
cls.data["tasks"][task.id()] = data
|
||||
|
||||
@classmethod
|
||||
@@ -433,6 +436,7 @@ class StatusData:
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"statuses_with_elements": cls.statuses_with_elements(),
|
||||
"active_element_status": cls.active_element_status(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -444,3 +448,9 @@ class StatusData:
|
||||
if tool.Sequence.get_elements_by_status(status):
|
||||
statuses_used.add(status)
|
||||
return statuses_used
|
||||
|
||||
@classmethod
|
||||
def active_element_status(cls) -> set[str]:
|
||||
if not (obj := bpy.context.active_object) or not (element := tool.Ifc.get_entity(obj)):
|
||||
return set()
|
||||
return tool.Sequence.get_element_status(element)
|
||||
|
||||
@@ -1281,6 +1281,17 @@ class DisableEditingSequence(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectTaskElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_elements"
|
||||
bl_label = "Select All Task Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
task: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_task_inputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
core.select_task_outputs(tool.Sequence, tool.Spatial, task=tool.Ifc.get().by_id(self.task))
|
||||
|
||||
|
||||
class SelectTaskRelatedProducts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_task_related_products"
|
||||
bl_label = "Select All Output Products"
|
||||
|
||||
@@ -75,6 +75,8 @@ def update_active_task_index(self, context):
|
||||
bonsai.bim.module.pset.data.refresh()
|
||||
if self.editing_task_type == "SEQUENCE":
|
||||
tool.Sequence.load_task_properties()
|
||||
if self.should_auto_select:
|
||||
tool.Sequence.select_active_task_elements(task)
|
||||
|
||||
|
||||
def update_active_task_outputs(self, context):
|
||||
@@ -485,6 +487,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
other_columns: EnumProperty(
|
||||
items=[
|
||||
("Controls.Calendar", "Calendar", ""),
|
||||
("Controls.TotalElements", "Total Elements", "The total number of assigned elements to this task"),
|
||||
("Controls.TotalInputs", "Total Inputs", "The total number of input elements to this task"),
|
||||
("Controls.TotalOutputs", "Total Outputs", "The total number of input elements to this task"),
|
||||
],
|
||||
name="Special Columns",
|
||||
)
|
||||
@@ -518,9 +523,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
active_task_input_index: IntProperty(name="Active Task Input Index")
|
||||
task_outputs: CollectionProperty(name="Task Outputs", type=TaskProduct)
|
||||
active_task_output_index: IntProperty(name="Active Task Output Index")
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_task_inputs)
|
||||
show_nested_outputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_outputs)
|
||||
show_nested_resources: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_resources)
|
||||
show_nested_inputs: BoolProperty(name="Show Nested Task Elements", default=False, update=update_active_task_inputs)
|
||||
product_input_tasks: CollectionProperty(name="Product Task Inputs", type=TaskProduct)
|
||||
product_output_tasks: CollectionProperty(name="Product Task Outputs", type=TaskProduct)
|
||||
active_product_output_task_index: IntProperty(name="Active Product Output Task Index")
|
||||
@@ -531,6 +536,9 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
filter_by_active_schedule: BoolProperty(
|
||||
name="Filter By Active Schedule", default=False, update=update_filter_by_active_schedule
|
||||
)
|
||||
should_auto_select: BoolProperty(
|
||||
name="Auto Select", description="Auto select elements when task is selected", default=False
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
work_schedule_predefined_types: str
|
||||
|
||||
@@ -72,6 +72,8 @@ class BIM_PT_status(Panel):
|
||||
for status in self.props.statuses:
|
||||
row = box.row(align=True)
|
||||
row.label(text=status.name)
|
||||
if status.name in StatusData.data["active_element_status"]:
|
||||
row.label(text="", icon="OBJECT_DATA")
|
||||
if status.name in StatusData.data["statuses_with_elements"]:
|
||||
row.label(text="", icon="ASSET_MANAGER")
|
||||
row.prop(status, "is_visible", text="", emboss=False, icon="HIDE_OFF" if status.is_visible else "HIDE_ON")
|
||||
@@ -288,13 +290,21 @@ class BIM_PT_work_schedules(Panel):
|
||||
row2 = self.layout.row(align=True)
|
||||
row2.alignment = "RIGHT"
|
||||
|
||||
row2.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row2.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row2.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row2.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
op = row_.operator("bim.select_task_elements", icon="RESTRICT_SELECT_OFF", text="Select Elements")
|
||||
op.task = task.ifc_definition_id
|
||||
row_.prop(self.props, "should_auto_select", icon="OBJECT_HIDDEN", text="")
|
||||
|
||||
col = row2.column()
|
||||
row_ = col.row(align=True)
|
||||
row_.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
row_.operator("bim.enable_editing_task_sequence", text="", icon="TRACKING")
|
||||
row_.operator("bim.enable_editing_task_time", text="", icon="TIME").task = ifc_definition_id
|
||||
row_.operator("bim.enable_editing_task_calendar", text="", icon="VIEW_ORTHO").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row2.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
row_.operator("bim.enable_editing_task_attributes", text="", icon="GREASEPENCIL").task = (
|
||||
ifc_definition_id
|
||||
)
|
||||
row.operator("bim.add_task", text="Add", icon="ADD").task = ifc_definition_id
|
||||
@@ -714,11 +724,10 @@ class BIM_PT_task_icom(Panel):
|
||||
input_id = self.props.task_inputs[self.props.active_task_input_index].ifc_definition_id
|
||||
op.related_object = input_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_inputs", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2.prop(self.props, "show_nested_inputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_inputs", text="Show Nested")
|
||||
row2 = col.row()
|
||||
row2.template_list("BIM_UL_task_inputs", "", self.props, "task_inputs", self.props, "active_task_input_index")
|
||||
|
||||
@@ -742,8 +751,7 @@ class BIM_PT_task_icom(Panel):
|
||||
op.related_object_type = "RESOURCE"
|
||||
op.resource = self.props.task_resources[self.props.active_task_resource_index].ifc_definition_id
|
||||
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_resources", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_resources", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
@@ -771,10 +779,10 @@ class BIM_PT_task_icom(Panel):
|
||||
output_id = self.props.task_outputs[self.props.active_task_output_index].ifc_definition_id
|
||||
op.relating_product = output_id
|
||||
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="Select")
|
||||
op = row2.operator("bim.select_task_related_products", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.task = task.ifc_definition_id
|
||||
row2 = col.row()
|
||||
row2.prop(self.props, "show_nested_outputs", text="Show Nested")
|
||||
row2.prop(self.props, "show_nested_outputs", icon="OUTLINER", text="")
|
||||
|
||||
row2 = col.row()
|
||||
row2.template_list(
|
||||
"BIM_UL_task_outputs", "", self.props, "task_outputs", self.props, "active_task_output_index"
|
||||
@@ -891,7 +899,7 @@ class BIM_UL_tasks(UIList):
|
||||
split2 = split1.split(factor=0.9 - min(0.5, 0.15 * len(self.props.columns)))
|
||||
split2.prop(item, "name", emboss=False, text="")
|
||||
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task)
|
||||
BIM_UL_tasks.draw_custom_columns(self.props, split2, item, task, item.ifc_definition_id)
|
||||
|
||||
if self.props.active_task_id and self.props.editing_task_type == "ATTRIBUTES":
|
||||
row.prop(
|
||||
@@ -958,6 +966,7 @@ class BIM_UL_tasks(UIList):
|
||||
row: bpy.types.UILayout,
|
||||
item: Optional[bpy.types.PropertyGroup] = None,
|
||||
task: Optional[dict[str, Any]] = None,
|
||||
ifc_definition_id: int = 0,
|
||||
*,
|
||||
header: bool = False,
|
||||
) -> None:
|
||||
@@ -997,6 +1006,21 @@ class BIM_UL_tasks(UIList):
|
||||
row.label(text=item.derived_calendar + "*")
|
||||
else:
|
||||
row.label(text=item.calendar or "-")
|
||||
elif column.name == "Controls.TotalInputs":
|
||||
if header:
|
||||
row.label(text="# Inputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalInputs"]))
|
||||
elif column.name == "Controls.TotalOutputs":
|
||||
if header:
|
||||
row.label(text="# Outputs")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalOutputs"]))
|
||||
elif column.name == "Controls.TotalElements":
|
||||
if header:
|
||||
row.label(text="# Elements")
|
||||
else:
|
||||
row.label(text=str(SequenceData.data["tasks"][ifc_definition_id]["TotalElements"]))
|
||||
else:
|
||||
ifc_class, name = column.name.split(".")
|
||||
if header:
|
||||
|
||||
@@ -148,14 +148,14 @@ class SpatialDecompositionData:
|
||||
elif tool.Ifc.get_schema() == "IFC4X3":
|
||||
results = {
|
||||
"IfcBridge": ["IfcBridge", "IfcBridgePart", "IfcSpace"],
|
||||
"IfcBridgePart": ["IfcSpace"],
|
||||
"IfcBridgePart": ["IfcBridgePart", "IfcSpace"],
|
||||
"IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
|
||||
"IfcExternalSpatialElement": ["IfcExternalSpatialElement"],
|
||||
"IfcFacility": ["IfcFacility", "IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcSpace"],
|
||||
"IfcFacilityPartCommon": ["IfcFacilityPartCommon", "IfcSpace"],
|
||||
"IfcMarineFacility": ["IfcMarineFacility", "IfcMarinePart", "IfcSpace"],
|
||||
"IfcMarinePart": ["IfcSpace"],
|
||||
"IfcMarinePart": ["IfcMarinePart", "IfcSpace"],
|
||||
"IfcProject": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
@@ -168,9 +168,9 @@ class SpatialDecompositionData:
|
||||
"IfcSpace",
|
||||
],
|
||||
"IfcRailway": ["IfcRailway", "IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcSpace"],
|
||||
"IfcRailwayPart": ["IfcRailwayPart", "IfcSpace"],
|
||||
"IfcRoad": ["IfcRoad", "IfcRoadPart", "IfcSpace"],
|
||||
"IfcRoadPart": ["IfcSpace"],
|
||||
"IfcRoadPart": ["IfcRoadPart", "IfcSpace"],
|
||||
"IfcSite": [
|
||||
"IfcBridge",
|
||||
"IfcBuilding",
|
||||
|
||||
@@ -70,7 +70,7 @@ class GridDecorator:
|
||||
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if not (obj := axis.obj) or obj.hide_get() == True:
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get() and context.mode != "OBJECT":
|
||||
continue
|
||||
@@ -125,21 +125,22 @@ class GridDecorator:
|
||||
unselected_edges = []
|
||||
grid_props = tool.Spatial.get_grid_props()
|
||||
for axis in grid_props.grid_axes:
|
||||
if (obj := axis.obj) and obj.hide_get() == False:
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
if not (obj := axis.obj) or obj.visible_get() is False:
|
||||
continue
|
||||
if obj.select_get():
|
||||
if context.mode != "OBJECT":
|
||||
continue
|
||||
edges = selected_edges
|
||||
verts = selected_verts
|
||||
else:
|
||||
edges = unselected_edges
|
||||
verts = unselected_verts
|
||||
i = len(verts)
|
||||
edges.append([i, i + 1])
|
||||
matrix_world = obj.matrix_world
|
||||
v1 = matrix_world @ obj.data.vertices[0].co
|
||||
v2 = matrix_world @ obj.data.vertices[1].co
|
||||
verts.extend([v1, v2])
|
||||
|
||||
if unselected_verts:
|
||||
self.draw_batch("LINES", unselected_verts, decorator_color_background, unselected_edges)
|
||||
|
||||
@@ -550,9 +550,7 @@ class ToggleGrids(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
for element in tool.Ifc.get().by_type("IfcGrid") + tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_grid_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -564,11 +562,5 @@ class ToggleSpatialElements(bpy.types.Operator):
|
||||
is_visible: bpy.props.BoolProperty(name="Is Visible", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
if tool.Ifc.get().schema == "IFC2X3":
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialStructureElement")
|
||||
else:
|
||||
elements = tool.Ifc.get().by_type("IfcSpatialElement")
|
||||
for element in elements:
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(not self.is_visible)
|
||||
tool.Spatial.set_space_visibility(self.is_visible)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -224,7 +224,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
||||
is_visible: BoolProperty(
|
||||
name="Is Visible",
|
||||
description="Show or hide spatial elements, such as buildings, sites, etc",
|
||||
default=True,
|
||||
default=False,
|
||||
update=update_spatial_is_visible,
|
||||
)
|
||||
container_filter: StringProperty(name="Container Filter", default="", options={"TEXTEDIT_UPDATE"})
|
||||
|
||||
@@ -57,9 +57,9 @@ class SystemData:
|
||||
|
||||
# We're only interested in systems for services. Not sure why IFC groups these together.
|
||||
return [
|
||||
(c, c, get_entity_doc(version, c).get("description", ""))
|
||||
for c in sorted([d.name() for d in declarations])
|
||||
if c not in ("IfcStructuralAnalysisModel")
|
||||
(c, c, get_entity_doc(version, c).get("description", ""), tool.System.SYSTEM_ICONS[c], i)
|
||||
for i, c in enumerate(sorted([d.name() for d in declarations]))
|
||||
if c != "IfcStructuralAnalysisModel"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -24,6 +24,7 @@ import bonsai.tool as tool
|
||||
import bonsai.core.system as core
|
||||
import bonsai.bim.helper
|
||||
from bonsai.bim.module.system.data import PortData, SystemData
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class LoadSystems(bpy.types.Operator):
|
||||
@@ -51,9 +52,22 @@ class AddSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
parent_system_id: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration]
|
||||
|
||||
if TYPE_CHECKING:
|
||||
parent_system_id: int
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties) -> str:
|
||||
if properties.parent_system_id:
|
||||
return "Add new subsystem to the active system."
|
||||
return "Add new IfcSystem."
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.System.get_system_props()
|
||||
core.add_system(tool.Ifc, tool.System, ifc_class=props.system_class)
|
||||
ifc_file = tool.Ifc.get()
|
||||
parent_system = None if self.parent_system_id == 0 else ifc_file.by_id(self.parent_system_id)
|
||||
core.add_system(tool.Ifc, tool.Group, tool.System, ifc_class=props.system_class, parent_system=parent_system)
|
||||
|
||||
|
||||
class EditSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -73,7 +87,7 @@ class RemoveSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_system(tool.Ifc, tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
core.remove_system(tool.Ifc, tool.Group, tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
|
||||
|
||||
class EnableEditingSystem(bpy.types.Operator):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user