Compare commits
423 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81fdfbacd0 | |||
| 7c2c946b2f | |||
| 5b34e08c37 | |||
| 0b16c07e3a | |||
| 0210131a56 | |||
| a44d7e997a | |||
| 1df8908689 | |||
| f9daba9fba | |||
| fd6f2f3fc4 | |||
| a4d0378405 | |||
| 1629205c3f | |||
| d058f17c15 | |||
| bbb3b62b8a | |||
| f9ef00fd45 | |||
| 709c530434 | |||
| 0ee93294dd | |||
| 73124dd597 | |||
| 759e134e3b | |||
| 1f11354188 | |||
| eaffb9d8c0 | |||
| f297b1709d | |||
| 1f637660d1 | |||
| c349523ea9 | |||
| 5cc11a170b | |||
| 21094c59d9 | |||
| 8ca5dd747e | |||
| ffe9ac6614 | |||
| d2083b9a3d | |||
| 70d1ad59c2 | |||
| 53687e6b7c | |||
| 67993071a6 | |||
| 29c98a5996 | |||
| 5b5a4649c5 | |||
| d654110fc1 | |||
| 735f308abd | |||
| f86f988d6e | |||
| a1a684cd2b | |||
| 6180f5be33 | |||
| cd58b994dd | |||
| 40854eb3d3 | |||
| 79453a6ae0 | |||
| eec4c23017 | |||
| beeba10b22 | |||
| 7a6005d0ea | |||
| 58150a296d | |||
| 7de454337d | |||
| 3e25ef9222 | |||
| 9278e43d72 | |||
| fafdc76494 | |||
| 99c47069cd | |||
| cee48d7424 | |||
| dc7062e182 | |||
| 5f6c584a58 | |||
| 5f7630e527 | |||
| 733e80737a | |||
| 64752e52c9 | |||
| 6c260ef5c9 | |||
| e0e0572ff5 | |||
| a3942ee5e2 | |||
| 40d5a94e2d | |||
| f9fe1607ff | |||
| 2a019e8090 | |||
| 901a582c6c | |||
| 29a4f78689 | |||
| f1e0375b6a | |||
| 52e354d1b8 | |||
| eef961a39e | |||
| fcab5d445f | |||
| b534895ae3 | |||
| 5b76e83965 | |||
| a21b73225a | |||
| a11a4032f6 | |||
| a09f864651 | |||
| 609fd35410 | |||
| df0c136b6c | |||
| ce6d4ea59a | |||
| 29594b50b5 | |||
| 2a2da56a0e | |||
| dd041478b3 | |||
| 650e5b0cd3 | |||
| afa0db3c7e | |||
| b9f1f3438c | |||
| 31f21dcb29 | |||
| 64a22fe5c6 | |||
| 3f85140d60 | |||
| 1b1588359f | |||
| 4c562c1af0 | |||
| a0fdb9f777 | |||
| 2c29f89fe9 | |||
| f4aac566d5 | |||
| 57521bd106 | |||
| 1413cf4f9d | |||
| 3fa74469f4 | |||
| e3d9dd747d | |||
| d71ebb8d25 | |||
| 0097d98669 | |||
| dd635340aa | |||
| 1b06cb8014 | |||
| 163e10b608 | |||
| dc67287d4b | |||
| 73f959c45a | |||
| 65e66352b7 | |||
| 205ff0914c | |||
| 2e76a800f5 | |||
| 1890bf4cb9 | |||
| 3fd472549e | |||
| 28d15a6c9f | |||
| 6026e037fa | |||
| 0ca90817d8 | |||
| 3436f3e0bb | |||
| 71f0170966 | |||
| 97cd07226d | |||
| 309ce20068 | |||
| 282c140c14 | |||
| bdf46f9431 | |||
| e79068854a | |||
| 7be93b5386 | |||
| 1b4da58a15 | |||
| 356ce48af5 | |||
| 3f24d54e8f | |||
| 1586c85314 | |||
| f9f9746a9a | |||
| 493faf22ff | |||
| bab6313313 | |||
| 618bed3889 | |||
| 8c9115c0ff | |||
| 0a561d1a30 | |||
| 443af3f7e7 | |||
| 2f6ac0be9f | |||
| 07c152f513 | |||
| 158c62bdca | |||
| 1029271e91 | |||
| 9b59d24fc7 | |||
| 1d0e8c59d2 | |||
| b479d82251 | |||
| 6b6ac38f96 | |||
| 57fe895ae0 | |||
| 4195d41e4c | |||
| 7eac5397cd | |||
| 084823164e | |||
| 87f110bae5 | |||
| 952719f19b | |||
| 2ab4e44248 | |||
| 6b9e670bf3 | |||
| 9e87f0f027 | |||
| c7272065e6 | |||
| 14bca5d199 | |||
| dbeb7d392c | |||
| 22cb7b3b67 | |||
| 018be92063 | |||
| 37f91461d2 | |||
| c73fedaaaf | |||
| 4ad4bdef3b | |||
| e50259f8fa | |||
| 7321138d57 | |||
| 319f63c861 | |||
| 662cd6232c | |||
| 3461a1e96f | |||
| 919c007509 | |||
| 5b77001568 | |||
| 1f4022a41b | |||
| c8c9262090 | |||
| 104cabfb67 | |||
| 4978a67e19 | |||
| e44e03f02e | |||
| 5a636a925b | |||
| d23f14f8aa | |||
| 1706ca5293 | |||
| ff3e2f14e1 | |||
| c4433489b7 | |||
| 4761c58571 | |||
| 1de6a7e792 | |||
| 7d6ba89496 | |||
| c4dc3934fa | |||
| 25f1d21a85 | |||
| 2bd6d9343c | |||
| bc2d76e685 | |||
| 124f786007 | |||
| b49cfe9744 | |||
| 6f5f189443 | |||
| d6f8448c83 | |||
| 0f85890c72 | |||
| bf3eb68b67 | |||
| ce3a6bba0f | |||
| f1e3d9c573 | |||
| 0a30d1a438 | |||
| 8311983d5e | |||
| 97bf4e3d0d | |||
| 15661536dc | |||
| f451c51451 | |||
| b5e27e4ced | |||
| 95881c6258 | |||
| cb8cfd7905 | |||
| 96fe1018ed | |||
| 73c006a977 | |||
| 133fa5547b | |||
| c95d263ca5 | |||
| 3507920c09 | |||
| cdabc507e1 | |||
| bcbee0659b | |||
| 053e4e1185 | |||
| d76679a45f | |||
| a1ed78e755 | |||
| d0f38bc919 | |||
| 4681d3198f | |||
| 96e4641ebf | |||
| 2ab808a216 | |||
| 1ec0e1ce19 | |||
| bc734c263e | |||
| a96bffa341 | |||
| fb3e9ad1b7 | |||
| 4c854fbb62 | |||
| 0e8d7bdc97 | |||
| a92d37cd64 | |||
| 00e46ce39e | |||
| 54b82fd149 | |||
| e508fb44f2 | |||
| 89e2ad9262 | |||
| 419901649e | |||
| 18cb767a95 | |||
| a11b51c971 | |||
| d7f913d464 | |||
| 0d982090e3 | |||
| be6617d9f9 | |||
| fc0efa2339 | |||
| c6b86e45f3 | |||
| 53b6247bb8 | |||
| eab06bda6c | |||
| 524fa58f05 | |||
| 4c4846bc8f | |||
| 0dfe1d995f | |||
| a4434413d2 | |||
| 5ca19ce2f7 | |||
| 3e1c2c4daf | |||
| 2d4e316df2 | |||
| 4b426e1fc0 | |||
| d2ad4b2b68 | |||
| 67522e03d1 | |||
| 24f307130c | |||
| 0a3e5f65fa | |||
| e302fea33b | |||
| c352181f8c | |||
| 3a1416536a | |||
| 4eec863cad | |||
| 96bcd31d99 | |||
| b09ff828de | |||
| 3cb68ca388 | |||
| c95d3693ea | |||
| 67d2ab2577 | |||
| 970b24b63b | |||
| fdaae048c2 | |||
| 4da5a38237 | |||
| 0cf2d7e05e | |||
| 3122cf21db | |||
| 4bf25d5d9c | |||
| dc10c7a360 | |||
| 94e082fc7d | |||
| 9671df8491 | |||
| 8aa38917eb | |||
| 0774100606 | |||
| 95b48b02e4 | |||
| c5de1d8727 | |||
| 7e09ab0ced | |||
| eb3e137a6e | |||
| f528ad9744 | |||
| 650f9eb7f5 | |||
| a78d6f42e5 | |||
| 4326a74c64 | |||
| 08fc62d4d9 | |||
| ae298de8dc | |||
| a24c4b269a | |||
| 083d9d3e3e | |||
| 12e7ed356c | |||
| 3f15dd71b1 | |||
| 42448cae4e | |||
| 7e28d193ec | |||
| 8f2b32532c | |||
| f0a00cbc94 | |||
| fd73b2cf0a | |||
| e5b3455533 | |||
| e677355438 | |||
| 93fd3f97e3 | |||
| 910d189f35 | |||
| c8d6d12442 | |||
| 1f3ba3d09d | |||
| f67941221e | |||
| 8252ef82ad | |||
| 3b825e35ae | |||
| 4a4912e817 | |||
| 5a38604ec2 | |||
| b15e0d0e2b | |||
| 5a5f52a5c2 | |||
| 816a690519 | |||
| 60303c6560 | |||
| cc88e6f112 | |||
| 5f1b5d0c63 | |||
| b54caa8967 | |||
| 73e048a9a8 | |||
| 316a2b7f53 | |||
| 42edcda8bd | |||
| 92432f453d | |||
| 4b6ebe7b1c | |||
| 3f6d25ed43 | |||
| d4b2cc7f2c | |||
| 53c050c6ed | |||
| 3213af2f48 | |||
| 85122ba184 | |||
| b8791d43aa | |||
| 6a82f27d48 | |||
| 4e2400952a | |||
| de1dae8b6e | |||
| dd127db969 | |||
| 194bc53bdb | |||
| 8c3e29c351 | |||
| 6e55a04550 | |||
| 9333839267 | |||
| 280236290e | |||
| c5773b5665 | |||
| 96babc4506 | |||
| f8acde0801 | |||
| 32c101cf74 | |||
| 2a1aa535ba | |||
| 02903c4929 | |||
| d80e26e4ab | |||
| a88b83306e | |||
| 9577c40386 | |||
| f54a5cf6e0 | |||
| abcb492e5a | |||
| 4d447c169f | |||
| b465605b63 | |||
| fb6dc45b1b | |||
| b2aca0ae36 | |||
| 03831d440c | |||
| ac4679a376 | |||
| 21fee721be | |||
| 6bcf00ef31 | |||
| 952d1c3b79 | |||
| 2d7a90bd2b | |||
| b14de15a68 | |||
| 29ecba293b | |||
| 8396c68e75 | |||
| 01c6a0d9be | |||
| 99d8a225a9 | |||
| 89f13aa1e4 | |||
| f089d03dc6 | |||
| a4366ef151 | |||
| 9483abcdd5 | |||
| 200a343746 | |||
| 2313fa00cc | |||
| 99c5f895fc | |||
| fcd5908ac9 | |||
| dfb980e0d1 | |||
| 445e77bac0 | |||
| 265ebaebb8 | |||
| 07fa7003fb | |||
| 3a90d664fa | |||
| 3f28ae18c0 | |||
| 9ea6499b5a | |||
| 11c3a6e547 | |||
| e7f3856d64 | |||
| 7e7d576855 | |||
| cfa1428a75 | |||
| d4ee74455c | |||
| 07d2cc62c1 | |||
| 8aff4badf7 | |||
| aa3325c010 | |||
| 9b13cd8d5f | |||
| 0fe32c0c75 | |||
| efc43c0fe9 | |||
| 5bfaa32c7c | |||
| 38e4ccdb0a | |||
| 7b71a7a866 | |||
| b5ee20cb7a | |||
| cc8da4abf4 | |||
| f2c4b20187 | |||
| 7c460bcf29 | |||
| 31ddb0f2c5 | |||
| c2cad06cfa | |||
| 421b58850c | |||
| 7c89aa1098 | |||
| be7e85fc2d | |||
| d91442de59 | |||
| 9cd3d73a23 | |||
| 0cf3c97eb2 | |||
| d49095844a | |||
| be65f77311 | |||
| 2081dc4c08 | |||
| bd16345b76 | |||
| 3264f338c7 | |||
| 0c06c67f2d | |||
| a66ba6a6f9 | |||
| 80d8665992 | |||
| 337ce89919 | |||
| 1237a8ce97 | |||
| c3c9ed2975 | |||
| 87cb1c69f4 | |||
| 3aa7f8e4c8 | |||
| 2c301d734b | |||
| 1705e6f565 | |||
| 2cd7b08c47 | |||
| 647a5105b5 | |||
| 9fc0baf175 | |||
| ca502cc293 | |||
| f70ab060e3 | |||
| 6fb3c28f1a | |||
| c79eb60959 | |||
| 538419a23f | |||
| eb19165ebc | |||
| 1138ffee7e | |||
| 0f3416774c | |||
| 1ba7977587 | |||
| 5b1b8e8f50 | |||
| 78811d331b | |||
| 4aa91cfa7f | |||
| c099a6cb75 | |||
| 029b402d11 | |||
| a243cdd6c6 | |||
| 1fae835de9 | |||
| f0c7ff1e57 | |||
| 460fd5ca87 | |||
| 054c0a9a27 | |||
| 5af1baa48b | |||
| 89023df4e5 |
@@ -3,8 +3,18 @@ name: Publish-blenderbim-multiplatform
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/blenderbim/**'
|
||||
- '.github/workflows/ci-blenderbim-matrix.yml'
|
||||
- 'src/blenderbim/**'
|
||||
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||
- 'src/bcf/src/bcf/**'
|
||||
- 'src/ifcclash/ifcclash/**'
|
||||
- 'src/ifccobie/**'
|
||||
- 'src/ifcdiff/**'
|
||||
- 'src/ifccsv/**'
|
||||
- 'src/ifcpatch/ifcpatch/**'
|
||||
- 'src/ifc4d/ifc4d/**'
|
||||
- 'src/ifc5d/ifc5d/**'
|
||||
- 'src/ifccityjson/**'
|
||||
branches:
|
||||
- v0.7.0
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
name: ci_py_only
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'test/**'
|
||||
- 'conda/**'
|
||||
- 'cmake/**'
|
||||
- '.github/workflows/ci_py_only.yml'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
!contains(github.event.head_commit.message, 'skip ci')
|
||||
steps:
|
||||
- run: echo ok go
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ \
|
||||
libboost-date-time-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-iostreams-dev \
|
||||
libboost-program-options-dev \
|
||||
libboost-regex-dev \
|
||||
libboost-system-dev \
|
||||
libboost-thread-dev \
|
||||
python3-all-dev python3-pip \
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
|
||||
- name: Build ifcopenshell
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||
-DBUILD_CONVERT=Off \
|
||||
-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 \
|
||||
../cmake
|
||||
sudo make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
sudo /usr/bin/python -m pip install -U pip
|
||||
sudo /usr/bin/python -m pip install xmlschema numpy lxml
|
||||
sudo /usr/bin/python -m pip install src/bcf
|
||||
sudo /usr/bin/python -m pip install pytest
|
||||
sudo /usr/bin/python -m pip install isodate
|
||||
sudo /usr/bin/python -m pip install lark
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cd test
|
||||
sudo /usr/bin/python tests.py
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
make test
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
"-DSCHEMA_VERSIONS=2x3;4" \
|
||||
"-DSCHEMA_VERSIONS=2x3;4;4x3" \
|
||||
-DGLTF_SUPPORT=On \
|
||||
-DJSON_INCLUDE_DIR=/usr/include \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
|
||||
@@ -19,13 +19,23 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
-
|
||||
name: Update ubuntu
|
||||
- name: Install C++ dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
|
||||
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ \
|
||||
libboost-date-time-dev \
|
||||
libboost-filesystem-dev \
|
||||
libboost-iostreams-dev \
|
||||
libboost-program-options-dev \
|
||||
libboost-regex-dev \
|
||||
libboost-system-dev \
|
||||
libboost-thread-dev \
|
||||
python3-all-dev python3-pip \
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev opencollada-dev
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
@@ -44,10 +54,18 @@ jobs:
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.8.so \
|
||||
-DCOLLADA_SUPPORT=Off \
|
||||
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
|
||||
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
|
||||
-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 \
|
||||
-DOPENCOLLADA_INCLUDE_DIR=/usr/include/opencollada \
|
||||
-DOPENCOLLADA_LIBRARY_DIR=/usr/lib/opencollada/ \
|
||||
../cmake
|
||||
make -j $(nproc)
|
||||
make install
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
/_installed-vs*-x*/
|
||||
/build/
|
||||
|
||||
# output directories
|
||||
/cmake/out/
|
||||
/docs/out/
|
||||
/src/examples/out/
|
||||
/src/ifcmax/out/
|
||||
/src/ifcwrap/out/
|
||||
/src/qtviewer/out/
|
||||
|
||||
|
||||
/win/BuildDepsCache*.txt
|
||||
# IfcExpressParser residue
|
||||
/src/ifcexpressparser/express_parser.py
|
||||
@@ -13,6 +22,7 @@ __pycache__
|
||||
*.py.bak
|
||||
# Visual Studio Code files
|
||||
.vscode
|
||||
.vs
|
||||
# PyCharm files
|
||||
.idea
|
||||
|
||||
@@ -57,3 +67,9 @@ htmlcov
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
|
||||
# Sphinx
|
||||
_build/
|
||||
|
||||
# IDS Docs
|
||||
src/ifcopenshell-python/test/build
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
[submodule "src/svgfill"]
|
||||
path = src/svgfill
|
||||
url = https://github.com/IfcOpenShell/svgfill
|
||||
[submodule "src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/ifcopenshell/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
[submodule "src/ifcopenshell-python/test/Sample-BIM-Files"]
|
||||
path = src/ifcopenshell-python/test/Sample-BIM-Files
|
||||
url = https://github.com/atomczak/Sample-BIM-Files
|
||||
url = https://github.com/IfcOpenShell/ids-test-files
|
||||
@@ -9,12 +9,14 @@ For more information, see
|
||||
* [http://ifcopenshell.org](http://ifcopenshell.org)
|
||||
* [http://academy.ifcopenshell.org](http://academy.ifcopenshell.org)
|
||||
|
||||
[](https://travis-ci.org/IfcOpenShell/IfcOpenShell)
|
||||
[](https://travis-ci.com/IfcOpenShell/IfcOpenShell)
|
||||
|
||||
[](https://opencollective.com/opensourcebim/)
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
* Git
|
||||
* CMake (2.6 or newer)
|
||||
* CMake (3.1.3 or newer)
|
||||
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* *nix: GCC 4.7 or newer, or Clang (any version)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Do not remove this test for UTF-8: if “Ω” doesn’t appear as greek uppercase omega letter enclosed in quotation marks, you should use an editor that supports UTF-8, not this one. -->
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>blenderbim-nightly</id>
|
||||
<version>blenderbim_build_version</version>
|
||||
<packageSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</packageSourceUrl>
|
||||
<owners>fbpyr</owners>
|
||||
<!-- == SOFTWARE SPECIFIC SECTION == -->
|
||||
<title>BlenderBIM install nightly</title>
|
||||
<authors>Dion Moult and many more, see: https://github.com/IfcOpenShell/IfcOpenShell/graphs/contributors</authors>
|
||||
<projectUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectUrl>
|
||||
<iconUrl>https://rawcdn.githack.com/fbpyr/IfcOpenShell/9662598ad4a7e4b6cd8980e430d07263835d0eda/choco/blenderbim/blenderbim.png</iconUrl>
|
||||
<!-- <copyright>Year Software Vendor</copyright> -->
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||
<docsUrl>https://blenderbim.org/docs/</docsUrl>
|
||||
<!--<mailingListUrl></mailingListUrl>-->
|
||||
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
|
||||
<tags>blender bim blenderbim python opensource foss</tags>
|
||||
<summary>opensource bim</summary>
|
||||
<description>opensource bim addon for blender</description>
|
||||
<!-- <releaseNotes>__REPLACE_OR_REMOVE__MarkDown_Okay</releaseNotes> -->
|
||||
|
||||
<dependencies>
|
||||
<dependency id="blender" version="latest_blender_version_maj_min_pat" />
|
||||
<dependency id="7zip" version="22.1"/>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="tools\**" target="tools" />
|
||||
</files>
|
||||
</package>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,78 @@
|
||||
import sys
|
||||
import re
|
||||
import datetime
|
||||
from urllib import request
|
||||
|
||||
|
||||
def request_repo_info(url: str):
|
||||
req = request.Request(url)
|
||||
resp = request.urlopen(req)
|
||||
if not resp.status == 200:
|
||||
print(f"[ERROR] could not contact server: {url}")
|
||||
quit(1)
|
||||
return resp
|
||||
|
||||
|
||||
if sys.argv[1] == "--do_choco_release?":
|
||||
now = datetime.datetime.now()
|
||||
blenderbim_date = now.strftime("%y%m%d")
|
||||
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases/latest"
|
||||
resp = request_repo_info(url)
|
||||
if blenderbim_date in resp.url:
|
||||
print("do_choco_release", end="")
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_release_min_maj_pat?":
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_release_min_maj?":
|
||||
re_blender_version_min_maj = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_python_version_maj_min?":
|
||||
# get latest blender version first
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
latest_blender_version_tag = f"v{found[0]}"
|
||||
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
|
||||
|
||||
elif sys.argv[1] == "--pyver?":
|
||||
# get latest blender version first
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
latest_blender_version_tag = f"v{found[0]}"
|
||||
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||
if found:
|
||||
print(f"py{found[0].replace('.', '')}", end="")
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
print("[INFO] inserting dynamic chocolatey package parameters")
|
||||
HERE_DIR = Path(__file__).parent.absolute()
|
||||
# print(f"[INFO] HERE_DIR: {HERE_DIR}")
|
||||
|
||||
topics = {
|
||||
"spec": {
|
||||
"path": HERE_DIR / "blenderbim.nuspec",
|
||||
"env_vars": {
|
||||
"latest_blender_version_maj_min_pat",
|
||||
"blenderbim_build_version",
|
||||
},
|
||||
},
|
||||
"install": {
|
||||
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
|
||||
"env_vars": {
|
||||
"url_blenderbim_py310_win_zip",
|
||||
"sha256sum_blenderbim_py310_win_zip",
|
||||
"latest_blender_version_maj_min",
|
||||
},
|
||||
},
|
||||
"uninstall": {
|
||||
"path": HERE_DIR / "tools" / "chocolateyuninstall.ps1",
|
||||
"env_vars": {
|
||||
"latest_blender_version_maj_min",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for topic, info in topics.items():
|
||||
print(f"[INFO] {topic}:")
|
||||
for env_var_name in info["env_vars"]:
|
||||
print(f"[INFO] {env_var_name} exists?: {os.environ[env_var_name]}")
|
||||
|
||||
with open(info["path"], encoding="utf-8") as txt:
|
||||
content = txt.read()
|
||||
|
||||
for env_var_name in info["env_vars"]:
|
||||
# print(f"[INFO] replace: {env_var_name}")
|
||||
content = content.replace(env_var_name, os.environ[env_var_name])
|
||||
|
||||
with open(info["path"], "w", encoding="utf-8") as txt:
|
||||
txt.write(content)
|
||||
print(f"[INFO] written: {info['path']}")
|
||||
|
||||
print("[INFO] inserting dynamic chocolatey package parameters successful")
|
||||
@@ -0,0 +1,11 @@
|
||||
$processName = "blender"
|
||||
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||
|
||||
if($blenderIsRunning -eq $null) {
|
||||
echo "Blender is not running ready for next action."
|
||||
}
|
||||
else {
|
||||
Write-Warning "$processName is still running - installer cannot safely proceed to next action."
|
||||
Write-Warning "Please retry after closing all running blender applications."
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$url64 = 'url_blenderbim_py310_win_zip'
|
||||
$checksum64 = 'sha256sum_blenderbim_py310_win_zip'
|
||||
$checksumType64 = 'sha256'
|
||||
|
||||
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||
$unzipTargetDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons"
|
||||
|
||||
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||
$addonEnable = "$chocoBaseDir\lib\blenderbim\tools\enable_blenderbim_addon.py"
|
||||
|
||||
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||
|
||||
$processName = "blender"
|
||||
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||
|
||||
|
||||
if($blenderIsRunning -eq $null) {
|
||||
echo "attempting to install blenderbim."
|
||||
Install-ChocolateyZipPackage -PackageName $env:ChocolateyPackageName -Url64 $url64 -Checksum64 $checksum64 -checksumType $checksumType64 -UnzipLocation $unzipTargetDir
|
||||
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonEnable"
|
||||
}
|
||||
else {
|
||||
Write-Warning "$processName is still running - blenderbim cannot be safely installed."
|
||||
Write-Warning "Please retry after closing all running blender applications."
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
$appDataUserDir = [System.Environment]::GetEnvironmentVariable('appdata')
|
||||
$unzippedDir = "$appDataUserDir\Blender Foundation\Blender\latest_blender_version_maj_min\scripts\addons\blenderbim"
|
||||
|
||||
$chocoBaseDir = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall')
|
||||
$addonDisable = "$chocoBaseDir\lib\blenderbim\tools\disable_blenderbim_addon.py"
|
||||
|
||||
$programFilesDir = [System.Environment]::GetEnvironmentVariable('ProgramFiles')
|
||||
$blenderExePath = "$env:ProgramFiles\Blender Foundation\Blender latest_blender_version_maj_min\blender.exe"
|
||||
|
||||
$processName = "blender"
|
||||
$blenderIsRunning = Get-Process -Name $processName -ErrorAction SilentlyContinue
|
||||
|
||||
if($blenderIsRunning -eq $null) {
|
||||
echo "attempting to uninstall blenderbim."
|
||||
Start-Process -FilePath $blenderExePath -ArgumentList "-b", "-y", "--python", "$addonDisable"
|
||||
Remove-Item -LiteralPath $unzippedDir -Force -Recurse
|
||||
}
|
||||
else {
|
||||
Write-Warning "$processName is still running - blenderbim cannot be safely uninstalled."
|
||||
Write-Warning "Please retry after closing all running blender applications."
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import bpy
|
||||
|
||||
|
||||
bpy.ops.preferences.addon_disable(module='blenderbim')
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
print("disabled blenderbim addon successfully.")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import bpy
|
||||
|
||||
|
||||
bpy.ops.preferences.addon_enable(module='blenderbim')
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
print("enabled blenderbim addon successfully.")
|
||||
|
||||
@@ -38,7 +38,7 @@ MESSAGE(STATUS "Autodesk 3ds Max SDK found at ${max_sdk}")
|
||||
set(HAS_MAX TRUE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
OPTION(MINIMAL_BUILD "The build is to make a minimal version of IFC converter from OCCT into IFC." OFF)
|
||||
OPTION(COLLADA_SUPPORT "Build IfcConvert with COLLADA support (requires OpenCOLLADA)." ON)
|
||||
OPTION(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
OPTION(ENABLE_BUILD_OPTIMIZATIONS "Enable certain compiler and linker optimizations on RelWithDebInfo and Release builds." OFF)
|
||||
@@ -53,6 +53,7 @@ OPTION(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
|
||||
OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only." OFF)
|
||||
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
|
||||
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
|
||||
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" ON)
|
||||
if (${HAS_MAX})
|
||||
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
endif()
|
||||
@@ -66,8 +67,12 @@ IF(NOT CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
|
||||
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
|
||||
if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
|
||||
# QtViewer requires Qt5
|
||||
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
|
||||
include(GNUInstallDirs)
|
||||
if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND (NOT BUILD_IFCGEOM))
|
||||
message(STATUS "'IfcGeom' is required with current outputs")
|
||||
@@ -121,6 +126,7 @@ ENDMACRO()
|
||||
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OCC_LIBRARY_DIR)
|
||||
if (NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(OPENCOLLADA_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(LIBXML2_INCLUDE_DIR)
|
||||
@@ -130,17 +136,20 @@ UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
endif()
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
endif()
|
||||
|
||||
if (GLTF_SUPPORT AND BUILD_CONVERT)
|
||||
if (NOT MINIMAL_BUILD AND GLTF_SUPPORT AND BUILD_CONVERT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
FIND_FILE(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
|
||||
IF(json_hpp)
|
||||
@@ -201,10 +210,12 @@ if(USE_MMAP)
|
||||
add_definitions(-DUSE_MMAP)
|
||||
endif()
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
find_package(LibXml2 REQUIRED)
|
||||
endif()
|
||||
|
||||
if (IFCXML_SUPPORT)
|
||||
if (NOT MINIMAL_BUILD AND IFCXML_SUPPORT)
|
||||
add_definitions(-DWITH_IFCXML)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
|
||||
endif()
|
||||
@@ -346,7 +357,7 @@ endif()
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
IF(COLLADA_SUPPORT)
|
||||
IF(NOT MINIMAL_BUILD AND COLLADA_SUPPORT)
|
||||
# Find OpenCOLLADA
|
||||
IF("${OPENCOLLADA_INCLUDE_DIR}" STREQUAL "")
|
||||
MESSAGE(STATUS "No OpenCOLLADA include directory specified")
|
||||
@@ -426,7 +437,7 @@ IF(COLLADA_SUPPORT)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
if(HDF5_SUPPORT)
|
||||
if(NOT MINIMAL_BUILD AND HDF5_SUPPORT)
|
||||
IF("${HDF5_INCLUDE_DIR}" STREQUAL "")
|
||||
MESSAGE(STATUS "No HDF5 include directory specified")
|
||||
ElSE()
|
||||
@@ -579,13 +590,16 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
endfunction()
|
||||
|
||||
if(NOT SCHEMA_VERSIONS)
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4")
|
||||
set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2" "4x3_rc1" "4x3_rc2" "4x3_rc3" "4x3_rc4" "4x3")
|
||||
endif()
|
||||
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
add_definitions(-DHAS_SCHEMA_${s})
|
||||
endforeach()
|
||||
|
||||
string(REPLACE ";" ")(" schema_version_seq "(${SCHEMA_VERSIONS})")
|
||||
ADD_DEFINITIONS(-DSCHEMA_SEQ=${schema_version_seq})
|
||||
|
||||
if(COMPILE_SCHEMA)
|
||||
# @todo, this appears to be untested at the moment
|
||||
|
||||
@@ -655,7 +669,7 @@ if (BUILD_IFCGEOM)
|
||||
endforeach()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
if (BUILD_CONVERT)
|
||||
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
foreach(s ${SCHEMA_VERSIONS})
|
||||
set(SERIALIZER_SCHEMA_LIBRARIES ${SERIALIZER_SCHEMA_LIBRARIES} Serializers_ifc${s})
|
||||
endforeach()
|
||||
@@ -728,7 +742,7 @@ TARGET_LINK_LIBRARIES(IfcGeom IfcParse ${IFCGEOM_SCHEMA_LIBRARIES} ${CMAKE_THREA
|
||||
|
||||
endif(BUILD_IFCGEOM)
|
||||
|
||||
if (BUILD_CONVERT)
|
||||
if (BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
# Serializers
|
||||
file(GLOB SERIALIZERS_H_FILES ../src/serializers/*.h)
|
||||
@@ -749,6 +763,10 @@ set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS $
|
||||
|
||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
|
||||
|
||||
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
|
||||
|
||||
if (BUILD_CONVERT)
|
||||
|
||||
# IfcConvert
|
||||
file(GLOB IFCCONVERT_CPP_FILES ../src/ifcconvert/*.cpp)
|
||||
file(GLOB IFCCONVERT_H_FILES ../src/ifcconvert/*.h)
|
||||
@@ -774,7 +792,7 @@ INSTALL(TARGETS IfcConvert
|
||||
endif(BUILD_CONVERT)
|
||||
|
||||
# IfcGeomServer
|
||||
if(BUILD_GEOMSERVER)
|
||||
if(NOT MINIMAL_BUILD AND BUILD_GEOMSERVER)
|
||||
|
||||
file(GLOB CPP_FILES ../src/ifcgeomserver/*.cpp)
|
||||
file(GLOB H_FILES ../src/ifcgeomserver/*.h)
|
||||
@@ -828,7 +846,7 @@ IF(BUILD_DOCUMENTATION)
|
||||
ADD_SUBDIRECTORY(../docs docs)
|
||||
ENDIF()
|
||||
|
||||
IF(BUILD_IFCPYTHON)
|
||||
IF(NOT MINIMAL_BUILD AND BUILD_IFCPYTHON)
|
||||
ADD_SUBDIRECTORY(../src/ifcwrap ifcwrap)
|
||||
ENDIF()
|
||||
|
||||
@@ -836,11 +854,12 @@ IF(BUILD_EXAMPLES)
|
||||
ADD_SUBDIRECTORY(../src/examples examples)
|
||||
ENDIF()
|
||||
|
||||
IF(BUILD_IFCMAX)
|
||||
IF(NOT MINIMAL_BUILD AND BUILD_IFCMAX)
|
||||
ADD_SUBDIRECTORY(../src/ifcmax ifcmax)
|
||||
ENDIF()
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
ADD_SUBDIRECTORY(../src/svgfill svgfill)
|
||||
endif()
|
||||
|
||||
# CMake installation targets
|
||||
INSTALL(FILES ${IFCPARSE_H_FILES}
|
||||
@@ -885,6 +904,9 @@ INSTALL(FILES ${SERIALIZERS_S_FILES}
|
||||
)
|
||||
endif()
|
||||
|
||||
IF(BUILD_QTVIEWER)
|
||||
ADD_SUBDIRECTORY(../src/qtviewer qtviewer)
|
||||
endif()
|
||||
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
.git
|
||||
|
||||
@@ -508,7 +508,7 @@ if USE_OCCT and "occ" in targets:
|
||||
"-DBUILD_MODULE_Draw=0",
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
|
||||
],
|
||||
download_url = "https://git.dev.opencascade.org/repos/occt.git",
|
||||
download_url = "https://github.com/Open-Cascade-SAS/OCCT",
|
||||
download_name = "occt",
|
||||
download_tool=download_tool_git,
|
||||
patch=None if OCCT_VERSION >= "7.4" else "./patches/occt/enable-exception-handling.patch",
|
||||
|
||||
@@ -646,7 +646,7 @@ class BcfXml:
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Comments" not in data["Topics"]:
|
||||
if "Comments" not in data["Topic"]:
|
||||
return comments
|
||||
|
||||
for item in data["Topic"]["Comments"].get("Comment", []):
|
||||
|
||||
@@ -115,7 +115,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-2985bba-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-cb8cfd7-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -210,9 +210,9 @@ endif
|
||||
|
||||
# Required by IFCDiff
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/Moult/deepdiff/archive/master.zip
|
||||
cd dist/working && unzip master.zip
|
||||
cp -r dist/working/deepdiff-master/deepdiff dist/blenderbim/libs/site/packages/
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/0f/ca/caead2949fbb824c7142e3774fa841aa853bb4d4331b440da8c8514dfc6f/deepdiff-5.8.1.tar.gz
|
||||
cd dist/working && tar -xzvf deepdiff*
|
||||
cp -r dist/working/deepdiff-5.8.1/deepdiff dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by deepdiff
|
||||
@@ -534,11 +534,19 @@ test-core:
|
||||
|
||||
.PHONY: test-bim
|
||||
test-bim:
|
||||
ifndef MODULE
|
||||
pytest test/bim
|
||||
else
|
||||
pytest test/bim -m "$(MODULE)" ./ --maxfail=1
|
||||
endif
|
||||
|
||||
.PHONY: test-tool
|
||||
test-tool:
|
||||
ifndef MODULE
|
||||
pytest test/tool
|
||||
else
|
||||
pytest test/tool/test_$(MODULE).py
|
||||
endif
|
||||
|
||||
.PHONY: qa
|
||||
qa:
|
||||
|
||||
@@ -27,6 +27,7 @@ modules = {
|
||||
"root": None,
|
||||
"unit": None,
|
||||
"model": None,
|
||||
"cad": None,
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
@@ -91,7 +92,9 @@ classes = [
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
operator.EditBlenderCollection,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
prop.Attribute,
|
||||
prop.ModuleVisibility,
|
||||
@@ -130,7 +133,8 @@ for mod in modules.values():
|
||||
|
||||
def on_register(scene):
|
||||
handler.setDefaultProperties(scene)
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
if not bpy.app.background:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
|
||||
|
||||
def register():
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Annotation.ifc','2020-01-01T00:00:00',(),(),'EPset_Annotation','EPset_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_ENUMERATEDVALUE.,'IfcLabel',$,#4,$,$,$,.READWRITE.);
|
||||
#4=IFCPROPERTYENUMERATION('Symbol',(IFCLABEL('rectangle-tag'), IFCLABEL('door-tag')),$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,24 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -23,12 +23,27 @@ text { fill: black; stroke: none; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
.solid {}
|
||||
.leader { marker-end: url(#leader-marker); }
|
||||
.stair { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.break { fill: white; }
|
||||
.breakline { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
.PredefinedType-SECTION { stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
.PredefinedType-SECTIONLEVEL { marker-start: url(#section-level-marker); stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
.PredefinedType-PLANLEVEL { marker-end: url(#plan-level-marker); }
|
||||
.PredefinedType-DIMENSION { marker-start: url(#dimension-marker-start); marker-end: url(#dimension-marker-end); }
|
||||
.PredefinedType-ANGLE { marker-start: url(#angle-marker-start); marker-end: url(#angle-marker-end); }
|
||||
.PredefinedType-RADIUS { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-BATTING { marker-start: url(#batting); stroke: none; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
.PredefinedType-FILLAREA { fill: white; stroke: none; }
|
||||
.PredefinedType-BREAKLINE { fill: none; stroke: black; stroke-width: 0.25; marker-mid: url(#breakline-marker); }
|
||||
.PredefinedType-TEXT { fill: black; stroke: none; }
|
||||
path.PredefinedType-TEXTLEADER { marker-end: url(#leader-marker); }
|
||||
text.PredefinedType-TEXTLEADER { fill: black; stroke: none; }
|
||||
.material-blank { fill: white; }
|
||||
.material-diagonal1 { fill: url(#diagonal1); }
|
||||
.material-diagonal2 { fill: url(#diagonal2); }
|
||||
@@ -44,5 +59,9 @@ text { fill: black; stroke: none; }
|
||||
.material-honeycomb { fill: url(#honeycomb); }
|
||||
.material-sand { fill: url(#sand); }
|
||||
.material-concrete { fill: url(#concrete); stroke-width: 0.5; }
|
||||
.material-boundary { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12,4,3,4,3,4; }
|
||||
.IfcSpace { fill: none; stroke: none; }
|
||||
.PredefinedType-STUD { stroke: black; stroke-width: 1; }
|
||||
.PredefinedType-WOOD { fill: url(#wood); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-STEEL { fill: url(#steel); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-CONCRETE { fill: url(#concrete); stroke: black; stroke-width: 0.5; }
|
||||
.PredefinedType-PLASTERBOARD { fill: url(#sand); stroke: black; stroke-width: 0.25; }
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<svg baseProfile="full" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<marker id="batting" markerHeight="10" markerWidth="4" orient="auto" refX="0" refY="5" markerUnits="userSpaceOnUse">
|
||||
<!-- Tip: https://svg-path-visualizer.netlify.app/ -->
|
||||
<path style="fill: none; stroke:black; stroke-width:0.18" d="M 0 2 A 2 2 0 0 1 4 2 L 2 8 M 0 2 L 2 8 A 2 2 0 0 0 4 10 M 2 8 A 2 2 0 0 1 0 10" />
|
||||
</marker>
|
||||
<marker id="eng-dimension-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
@@ -12,6 +16,35 @@
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="radius-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="angle-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="angle-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="diameter-marker-start" markerHeight="14" markerWidth="11" orient="auto" refX="1" refY="7">
|
||||
<g>
|
||||
<path d="M 11 3.5 L 11 10.5 L 1 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 1 0 L 1 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="diameter-marker-end" markerHeight="14" markerWidth="11" orient="auto" refX="10" refY="7">
|
||||
<g>
|
||||
<path d="M 0 3.5 L 0 10.5 L 10 7" class="annotation" style="fill:black;" />
|
||||
<path d="M 10 0 L 10 14" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="dimension-marker-start" markerHeight="30" markerWidth="20" refX="10" refY="15" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 15 L 20 15" class="annotation" style="stroke-width:1;" />
|
||||
@@ -53,7 +86,7 @@
|
||||
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10">
|
||||
<marker id="breakline-marker" markerHeight="20" markerWidth="15" refX="7.5" refY="10" orient="auto">
|
||||
<g>
|
||||
<path d="M 0 10 L 5 20 L 7.5 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
<path d="M 7.5 10 L 10 0 L 15 10" class="annotation" style="stroke-width:1; fill: white;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -3,6 +3,10 @@
|
||||
<pattern id="demolish" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:red; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="steel" width="3" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
<line x1="1" y1="0" x2="1" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
<pattern id="diagonal1" width="1" height="1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse">
|
||||
<line x1="0" y1="0" x2="0" y2="1" style="stroke:black; stroke-width:0.25" />
|
||||
</pattern>
|
||||
@@ -80,6 +84,9 @@
|
||||
<path d="M 1.25,2.165 L 2.5,4.33 L 5,4.33 L 6.25,2.165 L 5,0 L 2.5,0 L 1.25,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round;fill:none" />
|
||||
<path d="M 6.25,2.165 L 7.5,2.165" style="stroke:black;stroke-width:0.25;stroke-linecap:round" />
|
||||
</pattern>
|
||||
<pattern id="wood" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill:none; stroke: black; stroke-width:0.25;" d="M 49.649999,1.3460737 A 69.019995,69.019995 0 0 0 48.653938,0.34999949 M 49.649999,4.9389583 A 66.554994,66.554994 0 0 0 45.06105,0.34999949 M 49.649999,8.6983527 A 64.089999,64.089999 0 0 0 41.301656,0.34999949 M 49.649999,12.675003 A 61.624998,61.624998 0 0 0 37.325,0.34999949 M 49.649999,16.948067 A 59.159997,59.159997 0 0 0 33.05192,0.34999949 M 49.649999,21.652972 A 56.694997,56.694997 0 0 0 28.347017,0.34999949 M 49.649999,27.057906 A 54.229999,54.229999 0 0 0 22.942096,0.34999949 M 49.649999,33.866298 A 51.764998,51.764998 0 0 0 16.1337,0.34999949 M 49.649999,49.649989 A 49.299997,49.299997 0 0 0 0.34999949,0.34999949 M 47.185,49.649989 A 46.834999,46.834999 0 0 0 0.34999949,2.8149951 M 44.72,49.649989 A 44.369998,44.369998 0 0 0 0.34999949,5.2799906 M 42.254999,49.649989 A 41.904997,41.904997 0 0 0 0.34999949,7.7450123 M 39.790001,49.649989 A 39.439999,39.439999 0 0 0 0.34999949,10.210008 M 37.325,49.649989 A 36.974998,36.974998 0 0 0 0.34999949,12.675003 M 34.859999,49.649989 A 34.509998,34.509998 0 0 0 0.34999949,15.139998 M 32.395001,49.649989 A 32.044999,32.044999 0 0 0 0.34999949,17.604994 M 29.93,49.649989 A 29.579999,29.579999 0 0 0 0.34999949,20.069989 M 27.465,49.649989 A 27.114998,27.114998 0 0 0 0.34999949,22.535011 M 24.999997,49.649989 A 24.649999,24.649999 0 0 0 0.34999949,25.000007 M 22.534998,49.649989 A 22.184999,22.184999 0 0 0 0.34999949,27.465003 M 20.069998,49.649989 A 19.719999,19.719999 0 0 0 0.34999949,29.929999 M 17.604998,49.649989 A 17.254999,17.254999 0 0 0 0.34999949,32.394994 M 15.139998,49.649989 A 14.789999,14.789999 0 0 0 0.34999949,34.85999 M 12.674999,49.649989 A 12.325,12.325 0 0 0 0.34999949,37.325011 M 10.209999,49.649989 A 9.8599994,9.8599994 0 0 0 0.34999949,39.790007 M 7.7449994,49.649989 A 7.3949999,7.3949999 0 0 0 0.34999949,42.255002 M 5.2799992,49.649989 A 4.9299997,4.9299997 0 0 0 0.34999949,44.719998 M 2.8149995,49.649989 A 2.4649999,2.4649999 0 0 0 0.34999949,47.184993 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003 M 0.34999949,49.649989 H 3.8010003" />
|
||||
</pattern>
|
||||
<pattern id="sand" width="50" height="50" patternUnits="userSpaceOnUse">
|
||||
<path style="fill: white;" d="M 0 0 50 0 50 50 0 50" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 67 KiB |
@@ -173,7 +173,7 @@
|
||||
style="stroke-width:0.999999px"
|
||||
y="1036.1166"
|
||||
x="3030.9653"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3918px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -183,7 +183,7 @@
|
||||
id="tspan2531"
|
||||
x="2804.7021"
|
||||
y="1036.1166"
|
||||
style="stroke-width:0.999999px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.999999px">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458334,-3.180917e-5,297.00003)"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -169,7 +169,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="397.13919"
|
||||
x="554.94287"
|
||||
id="tspan2527">{{revision}}</tspan></text>
|
||||
id="tspan2527">{{Revision}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325px;line-height:125%;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
@@ -179,7 +179,7 @@
|
||||
id="tspan2531"
|
||||
x="495.07742"
|
||||
y="397.13919"
|
||||
style="stroke-width:0.264583px">{{number}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Identification}} - {{Name}}</tspan></text>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
id="g90669"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -172,7 +172,7 @@
|
||||
id="tspan2527"
|
||||
x="544.94287"
|
||||
y="284.13919"
|
||||
style="stroke-width:0.264583px">{{revision}}</tspan></text>
|
||||
style="stroke-width:0.264583px">{{Revision}}</tspan></text>
|
||||
<text
|
||||
id="text2533"
|
||||
y="284.13919"
|
||||
@@ -182,7 +182,7 @@
|
||||
style="stroke-width:0.264583px"
|
||||
y="284.13919"
|
||||
x="485.07742"
|
||||
id="tspan2531">{{number}}</tspan></text>
|
||||
id="tspan2531">{{Identification}} - {{Name}}</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:3.77953"
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -70,7 +70,7 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.96055365px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:center;text-anchor:middle;stroke-width:0.26458332px"
|
||||
y="103.10933"
|
||||
x="32.822662"
|
||||
id="tspan857">{{no}}</tspan></text>
|
||||
id="tspan857">{{Identification}}</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.71626091px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458329px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
@@ -80,7 +80,7 @@
|
||||
id="tspan861"
|
||||
x="39.12896"
|
||||
y="100.19173"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{name}}</tspan></text>
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458329px">{{Name}}</tspan></text>
|
||||
<text
|
||||
id="text867"
|
||||
y="105.0553"
|
||||
@@ -90,6 +90,6 @@
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:3.54325247px;font-family:'OpenGost Type B TT';-inkscape-font-specification:'OpenGost Type B TT';text-align:start;text-anchor:start;stroke-width:0.26458332px"
|
||||
y="105.0553"
|
||||
x="39.285267"
|
||||
id="tspan865">{{scale}}</tspan></text>
|
||||
id="tspan865">{{Scale}}</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -97,6 +97,7 @@ class IfcExporter:
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
IfcStore.deleted_ids.clear()
|
||||
return results
|
||||
|
||||
def sync_all_objects(self):
|
||||
@@ -148,7 +149,7 @@ class IfcExporter:
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
if not self.has_object_moved(obj):
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
@@ -162,17 +163,6 @@ class IfcExporter:
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
return element
|
||||
|
||||
def has_object_moved(self, obj):
|
||||
if not obj.BIMObjectProperties.location_checksum:
|
||||
return True # Let's be conservative
|
||||
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
|
||||
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
|
||||
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
|
||||
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
|
||||
return False
|
||||
return True
|
||||
|
||||
def sync_grid_axis_object_placement(self, obj, element):
|
||||
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
|
||||
@@ -157,12 +157,12 @@ def loadIfcStore(scene):
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
IfcStore.reload_linked_elements()
|
||||
IfcStore.relink_all_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -171,13 +171,13 @@ def undo_post(scene):
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_pre(scene):
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
@@ -186,8 +186,8 @@ def redo_post(scene):
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo()
|
||||
purge_module_data()
|
||||
IfcStore.update_undo_redo_stack_objects()
|
||||
IfcStore.reload_linked_elements(objects=[bpy.data.objects.get(o) for o in IfcStore.undo_redo_stack_objects])
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
|
||||
@@ -38,11 +38,14 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
if not value_name:
|
||||
layout.label(text=attribute.name)
|
||||
return
|
||||
layout.prop(
|
||||
attribute,
|
||||
value_name,
|
||||
text=attribute.name,
|
||||
)
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
else:
|
||||
layout.prop(
|
||||
attribute,
|
||||
value_name,
|
||||
text=attribute.name,
|
||||
)
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
@@ -105,6 +108,39 @@ def export_attributes(props, callback=None):
|
||||
return attributes
|
||||
|
||||
|
||||
def prop_with_search(layout, data, prop_name, **kwargs):
|
||||
# kwargs are layout.prop arguments (text, icon, etc.)
|
||||
row = layout.row(align=True)
|
||||
# Magick courtesy of https://blender.stackexchange.com/a/203443/86891
|
||||
row.context_pointer_set(name="data", data=data)
|
||||
row.prop(data, prop_name, **kwargs)
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||
op.prop_name = prop_name
|
||||
|
||||
|
||||
def get_enum_items(data, prop_name, context):
|
||||
# Retrieve items from a dynamic EnumProperty, which is otherwise not supported
|
||||
# Or throws an error in the console when the items callback returns an empty list
|
||||
# See https://blender.stackexchange.com/q/215781/86891
|
||||
prop = data.__annotations__[prop_name]
|
||||
items = prop.keywords.get("items")
|
||||
if items is None:
|
||||
return
|
||||
if not isinstance(items, (list, tuple)):
|
||||
# items are retrieved through a callback, not a static list :
|
||||
items = items(data, context)
|
||||
return items
|
||||
|
||||
|
||||
# hack to close popup
|
||||
# https://blender.stackexchange.com/a/202576/130742
|
||||
def close_operator_panel(event):
|
||||
x, y = event.mouse_x, event.mouse_y
|
||||
bpy.context.window.cursor_warp(10, 10)
|
||||
move_back = lambda: bpy.context.window.cursor_warp(x, y)
|
||||
bpy.app.timers.register(move_back, first_interval=0.01)
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
|
||||
@@ -43,6 +43,7 @@ class IfcStore:
|
||||
library_file = None
|
||||
element_listeners = set()
|
||||
undo_redo_stack_objects = set()
|
||||
undo_redo_stack_object_names = {}
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -64,6 +65,9 @@ class IfcStore:
|
||||
IfcStore.pset_template_file = None
|
||||
IfcStore.library_path = ""
|
||||
IfcStore.library_file = None
|
||||
IfcStore.last_transaction = ""
|
||||
IfcStore.history = []
|
||||
IfcStore.future = []
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
|
||||
|
||||
@staticmethod
|
||||
@@ -147,14 +151,32 @@ class IfcStore:
|
||||
IfcStore.element_listeners.add(callback)
|
||||
|
||||
@staticmethod
|
||||
def update_undo_redo_stack_objects():
|
||||
def track_undo_redo_stack_object_map():
|
||||
"""Keeps track of currently mapped object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
occur whenever an object is affected during an operation.
|
||||
|
||||
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
|
||||
"""
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
IfcStore.undo_redo_stack_object_names[key] = value.name
|
||||
except:
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
def track_undo_redo_stack_selected_objects():
|
||||
"""Keeps track of selected object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
only occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children.
|
||||
occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children. This is
|
||||
important because selected objects are often deleted from the scene.
|
||||
|
||||
So if I first select o1, then o2, then o3, then press undo, o3 will be
|
||||
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
|
||||
@@ -169,14 +191,23 @@ class IfcStore:
|
||||
IfcStore.undo_redo_stack_objects |= objects
|
||||
|
||||
@staticmethod
|
||||
def reload_linked_elements(objects=None):
|
||||
def reload_undo_redo_stack_objects():
|
||||
"""Reloads any invalidated objects after undo or redo
|
||||
|
||||
After an undo or redo operation, objects may have been invalidated in
|
||||
our id_map and guid_map. Invalidated objects are typically those that
|
||||
have been manipulated or deleted. This checks the cache of mapped and
|
||||
selected objects prior to the operation and ensures that if the object
|
||||
is invalidated, they are reloaded based on the object name that was
|
||||
tracked prior to the undo / redo.
|
||||
"""
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
if objects is None:
|
||||
objects = bpy.data.objects
|
||||
|
||||
for obj in objects:
|
||||
# First, reload objects that were selected or active
|
||||
for name in IfcStore.undo_redo_stack_objects:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -187,6 +218,45 @@ class IfcStore:
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
value.name
|
||||
except:
|
||||
obj = bpy.data.objects.get(IfcStore.undo_redo_stack_object_names[key])
|
||||
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
IfcStore.relink_object(obj)
|
||||
for obj in bpy.data.materials:
|
||||
IfcStore.relink_object(obj)
|
||||
|
||||
@staticmethod
|
||||
def relink_object(obj):
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
if hasattr(obj, "BIMMaterialProperties") and obj.BIMMaterialProperties.ifc_style_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMMaterialProperties.ifc_style_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def delete_element(element):
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
@@ -240,6 +310,8 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def commit_link_element(data):
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
if not obj:
|
||||
obj = bpy.data.materials.get(data["obj"])
|
||||
IfcStore.id_map[data["id"]] = obj
|
||||
if "guid" in data:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
@@ -312,6 +384,7 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
IfcStore.undo_redo_stack_objects = set()
|
||||
IfcStore.undo_redo_stack_object_names = {}
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
|
||||
@@ -121,7 +121,14 @@ class MaterialCreator:
|
||||
def parse_representation_item(self, item):
|
||||
if not item.StyledByItem:
|
||||
return
|
||||
style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
|
||||
style_ids = []
|
||||
styles = list(item.StyledByItem[0].Styles)
|
||||
while styles:
|
||||
style = styles.pop()
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
style_ids.append(style.id())
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
if not style_ids:
|
||||
return
|
||||
for style_id in style_ids:
|
||||
@@ -168,6 +175,7 @@ class IfcImporter:
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.collections = {}
|
||||
self.elements = set()
|
||||
@@ -236,24 +244,19 @@ class IfcImporter:
|
||||
self.profile_code("Create spatial elements")
|
||||
self.create_structural_items()
|
||||
self.profile_code("Create structural items")
|
||||
self.create_type_products()
|
||||
self.profile_code("Create type products")
|
||||
if not self.ifc_import_settings.is_coordinating:
|
||||
self.create_element_types()
|
||||
self.profile_code("Create element types")
|
||||
self.place_objects_in_collections()
|
||||
self.profile_code("Place objects in collections")
|
||||
if self.ifc_import_settings.should_merge_by_class:
|
||||
self.merge_by_class()
|
||||
self.profile_code("Merging by class")
|
||||
elif self.ifc_import_settings.should_merge_by_material:
|
||||
self.merge_by_material()
|
||||
self.profile_code("Merging by material")
|
||||
if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300:
|
||||
self.merge_materials_by_colour()
|
||||
self.profile_code("Merging by colour")
|
||||
self.add_project_to_scene()
|
||||
self.profile_code("Add project to scene")
|
||||
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 1000:
|
||||
self.clean_mesh()
|
||||
self.profile_code("Mesh cleaning")
|
||||
if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300:
|
||||
self.merge_materials_by_colour()
|
||||
self.profile_code("Merging by colour")
|
||||
self.set_default_context()
|
||||
self.profile_code("Setting default context")
|
||||
self.update_progress(100)
|
||||
@@ -269,7 +272,9 @@ class IfcImporter:
|
||||
|
||||
def is_point_far_away(self, point, is_meters=True):
|
||||
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
|
||||
limit = 1000 if is_meters else (1000 / self.unit_scale)
|
||||
# Users can configure this if they have to handle larger sites but beware of surveying precision
|
||||
limit = self.ifc_import_settings.distance_limit
|
||||
limit = limit if is_meters else (limit / self.unit_scale)
|
||||
coords = point
|
||||
if hasattr(point, "Coordinates"):
|
||||
coords = point.Coordinates
|
||||
@@ -304,14 +309,25 @@ class IfcImporter:
|
||||
self.settings_2d.set_context_ids(self.plan_contexts)
|
||||
|
||||
def process_element_filter(self):
|
||||
offset = self.ifc_import_settings.element_offset
|
||||
offset_limit = offset + self.ifc_import_settings.element_limit
|
||||
|
||||
if self.ifc_import_settings.has_filter:
|
||||
self.elements = set(self.ifc_import_settings.elements)
|
||||
self.elements = self.ifc_import_settings.elements
|
||||
if isinstance(self.elements, set):
|
||||
self.elements = list(self.elements)
|
||||
# TODO: enable filtering for annotations
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
else:
|
||||
self.elements = set(self.file.by_type("IfcElement"))
|
||||
self.elements = self.file.by_type("IfcElement")
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
|
||||
if self.ifc_import_settings.is_coordinating:
|
||||
self.elements = [e for e in self.elements if e.Representation and not e.is_a("IfcFeatureElement")]
|
||||
|
||||
self.elements = set(self.elements[offset:offset_limit])
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
else:
|
||||
@@ -354,15 +370,26 @@ class IfcImporter:
|
||||
"type": "IfcSweptDiskSolid",
|
||||
}
|
||||
return True
|
||||
|
||||
if not self.ifc_import_settings.should_use_native_meshes:
|
||||
return False # Performance improvements only occur on edge cases currently
|
||||
|
||||
# FacetedBreps (without voids) are meshes. See #841.
|
||||
# Commented out as seems currently too slow.
|
||||
# if self.is_native_faceted_brep(representations):
|
||||
# self.native_data[element.GlobalId] = {
|
||||
# "representations": representations,
|
||||
# "representation": self.get_body_representation(element.Representation.Representations),
|
||||
# "type": "IfcFacetedBrep",
|
||||
# }
|
||||
# return True
|
||||
if self.is_native_faceted_brep(representations):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
"type": "IfcFacetedBrep",
|
||||
}
|
||||
return True
|
||||
|
||||
if self.is_native_face_based_surface_model(representations):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
"type": "IfcFaceBasedSurfaceModel",
|
||||
}
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(self, representations):
|
||||
for representation in representations:
|
||||
@@ -378,6 +405,13 @@ class IfcImporter:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_native_face_based_surface_model(self, representations):
|
||||
for representation in representations:
|
||||
for i in representation["raw"].Items:
|
||||
if i.is_a() != "IfcFaceBasedSurfaceModel":
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_products_from_shape_representation(self, element):
|
||||
products = [pr.ShapeOfProduct[0] for pr in element.OfProductRepresentation]
|
||||
for rep_map in element.RepresentationMap:
|
||||
@@ -391,6 +425,8 @@ class IfcImporter:
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
return
|
||||
if self.ifc_import_settings.false_origin:
|
||||
return self.set_manual_blender_offset()
|
||||
if self.file.schema == "IFC2X3":
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
else:
|
||||
@@ -403,6 +439,13 @@ class IfcImporter:
|
||||
return self.guess_georeferencing(building)
|
||||
return self.guess_absolute_coordinate()
|
||||
|
||||
def set_manual_blender_offset(self):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.blender_eastings = str(self.ifc_import_settings.false_origin[0])
|
||||
props.blender_northings = str(self.ifc_import_settings.false_origin[1])
|
||||
props.blender_orthogonal_height = str(self.ifc_import_settings.false_origin[2])
|
||||
props.has_blender_offset = True
|
||||
|
||||
def guess_georeferencing(self, element):
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return
|
||||
@@ -419,10 +462,12 @@ class IfcImporter:
|
||||
props.has_blender_offset = True
|
||||
|
||||
def guess_absolute_coordinate(self):
|
||||
# Civil BIM applications like to work in absolute coordinates, where the ObjectPlacement is 0,0,0 but each
|
||||
# individual coordinate of the shape representation is in absolute values.
|
||||
# Civil BIM applications like to work in absolute coordinates, where the
|
||||
# ObjectPlacement is usually 0,0,0 (but not always, so we'll need to
|
||||
# check for the actual transformation) but each individual coordinate of
|
||||
# the shape representation is in absolute values.
|
||||
offset_point = self.get_offset_point()
|
||||
if not offset_point:
|
||||
if offset_point is None:
|
||||
return
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.blender_eastings = str(offset_point[0])
|
||||
@@ -432,34 +477,41 @@ class IfcImporter:
|
||||
|
||||
def get_offset_point(self):
|
||||
elements_checked = 0
|
||||
# If more than these points aren't far away, the file probably isn't absolutely positioned
|
||||
element_checking_threshold = 100
|
||||
if self.file.schema == "IFC2X3":
|
||||
# IFC2X3 does not have IfcCartesianPointList3D
|
||||
point_lists = []
|
||||
else:
|
||||
point_lists = self.file.by_type("IfcCartesianPointList3D")
|
||||
for point_list in point_lists:
|
||||
elements_checked += 1
|
||||
if elements_checked > element_checking_threshold:
|
||||
return
|
||||
for i, point in enumerate(point_list.CoordList):
|
||||
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
|
||||
return point
|
||||
|
||||
for point in self.file.by_type("IfcCartesianPoint"):
|
||||
is_used_in_placement = False
|
||||
for inverse in self.file.get_inverse(point):
|
||||
if inverse.is_a("IfcAxis2Placement3D"):
|
||||
is_used_in_placement = True
|
||||
break
|
||||
if is_used_in_placement:
|
||||
# If more than these elements aren't far away, the file probably isn't absolutely positioned
|
||||
element_checking_threshold = 10
|
||||
for element in self.file.by_type("IfcElement"):
|
||||
if not element.Representation:
|
||||
continue
|
||||
elements_checked += 1
|
||||
if elements_checked > element_checking_threshold:
|
||||
return
|
||||
if len(point.Coordinates) == 3 and self.is_point_far_away(point, is_meters=False):
|
||||
return point.Coordinates
|
||||
if not self.does_element_likely_have_geometry_far_away(element):
|
||||
continue
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, element)
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
point = np.array(
|
||||
(
|
||||
shape.geometry.verts[0] / self.unit_scale,
|
||||
shape.geometry.verts[1] / self.unit_scale,
|
||||
shape.geometry.verts[2] / self.unit_scale,
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
return mat @ point
|
||||
|
||||
def does_element_likely_have_geometry_far_away(self, element):
|
||||
for representation in element.Representation.Representations:
|
||||
for subelement in self.file.traverse(representation):
|
||||
if subelement.is_a("IfcCartesianPointList3D"):
|
||||
for point in subelement.CoordList:
|
||||
if len(point) == 3 and self.is_point_far_away(point, is_meters=False):
|
||||
return True
|
||||
if subelement.is_a("IfcCartesianPoint"):
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
|
||||
def apply_blender_offset_to_matrix_world(self, obj, matrix):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
@@ -477,11 +529,6 @@ class IfcImporter:
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
|
||||
if self.ifc_import_settings.should_offset_model:
|
||||
matrix[0, 3] += self.ifc_import_settings.model_offset_coordinates[0]
|
||||
matrix[1, 3] += self.ifc_import_settings.model_offset_coordinates[1]
|
||||
matrix[2, 3] += self.ifc_import_settings.model_offset_coordinates[2]
|
||||
|
||||
return mathutils.Matrix(matrix.tolist())
|
||||
|
||||
def find_decomposed_ifc_class(self, element, ifc_class):
|
||||
@@ -505,6 +552,8 @@ class IfcImporter:
|
||||
if grid.Representation:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
|
||||
grid_obj = self.create_product(grid, shape)
|
||||
grid_obj.lock_location = (True, True, True)
|
||||
grid_obj.lock_rotation = (True, True, True)
|
||||
collection = bpy.data.collections.new(self.get_name(grid))
|
||||
u_axes = bpy.data.collections.new("UAxes")
|
||||
collection.children.link(u_axes)
|
||||
@@ -522,23 +571,19 @@ class IfcImporter:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
|
||||
obj.lock_location = (True, True, True)
|
||||
obj.lock_rotation = (True, True, True)
|
||||
self.link_element(axis, obj)
|
||||
self.set_matrix_world(obj, grid_obj.matrix_world)
|
||||
grid_collection.objects.link(obj)
|
||||
|
||||
def create_type_products(self):
|
||||
# TODO allow filtering of spatial elements too
|
||||
if self.ifc_import_settings.has_filter:
|
||||
type_products = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
type_products = self.file.by_type("IfcTypeProduct")
|
||||
|
||||
for type_product in type_products:
|
||||
if not type_product:
|
||||
def create_element_types(self):
|
||||
for element_type in self.element_types:
|
||||
if not element_type:
|
||||
continue
|
||||
self.create_type_product(type_product)
|
||||
self.create_element_type(element_type)
|
||||
|
||||
def create_type_product(self, element):
|
||||
def create_element_type(self, element):
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
representation_map = self.get_type_product_body_representation_map(element)
|
||||
mesh = None
|
||||
@@ -572,13 +617,20 @@ class IfcImporter:
|
||||
return representation_map
|
||||
|
||||
def create_native_elements(self):
|
||||
total = 0
|
||||
progress = 0
|
||||
checkpoint = time.time()
|
||||
total = len(self.native_elements)
|
||||
for element in self.native_elements:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
|
||||
progress += 1
|
||||
if progress % 250 == 0:
|
||||
percent = round(progress / total * 100)
|
||||
print(
|
||||
"{} / {} ({}%) elements processed in {:.2f}s ...".format(
|
||||
progress, total, percent, time.time() - checkpoint
|
||||
)
|
||||
)
|
||||
checkpoint = time.time()
|
||||
self.incrementally_merge_objects()
|
||||
native_data = self.native_data[element.GlobalId]
|
||||
representation = native_data["representation"]
|
||||
if not representation:
|
||||
@@ -591,6 +643,8 @@ class IfcImporter:
|
||||
mesh = self.create_native_swept_disk_solid(element, mesh_name)
|
||||
elif native_data["type"] == "IfcFacetedBrep":
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
mesh.BIMMeshProperties.ifc_definition_id = representation.id()
|
||||
mesh.name = mesh_name
|
||||
self.meshes[mesh_name] = mesh
|
||||
@@ -630,26 +684,32 @@ class IfcImporter:
|
||||
)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
checkpoint = time.time()
|
||||
total = 0
|
||||
progress = 0
|
||||
total = len(products)
|
||||
start_progress = self.progress
|
||||
progress_range = 85 - start_progress
|
||||
while True:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
progress += 1
|
||||
if progress % 250 == 0:
|
||||
percent_created = round(progress / total * 100)
|
||||
percent_preprocessed = iterator.progress()
|
||||
percent_average = (percent_created + percent_preprocessed) / 2
|
||||
print(
|
||||
"{} ({}%) elements processed in {:.2f}s ...".format(
|
||||
total, iterator.progress(), time.time() - checkpoint
|
||||
"{} / {} ({}% created, {}% preprocessed) elements processed in {:.2f}s ...".format(
|
||||
progress, total, percent_created, percent_preprocessed, time.time() - checkpoint
|
||||
)
|
||||
)
|
||||
checkpoint = time.time()
|
||||
self.update_progress((iterator.progress() / 100 * progress_range) + start_progress)
|
||||
self.update_progress((percent_average / 100 * progress_range) + start_progress)
|
||||
self.incrementally_merge_objects()
|
||||
shape = iterator.get()
|
||||
if shape:
|
||||
product = self.file.by_id(shape.guid)
|
||||
@@ -668,6 +728,19 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
return results
|
||||
|
||||
def incrementally_merge_objects(self):
|
||||
if not self.ifc_import_settings.is_coordinating:
|
||||
return
|
||||
if self.ifc_import_settings.merge_mode == "IFC_CLASS":
|
||||
self.merge_by_class()
|
||||
self.profile_code("Merging by class")
|
||||
elif self.ifc_import_settings.merge_mode == "IFC_TYPE":
|
||||
self.merge_by_type()
|
||||
self.profile_code("Merging by type")
|
||||
elif self.ifc_import_settings.merge_mode == "MATERIAL":
|
||||
self.merge_by_material()
|
||||
self.profile_code("Merging by material")
|
||||
|
||||
def create_structural_items(self):
|
||||
# Create structural collections
|
||||
self.structural_member_collection = bpy.data.collections.new("Members")
|
||||
@@ -778,9 +851,10 @@ class IfcImporter:
|
||||
)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
@@ -794,7 +868,7 @@ class IfcImporter:
|
||||
shape = iterator.get()
|
||||
if shape:
|
||||
product = self.file.by_id(shape.guid)
|
||||
self.create_product(self.file.by_id(shape.guid), shape)
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
if not iterator.next():
|
||||
break
|
||||
@@ -851,8 +925,13 @@ class IfcImporter:
|
||||
def get_representation_item_material_name(self, item):
|
||||
if not item.StyledByItem:
|
||||
return
|
||||
style_ids = [e.id() for e in self.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")]
|
||||
return style_ids[0] if style_ids else None
|
||||
styles = list(item.StyledByItem[0].Styles)
|
||||
while styles:
|
||||
style = styles.pop()
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
return style.id()
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
|
||||
def create_native_faceted_brep(self, element, mesh_name):
|
||||
# TODO: georeferencing?
|
||||
@@ -901,22 +980,32 @@ class IfcImporter:
|
||||
self.convert_representation(item.MappingSource.MappedRepresentation)
|
||||
elif item.is_a() == "IfcFacetedBrep":
|
||||
self.convert_representation_item_faceted_brep(item)
|
||||
elif item.is_a() == "IfcFaceBasedSurfaceModel":
|
||||
self.convert_representation_item_face_based_surface_model(item)
|
||||
|
||||
def convert_representation_item_face_based_surface_model(self, item):
|
||||
mesh = item.get_info_2(recursive=True)
|
||||
for face_set in mesh["FbsmFaces"]:
|
||||
self.convert_representation_item_face_set(item, face_set)
|
||||
|
||||
def convert_representation_item_faceted_brep(self, item):
|
||||
mesh = item.get_info_2(recursive=True)
|
||||
return self.convert_representation_item_face_set(item, mesh["Outer"])
|
||||
|
||||
def convert_representation_item_face_set(self, item, mesh):
|
||||
# On a few occasions, we flatten a list. This seems to be the most efficient way to do it.
|
||||
# https://stackoverflow.com/questions/20112776/how-do-i-flatten-a-list-of-lists-nested-lists
|
||||
mesh = item.get_info_2(recursive=True)
|
||||
|
||||
# For huge face sets it might be better to do a "flatmap" instead of sum()
|
||||
# bounds = sum((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1), ())
|
||||
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))
|
||||
bounds = tuple(chain.from_iterable(f["Bounds"] for f in mesh["CfsFaces"] if len(f["Bounds"]) == 1))
|
||||
# Here are some untested alternatives, are they faster?
|
||||
# bounds = tuple((f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1))[0]
|
||||
# bounds = chain.from_iterable(f["Bounds"] for f in mesh["Outer"]["CfsFaces"] if len(f["Bounds"]) == 1)
|
||||
|
||||
polygons = [[(p["id"], p["Coordinates"]) for p in b["Bound"]["Polygon"]] for b in bounds]
|
||||
|
||||
for face in mesh["Outer"]["CfsFaces"]:
|
||||
for face in mesh["CfsFaces"]:
|
||||
# Blender cannot handle faces with holes.
|
||||
if len(face["Bounds"]) > 1:
|
||||
inner_bounds = []
|
||||
@@ -1046,33 +1135,74 @@ class IfcImporter:
|
||||
|
||||
def merge_by_class(self):
|
||||
merge_set = {}
|
||||
for obj in self.added_data.values():
|
||||
if not isinstance(obj, bpy.types.Object):
|
||||
id_set = {}
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if not isinstance(obj, bpy.types.Object) or not obj.data:
|
||||
continue
|
||||
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
|
||||
element = self.file.by_id(ifc_definition_id)
|
||||
if not element.is_a("IfcElement"):
|
||||
continue
|
||||
merge_set.setdefault(obj.name.split("/")[0], []).append(obj)
|
||||
self.merge_objects(merge_set)
|
||||
merge_set.setdefault(element.is_a(), []).append(obj)
|
||||
id_set.setdefault(element.is_a(), []).append(ifc_definition_id)
|
||||
self.merge_objects(merge_set, id_set)
|
||||
|
||||
def merge_by_type(self):
|
||||
merge_set = {}
|
||||
id_set = {}
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if not isinstance(obj, bpy.types.Object) or not obj.data:
|
||||
continue
|
||||
element = self.file.by_id(ifc_definition_id)
|
||||
if not element.is_a("IfcElement"):
|
||||
continue
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
continue
|
||||
merge_key = str(element_type.id()) + "-" + element_type.Name or "Unnamed"
|
||||
merge_set.setdefault(merge_key, []).append(obj)
|
||||
id_set.setdefault(merge_key, []).append(ifc_definition_id)
|
||||
self.merge_objects(merge_set, id_set)
|
||||
|
||||
def merge_by_material(self):
|
||||
merge_set = {}
|
||||
for obj in self.added_data.values():
|
||||
if not isinstance(obj, bpy.types.Object):
|
||||
id_set = {}
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if not isinstance(obj, bpy.types.Object) or not obj.data:
|
||||
continue
|
||||
if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name:
|
||||
element = self.file.by_id(ifc_definition_id)
|
||||
if not element.is_a("IfcElement"):
|
||||
continue
|
||||
if not obj.material_slots:
|
||||
merge_set.setdefault("no-material", []).append(obj)
|
||||
else:
|
||||
merge_set.setdefault(obj.material_slots[0].name, []).append(obj)
|
||||
self.merge_objects(merge_set)
|
||||
merge_key = obj.material_slots[0].name if obj.material_slots else "no-material"
|
||||
merge_set.setdefault(merge_key, []).append(obj)
|
||||
id_set.setdefault(merge_key, []).append(ifc_definition_id)
|
||||
self.merge_objects(merge_set, id_set)
|
||||
|
||||
def merge_objects(self, merge_set):
|
||||
for ifc_class, objs in merge_set.items():
|
||||
context_override = {}
|
||||
context_override["object"] = context_override["active_object"] = objs[0]
|
||||
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
|
||||
bpy.ops.object.join(context_override)
|
||||
def merge_objects(self, merge_set, id_set):
|
||||
total_objs = sum([len(o) for o in merge_set.values()])
|
||||
cumulative_total = 0
|
||||
merge_set = {k: v for k, v in sorted(merge_set.items(), key=lambda i: len(i[1]), reverse=True)}
|
||||
for group_name, objs in merge_set.items():
|
||||
total_group_objs = len(objs)
|
||||
if total_group_objs < 10:
|
||||
continue
|
||||
merge_potential = total_objs - cumulative_total
|
||||
if merge_potential < 250:
|
||||
print("Merge target achieved")
|
||||
return
|
||||
cumulative_total += total_group_objs
|
||||
print(f"Merging {total_group_objs} objects, {merge_potential} potentially remaining -", group_name)
|
||||
try:
|
||||
target = objs[0]
|
||||
target.data = target.data.copy()
|
||||
context_override = {}
|
||||
context_override["object"] = context_override["active_object"] = target
|
||||
context_override["selected_objects"] = context_override["selected_editable_objects"] = objs
|
||||
bpy.ops.object.join(context_override)
|
||||
target.data.name += "-merge"
|
||||
for ifc_definition_id in id_set[group_name][1:]:
|
||||
del self.added_data[ifc_definition_id]
|
||||
except:
|
||||
print("Merge failed")
|
||||
|
||||
def merge_materials_by_colour(self):
|
||||
cleaned_materials = {}
|
||||
@@ -1282,11 +1412,18 @@ class IfcImporter:
|
||||
|
||||
for material_definition_representation in self.file.by_type("IfcMaterialDefinitionRepresentation"):
|
||||
material = material_definition_representation.RepresentedMaterial
|
||||
blender_material = self.material_creator.materials[material.id()]
|
||||
for representation in material_definition_representation.Representations:
|
||||
for style in [e for e in self.file.traverse(representation) if e.is_a("IfcSurfaceStyle")]:
|
||||
blender_material = self.material_creator.materials[material.id()]
|
||||
self.create_style(style, blender_material)
|
||||
parsed_styles.add(style.id())
|
||||
styles = []
|
||||
for styled_item in representation.Items:
|
||||
styles.extend(styled_item.Styles)
|
||||
while styles:
|
||||
style = styles.pop()
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
self.create_style(style, blender_material)
|
||||
parsed_styles.add(style.id())
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
|
||||
for style in self.file.by_type("IfcSurfaceStyle"):
|
||||
if style.id() in parsed_styles:
|
||||
@@ -1509,9 +1646,10 @@ class IfcImporter:
|
||||
return self.structural_member_collection.objects.link(obj)
|
||||
elif element.is_a("IfcStructuralConnection"):
|
||||
return self.structural_connection_collection.objects.link(obj)
|
||||
elif element.is_a("IfcAnnotation") and self.is_drawing_annotation(element):
|
||||
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
return self.collections[group.GlobalId].objects.link(obj)
|
||||
elif element.is_a("IfcAnnotation"):
|
||||
group = self.get_drawing_group(element)
|
||||
if group:
|
||||
return self.collections[group.GlobalId].objects.link(obj)
|
||||
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if container:
|
||||
@@ -1531,32 +1669,16 @@ class IfcImporter:
|
||||
return element.ObjectType in [
|
||||
"DIMENSION",
|
||||
"EQUAL_DIMENSION",
|
||||
"MISC",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT_LEADER",
|
||||
]
|
||||
|
||||
def is_drawing_annotation(self, element):
|
||||
return element.ObjectType in [
|
||||
"BREAKLINE",
|
||||
"DIMENSION",
|
||||
"DRAWING",
|
||||
"EQUAL_DIMENSION",
|
||||
"GRID",
|
||||
"HIDDEN_LINE",
|
||||
"MISC",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT",
|
||||
"TEXT_LEADER",
|
||||
"LEADER",
|
||||
"HATCH",
|
||||
"LINEWORK",
|
||||
"AREA",
|
||||
]
|
||||
def get_drawing_group(self, element):
|
||||
for rel in element.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToGroup"):
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
@@ -1682,6 +1804,12 @@ class IfcImporter:
|
||||
else:
|
||||
camera.BIMCameraProperties.diagram_scale = "CUSTOM"
|
||||
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
|
||||
if "HasUnderlay" in pset:
|
||||
camera.BIMCameraProperties.has_underlay = pset["HasUnderlay"]
|
||||
if "HasLinework" in pset:
|
||||
camera.BIMCameraProperties.has_linework = pset["HasLinework"]
|
||||
if "HasAnnotation" in pset:
|
||||
camera.BIMCameraProperties.has_annotation = pset["HasAnnotation"]
|
||||
return camera
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
@@ -1724,15 +1852,27 @@ class IfcImporter:
|
||||
and geometry.verts
|
||||
and self.is_point_far_away((geometry.verts[0], geometry.verts[1], geometry.verts[2]))
|
||||
):
|
||||
m = shape.transformation.matrix.data
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
offset_point = np.linalg.inv(mat) @ np.array(
|
||||
(
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
0.0,
|
||||
)
|
||||
)
|
||||
verts = [None] * len(geometry.verts)
|
||||
for i in range(0, len(geometry.verts), 3):
|
||||
verts[i], verts[i + 1], verts[i + 2] = ifcopenshell.util.geolocation.enh2xyz(
|
||||
geometry.verts[i],
|
||||
geometry.verts[i + 1],
|
||||
geometry.verts[i + 2],
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
offset_point[0] * self.unit_scale,
|
||||
offset_point[1] * self.unit_scale,
|
||||
offset_point[2] * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
@@ -1841,14 +1981,18 @@ class IfcImportSettings:
|
||||
self.input_file = None
|
||||
self.diff_file = None
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.should_merge_by_class = False
|
||||
self.should_merge_by_material = False
|
||||
self.merge_mode = None
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_use_native_meshes = False
|
||||
self.should_clean_mesh = True
|
||||
self.should_cache = True
|
||||
self.is_coordinating = True
|
||||
self.deflection_tolerance = 0.001
|
||||
self.angular_tolerance = 0.5
|
||||
self.should_offset_model = False
|
||||
self.model_offset_coordinates = (0, 0, 0)
|
||||
self.distance_limit = 1000
|
||||
self.false_origin = None
|
||||
self.element_offset = 0
|
||||
self.element_limit = 30000
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.elements = set()
|
||||
@@ -1866,16 +2010,18 @@ class IfcImportSettings:
|
||||
settings.diff_file = scene_diff.diff_json_file
|
||||
settings.collection_mode = props.collection_mode
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.should_merge_by_class = props.should_merge_by_class
|
||||
settings.should_merge_by_material = props.should_merge_by_material
|
||||
settings.merge_mode = props.merge_mode
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_use_native_meshes = props.should_use_native_meshes
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = props.should_cache
|
||||
settings.is_coordinating = props.is_coordinating
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.should_offset_model = props.should_offset_model
|
||||
settings.model_offset_coordinates = (
|
||||
[float(o) for o in props.model_offset_coordinates.split(",")]
|
||||
if props.model_offset_coordinates
|
||||
else (0, 0, 0)
|
||||
)
|
||||
settings.distance_limit = props.distance_limit
|
||||
settings.false_origin = [float(o) for o in props.false_origin.split(",")] if props.false_origin else None
|
||||
if settings.false_origin == [0, 0, 0]:
|
||||
settings.false_origin = None
|
||||
settings.element_offset = props.element_offset
|
||||
settings.element_limit = props.element_limit
|
||||
return settings
|
||||
|
||||
@@ -768,6 +768,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
if blender_topic is None:
|
||||
return False
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
return topic.viewpoints
|
||||
|
||||
@@ -880,7 +882,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
# Operators with context overrides are used because they are
|
||||
# significantly faster than looping through all objects
|
||||
|
||||
exception_global_ids = [v.ifc_guid for v in viewpoint.components.visibility.exceptions]
|
||||
exception_global_ids = {v.ifc_guid for v in viewpoint.components.visibility.exceptions}
|
||||
|
||||
if viewpoint.components.visibility.default_visibility:
|
||||
old = context.area.type
|
||||
|
||||
@@ -95,7 +95,7 @@ def getBcfViewpoints(self, context, force_update=False):
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = props.active_topic
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name)
|
||||
viewpoints = bcfxml.get_viewpoints(topic.name) if topic else {}
|
||||
bcfviewpoints_enum.extend([(v, f"Viewpoint {i+1}", "") for i, v in enumerate(viewpoints.keys())])
|
||||
return bcfviewpoints_enum
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import blenderbim.bim.import_ifc as import_ifc
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
space_collection = bpy.data.collections.get(blender_space.name)
|
||||
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
|
||||
|
||||
@@ -48,7 +49,8 @@ class BIM_PT_brickschema(Panel):
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "namespace", text="")
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
prop_with_search(row, self.props, "brick_equipment_class", text="")
|
||||
row.prop(self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
@@ -101,7 +103,7 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "libraries")
|
||||
prop_with_search(row, self.props, "libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import operator, workspace
|
||||
|
||||
classes = (
|
||||
operator.CadArcFrom2Points,
|
||||
operator.CadArcFrom3Points,
|
||||
operator.CadFillet,
|
||||
operator.CadMitre,
|
||||
operator.CadTrimExtend,
|
||||
workspace.CadHotkey,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.CadTool)
|
||||
@@ -0,0 +1,345 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import mathutils
|
||||
import bpy_extras
|
||||
import blenderbim.tool as tool
|
||||
|
||||
messages = {
|
||||
"SHARED_VERTEX": "Shared Vertex, no intersection possible",
|
||||
"PARALLEL_EDGES": "Edges Parallel, no intersection possible",
|
||||
"NON_PLANAR_EDGES": "Non Planar Edges, no clean intersection point",
|
||||
}
|
||||
|
||||
|
||||
class CadTrimExtend(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_trim_extend"
|
||||
bl_label = "CAD Trim / Extend"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def cancel_message(self, msg):
|
||||
print(msg)
|
||||
self.report({"WARNING"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def execute(self, context):
|
||||
# final attempt to enter unfragmented bm/mesh
|
||||
# ghastly, but what can I do? it works with these
|
||||
# fails without.
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = context.active_object
|
||||
me = obj.data
|
||||
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
if len(edges) == 2:
|
||||
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="T")
|
||||
if isinstance(message, set):
|
||||
msg = messages.get(message.pop())
|
||||
return self.cancel_message(msg)
|
||||
bm = message
|
||||
else:
|
||||
return self.cancel_message("select two edges!")
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(me, loop_triangles=True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadMitre(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_mitre"
|
||||
bl_label = "CAD Mitre"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def cancel_message(self, msg):
|
||||
print(msg)
|
||||
self.report({"WARNING"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def execute(self, context):
|
||||
# final attempt to enter unfragmented bm/mesh
|
||||
# ghastly, but what can I do? it works with these
|
||||
# fails without.
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = context.active_object
|
||||
me = obj.data
|
||||
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
if len(edges) == 2:
|
||||
message = tool.Cad.do_vtx_if_appropriate(bm, edges, mode="V")
|
||||
if isinstance(message, set):
|
||||
msg = messages.get(message.pop())
|
||||
return self.cancel_message(msg)
|
||||
bm = message
|
||||
else:
|
||||
return self.cancel_message("select two edges!")
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(me, loop_triangles=True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadFillet(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_fillet"
|
||||
bl_label = "CAD Fillet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=0, default=1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = bpy.context.active_object
|
||||
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
mesh = obj.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_edges = [e for e in bm.edges if e.select]
|
||||
if len(selected_edges) != 2:
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Assume the user has selected two edges sharing a vert, but merge verts
|
||||
# just in case the vertex is coincident but not shared.
|
||||
all_verts = list(set(selected_edges[0].verts) | set(selected_edges[1].verts))
|
||||
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
|
||||
|
||||
# Subdivide one of the edges to give us an extra vert to play with
|
||||
# without damaging any faces that already exist.
|
||||
cut = bmesh.ops.bisect_edges(bm, edges=[selected_edges[1]], cuts=1)
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
|
||||
is_wire = selected_edges[0].is_wire
|
||||
|
||||
# Recover the original intention of the selected edges after our bisect operation
|
||||
cut_edges = cut["geom_split"][1:]
|
||||
selected_edges = [e for e in bm.edges if e.select and e not in cut_edges]
|
||||
if set(selected_edges[0].verts) & set(cut_edges[0].verts):
|
||||
selected_edges.append(cut_edges[0])
|
||||
elif set(selected_edges[0].verts) & set(cut_edges[1].verts):
|
||||
selected_edges.append(cut_edges[1])
|
||||
|
||||
# Calculate the distance to slide each edge to make space for the fillet arc
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
dir1 = (v1.co - shared_vert.co).normalized()
|
||||
dir2 = (v2.co - shared_vert.co).normalized()
|
||||
edge_angle = dir1.angle(dir2)
|
||||
slide_distance = self.radius / math.tan(edge_angle / 2)
|
||||
|
||||
angle = math.pi - edge_angle
|
||||
fillet_v1 = shared_vert.co + (dir1 * slide_distance)
|
||||
fillet_v2 = shared_vert.co + (dir2 * slide_distance)
|
||||
|
||||
normal = mathutils.geometry.normal([v1.co, shared_vert.co, v2.co])
|
||||
|
||||
center = mathutils.geometry.intersect_line_line(
|
||||
fillet_v1, fillet_v1 + normal.cross(dir1), fillet_v2, fillet_v2 + normal.cross(dir2)
|
||||
)[0]
|
||||
|
||||
v = bm.verts.new(fillet_v1)
|
||||
steps = self.resolution * 4 if self.resolution else 1
|
||||
spin = bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=steps, angle=angle)
|
||||
|
||||
bmesh.ops.pointmerge(bm, verts=[shared_vert, v], merge_co=v.co)
|
||||
bmesh.ops.pointmerge(bm, verts=[v2, spin["geom_last"][0]], merge_co=spin["geom_last"][0].co)
|
||||
|
||||
# If faces are involved, then the chamfer edge protects existing faces from the newly created fillet.
|
||||
# If no faces are involved, we delete the chamfer edge.
|
||||
if is_wire:
|
||||
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
|
||||
bm.edges.remove(chamfer_edge)
|
||||
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadArcFrom2Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_2_points"
|
||||
bl_label = "CAD Arc from 2 Points"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
obj = bpy.context.active_object
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
cursor = bpy.context.scene.cursor.location
|
||||
center = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, cursor)
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
mesh = obj.data
|
||||
mw = obj.matrix_world
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 2:
|
||||
return {"CANCELLED"}
|
||||
v1 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[0].co)
|
||||
v2 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[1].co)
|
||||
l1 = v1 - center
|
||||
l2 = v2 - center
|
||||
angle = l1.angle_signed(l2)
|
||||
|
||||
if self.should_flip:
|
||||
if angle > 0:
|
||||
angle = -((math.pi * 2) - angle)
|
||||
else:
|
||||
angle = (math.pi * 2) + angle
|
||||
|
||||
v = selected_verts[0]
|
||||
bm.verts.remove(selected_verts[1])
|
||||
axis = region_3d.view_rotation @ mathutils.Vector((0, 0, 1))
|
||||
bmesh.ops.spin(
|
||||
bm,
|
||||
geom=[v],
|
||||
axis=mw.inverted().to_quaternion() @ axis,
|
||||
cent=mw.inverted() @ cursor,
|
||||
steps=self.resolution * 4,
|
||||
angle=-angle,
|
||||
)
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CadArcFrom3Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_3_points"
|
||||
bl_label = "CAD Arc from 3 Points"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
only_recalculate_center: bpy.props.BoolProperty(name="Only Recalculate Center", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for prop in self.__class__.__annotations__.keys():
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
|
||||
obj = bpy.context.edit_object
|
||||
mesh = obj.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 3:
|
||||
return {"CANCELLED"}
|
||||
pts = [v.co for v in selected_verts]
|
||||
center = tool.Cad.get_center_of_arc(pts, obj)
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
|
||||
bpy.context.scene.cursor.location = center
|
||||
if self.only_recalculate_center:
|
||||
return {"FINISHED"}
|
||||
|
||||
center = obj.matrix_world.inverted() @ center
|
||||
|
||||
def get_distance_to_other_points(vert):
|
||||
other_verts = [v for v in selected_verts if v != vert]
|
||||
total_vector = mathutils.Vector((0, 0, 0))
|
||||
for other_vert in other_verts:
|
||||
total_vector += other_vert.co - vert.co
|
||||
return total_vector.length
|
||||
|
||||
# For three selected points that represent a start, end, and point on an
|
||||
# arc, the start and end verts can be identified by being furthest away
|
||||
# from the other 2 points.
|
||||
arc_end_verts = sorted(selected_verts, key=get_distance_to_other_points)[-2:]
|
||||
normal = mathutils.geometry.normal(pts)
|
||||
|
||||
dir1 = (arc_end_verts[0].co - center).normalized()
|
||||
dir2 = (arc_end_verts[1].co - center).normalized()
|
||||
|
||||
# Let's get the matrix that represents the coordinate system of the arc.
|
||||
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
|
||||
z = normal
|
||||
x = (arc_end_verts[0].co - center).normalized()
|
||||
y = z.cross(x)
|
||||
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
|
||||
dir1 = ((arc_matrix.inverted() @ arc_end_verts[0].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||
dir2 = ((arc_matrix.inverted() @ arc_end_verts[1].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||
angle = dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
v = arc_end_verts[0]
|
||||
bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=self.resolution * 4, angle=-angle)
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
mesh.update()
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,93 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
|
||||
|
||||
class CadTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "EDIT_MESH"
|
||||
|
||||
bl_idname = "bim.cad_tool"
|
||||
bl_label = "CAD Tool"
|
||||
bl_description = "Gives you CAD authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = (
|
||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
("bim.cad_hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
("bim.cad_fillet", {"type": "F", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
# Enable Mesh Tools add-on to get this amazing tool
|
||||
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
("bim.cad_arc_from_3_points", {"type": "V", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Mitre", icon="EVENT_T")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Fillet", icon="EVENT_F")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Offset", icon="EVENT_O")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="2-Point Arc", icon="EVENT_C")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="3-Point Arc", icon="EVENT_V")
|
||||
|
||||
|
||||
class CadHotkey(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_hotkey"
|
||||
bl_label = "CAD Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMModelProperties
|
||||
self.has_ifc_class = True
|
||||
try:
|
||||
self.has_ifc_class = bool(self.props.ifc_class)
|
||||
except:
|
||||
pass
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def hotkey_S_E(self):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
@@ -64,8 +64,8 @@ class BIM_PT_context(bpy.types.Panel):
|
||||
row.operator("bim.remove_context", icon="X", text="").context = ifc_context["id"]
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "subcontexts", text="")
|
||||
row.prop(props, "target_views", text="")
|
||||
blenderbim.bim.helper.prop_with_search(row, props, "subcontexts", text="")
|
||||
blenderbim.bim.helper.prop_with_search(row, props, "target_views", text="")
|
||||
op = row.operator("bim.add_context", icon="ADD", text="")
|
||||
op.context_type = ifc_context["context_type"]
|
||||
op.context_identifier = props.subcontexts
|
||||
|
||||
@@ -112,6 +112,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcSpace")
|
||||
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
failures = []
|
||||
|
||||
@@ -25,6 +25,7 @@ classes = (
|
||||
operator.SelectDiffOldFile,
|
||||
operator.SelectDiffNewFile,
|
||||
operator.ExecuteIfcDiff,
|
||||
prop.Relationships,
|
||||
prop.DiffProperties,
|
||||
ui.BIM_PT_diff,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
DiffData.is_loaded = False
|
||||
|
||||
|
||||
class DiffData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
diff_json_file = None
|
||||
diff = None
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"diff_json": cls.diff_json(), "changes": cls.changes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def diff_json(cls):
|
||||
props = bpy.context.scene.DiffProperties
|
||||
if not props.diff_json_file:
|
||||
cls.diff = None
|
||||
return
|
||||
if props.diff_json_file != cls.diff_json_file:
|
||||
cls.diff_json_file = props.diff_json_file
|
||||
with open(props.diff_json_file, "r") as file:
|
||||
cls.diff = json.load(file)
|
||||
return cls.diff
|
||||
|
||||
@classmethod
|
||||
def changes(cls):
|
||||
diff = cls.diff_json()
|
||||
if not diff:
|
||||
return {}
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not hasattr(element, "GlobalId"):
|
||||
return {}
|
||||
return {k.upper().replace("_", " "): str(v) for k, v in diff["changed"].get(element.GlobalId, {}).items()}
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import ifccsv
|
||||
import ifcopenshell
|
||||
import json
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -44,21 +45,23 @@ class VisualiseDiff(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
# ifc_file = IfcStore.get_file() # In case we get from Store
|
||||
ifc_file = ifcopenshell.open(context.scene.DiffProperties.diff_new_file) # for Now refer to the new file
|
||||
ifc_file = tool.Ifc.get()
|
||||
with open(context.scene.DiffProperties.diff_json_file, "r") as file:
|
||||
diff = json.load(file)
|
||||
for obj in context.visible_objects:
|
||||
obj.color = (1.0, 1.0, 1.0, 0.2)
|
||||
global_id = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId
|
||||
obj.color = (1.0, 1.0, 1.0, 1.0)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
global_id = getattr(element, "GlobalId", None)
|
||||
if not global_id:
|
||||
continue
|
||||
if global_id in diff["deleted"]:
|
||||
obj.color = (1.0, 0.0, 0.0, 0.2)
|
||||
obj.color = (1.0, 0.0, 0.0, 1.0)
|
||||
elif global_id in diff["added"]:
|
||||
obj.color = (0.0, 1.0, 0.0, 0.2)
|
||||
obj.color = (0.0, 1.0, 0.0, 1.0)
|
||||
elif global_id in diff["changed"]:
|
||||
obj.color = (0.0, 0.0, 1.0, 0.2)
|
||||
obj.color = (0.0, 0.0, 1.0, 1.0)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
@@ -113,9 +116,11 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
context.scene.DiffProperties.diff_old_file,
|
||||
context.scene.DiffProperties.diff_new_file,
|
||||
self.filepath,
|
||||
context.scene.DiffProperties.diff_relationships.split(),
|
||||
[r.relationship for r in context.scene.DiffProperties.diff_relationships],
|
||||
context.scene.DiffProperties.diff_filter_elements,
|
||||
)
|
||||
ifc_diff.diff()
|
||||
diff = ifc_diff.diff()
|
||||
ifc_diff.export()
|
||||
context.scene.DiffProperties.diff_json_file = self.filepath
|
||||
context.scene.DiffProperties.diff_result = diff
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.module.diff.data import DiffData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -31,8 +32,21 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def update_diff_json_file(self, context):
|
||||
DiffData.data["diff_json"] = DiffData.diff_json()
|
||||
|
||||
|
||||
class Relationships(PropertyGroup):
|
||||
relationship: EnumProperty(
|
||||
name="Relationship",
|
||||
items=[(r, r.capitalize(), r) for r in ["type", "property", "container", "aggregate", "classification"]],
|
||||
)
|
||||
|
||||
|
||||
class DiffProperties(PropertyGroup):
|
||||
diff_json_file: StringProperty(default="", name="Diff JSON File")
|
||||
diff_json_file: StringProperty(default="", name="Diff JSON File", update=update_diff_json_file)
|
||||
diff_old_file: StringProperty(default="", name="Diff Old IFC File")
|
||||
diff_new_file: StringProperty(default="", name="Diff New IFC File")
|
||||
diff_relationships: StringProperty(default="", name="Diff Relationships")
|
||||
diff_relationships: CollectionProperty(type=Relationships, name="Diff Relationships")
|
||||
diff_filter_elements: StringProperty(default="", name="Diff Filter")
|
||||
diff_result: StringProperty(default="", name="Diff Result")
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.diff.data import DiffData
|
||||
import blenderbim.tool as tool
|
||||
import json
|
||||
|
||||
|
||||
class BIM_PT_diff(Panel):
|
||||
@@ -30,6 +33,9 @@ class BIM_PT_diff(Panel):
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
if not DiffData.is_loaded:
|
||||
DiffData.load()
|
||||
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
|
||||
@@ -48,12 +54,41 @@ class BIM_PT_diff(Panel):
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_relationships")
|
||||
row.context_pointer_set("bim_prop_group", bim_properties)
|
||||
add = row.operator("bim.edit_blender_collection", icon="ADD", text="")
|
||||
add.option = "add"
|
||||
add.collection = "diff_relationships"
|
||||
|
||||
for index, r in enumerate(bim_properties.diff_relationships):
|
||||
row = layout.row(align=True)
|
||||
row.context_pointer_set("bim_prop_group", bim_properties)
|
||||
row.prop(r, "relationship", text=" ")
|
||||
remove = row.operator("bim.edit_blender_collection", icon="REMOVE", text="")
|
||||
remove.option = "remove"
|
||||
remove.collection = "diff_relationships"
|
||||
remove.index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_filter_elements")
|
||||
row.operator("bim.ifc_selector", icon="FILTER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.execute_ifc_diff")
|
||||
if bim_properties.diff_result:
|
||||
row = layout.row()
|
||||
row.alignment = "CENTER"
|
||||
row.label(text=bim_properties.diff_result)
|
||||
|
||||
# TODO: show if there ifc diff operation is sucessful
|
||||
row = layout.row(align=True)
|
||||
row.prop(bim_properties, "diff_json_file")
|
||||
row.operator("bim.select_diff_json_file", icon="FILE_FOLDER", text="")
|
||||
row.operator("bim.visualise_diff", icon="HIDE_OFF", text="")
|
||||
|
||||
if DiffData.data["changes"]:
|
||||
row = layout.row()
|
||||
row.label(text="Diff Results:")
|
||||
for key, value in DiffData.data["changes"].items():
|
||||
row = layout.row()
|
||||
row.label(text=key)
|
||||
row.label(text=value)
|
||||
|
||||
@@ -37,19 +37,21 @@ classes = (
|
||||
operator.CopyGrid,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableEditingAssignedProduct,
|
||||
operator.DisableEditingDrawings,
|
||||
operator.DisableEditingSchedules,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
operator.DisableEditingTextProduct,
|
||||
operator.EditAssignedProduct,
|
||||
operator.EditText,
|
||||
operator.EditTextProduct,
|
||||
operator.EditVectorStyle,
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingText,
|
||||
operator.EnableEditingTextProduct,
|
||||
operator.ExpandSheet,
|
||||
operator.GenerateReferences,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadSchedules,
|
||||
operator.LoadSheets,
|
||||
operator.OpenSchedule,
|
||||
operator.OpenSheet,
|
||||
operator.OpenView,
|
||||
operator.RemoveDrawing,
|
||||
@@ -61,7 +63,6 @@ classes = (
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.SelectScheduleFile,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
@@ -70,12 +71,14 @@ classes = (
|
||||
prop.DocProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_product_assignments,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_UL_drawinglist,
|
||||
ui.BIM_UL_sheets,
|
||||
@@ -90,6 +93,7 @@ classes = (
|
||||
def register():
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
|
||||
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
@@ -99,6 +103,8 @@ def register():
|
||||
def unregister():
|
||||
del bpy.types.Scene.DocProperties
|
||||
del bpy.types.Camera.BIMCameraProperties
|
||||
del bpy.types.Object.BIMAssignedProductProperties
|
||||
del bpy.types.Object.BIMTextProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
|
||||
|
||||
@@ -151,23 +151,32 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_annotation_obj(object_type, data_type):
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
def get_annotation_obj(drawing, object_type, data_type):
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world[0][3] = co1.x
|
||||
matrix_world[1][3] = co1.y
|
||||
matrix_world[2][3] = co1.z
|
||||
collection = camera.users_collection[0]
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
elif object_type == "TEXT_LEADER":
|
||||
elif object_type in ("TEXT_LEADER", "SECTION_LEVEL"):
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.ObjectType == object_type:
|
||||
return obj
|
||||
if object_type != "ANGLE":
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.ObjectType == object_type:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new(object_type)
|
||||
elif data_type == "curve":
|
||||
@@ -175,12 +184,14 @@ class Annotator:
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_placeholder_coords():
|
||||
camera = bpy.context.scene.camera
|
||||
def get_placeholder_coords(camera=None):
|
||||
if not camera:
|
||||
camera = bpy.context.scene.camera
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
if bpy.context.scene.render.resolution_x > bpy.context.scene.render.resolution_y:
|
||||
y = (
|
||||
|
||||
@@ -22,18 +22,40 @@ import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
ProductAssignmentsData.is_loaded = False
|
||||
TextData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
|
||||
|
||||
class ProductAssignmentsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"relating_product": cls.relating_product()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def relating_product(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation"):
|
||||
return
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
name = rel.RelatingProduct.Name or "Unnamed"
|
||||
return f"{rel.RelatingProduct.is_a()}/{name}"
|
||||
|
||||
|
||||
class TextData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes(), "relating_product": cls.relating_product()}
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -48,15 +70,6 @@ class TextData:
|
||||
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def relating_product(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
return
|
||||
for rel in element.HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct"):
|
||||
return rel.RelatingProduct.Name or "Unnamed"
|
||||
|
||||
|
||||
class SheetsData:
|
||||
data = {}
|
||||
@@ -94,3 +107,17 @@ class DrawingsData:
|
||||
)
|
||||
return results
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
|
||||
class SchedulesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"total_schedules": cls.total_schedules()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def total_schedules(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
|
||||
|
||||
@@ -170,12 +170,32 @@ class BaseDecorator:
|
||||
returns: iterable of blender objects
|
||||
"""
|
||||
results = []
|
||||
decoration_presets = (
|
||||
"DIMENSION",
|
||||
"TEXT_LEADER",
|
||||
"STAIR_ARROW",
|
||||
"HIDDEN_LINE",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"BREAKLINE",
|
||||
"GRID",
|
||||
"ELEVATION",
|
||||
"SECTION",
|
||||
"TEXT",
|
||||
)
|
||||
for obj in collection.all_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == self.objecttype:
|
||||
results.append(obj)
|
||||
if element.is_a("IfcAnnotation"):
|
||||
if element.ObjectType == self.objecttype:
|
||||
results.append(obj)
|
||||
elif (
|
||||
self.objecttype == "MISC"
|
||||
and element.ObjectType not in decoration_presets
|
||||
and isinstance(obj.data, bpy.types.Mesh)
|
||||
):
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
def get_path_geom(self, obj, topo=True):
|
||||
@@ -247,7 +267,7 @@ class BaseDecorator:
|
||||
"""perform actual drawing stuff"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def draw_lines(self, context, obj, vertices, indices, topology=None):
|
||||
def draw_lines(self, context, obj, vertices, indices, topology=None, is_scale_dependant=True):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
color = context.scene.DocProperties.decorations_colour
|
||||
@@ -277,14 +297,15 @@ class BaseDecorator:
|
||||
self.shader.uniform_float("winsize", (region.width, region.height))
|
||||
self.shader.uniform_float("color", color)
|
||||
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
# 0.00025 is a magic constant number I visually discovered to get the right number.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
viewport_drawing_scale = 0.00025 * mm_to_px
|
||||
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
|
||||
if is_scale_dependant:
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
# 0.00025 is a magic constant number I visually discovered to get the right number.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
viewport_drawing_scale = 0.00025 * mm_to_px
|
||||
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
|
||||
|
||||
batch.draw(self.shader)
|
||||
|
||||
@@ -357,11 +378,11 @@ class BaseDecorator:
|
||||
elif "/" in precision:
|
||||
precision = int(precision.split("/")[1])
|
||||
elif unit_system == "METRIC":
|
||||
precision = 3
|
||||
precision = 4
|
||||
else:
|
||||
return
|
||||
|
||||
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision=precision, split_unit=True)
|
||||
return bpy.utils.units.to_string(unit_system, "LENGTH", value, precision=precision)
|
||||
|
||||
|
||||
class DimensionDecorator(BaseDecorator):
|
||||
@@ -459,27 +480,8 @@ class DimensionDecorator(BaseDecorator):
|
||||
self.draw_label(context, text, p0 + (dir) * 0.5, dir)
|
||||
|
||||
|
||||
class EqualityDecorator(DimensionDecorator):
|
||||
"""Decorator for equality objects
|
||||
- outlines each segment
|
||||
- augments with arrows on both sides
|
||||
- puts 'EQ' label
|
||||
"""
|
||||
|
||||
objecttype = "EQUAL_DIMENSION"
|
||||
|
||||
def draw_labels(self, context, obj, vertices, indices):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
for i0, i1 in indices:
|
||||
v0 = Vector(vertices[i0])
|
||||
v1 = Vector(vertices[i1])
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
dir = p1 - p0
|
||||
if dir.length < 1:
|
||||
continue
|
||||
self.draw_label(context, "EQ", p0 + (dir) * 0.5, dir)
|
||||
class DiameterDecorator(DimensionDecorator):
|
||||
objecttype = "DIAMETER"
|
||||
|
||||
|
||||
class LeaderDecorator(BaseDecorator):
|
||||
@@ -568,6 +570,98 @@ class LeaderDecorator(BaseDecorator):
|
||||
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class RadiusDecorator(BaseDecorator):
|
||||
"""Decorating text with arrows
|
||||
- head point with arrow
|
||||
"""
|
||||
|
||||
objecttype = "RADIUS"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 16.0
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
in uint type[];
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
uint t0 = type[0], t1 = type[1];
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
vec4 gap1 = vec4(0);
|
||||
|
||||
vec4 p;
|
||||
|
||||
// end edge arrow for last segment
|
||||
if (t1 == 2u) {
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
|
||||
}
|
||||
|
||||
// stem, adjusted for and arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p1w - gap1;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
def get_spline_end(self, obj):
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if not spline_points:
|
||||
return Vector((0, 0, 0))
|
||||
return obj.matrix_world @ spline_points[0].co
|
||||
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, topo = self.get_path_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, topo)
|
||||
self.draw_labels(context, obj)
|
||||
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
|
||||
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if spline_points:
|
||||
length = (spline_points[-1].co - spline_points[-2].co).length
|
||||
text = self.format_value(context, length)
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class StairDecorator(BaseDecorator):
|
||||
"""Decorating stairs
|
||||
- head point with arrow
|
||||
@@ -671,6 +765,7 @@ class HiddenDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -725,10 +820,61 @@ class HiddenDecorator(BaseDecorator):
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
|
||||
class MiscDecorator(HiddenDecorator):
|
||||
class MiscDecorator(BaseDecorator):
|
||||
objecttype = "MISC"
|
||||
|
||||
FRAG_GLSL = BaseDecorator.FRAG_GLSL
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
|
||||
out float dist; // distance from starging point along segment
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
vec4 gap = dir * 1;
|
||||
|
||||
vec4 p;
|
||||
|
||||
// NB: something should be used to affect position, otherwise compiler eliminates winsize
|
||||
|
||||
dist = 0;
|
||||
p = p0w + gap;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
dist = length(edge);
|
||||
p = p1w - gap;
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
FRAG_GLSL = """
|
||||
uniform vec4 color;
|
||||
in vec2 gl_FragCoord;
|
||||
in float dist;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = color;
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
if obj.data.is_editmode:
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
verts, idxs = self.get_mesh_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, is_scale_dependant=False)
|
||||
|
||||
|
||||
class LevelDecorator(BaseDecorator):
|
||||
@@ -923,7 +1069,7 @@ class SectionLevelDecorator(LevelDecorator):
|
||||
|
||||
|
||||
class BreakDecorator(BaseDecorator):
|
||||
"""Decorator for dimension objects
|
||||
"""Decorator for breakline objects
|
||||
- first edge of a mesh with zigzag thingy in the middle
|
||||
|
||||
Uses first two vertices in verts list.
|
||||
@@ -990,21 +1136,10 @@ class BreakDecorator(BaseDecorator):
|
||||
|
||||
def decorate(self, context, obj):
|
||||
if obj.data.is_editmode:
|
||||
verts = self.get_editmesh_geom(obj)
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
verts = self.get_mesh_geom(obj)
|
||||
self.draw_lines(context, obj, verts, [(0, 1)])
|
||||
|
||||
def get_mesh_geom(self, obj):
|
||||
# first vertices only
|
||||
vertices = [obj.matrix_world @ obj.data.vertices[i].co for i in (0, 1)]
|
||||
return vertices
|
||||
|
||||
def get_editmesh_geom(self, obj):
|
||||
# first vertices only
|
||||
mesh = bmesh.from_edit_mesh(obj.data)
|
||||
vertices = [obj.matrix_world @ v.co for v in mesh.edges[0].verts]
|
||||
return vertices
|
||||
verts, idxs = self.get_mesh_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
|
||||
class GridDecorator(BaseDecorator):
|
||||
@@ -1363,7 +1498,6 @@ class SectionDecorator(LevelDecorator):
|
||||
self.draw_lines(context, obj, verts, idxs)
|
||||
|
||||
|
||||
|
||||
class TextDecorator(BaseDecorator):
|
||||
"""Decorator for text objects
|
||||
- draws the text next to the origin
|
||||
@@ -1451,10 +1585,11 @@ class TextDecorator(BaseDecorator):
|
||||
class DecorationsHandler:
|
||||
decorators_classes = [
|
||||
DimensionDecorator,
|
||||
EqualityDecorator,
|
||||
GridDecorator,
|
||||
HiddenDecorator,
|
||||
LeaderDecorator,
|
||||
RadiusDecorator,
|
||||
DiameterDecorator,
|
||||
MiscDecorator,
|
||||
PlanLevelDecorator,
|
||||
SectionLevelDecorator,
|
||||
|
||||
@@ -40,7 +40,8 @@ import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.export_ifc
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
from mathutils import Vector
|
||||
from timeit import default_timer as timer
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data as GroupData
|
||||
@@ -49,11 +50,26 @@ from ifcopenshell.api.pset.data import Data as PsetData
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
class profile:
|
||||
"""
|
||||
A python context manager timing utility
|
||||
"""
|
||||
|
||||
def __init__(self, task):
|
||||
self.task = task
|
||||
|
||||
def __enter__(self):
|
||||
self.start = timer()
|
||||
|
||||
def __exit__(self, *args):
|
||||
print(self.task, timer() - self.start)
|
||||
|
||||
|
||||
def open_with_user_command(user_command, path):
|
||||
if user_command:
|
||||
commands = eval(user_command)
|
||||
for command in commands:
|
||||
subprocess.run(command)
|
||||
subprocess.Popen(command)
|
||||
else:
|
||||
webbrowser.open("file://" + path)
|
||||
|
||||
@@ -115,52 +131,75 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
self.file = IfcStore.get_file()
|
||||
self.time = None
|
||||
start = time.time()
|
||||
self.profile_code("Start drawing generation process")
|
||||
self.props = context.scene.DocProperties
|
||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||
self.get_scale()
|
||||
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
self.profile_code("Generate underlay")
|
||||
linework_svg = self.generate_linework(context)
|
||||
self.profile_code("Generate linework")
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
self.profile_code("Generate annotation")
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
self.profile_code("Combine SVG layers")
|
||||
|
||||
with profile("Drawing generation process"):
|
||||
|
||||
with profile("Initialize drawing generation process"):
|
||||
self.props = context.scene.DocProperties
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||
self.get_scale()
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
|
||||
self.svg_writer = svgwriter.SvgWriter()
|
||||
self.svg_writer.human_scale = self.human_scale
|
||||
self.svg_writer.scale = self.scale
|
||||
self.svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
self.svg_writer.camera = self.camera
|
||||
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
||||
self.svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||
|
||||
with profile("Generate underlay"):
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
|
||||
with profile("Generate linework"):
|
||||
linework_svg = self.generate_linework(context)
|
||||
|
||||
with profile("Generate annotation"):
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
print("Total Time: {:.2f}".format(time.time() - start))
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def profile_code(self, message):
|
||||
if not self.time:
|
||||
self.time = time.time()
|
||||
print("{} :: {:.2f}".format(message, time.time() - self.time))
|
||||
self.time = time.time()
|
||||
def get_camera_dimensions(self):
|
||||
render = bpy.context.scene.render
|
||||
if self.is_landscape(render):
|
||||
width = self.camera.data.ortho_scale
|
||||
height = width / render.resolution_x * render.resolution_y
|
||||
else:
|
||||
height = self.camera.data.ortho_scale
|
||||
width = height / render.resolution_y * render.resolution_x
|
||||
return width, height
|
||||
|
||||
def combine_svgs(self, context, underlay, linework, annotation):
|
||||
# Hacky :)
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "diagrams", self.drawing_name + ".svg")
|
||||
with open(svg_path, "w") as outfile:
|
||||
has_boilerplate = False
|
||||
pset = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]
|
||||
self.svg_writer.create_blank_svg(svg_path).define_boilerplate(
|
||||
pset.get("Stylesheet"), pset.get("Markers"), pset.get("Symbols"), pset.get("Patterns")
|
||||
)
|
||||
boilerplate = self.svg_writer.svg.tostring()
|
||||
outfile.write(boilerplate.replace("</svg>", ""))
|
||||
if underlay:
|
||||
with open(underlay) as infile:
|
||||
for line in infile:
|
||||
if "<svg " in line:
|
||||
line = line.replace('">', '" xmlns:ifc="http://www.ifcopenshell.org/ns">')
|
||||
if "</svg>" in line:
|
||||
for i, line in enumerate(infile):
|
||||
if i < 2:
|
||||
continue
|
||||
elif "</svg>" in line:
|
||||
continue
|
||||
outfile.write(line)
|
||||
shutil.copyfile(underlay[0:-4] + ".png", svg_path[0:-4] + "-underlay.png")
|
||||
has_boilerplate = True
|
||||
if linework:
|
||||
with open(linework) as infile:
|
||||
should_skip = False
|
||||
for i, line in enumerate(infile):
|
||||
if has_boilerplate and i == 0:
|
||||
if i == 0:
|
||||
continue
|
||||
if "</svg>" in line:
|
||||
continue
|
||||
@@ -173,11 +212,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
elif should_skip:
|
||||
continue
|
||||
outfile.write(line)
|
||||
has_boilerplate = True
|
||||
if annotation:
|
||||
with open(annotation) as infile:
|
||||
for i, line in enumerate(infile):
|
||||
if has_boilerplate and i in [0, 1]:
|
||||
if i < 2:
|
||||
continue
|
||||
if "</svg>" in line:
|
||||
continue
|
||||
@@ -186,13 +224,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def generate_underlay(self, context):
|
||||
if not self.props.has_underlay:
|
||||
if not self.cprops.has_underlay:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-underlay.svg")
|
||||
context.scene.render.filepath = svg_path[0:-4] + ".png"
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[
|
||||
self.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
]
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
|
||||
|
||||
if drawing_style.render_type == "DEFAULT":
|
||||
bpy.ops.render.render(write_still=True)
|
||||
@@ -224,41 +260,24 @@ class CreateDrawing(bpy.types.Operator):
|
||||
for name, value in previous_visibility.items():
|
||||
bpy.data.objects[name].hide_set(value)
|
||||
|
||||
svg_writer = svgwriter.SvgWriter()
|
||||
render = context.scene.render
|
||||
if self.is_landscape(render):
|
||||
width = self.camera.data.ortho_scale
|
||||
height = width / render.resolution_x * render.resolution_y
|
||||
else:
|
||||
height = self.camera.data.ortho_scale
|
||||
width = height / render.resolution_y * render.resolution_x
|
||||
svg_writer.human_scale = self.human_scale
|
||||
svg_writer.scale = self.scale
|
||||
svg_writer.output = svg_path
|
||||
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
svg_writer.vector_style = drawing_style.vector_style
|
||||
svg_writer.camera = self.camera
|
||||
svg_writer.camera_width = width
|
||||
svg_writer.camera_height = height
|
||||
svg_writer.camera_projection = tuple(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||
svg_writer.background_image = context.scene.render.filepath
|
||||
svg_writer.write("underlay")
|
||||
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
|
||||
return svg_path
|
||||
|
||||
def generate_linework(self, context):
|
||||
if not self.props.has_linework:
|
||||
if not self.cprops.has_linework:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||
return svg_path
|
||||
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_guids = exporter.sync_deletions()
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
with profile("sync"):
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_guids = exporter.sync_deletions()
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
|
||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||
edited_guids = set()
|
||||
@@ -303,46 +322,52 @@ class CreateDrawing(bpy.types.Operator):
|
||||
body_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
|
||||
elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
|
||||
if target_view_contexts and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
geom_settings.set_context_ids(target_view_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
elements -= processed
|
||||
with profile("Processing target view context"):
|
||||
if target_view_contexts and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
geom_settings.set_context_ids(target_view_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
elements -= processed
|
||||
|
||||
if body_contexts and elements:
|
||||
with profile("Processing body context"):
|
||||
if body_contexts and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
geom_settings.set_context_ids(body_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
with profile("Camera element"):
|
||||
# @todo tfk: is this needed?
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
geom_settings.set_context_ids(body_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
self.serialiser.finalize()
|
||||
with profile("Finalizing"):
|
||||
self.serialiser.finalize()
|
||||
results = self.svg_buffer.get_value()
|
||||
|
||||
root = etree.fromstring(results)
|
||||
self.enrich_linework_with_metadata(root)
|
||||
self.move_projection_to_bottom(root)
|
||||
with open(svg_path, "wb") as svg:
|
||||
svg.write(etree.tostring(root))
|
||||
with profile("Post processing"):
|
||||
root = etree.fromstring(results)
|
||||
self.enrich_linework_with_metadata(root)
|
||||
self.move_projection_to_bottom(root)
|
||||
with open(svg_path, "wb") as svg:
|
||||
svg.write(etree.tostring(root))
|
||||
|
||||
return svg_path
|
||||
|
||||
@@ -410,78 +435,19 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not self.props.has_annotation:
|
||||
if not self.cprops.has_annotation:
|
||||
return
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-annotation.svg")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
|
||||
return svg_path
|
||||
|
||||
camera = self.camera
|
||||
svg_writer = svgwriter.SvgWriter()
|
||||
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
|
||||
annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
|
||||
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[
|
||||
camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
]
|
||||
self.svg_writer.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||
self.svg_writer.create_blank_svg(svg_path).draw_annotations(annotations).save()
|
||||
|
||||
render = context.scene.render
|
||||
if self.is_landscape(render):
|
||||
width = camera.data.ortho_scale
|
||||
height = width / render.resolution_x * render.resolution_y
|
||||
else:
|
||||
height = camera.data.ortho_scale
|
||||
width = height / render.resolution_y * render.resolution_x
|
||||
|
||||
svg_writer.human_scale = self.human_scale
|
||||
svg_writer.scale = self.scale
|
||||
svg_writer.output = svg_path
|
||||
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
svg_writer.vector_style = drawing_style.vector_style
|
||||
svg_writer.camera = camera
|
||||
svg_writer.camera_width = width
|
||||
svg_writer.camera_height = height
|
||||
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||
|
||||
for obj in camera.users_collection[0].objects:
|
||||
if "IfcAnnotation/" not in obj.name:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element.ObjectType == "GRID":
|
||||
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
|
||||
elif obj.type == "CAMERA":
|
||||
continue
|
||||
elif element.ObjectType == "TEXT_LEADER":
|
||||
svg_writer.annotations.setdefault("leader_objs", []).append(obj)
|
||||
elif element.ObjectType == "STAIR_ARROW":
|
||||
svg_writer.annotations["stair_obj"] = obj
|
||||
elif element.ObjectType == "EQUAL_DIMENSION":
|
||||
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
|
||||
elif element.ObjectType == "DIMENSION":
|
||||
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
|
||||
elif element.ObjectType == "ELEVATION":
|
||||
svg_writer.annotations.setdefault("elevation_objs", []).append(obj)
|
||||
elif element.ObjectType == "SECTION":
|
||||
svg_writer.annotations.setdefault("section_objs", []).append(obj)
|
||||
elif element.ObjectType == "BREAKLINE":
|
||||
svg_writer.annotations["break_obj"] = obj
|
||||
elif element.ObjectType == "HIDDEN_LINE":
|
||||
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
|
||||
elif element.ObjectType == "SOLID_LINE":
|
||||
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
|
||||
elif element.ObjectType == "PLAN_LEVEL":
|
||||
svg_writer.annotations.setdefault("plan_level_objs", []).append(obj)
|
||||
elif element.ObjectType == "SECTION_LEVEL":
|
||||
svg_writer.annotations["section_level_obj"] = obj
|
||||
elif element.ObjectType == "TEXT":
|
||||
svg_writer.annotations.setdefault("text_objs", []).append(obj)
|
||||
elif element.ObjectType == "MISC":
|
||||
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
|
||||
|
||||
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
|
||||
# TODO: This was the old 2D annotation box checking system, prepare to deprecate
|
||||
svg_writer.annotations["annotation_objs"] = []
|
||||
|
||||
svg_writer.write("annotation")
|
||||
return svg_writer.output
|
||||
return svg_path
|
||||
|
||||
def get_scale(self):
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
@@ -543,8 +509,11 @@ class AddAnnotation(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not drawing:
|
||||
self.report({"WARNING"}, "Not a BIM camera")
|
||||
return
|
||||
core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
||||
r = core.add_annotation(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing, object_type=self.object_type)
|
||||
if isinstance(r, str):
|
||||
self.report({"WARNING"}, r)
|
||||
|
||||
|
||||
class AddSheet(bpy.types.Operator, Operator):
|
||||
@@ -593,17 +562,11 @@ class AddDrawingToSheet(bpy.types.Operator):
|
||||
|
||||
has_drawing = False
|
||||
for reference in references:
|
||||
new = props.sheets.add()
|
||||
new.ifc_definition_id = reference.id()
|
||||
new.is_sheet = False
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
new.identification = reference.ItemReference or "X"
|
||||
element = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
|
||||
0
|
||||
].RelatedObjects[0]
|
||||
else:
|
||||
new.identification = reference.Identification or "X"
|
||||
element = reference.DocumentRefForObjects[0].RelatedObjects[0]
|
||||
if element == drawing:
|
||||
has_drawing = True
|
||||
@@ -621,7 +584,7 @@ class AddDrawingToSheet(bpy.types.Operator):
|
||||
tool.Ifc.run("document.assign_document", product=drawing, document=reference)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_drawing(drawing, sheet)
|
||||
sheet_builder.add_drawing(reference, drawing, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
return {"FINISHED"}
|
||||
@@ -635,21 +598,18 @@ class RemoveDrawingFromSheet(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
reference = tool.Ifc.get().by_id(self.reference)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
sheet = reference.ReferenceToDocument[0]
|
||||
drawing = [r for r in tool.Ifc.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == reference][
|
||||
0
|
||||
].RelatedObjects[0]
|
||||
else:
|
||||
sheet = reference.ReferencedDocument
|
||||
drawing = reference.DocumentRefForObjects[0].RelatedObjects[0]
|
||||
|
||||
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
sheet = tool.Drawing.get_reference_document(reference)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.remove_drawing(drawing, sheet)
|
||||
sheet_builder.remove_drawing(reference, sheet)
|
||||
|
||||
drawing = tool.Drawing.get_reference_element(reference)
|
||||
if drawing:
|
||||
tool.Ifc.run("document.unassign_document", product=drawing, document=reference)
|
||||
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -667,10 +627,15 @@ class CreateSheets(bpy.types.Operator):
|
||||
scene = context.scene
|
||||
props = scene.DocProperties
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
name = active_sheet.name
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
return {"FINISHED"}
|
||||
|
||||
name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = scene.BIMProperties.data_dir
|
||||
sheet_builder.build(name)
|
||||
sheet_builder.build(sheet)
|
||||
|
||||
svg2pdf_command = context.preferences.addons["blenderbim"].preferences.svg2pdf_command
|
||||
svg2dxf_command = context.preferences.addons["blenderbim"].preferences.svg2dxf_command
|
||||
@@ -777,68 +742,6 @@ class SelectDocIfcFile(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class GenerateReferences(bpy.types.Operator):
|
||||
bl_idname = "bim.generate_references"
|
||||
bl_label = "Generate References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.camera = context.scene.camera
|
||||
self.filter_potential_references()
|
||||
if self.camera.data.BIMCameraProperties.target_view == "PLAN_VIEW":
|
||||
self.generate_grids()
|
||||
if self.camera.data.BIMCameraProperties.target_view == "ELEVATION_VIEW":
|
||||
self.generate_grids()
|
||||
self.generate_levels(context)
|
||||
if self.camera.data.BIMCameraProperties.target_view == "SECTION_VIEW":
|
||||
self.generate_grids()
|
||||
self.generate_levels(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def filter_potential_references(self):
|
||||
for name in ["grids", "levels"]:
|
||||
setattr(self, name, [])
|
||||
for obj in bpy.data.objects:
|
||||
if "IfcGridAxis/" in obj.name:
|
||||
self.grids.append(obj)
|
||||
if "IfcBuildingStorey/" in obj.name:
|
||||
self.levels.append(obj)
|
||||
|
||||
def generate_grids(self):
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def generate_levels(self, context):
|
||||
if self.camera.data.BIMCameraProperties.raster_x > self.camera.data.BIMCameraProperties.raster_y:
|
||||
width = self.camera.data.ortho_scale
|
||||
height = (
|
||||
width / self.camera.data.BIMCameraProperties.raster_x * self.camera.data.BIMCameraProperties.raster_y
|
||||
)
|
||||
else:
|
||||
height = self.camera.data.ortho_scale
|
||||
width = (
|
||||
height / self.camera.data.BIMCameraProperties.raster_y * self.camera.data.BIMCameraProperties.raster_x
|
||||
)
|
||||
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
|
||||
|
||||
width_in_mm = width * 1000
|
||||
real_world_width_in_mm = width_in_mm * scale
|
||||
offset_in_mm = 20
|
||||
offset_percentage = offset_in_mm / real_world_width_in_mm
|
||||
|
||||
for obj in self.levels:
|
||||
projection = self.project_point_onto_camera(obj.location)
|
||||
co1 = self.camera.matrix_world @ Vector((width / 2 - (offset_percentage * width), projection[1], -1))
|
||||
co2 = self.camera.matrix_world @ Vector((-(width / 2), projection[1], -1))
|
||||
annotation.Annotator.add_line_to_annotation(level_obj, context, co1, co2)
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
projection = self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
point.xyz, point.xyz - projection, self.camera.location, projection
|
||||
)
|
||||
|
||||
|
||||
class ResizeText(bpy.types.Operator):
|
||||
bl_idname = "bim.resize_text"
|
||||
bl_label = "Resize Text"
|
||||
@@ -1018,63 +921,56 @@ class RemoveSheet(bpy.types.Operator, Operator):
|
||||
core.remove_sheet(tool.Ifc, tool.Drawing, sheet=tool.Ifc.get().by_id(self.sheet))
|
||||
|
||||
|
||||
class AddSchedule(bpy.types.Operator):
|
||||
class AddSchedule(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_schedule"
|
||||
bl_label = "Add Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.DocProperties.schedules.add()
|
||||
new.name = "SCHEDULE {}".format(len(context.scene.DocProperties.schedules))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveSchedule(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_schedule"
|
||||
bl_label = "Remove Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
props.schedules.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectScheduleFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_schedule_file"
|
||||
bl_label = "Select Documentation IFC File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ods", options={"HIDDEN"})
|
||||
index: bpy.props.IntProperty()
|
||||
filter_glob: bpy.props.StringProperty(default="*.ods;*.xls;*.xlsx", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
props.active_schedule.file = self.filepath
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
core.add_schedule(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
uri=filepath,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class BuildSchedule(bpy.types.Operator):
|
||||
class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_schedule"
|
||||
bl_label = "Remove Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_schedule"
|
||||
bl_label = "Open Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.open_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class BuildSchedule(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.build_schedule"
|
||||
bl_label = "Build Schedule"
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.DocProperties.active_schedule.file
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
schedule = props.active_schedule
|
||||
schedule_creator = scheduler.Scheduler()
|
||||
outfile = os.path.join(context.scene.BIMProperties.data_dir, "schedules", schedule.name + ".svg")
|
||||
schedule_creator.schedule(schedule.file, outfile)
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, outfile)
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.build_schedule(tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class AddScheduleToSheet(bpy.types.Operator):
|
||||
@@ -1090,9 +986,40 @@ class AddScheduleToSheet(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_schedule = props.schedules[props.active_schedule_index]
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
return {"FINISHED"}
|
||||
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
references = sheet.DocumentReferences or []
|
||||
else:
|
||||
references = sheet.HasDocumentReferences or []
|
||||
|
||||
has_schedule = False
|
||||
for reference in references:
|
||||
if reference.Location == tool.Drawing.get_schedule_location(schedule):
|
||||
has_schedule = True
|
||||
break
|
||||
|
||||
if has_schedule:
|
||||
return {"FINISHED"}
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
attributes = {"ItemReference": str(len(sheet.DocumentReferences or []))}
|
||||
else:
|
||||
attributes = {"Identification": str(len(sheet.HasDocumentReferences or []))}
|
||||
attributes["Location"] = tool.Drawing.get_schedule_location(schedule)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_schedule(props.active_schedule.name, props.active_sheet.name)
|
||||
sheet_builder.add_schedule(reference, schedule, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1326,34 +1253,34 @@ class DisableEditingText(bpy.types.Operator, Operator):
|
||||
core.disable_editing_text(tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class EditTextProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_text_product"
|
||||
class EditAssignedProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_assigned_product"
|
||||
bl_label = "Edit Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
product = None
|
||||
if context.active_object.BIMTextProperties.relating_product:
|
||||
product = tool.Ifc.get_entity(context.active_object.BIMTextProperties.relating_product)
|
||||
core.edit_text_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
|
||||
if context.active_object.BIMAssignedProductProperties.relating_product:
|
||||
product = tool.Ifc.get_entity(context.active_object.BIMAssignedProductProperties.relating_product)
|
||||
core.edit_assigned_product(tool.Ifc, tool.Drawing, obj=context.active_object, product=product)
|
||||
|
||||
|
||||
class EnableEditingTextProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_text_product"
|
||||
class EnableEditingAssignedProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_assigned_product"
|
||||
bl_label = "Enable Editing Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_text_product(tool.Drawing, obj=context.active_object)
|
||||
core.enable_editing_assigned_product(tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class DisableEditingTextProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_text_product"
|
||||
class DisableEditingAssignedProduct(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_assigned_product"
|
||||
bl_label = "Disable Editing Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_text_product(tool.Drawing, obj=context.active_object)
|
||||
core.disable_editing_assigned_product(tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class LoadSheets(bpy.types.Operator, Operator):
|
||||
@@ -1374,6 +1301,24 @@ class DisableEditingSheets(bpy.types.Operator, Operator):
|
||||
core.disable_editing_sheets(tool.Drawing)
|
||||
|
||||
|
||||
class LoadSchedules(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_schedules"
|
||||
bl_label = "Load Schedules"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_schedules(tool.Drawing)
|
||||
|
||||
|
||||
class DisableEditingSchedules(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_schedules"
|
||||
bl_label = "Disable Editing Text Product"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_schedules(tool.Drawing)
|
||||
|
||||
|
||||
class LoadDrawings(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_drawings"
|
||||
bl_label = "Load Drawings"
|
||||
|
||||
@@ -72,8 +72,10 @@ def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
scale = scale.split("|")[1]
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if "|" not in scale:
|
||||
return
|
||||
human_scale, scale = scale.split("|")
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
@@ -81,7 +83,9 @@ def update_diagram_scale(self, context):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale})
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
|
||||
)
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
@@ -95,7 +99,8 @@ def get_diagram_scales(self, context):
|
||||
diagram_scales_enum = [
|
||||
("CUSTOM", "Custom", ""),
|
||||
("1'=1'-0\"|1/1", "1'=1'-0\"", ""),
|
||||
('6"=1\'-0"|1/6', '6"=1\'-0"', ""),
|
||||
('6"=1\'-0"|1/2', '6"=1\'-0"', ""),
|
||||
('3"=1\'-0"|1/4', '3"=1\'-0"', ""),
|
||||
('1-1/2"=1\'-0"|1/8', '1-1/2"=1\'-0"', ""),
|
||||
('1"=1\'-0"|1/12', '1"=1\'-0"', ""),
|
||||
('3/4"=1\'-0"|1/16', '3/4"=1\'-0"', ""),
|
||||
@@ -149,6 +154,35 @@ def update_drawing_name(self, context):
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def update_schedule_name(self, context):
|
||||
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
|
||||
|
||||
|
||||
def update_has_underlay(self, context):
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
|
||||
|
||||
def update_has_linework(self, context):
|
||||
update_layer(self, context, "HasLinework", self.has_linework)
|
||||
|
||||
|
||||
def update_has_annotation(self, context):
|
||||
update_layer(self, context, "HasAnnotation", self.has_annotation)
|
||||
|
||||
|
||||
def update_layer(self, context, name, value):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
|
||||
|
||||
|
||||
def getTitleblocks(self, context):
|
||||
global titleblocks_enum
|
||||
if len(titleblocks_enum) < 1:
|
||||
@@ -203,8 +237,9 @@ class Drawing(PropertyGroup):
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
file: StringProperty(name="File")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_schedule_name)
|
||||
identification: StringProperty(name="Identification")
|
||||
|
||||
|
||||
class Sheet(PropertyGroup):
|
||||
@@ -272,14 +307,12 @@ class RasterStyleProperty(enum.Enum):
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False)
|
||||
has_linework: BoolProperty(name="Linework", default=True)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True)
|
||||
should_use_underlay_cache: BoolProperty(name="Use Underlay Cache", default=False)
|
||||
should_use_linework_cache: BoolProperty(name="Use Linework Cache", default=False)
|
||||
should_use_annotation_cache: BoolProperty(name="Use Annotation Cache", default=False)
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
|
||||
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "Plan", ""),
|
||||
@@ -310,12 +343,11 @@ class DocProperties(PropertyGroup):
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
|
||||
@property
|
||||
def active_schedule(self):
|
||||
return self.schedules[self.active_schedule_index]
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
|
||||
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
|
||||
has_annotation: BoolProperty(name="Annotation", default=True, update=update_has_annotation)
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
@@ -323,19 +355,6 @@ class BIMCameraProperties(PropertyGroup):
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
is_nts: BoolProperty(name="Is NTS")
|
||||
cut_objects: EnumProperty(
|
||||
items=[
|
||||
(
|
||||
".IfcWall|.IfcSlab|.IfcCurtainWall|.IfcStair|.IfcStairFlight|.IfcColumn|.IfcBeam|.IfcMember|.IfcCovering|.IfcSpace",
|
||||
"Overall Plan / Section",
|
||||
"",
|
||||
),
|
||||
(".IfcElement", "Detail Drawing", ""),
|
||||
("CUSTOM", "Custom", ""),
|
||||
],
|
||||
name="Cut Objects",
|
||||
)
|
||||
cut_objects_custom: StringProperty(name="Custom Cut")
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
@@ -358,7 +377,6 @@ class BIMCameraProperties(PropertyGroup):
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
font_size: EnumProperty(
|
||||
@@ -373,13 +391,8 @@ class BIMTextProperties(PropertyGroup):
|
||||
update=refreshFontSize,
|
||||
name="Font Size",
|
||||
)
|
||||
symbol: EnumProperty(
|
||||
items=[
|
||||
("None", "None", ""),
|
||||
("rectangle-tag", "Rectangle Tag", ""),
|
||||
("door-tag", "Door Tag", ""),
|
||||
],
|
||||
update=refreshFontSize,
|
||||
name="Symbol",
|
||||
)
|
||||
|
||||
|
||||
class BIMAssignedProductProperties(PropertyGroup):
|
||||
is_editing_product: BoolProperty(name="Is Editing Product", default=False)
|
||||
relating_product: PointerProperty(name="Relating Product", type=bpy.types.Object)
|
||||
|
||||
@@ -31,8 +31,7 @@ class SheetBuilder:
|
||||
self.data_dir = None
|
||||
self.scale = "NTS"
|
||||
|
||||
def create(self, name, titleblock_name):
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{name}.svg")
|
||||
def create(self, sheet_path, titleblock_name):
|
||||
root = ET.Element("svg")
|
||||
root.attrib["xmlns"] = "http://www.w3.org/2000/svg"
|
||||
root.attrib["xmlns:xlink"] = "http://www.w3.org/1999/xlink"
|
||||
@@ -60,14 +59,14 @@ class SheetBuilder:
|
||||
with open(sheet_path, "w") as f:
|
||||
f.write(minidom.parseString(ET.tostring(root)).toprettyxml(indent=" "))
|
||||
|
||||
def add_drawing(self, drawing, sheet):
|
||||
def add_drawing(self, reference, drawing, sheet):
|
||||
filename = drawing.Name
|
||||
sheet_name = tool.Drawing.get_sheet_filename(sheet)
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
sheet_dir = os.path.join(self.data_dir, "sheets")
|
||||
drawing_dir = os.path.join(self.data_dir, "diagrams")
|
||||
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
|
||||
drawing_path = os.path.join(drawing_dir, filename + ".svg")
|
||||
underlay_path = os.path.join(drawing_dir, filename + ".png")
|
||||
underlay_path = os.path.join(drawing_dir, filename + "-underlay.png")
|
||||
|
||||
if not os.path.isfile(sheet_path):
|
||||
raise FileNotFoundError
|
||||
@@ -86,12 +85,14 @@ class SheetBuilder:
|
||||
# here to accommodate browsers which do not nest images.
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view.attrib["data-type"] = "drawing"
|
||||
view.attrib["data-guid"] = drawing.GlobalId
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-drawing"] = drawing.GlobalId
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
if os.path.isfile(underlay_path):
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["data-type"] = "background"
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, sheet_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
@@ -100,6 +101,7 @@ class SheetBuilder:
|
||||
|
||||
if os.path.isfile(drawing_path):
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "foreground"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, sheet_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
@@ -109,28 +111,24 @@ class SheetBuilder:
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def remove_drawing(self, drawing, sheet):
|
||||
sheet_name = tool.Drawing.get_sheet_filename(sheet)
|
||||
sheet_dir = os.path.join(self.data_dir, "sheets")
|
||||
sheet_path = os.path.join(sheet_dir, sheet_name + ".svg")
|
||||
|
||||
def remove_drawing(self, reference, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
sheet_root = sheet_tree.getroot()
|
||||
|
||||
print('removing drawing', sheet_root.findall("{http://www.w3.org/2000/svg}g"))
|
||||
for g in sheet_root.findall("{http://www.w3.org/2000/svg}g"):
|
||||
print('checking g', g)
|
||||
if g.attrib["data-type"] == "drawing" and g.attrib["data-guid"] == drawing.GlobalId:
|
||||
if g.attrib.get("data-id") == str(reference.id()):
|
||||
sheet_root.remove(g)
|
||||
break
|
||||
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def add_schedule(self, schedule_name, sheet_name):
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", sheet_name + ".svg")
|
||||
view_path = os.path.join(self.data_dir, "schedules", schedule_name + ".svg")
|
||||
def add_schedule(self, reference, schedule, sheet):
|
||||
view_path = tool.Drawing.get_document_uri(schedule)
|
||||
schedule_name = os.path.splitext(os.path.basename(view_path))[0]
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet)
|
||||
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
@@ -143,30 +141,35 @@ class SheetBuilder:
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
group = ET.SubElement(sheet_root, "g")
|
||||
group.attrib["data-type"] = "schedule"
|
||||
view = ET.SubElement(sheet_root, "g")
|
||||
view.attrib["data-type"] = "schedule"
|
||||
view.attrib["data-id"] = str(reference.id())
|
||||
view.attrib["data-schedule"] = str(schedule.id())
|
||||
|
||||
foreground = ET.SubElement(group, "image")
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["xlink:href"] = "../schedules/{}.svg".format(schedule_name)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, group)
|
||||
self.add_view_title(30, view_height + 35, view)
|
||||
sheet_tree.write(sheet_path)
|
||||
|
||||
def add_view_title(self, x, y, parent):
|
||||
title_tree = ET.parse(os.path.join(self.data_dir, "templates", "view-title.svg"))
|
||||
title_root = title_tree.getroot()
|
||||
title = ET.SubElement(parent, "image")
|
||||
title.attrib["data-type"] = "view-title"
|
||||
title.attrib["xlink:href"] = "../templates/view-title.svg"
|
||||
title.attrib["x"] = str(x)
|
||||
title.attrib["y"] = str(y)
|
||||
title.attrib["width"] = str(self.convert_to_mm(title_root.attrib.get("width")))
|
||||
title.attrib["height"] = str(self.convert_to_mm(title_root.attrib.get("height")))
|
||||
|
||||
def build(self, sheet_name):
|
||||
def build(self, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
os.makedirs(os.path.join(self.data_dir, "build", sheet_name), exist_ok=True)
|
||||
|
||||
sheet_path = os.path.join(self.data_dir, "sheets", f"{sheet_name}.svg")
|
||||
@@ -177,68 +180,86 @@ class SheetBuilder:
|
||||
tree = ET.parse(sheet_path)
|
||||
root = tree.getroot()
|
||||
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, {"number": sheet_name, "revision": "A"}))
|
||||
titleblock.remove(image)
|
||||
|
||||
self.group_number = 1
|
||||
self.build_drawings(root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'), sheet_name)
|
||||
self.build_schedules(root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'))
|
||||
self.build_titleblock(root, sheet)
|
||||
self.build_drawings(root, sheet)
|
||||
self.build_schedules(root)
|
||||
|
||||
with open(os.path.join(self.data_dir, "build", sheet_name, f"{sheet_name}.svg"), "wb") as output:
|
||||
tree.write(output)
|
||||
|
||||
def build_drawings(self, drawings, sheet_name):
|
||||
for view in drawings:
|
||||
def build_titleblock(self, root, sheet):
|
||||
titleblock = root.findall('{http://www.w3.org/2000/svg}g[@data-type="titleblock"]')[0]
|
||||
image = titleblock.findall("{http://www.w3.org/2000/svg}image")[0]
|
||||
titleblock.append(self.parse_embedded_svg(image, sheet.get_info()))
|
||||
titleblock.remove(image)
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
sheet_name = os.path.splitext(os.path.basename(tool.Drawing.get_document_uri(sheet)))[0]
|
||||
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
background = images[0]
|
||||
foreground = images[1]
|
||||
view_title = images[2]
|
||||
self.scale = "NTS"
|
||||
|
||||
# Add foreground
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
background = None
|
||||
foreground = None
|
||||
view_title = None
|
||||
|
||||
# Add background
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
for image in images:
|
||||
if image.attrib["data-type"] == "background":
|
||||
background = image
|
||||
elif image.attrib["data-type"] == "foreground":
|
||||
foreground = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
|
||||
# Add view title
|
||||
foreground_path = self.get_href(foreground)
|
||||
view.append(
|
||||
self.parse_embedded_svg(
|
||||
view_title,
|
||||
{"no": self.group_number, "name": ntpath.basename(foreground_path)[0:-4], "scale": self.scale},
|
||||
)
|
||||
)
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.data_dir, "sheets", self.get_href(background))
|
||||
copy(background_path, os.path.join(self.data_dir, "build", sheet_name))
|
||||
|
||||
if view_title is not None:
|
||||
foreground_path = self.get_href(foreground)
|
||||
data = reference.get_info()
|
||||
if not data["Name"]:
|
||||
data["Name"] = ntpath.basename(foreground_path)[0:-4]
|
||||
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
self.group_number += 1
|
||||
def build_schedules(self, root):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="schedule"]'):
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
schedule = tool.Ifc.get().by_id(int(view.attrib["data-schedule"]))
|
||||
|
||||
def build_schedules(self, schedules):
|
||||
for group in schedules:
|
||||
images = group.findall("{http://www.w3.org/2000/svg}image")
|
||||
schedule = images[0]
|
||||
group_title = images[1]
|
||||
self.scale = "NTS"
|
||||
images = view.findall("{http://www.w3.org/2000/svg}image")
|
||||
|
||||
group.append(self.parse_embedded_svg(schedule, {}))
|
||||
|
||||
path = self.get_href(schedule)
|
||||
group.append(
|
||||
self.parse_embedded_svg(
|
||||
group_title, {"no": self.group_number, "name": ntpath.basename(path)[0:-4], "scale": self.scale}
|
||||
)
|
||||
)
|
||||
table = None
|
||||
view_title = None
|
||||
|
||||
for image in images:
|
||||
group.remove(image)
|
||||
if image.attrib["data-type"] == "table":
|
||||
table = image
|
||||
elif image.attrib["data-type"] == "view-title":
|
||||
view_title = image
|
||||
|
||||
self.group_number += 1
|
||||
if table is not None:
|
||||
view.append(self.parse_embedded_svg(table, {}))
|
||||
|
||||
if view_title is not None:
|
||||
path = self.get_href(table)
|
||||
data = reference.get_info()
|
||||
if not data["Name"]:
|
||||
data["Name"] = schedule.Name or "Unnamed"
|
||||
view.append(self.parse_embedded_svg(view_title, data))
|
||||
|
||||
for image in images:
|
||||
view.remove(image)
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
|
||||
@@ -20,7 +20,7 @@ import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import TextData, SheetsData, DrawingsData
|
||||
from blenderbim.bim.module.drawing.data import ProductAssignmentsData, TextData, SheetsData, SchedulesData, DrawingsData
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -47,13 +47,13 @@ class BIM_PT_camera(Panel):
|
||||
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(props, "has_underlay", icon="OUTLINER_OB_IMAGE")
|
||||
row.prop(dprops, "should_use_underlay_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_linework", icon="IMAGE_DATA")
|
||||
row.prop(props, "has_linework", icon="IMAGE_DATA")
|
||||
row.prop(dprops, "should_use_linework_cache", text="", icon="FILE_REFRESH")
|
||||
row = col.row(align=True)
|
||||
row.prop(dprops, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(props, "has_annotation", icon="MOD_EDGESPLIT")
|
||||
row.prop(dprops, "should_use_annotation_cache", text="", icon="FILE_REFRESH")
|
||||
|
||||
row = layout.row()
|
||||
@@ -62,17 +62,9 @@ class BIM_PT_camera(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "is_nts")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.generate_references")
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects")
|
||||
if props.cut_objects == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "cut_objects_custom")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
@@ -152,6 +144,10 @@ class BIM_PT_drawings(Panel):
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
@@ -203,23 +199,40 @@ class BIM_PT_schedules(Panel):
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.scene.DocProperties
|
||||
if not SchedulesData.is_loaded:
|
||||
SchedulesData.load()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_schedule")
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if props.schedules:
|
||||
row.operator("bim.build_schedule", icon="LINENUMBERS_ON", text="")
|
||||
row.operator("bim.remove_schedule", icon="X", text="").index = props.active_schedule_index
|
||||
if not self.props.is_editing_schedules:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
|
||||
row.operator("bim.load_schedules", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
layout.template_list("BIM_UL_generic", "", props, "schedules", props, "active_schedule_index")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_schedule", icon="ADD")
|
||||
row.operator("bim.disable_editing_schedules", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props.active_schedule, "file")
|
||||
row.operator("bim.select_schedule_file", icon="FILE_FOLDER", text="")
|
||||
if self.props.schedules:
|
||||
if self.props.active_schedule_index < len(self.props.schedules):
|
||||
active_schedule = self.props.schedules[self.props.active_schedule_index]
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.open_schedule", icon="URL", text="").schedule = active_schedule.ifc_definition_id
|
||||
row.operator(
|
||||
"bim.build_schedule", icon="LINENUMBERS_ON", text=""
|
||||
).schedule = active_schedule.ifc_definition_id
|
||||
row.operator("bim.remove_schedule", icon="X", text="").schedule = active_schedule.ifc_definition_id
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_generic", "", self.props, "schedules", self.props, "active_schedule_index"
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_sheets(Panel):
|
||||
@@ -229,6 +242,10 @@ class BIM_PT_sheets(Panel):
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not SheetsData.is_loaded:
|
||||
SheetsData.load()
|
||||
@@ -263,6 +280,39 @@ class BIM_PT_sheets(Panel):
|
||||
self.layout.template_list("BIM_UL_sheets", "", self.props, "sheets", self.props, "active_sheet_index")
|
||||
|
||||
|
||||
class BIM_PT_product_assignments(Panel):
|
||||
bl_label = "IFC Product Assignments"
|
||||
bl_idname = "BIM_PT_product_assignments"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Ifc.get() or not context.active_object:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
return element.is_a("IfcAnnotation")
|
||||
|
||||
def draw(self, context):
|
||||
if not ProductAssignmentsData.is_loaded:
|
||||
ProductAssignmentsData.load()
|
||||
|
||||
props = context.active_object.BIMAssignedProductProperties
|
||||
|
||||
if props.is_editing_product:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "relating_product", text="")
|
||||
row.operator("bim.edit_assigned_product", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_assigned_product", icon="CANCEL", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=ProductAssignmentsData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
|
||||
row.operator("bim.enable_editing_assigned_product", icon="GREASEPENCIL", text="")
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
bl_label = "IFC Text"
|
||||
bl_idname = "BIM_PT_text"
|
||||
@@ -299,16 +349,6 @@ class BIM_PT_text(Panel):
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
if props.is_editing_product:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "relating_product", text="")
|
||||
row.operator("bim.edit_text_product", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_text_product", icon="CANCEL", text="")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=TextData.data["relating_product"] or "No Relating Product", icon="OBJECT_DATA")
|
||||
row.operator("bim.enable_editing_text_product", icon="GREASEPENCIL", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "font_size")
|
||||
row = self.layout.row()
|
||||
@@ -328,11 +368,19 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
|
||||
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
|
||||
op.object_type = "DIMENSION"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT")
|
||||
op.object_type = "EQUAL_DIMENSION"
|
||||
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
op.object_type = "ANGLE"
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
|
||||
op.object_type = "RADIUS"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
|
||||
op.object_type = "DIAMETER"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
@@ -363,10 +411,14 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
|
||||
op.object_type = "BREAKLINE"
|
||||
op.data_type = "mesh"
|
||||
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
|
||||
op.object_type = "MISC"
|
||||
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
|
||||
op.object_type = "LINEWORK"
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
|
||||
op.object_type = "FILL_AREA"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
|
||||
|
||||
@@ -407,6 +459,8 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.reference_type == "DRAWING":
|
||||
row.label(text="", icon="IMAGE_DATA")
|
||||
elif item.reference_type == "SCHEDULE":
|
||||
row.label(text="", icon="LONGDISPLAY")
|
||||
|
||||
name = "{} - {}".format(item.identification or "X", item.name or "Unnamed")
|
||||
row.label(text=name)
|
||||
|
||||
@@ -130,10 +130,6 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object.mode == "OBJECT"
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
@@ -141,6 +137,10 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
if not ContextData.is_loaded:
|
||||
ContextData.load(IfcStore.get_file())
|
||||
|
||||
if context.active_object and context.active_object.mode != "OBJECT":
|
||||
# Ensure mode is object to prevent invalid mesh data causing CTD
|
||||
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
|
||||
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
@@ -206,25 +206,17 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
[
|
||||
blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
|
||||
for s in obj.material_slots
|
||||
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
|
||||
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
|
||||
if tool.Geometry.is_body_representation(new_representation):
|
||||
[
|
||||
tool.Geometry.run_style_add_style(obj=mat)
|
||||
for mat in tool.Geometry.get_object_materials_without_styles(obj)
|
||||
]
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
**{
|
||||
"shape_representation": new_representation,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
shape_representation=new_representation,
|
||||
styles=tool.Geometry.get_styles(obj),
|
||||
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
@@ -248,7 +240,7 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object.mode == "OBJECT"
|
||||
return context.active_object and context.active_object.mode == "OBJECT"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
@@ -332,8 +324,8 @@ class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
|
||||
class OverrideDeleteTrait:
|
||||
def delete_ifc_object(self, obj):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
IfcStore.delete_element(element)
|
||||
if getattr(element, "FillsVoids", None):
|
||||
self.remove_filling(element)
|
||||
@@ -342,9 +334,12 @@ class OverrideDeleteTrait:
|
||||
self.remove_filling(rel.RelatedBuildingElement)
|
||||
if element.VoidsElements:
|
||||
self.delete_opening_element(element)
|
||||
elif getattr(element, "HasOpenings", None):
|
||||
for rel in element.HasOpenings:
|
||||
self.delete_opening_element(rel.RelatedOpeningElement)
|
||||
else:
|
||||
if getattr(element, "HasOpenings", None):
|
||||
for rel in element.HasOpenings:
|
||||
self.delete_opening_element(rel.RelatedOpeningElement)
|
||||
for port in ifcopenshell.util.system.get_ports(element):
|
||||
self.remove_port(port)
|
||||
|
||||
def delete_opening_element(self, element):
|
||||
obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
|
||||
@@ -354,6 +349,9 @@ class OverrideDeleteTrait:
|
||||
obj = IfcStore.get_element(element.id())
|
||||
bpy.ops.bim.remove_filling(obj=obj.name)
|
||||
|
||||
def remove_port(self, port):
|
||||
blenderbim.core.system.remove_port(tool.Ifc, tool.System, port=port)
|
||||
|
||||
|
||||
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "object.delete"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
|
||||
|
||||
|
||||
@@ -50,7 +51,7 @@ class BIM_PT_representations(Panel):
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene.BIMRootProperties, "contexts", text="")
|
||||
prop_with_search(row, context.scene.BIMRootProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
GeoreferenceData.is_loaded = False
|
||||
|
||||
|
||||
class GeoreferenceData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data["blender_derived_angle"] = cls.blender_derived_angle()
|
||||
cls.data["map_conversion"] = cls.map_conversion()
|
||||
cls.data["map_derived_angle"] = cls.map_derived_angle()
|
||||
cls.data["projected_crs"] = cls.projected_crs()
|
||||
cls.data["true_north"] = cls.true_north()
|
||||
cls.data["true_derived_angle"] = cls.true_derived_angle()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def blender_derived_angle(cls):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
return str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def map_conversion(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
map_conversion = context.HasCoordinateOperation[0].get_info()
|
||||
del map_conversion["id"]
|
||||
del map_conversion["type"]
|
||||
del map_conversion["SourceCRS"]
|
||||
del map_conversion["TargetCRS"]
|
||||
return map_conversion
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def map_derived_angle(cls):
|
||||
if (
|
||||
cls.data["map_conversion"]
|
||||
and cls.data["map_conversion"]["XAxisAbscissa"] is not None
|
||||
and cls.data["map_conversion"]["XAxisOrdinate"] is not None
|
||||
):
|
||||
return str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
cls.data["map_conversion"]["XAxisAbscissa"], cls.data["map_conversion"]["XAxisOrdinate"]
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def projected_crs(cls):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return {}
|
||||
|
||||
for context in tool.Ifc.get().by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.HasCoordinateOperation:
|
||||
projected_crs = context.HasCoordinateOperation[0].TargetCRS.get_info()
|
||||
del projected_crs["id"]
|
||||
del projected_crs["type"]
|
||||
if projected_crs["MapUnit"]:
|
||||
unit = projected_crs["MapUnit"]
|
||||
projected_crs["MapUnit"] = (getattr(unit, "Prefix", "") or "") + unit.Name
|
||||
return projected_crs
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def true_north(cls):
|
||||
ifc = tool.Ifc.get()
|
||||
true_north = {}
|
||||
|
||||
if ifc.schema == "IFC2X3":
|
||||
return
|
||||
|
||||
for context in ifc.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if not context.TrueNorth:
|
||||
continue
|
||||
true_north = context.TrueNorth.DirectionRatios
|
||||
break
|
||||
|
||||
return true_north
|
||||
|
||||
@classmethod
|
||||
def true_derived_angle(cls):
|
||||
if cls.data["true_north"]:
|
||||
return str(round(ifcopenshell.util.geolocation.yaxis2angle(*cls.data["true_north"][0:2]), 3))
|
||||
@@ -17,345 +17,157 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.georeference.data import Data
|
||||
from ifcopenshell.api.unit.data import Data as UnitData
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.georeference as core
|
||||
|
||||
|
||||
class EnableEditingGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_georeferencing"
|
||||
bl_label = "Enable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
self.props = props
|
||||
|
||||
props.projected_crs.clear()
|
||||
|
||||
blenderbim.bim.helper.import_attributes(
|
||||
"IfcProjectedCRS", props.projected_crs, Data.projected_crs, self.import_projected_crs_attributes
|
||||
)
|
||||
|
||||
props.map_conversion.clear()
|
||||
blenderbim.bim.helper.import_attributes(
|
||||
"IfcMapConversion", props.map_conversion, Data.map_conversion, self.import_map_conversion_attributes
|
||||
)
|
||||
|
||||
props.has_true_north = bool(Data.true_north)
|
||||
if Data.true_north:
|
||||
props.true_north_abscissa = str(Data.true_north[0])
|
||||
props.true_north_ordinate = str(Data.true_north[1])
|
||||
|
||||
props.is_editing = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def import_projected_crs_attributes(self, name, prop, data):
|
||||
if name == "MapUnit":
|
||||
new = self.props.projected_crs.add()
|
||||
new.name = name
|
||||
new.data_type = "enum"
|
||||
new.is_null = data[name] is None
|
||||
new.is_optional = True
|
||||
new.enum_items = json.dumps(
|
||||
{u["id"]: u["Name"] for u in UnitData.units.values() if u["UnitType"] == "LENGTHUNIT"}
|
||||
)
|
||||
if data["MapUnit"]:
|
||||
new.enum_value = str(data["MapUnit"]["id"])
|
||||
return True
|
||||
|
||||
def import_map_conversion_attributes(self, name, prop, data):
|
||||
if name not in ["SourceCRS", "TargetCRS"]:
|
||||
# Enforce a string data type to prevent data loss in single-precision Blender props
|
||||
prop.data_type = "string"
|
||||
prop.string_value = "" if prop.is_null else str(data[name])
|
||||
return True
|
||||
|
||||
|
||||
class DisableEditingGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_georeferencing"
|
||||
bl_label = "Disable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
props.is_editing = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_georeferencing"
|
||||
bl_label = "Edit Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
projected_crs = blenderbim.bim.helper.export_attributes(props.projected_crs, self.export_crs_attributes)
|
||||
map_conversion = blenderbim.bim.helper.export_attributes(props.map_conversion, self.export_map_attributes)
|
||||
|
||||
true_north = None
|
||||
if props.has_true_north:
|
||||
try:
|
||||
true_north = [float(props.true_north_abscissa), float(props.true_north_ordinate)]
|
||||
except ValueError:
|
||||
self.report({"ERROR"}, "True North Abscissa and Ordinate expect a number")
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"georeference.edit_georeferencing",
|
||||
self.file,
|
||||
**{
|
||||
"map_conversion": map_conversion,
|
||||
"projected_crs": projected_crs,
|
||||
"true_north": true_north,
|
||||
}
|
||||
)
|
||||
Data.load(self.file)
|
||||
bpy.ops.bim.disable_editing_georeferencing()
|
||||
return {"FINISHED"}
|
||||
|
||||
def export_map_attributes(self, attributes, prop):
|
||||
if not prop.is_null and prop.data_type == "string":
|
||||
# We store our floats as string to prevent single precision data loss
|
||||
attributes[prop.name] = float(prop.string_value)
|
||||
return True
|
||||
|
||||
def export_crs_attributes(self, attributes, prop):
|
||||
if not prop.is_null and prop.name == "MapUnit":
|
||||
attributes[prop.name] = self.file.by_id(int(prop.enum_value))
|
||||
return True
|
||||
|
||||
|
||||
class SetBlenderGridNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_grid_north"
|
||||
bl_label = "Set Blender Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sun_pos_properties.north_offset = -radians(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value),
|
||||
float(context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value),
|
||||
)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetIfcGridNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_ifc_grid_north"
|
||||
bl_label = "Set IFC Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
x_angle = -context.scene.sun_pos_properties.north_offset
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisAbscissa").string_value = str(cos(x_angle))
|
||||
context.scene.BIMGeoreferenceProperties.map_conversion.get("XAxisOrdinate").string_value = str(sin(x_angle))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetBlenderTrueNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_blender_true_north"
|
||||
bl_label = "Set Blender True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.sun_pos_properties.north_offset = -radians(
|
||||
ifcopenshell.util.geolocation.yaxis2angle(
|
||||
float(context.scene.BIMGeoreferenceProperties.true_north_abscissa),
|
||||
float(context.scene.BIMGeoreferenceProperties.true_north_ordinate),
|
||||
)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetIfcTrueNorth(bpy.types.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
y_angle = -context.scene.sun_pos_properties.north_offset + radians(90)
|
||||
context.scene.BIMGeoreferenceProperties.true_north_abscissa = str(cos(y_angle))
|
||||
context.scene.BIMGeoreferenceProperties.true_north_ordinate = str(sin(y_angle))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveGeoreferencing(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_georeferencing"
|
||||
bl_label = "Remove Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run("georeference.remove_georeferencing", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddGeoreferencing(bpy.types.Operator):
|
||||
class AddGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_georeferencing"
|
||||
bl_label = "Add Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
bl_description = "Add a new georeference"
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
core.add_georeferencing(tool.Ifc)
|
||||
|
||||
|
||||
class ConvertLocalToGlobal(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_local_to_global"
|
||||
bl_label = "Convert Local To Global"
|
||||
class EnableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_georeferencing"
|
||||
bl_label = "Enable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Enable editing georeferencing"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and props.coordinate_input.count(",") == 2
|
||||
|
||||
def execute(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
|
||||
|
||||
if props.has_blender_offset:
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
x, y, z = results
|
||||
|
||||
# TODO: what if the project CRS units and the project units are different?
|
||||
|
||||
if Data.map_conversion:
|
||||
results = ifcopenshell.util.geolocation.xyz2enh(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
else:
|
||||
results = (x, y, z)
|
||||
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
context.scene.cursor.location = results
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.enable_editing_georeferencing(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertGlobalToLocal(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_global_to_local"
|
||||
bl_label = "Convert Global To Local"
|
||||
class RemoveGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_georeferencing"
|
||||
bl_label = "Remove Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Remove the georeferencing"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
|
||||
|
||||
def execute(self, context):
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
x, y, z = [float(co) for co in props.coordinate_input.split(",")]
|
||||
|
||||
if Data.map_conversion:
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Data.map_conversion["Eastings"],
|
||||
Data.map_conversion["Northings"],
|
||||
Data.map_conversion["OrthogonalHeight"],
|
||||
Data.map_conversion.get("XAxisAbscissa", 1.0),
|
||||
Data.map_conversion.get("XAxisOrdinate", 0.0),
|
||||
Data.map_conversion.get("Scale", 1.0),
|
||||
)
|
||||
else:
|
||||
results = (x, y, z)
|
||||
|
||||
if props.has_blender_offset:
|
||||
results = ifcopenshell.util.geolocation.enh2xyz(
|
||||
results[0],
|
||||
results[1],
|
||||
results[2],
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
1.0,
|
||||
)
|
||||
|
||||
props.coordinate_output = ",".join([str(r) for r in results])
|
||||
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
context.scene.cursor.location = [o * scale for o in results]
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.remove_georeferencing(tool.Ifc)
|
||||
|
||||
|
||||
class GetCursorLocation(bpy.types.Operator):
|
||||
class EditGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_georeferencing"
|
||||
bl_label = "Edit Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Edit the georeferencing"
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_georeferencing(tool.Ifc, tool.Georeference)
|
||||
|
||||
|
||||
class DisableEditingGeoreferencing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_georeferencing"
|
||||
bl_label = "Disable Editing Georeferencing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close editing panel"
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_georeferencing(tool.Georeference)
|
||||
|
||||
|
||||
class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_grid_north"
|
||||
bl_label = "Set IFC Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC grid north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_grid_north(tool.Georeference)
|
||||
|
||||
|
||||
class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_grid_north"
|
||||
bl_label = "Set Blender Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender grid north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_grid_north(tool.Georeference)
|
||||
|
||||
|
||||
class GetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.get_cursor_location"
|
||||
bl_label = "Get Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Insert the current cursor coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = IfcStore.get_file()
|
||||
return file and file.by_type("IfcUnitAssignment")
|
||||
return tool.Ifc.get()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
project_coordinates = [o / scale for o in context.scene.cursor.location]
|
||||
props.coordinate_input = ",".join([str(o) for o in project_coordinates])
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.get_cursor_location(tool.Georeference)
|
||||
|
||||
|
||||
class SetCursorLocation(bpy.types.Operator):
|
||||
class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Move curson location to the specified coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = IfcStore.get_file()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and file.by_type("IfcUnitAssignment") and props.coordinate_output.count(",") == 2
|
||||
return tool.Ifc.get() and props.coordinate_output.count(",") == 2
|
||||
|
||||
def execute(self, context):
|
||||
def _execute(self, context):
|
||||
core.set_cursor_location(tool.Georeference)
|
||||
|
||||
|
||||
class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC True north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_true_north(tool.Georeference)
|
||||
|
||||
|
||||
class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_true_north"
|
||||
bl_label = "Set Blender True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender true north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_true_north(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertLocalToGlobal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_local_to_global"
|
||||
bl_label = "Convert Local To Global"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert local coordinate to global coordinate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
scale = ifcopenshell.util.unit.calculate_unit_scale(IfcStore.get_file())
|
||||
context.scene.cursor.location = [float(co) * scale for co in props.coordinate_output.split(",")]
|
||||
return {"FINISHED"}
|
||||
return file and props.coordinate_input.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_local_to_global(tool.Georeference)
|
||||
|
||||
|
||||
class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_global_to_local"
|
||||
bl_label = "Convert Global To Local"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert global coordinate to local coordinate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and file.by_type("IfcUnitAssignment") and props.coordinate_input.count(",") == 2
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_global_to_local(tool.Georeference)
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.georeference.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes, draw_attribute
|
||||
from blenderbim.bim.module.georeference.data import GeoreferenceData
|
||||
|
||||
|
||||
class BIM_PT_gis(Panel):
|
||||
@@ -32,16 +31,13 @@ class BIM_PT_gis(Panel):
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
self.layout.use_property_decorate = False
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
if not GeoreferenceData.is_loaded:
|
||||
GeoreferenceData.load()
|
||||
|
||||
if props.is_editing:
|
||||
return self.draw_editable_ui(context)
|
||||
@@ -82,10 +78,10 @@ class BIM_PT_gis(Panel):
|
||||
def draw_ui(self, context):
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
|
||||
if not Data.projected_crs:
|
||||
if not GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Not Georeferenced")
|
||||
if IfcStore.get_file().schema != "IFC2X3":
|
||||
if tool.Ifc.get_schema != "IFC2X3":
|
||||
row.operator("bim.add_georeferencing", icon="ADD", text="")
|
||||
|
||||
if props.has_blender_offset:
|
||||
@@ -109,43 +105,27 @@ class BIM_PT_gis(Panel):
|
||||
row.label(text=props.blender_x_axis_ordinate)
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Grid North")
|
||||
row.label(
|
||||
text=str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
float(props.blender_x_axis_abscissa), float(props.blender_x_axis_ordinate)
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
)
|
||||
elif IfcStore.get_file().schema == "IFC2X3":
|
||||
row = self.layout.row()
|
||||
row.label(text="Not Georeferenced")
|
||||
row.label(text=GeoreferenceData.data["blender_derived_angle"])
|
||||
|
||||
if Data.projected_crs:
|
||||
if GeoreferenceData.data["projected_crs"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Projected CRS", icon="WORLD")
|
||||
row.operator("bim.enable_editing_georeferencing", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.remove_georeferencing", icon="X", text="")
|
||||
|
||||
for key, value in Data.projected_crs.items():
|
||||
if key == "id" or key == "type" or not value:
|
||||
for key, value in GeoreferenceData.data["projected_crs"].items():
|
||||
if not value:
|
||||
continue
|
||||
if key == "MapUnit":
|
||||
unit_value = value.get("Prefix", "") or ""
|
||||
unit_value += value["Name"]
|
||||
value = unit_value
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=key)
|
||||
row.label(text=str(value))
|
||||
|
||||
if Data.map_conversion:
|
||||
if GeoreferenceData.data["map_conversion"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Map Conversion", icon="GRID")
|
||||
|
||||
for key, value in Data.map_conversion.items():
|
||||
if key == "id" or key == "type" or key == "SourceCRS" or key == "TargetCRS" or value is None:
|
||||
for key, value in GeoreferenceData.data["map_conversion"].items():
|
||||
if value is None:
|
||||
continue
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=key)
|
||||
@@ -153,26 +133,17 @@ class BIM_PT_gis(Panel):
|
||||
if key == "XAxisOrdinate":
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(
|
||||
text=str(
|
||||
round(
|
||||
ifcopenshell.util.geolocation.xaxis2angle(
|
||||
Data.map_conversion["XAxisAbscissa"], Data.map_conversion["XAxisOrdinate"]
|
||||
),
|
||||
3,
|
||||
)
|
||||
)
|
||||
)
|
||||
row.label(text=GeoreferenceData.data["map_derived_angle"])
|
||||
|
||||
if Data.true_north:
|
||||
if GeoreferenceData.data["true_north"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="True North", icon="LIGHT_SUN")
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Vector")
|
||||
row.label(text=str(Data.true_north[0:2])[1:-1])
|
||||
row.label(text=str(GeoreferenceData.data["true_north"][0:2])[1:-1])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Derived Angle")
|
||||
row.label(text=str(round(ifcopenshell.util.geolocation.yaxis2angle(*Data.true_north[0:2]), 3)))
|
||||
row.label(text=GeoreferenceData.data["true_derived_angle"])
|
||||
|
||||
|
||||
class BIM_PT_gis_utilities(Panel):
|
||||
|
||||
@@ -21,8 +21,10 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.LoadGroups,
|
||||
operator.ToggleGroup,
|
||||
operator.DisableGroupEditingUI,
|
||||
operator.AddGroup,
|
||||
operator.AddGroupToGroup,
|
||||
operator.EditGroup,
|
||||
operator.RemoveGroup,
|
||||
operator.ToggleAssigningGroup,
|
||||
@@ -31,6 +33,8 @@ classes = (
|
||||
operator.EnableEditingGroup,
|
||||
operator.DisableEditingGroup,
|
||||
operator.SelectGroupProducts,
|
||||
operator.UpdateGroup,
|
||||
prop.ExpandedGroups,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
ui.BIM_PT_groups,
|
||||
@@ -42,7 +46,9 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
|
||||
bpy.types.Scene.ExpandedGroups = bpy.props.PointerProperty(type=prop.ExpandedGroups)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMGroupProperties
|
||||
del bpy.types.Scene.ExpandedGroups
|
||||
|
||||
@@ -22,22 +22,86 @@ import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data
|
||||
from ifcopenshell.util.selector import Selector
|
||||
import json
|
||||
|
||||
|
||||
class LoadGroups(bpy.types.Operator):
|
||||
bl_idname = "bim.load_groups"
|
||||
bl_label = "Load Groups"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
is_refresh: bpy.props.BoolProperty(default=False)
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
props.groups.clear()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string)
|
||||
self.props.groups.clear()
|
||||
self.ifc = IfcStore.get_file()
|
||||
if not self.is_refresh:
|
||||
context.scene.ExpandedGroups.json_string = "{}"
|
||||
|
||||
for ifc_definition_id, group in Data.groups.items():
|
||||
new = props.groups.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = group["Name"]
|
||||
props.is_editing = True
|
||||
if not group["HasAssignments"]:
|
||||
new = self.props.groups.add()
|
||||
new.ifc_definition_id = ifc_definition_id
|
||||
new.name = group["Name"]
|
||||
new.selection_query = group["Description"].split("*selector*")[1] if group["Description"] else ""
|
||||
new.tree_depth = 0
|
||||
|
||||
if group["IsGroupedBy"]:
|
||||
# assumes 1:1 cardinality, will need to be updated to reflect IFC4 changes
|
||||
# where the cardinality is 0:? - vulevukusej
|
||||
sub_groups = [g for g in group["IsGroupedBy"][0].RelatedObjects if g.is_a("IfcGroup")]
|
||||
new.has_children = True if len(sub_groups) != 0 else False
|
||||
|
||||
if self.is_refresh:
|
||||
self.recursively_load_sub_groups(ifc_definition_id, new)
|
||||
|
||||
self.props.is_editing = True
|
||||
bpy.ops.bim.disable_editing_group()
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
def recursively_load_sub_groups(self, ifc_definition_id, parent):
|
||||
sub_groups = [
|
||||
k for k, v in Data.products.items() if self.ifc.by_id(k).is_a("IfcGroup") and ifc_definition_id in v
|
||||
]
|
||||
if str(ifc_definition_id) not in self.expanded_groups or sub_groups == []:
|
||||
return
|
||||
else:
|
||||
parent.is_expanded = True
|
||||
parent.has_children = True
|
||||
for group in sub_groups:
|
||||
new = self.props.groups.add()
|
||||
new.ifc_definition_id = group
|
||||
new.name = Data.groups[group]["Name"]
|
||||
new.selection_query = (
|
||||
Data.groups[group]["Description"].split("*selector*")[1]
|
||||
if Data.groups[group]["Description"]
|
||||
else ""
|
||||
)
|
||||
new.has_children = True if len(Data.groups[group]["IsGroupedBy"]) != 0 else False
|
||||
new.tree_depth = parent.tree_depth + 1
|
||||
self.recursively_load_sub_groups(new.ifc_definition_id, new)
|
||||
|
||||
|
||||
class ToggleGroup(bpy.types.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")
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMGroupProperties.groups[self.index].is_expanded = True if self.option == "Expand" else False
|
||||
json_string = json.loads(context.scene.ExpandedGroups.json_string)
|
||||
if self.option == "Expand":
|
||||
json_string.setdefault(str(self.ifc_definition_id), None)
|
||||
else:
|
||||
json_string.pop(str(self.ifc_definition_id), None)
|
||||
context.scene.ExpandedGroups.json_string = json.dumps(json_string)
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -54,7 +118,7 @@ class DisableGroupEditingUI(bpy.types.Operator):
|
||||
|
||||
class AddGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.add_group"
|
||||
bl_label = "Add Group"
|
||||
bl_label = "Add New Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -63,15 +127,38 @@ class AddGroup(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("group.add_group", IfcStore.get_file())
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups()
|
||||
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
bpy.ops.bim.enable_editing_group(group=result.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddGroupToGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.add_group_to_group"
|
||||
bl_label = "Add Group to Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty(name="Group ID")
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
result = ifcopenshell.api.run("group.add_group", self.file)
|
||||
ifcopenshell.api.run(
|
||||
"group.assign_group", IfcStore.get_file(), **{"product": [result], "group": self.file.by_id(self.group)}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
bpy.ops.bim.disable_group_editing_ui()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_group"
|
||||
bl_label = "Edit Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
copy_from_selector: bpy.props.BoolProperty(name="Copy from Selector", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -84,12 +171,16 @@ class EditGroup(bpy.types.Operator):
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
|
||||
if self.copy_from_selector:
|
||||
attributes["Description"] = context.scene.IFCSelector.selection_query
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"group.edit_group", self.file, **{"group": self.file.by_id(props.active_group_id), "attributes": attributes}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups()
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -107,7 +198,7 @@ class RemoveGroup(bpy.types.Operator):
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups()
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -120,9 +211,7 @@ class EnableEditingGroup(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
props.group_attributes.clear()
|
||||
|
||||
blenderbim.bim.helper.import_attributes("IfcGroup", props.group_attributes, Data.groups[self.group])
|
||||
|
||||
props.active_group_id = self.group
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -167,7 +256,7 @@ class AssignGroup(bpy.types.Operator):
|
||||
"group.assign_group",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"product": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
|
||||
"group": self.file.by_id(self.group),
|
||||
}
|
||||
)
|
||||
@@ -219,3 +308,33 @@ class SelectGroupProducts(bpy.types.Operator):
|
||||
if self.group in product_groups:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateGroup(bpy.types.Operator):
|
||||
bl_idname = "bim.update_group"
|
||||
bl_label = "Update Group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
query: bpy.props.StringProperty()
|
||||
group_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
group = self.file.by_id(self.group_id)
|
||||
query = self.query
|
||||
|
||||
new_products = Selector.parse(self.file, query)
|
||||
ifcopenshell.api.run(
|
||||
"group.update_group_products",
|
||||
self.file,
|
||||
**{
|
||||
"group": group,
|
||||
"products": new_products,
|
||||
|
||||
}
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.load_groups(is_refresh=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.helper import import_attributes
|
||||
from ifcopenshell.api.group.data import Data
|
||||
from bpy.types import PropertyGroup
|
||||
import json
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
@@ -31,9 +34,17 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
class ExpandedGroups(StrProperty):
|
||||
json_string: StringProperty(name="JSON String", default="{}")
|
||||
|
||||
|
||||
class Group(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
selection_query: StringProperty(name="Selection Query")
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||
has_children: BoolProperty(name="Has Children", default=False)
|
||||
tree_depth: IntProperty(name="Tree Depth")
|
||||
|
||||
|
||||
class BIMGroupProperties(PropertyGroup):
|
||||
|
||||
@@ -40,7 +40,7 @@ class BIM_PT_groups(Panel):
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Groups Found".format(len(Data.groups)), icon="OUTLINER")
|
||||
row.label(text=f"{len(Data.groups)} Groups Found", icon="OUTLINER")
|
||||
if self.props.is_editing:
|
||||
row.operator("bim.add_group", text="", icon="ADD")
|
||||
row.operator("bim.disable_group_editing_ui", text="", icon="CANCEL")
|
||||
@@ -115,10 +115,22 @@ class BIM_PT_object_groups(Panel):
|
||||
|
||||
|
||||
class BIM_UL_groups(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
for i in range(0, item.tree_depth):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
op = row.operator(
|
||||
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
|
||||
)
|
||||
op.ifc_definition_id = item.ifc_definition_id
|
||||
op.index = index
|
||||
op.option = "Collapse" if item.is_expanded else "Expand"
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
|
||||
row.label(text=f"*{item.name}") if item.selection_query != "" else row.label(text=item.name)
|
||||
group_id = item.ifc_definition_id
|
||||
if context.scene.BIMGroupProperties.active_group_id == group_id:
|
||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
@@ -128,21 +140,46 @@ class BIM_UL_groups(UIList):
|
||||
elif context.scene.BIMGroupProperties.active_group_id:
|
||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.group = group_id
|
||||
op = row.operator("bim.add_group_to_group", text="", icon="ADD")
|
||||
op.group = group_id
|
||||
op = row.operator("bim.remove_group", text="", icon="X")
|
||||
op.group = group_id
|
||||
if item.selection_query != "":
|
||||
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
|
||||
op.group_id = item.ifc_definition_id
|
||||
op.query = item.selection_query
|
||||
else:
|
||||
op = row.operator("bim.select_group_products", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.group = group_id
|
||||
op = row.operator("bim.enable_editing_group", text="", icon="GREASEPENCIL")
|
||||
op.group = group_id
|
||||
op = row.operator("bim.add_group_to_group", text="", icon="ADD")
|
||||
op.group = group_id
|
||||
op = row.operator("bim.remove_group", text="", icon="X")
|
||||
op.group = group_id
|
||||
if item.selection_query != "":
|
||||
op = row.operator("bim.update_group", text="", icon="FILE_REFRESH")
|
||||
op.group_id = item.ifc_definition_id
|
||||
op.query = item.selection_query
|
||||
|
||||
|
||||
class BIM_UL_object_groups(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
for i in range(0, item.tree_depth):
|
||||
row.label(text="", icon="BLANK1")
|
||||
if item.has_children:
|
||||
op = row.operator(
|
||||
"bim.toggle_group", icon="TRIA_DOWN" if item.is_expanded else "TRIA_RIGHT", text="", emboss=False
|
||||
)
|
||||
op.ifc_definition_id = item.ifc_definition_id
|
||||
op.index = index
|
||||
op.option = "Collapse" if item.is_expanded else "Expand"
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.remove_group", text="", icon="X")
|
||||
op.group = item.ifc_definition_id
|
||||
op = row.operator("bim.assign_group", text="", icon="ADD")
|
||||
op.group = item.ifc_definition_id
|
||||
|
||||
@@ -28,6 +28,7 @@ classes = (
|
||||
operator.AddProfile,
|
||||
operator.AssignMaterial,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.ContractMaterialCategory,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
@@ -36,6 +37,7 @@ classes = (
|
||||
operator.EditMaterialSetItem,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.ExpandMaterialCategory,
|
||||
operator.LoadMaterials,
|
||||
operator.RemoveConstituent,
|
||||
operator.RemoveLayer,
|
||||
|
||||
@@ -78,9 +78,26 @@ class ObjectMaterialData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"materials": cls.materials(),
|
||||
"type_material": cls.type_material(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def materials(cls):
|
||||
return sorted([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1])
|
||||
return sorted(
|
||||
[(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")], key=lambda x: x[1]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def type_material(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type and element_type != element:
|
||||
material = ifcopenshell.util.element.get_material(element_type)
|
||||
if not material:
|
||||
return
|
||||
if material.is_a() in ("IfcMaterialLayerSetUsage", "IfcMaterialLayerSet"):
|
||||
name_attr = "LayerSetName"
|
||||
else:
|
||||
name_attr = "Name"
|
||||
return getattr(material, name_attr, "Unnamed") or "Unnamed"
|
||||
|
||||
@@ -778,3 +778,31 @@ class CopyMaterial(bpy.types.Operator):
|
||||
if material.id() in object_material_ids:
|
||||
return
|
||||
obj.data.materials.append(IfcStore.get_element(material.id()))
|
||||
|
||||
|
||||
class ExpandMaterialCategory(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_material_category"
|
||||
bl_label = "Expand Material Category"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
category: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMMaterialProperties
|
||||
for category in [c for c in props.materials if c.is_category and c.name == self.category]:
|
||||
category.is_expanded = True
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ContractMaterialCategory(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_material_category"
|
||||
bl_label = "Contract Material Category"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
category: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMMaterialProperties
|
||||
for category in [c for c in props.materials if c.is_category and c.name == self.category]:
|
||||
category.is_expanded = False
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -109,6 +109,8 @@ def get_material_types(self, context):
|
||||
class Material(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
is_category: BoolProperty(name="Is Category", default=False)
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=True)
|
||||
total_elements: IntProperty(name="Total Elements")
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialData
|
||||
|
||||
|
||||
@@ -50,7 +51,7 @@ class BIM_PT_materials(Panel):
|
||||
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "material_type", text="")
|
||||
prop_with_search(row, self.props, "material_type", text="")
|
||||
row.operator("bim.load_materials", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
@@ -61,16 +62,18 @@ class BIM_PT_materials(Panel):
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
if material.ifc_definition_id:
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
|
||||
if material.ifc_definition_id:
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id
|
||||
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
@@ -139,6 +142,10 @@ class BIM_PT_object_material(Panel):
|
||||
row.operator("bim.add_material", icon="ADD", text="").obj = ""
|
||||
return
|
||||
|
||||
if ObjectMaterialData.data["type_material"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT")
|
||||
|
||||
if self.product_data:
|
||||
if self.product_data["type"] == "IfcMaterialConstituentSet":
|
||||
self.material_set_id = self.product_data["id"]
|
||||
@@ -182,9 +189,9 @@ class BIM_PT_object_material(Panel):
|
||||
return self.draw_material_ui()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "material_type", text="")
|
||||
prop_with_search(row, self.props, "material_type", text="")
|
||||
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
|
||||
row.prop(self.props, "material", text="")
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
row.operator("bim.assign_material", icon="ADD", text="")
|
||||
|
||||
def draw_material_ui(self):
|
||||
@@ -214,8 +221,7 @@ class BIM_PT_object_material(Panel):
|
||||
return self.draw_read_only_single_ui()
|
||||
|
||||
def draw_editable_single_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "material", text="")
|
||||
prop_with_search(self.layout, self.props, "material", text="")
|
||||
|
||||
def draw_read_only_single_ui(self):
|
||||
material = Data.materials[self.product_data["id"]]
|
||||
@@ -236,7 +242,7 @@ class BIM_PT_object_material(Panel):
|
||||
row.prop(attribute, "string_value", text=attribute.name)
|
||||
row.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "material", text="")
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
|
||||
op = row.operator(f"bim.add_{self.set_item_name}", icon="ADD", text="")
|
||||
setattr(op, f"{self.set_item_name}_set", self.material_set_id)
|
||||
@@ -395,7 +401,21 @@ class BIM_UL_materials(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
row2 = row.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.label(text=str(item.total_elements))
|
||||
|
||||
if item.is_category:
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).category = item.name
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).category = item.name
|
||||
row.label(text=item.name or "Uncategorised")
|
||||
else:
|
||||
row.label(text="", icon="BLANK1")
|
||||
row.label(text=item.name, icon="MATERIAL")
|
||||
|
||||
row2 = row.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.label(text=str(item.total_elements))
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace
|
||||
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace, profile
|
||||
|
||||
classes = (
|
||||
product.AddEmptyType,
|
||||
product.AddTypeInstance,
|
||||
product.AddConstrTypeInstance,
|
||||
product.DisplayConstrTypes,
|
||||
product.AlignProduct,
|
||||
product.DynamicallyVoidProduct,
|
||||
workspace.Hotkey,
|
||||
@@ -32,8 +33,10 @@ classes = (
|
||||
opening.AddElementOpening,
|
||||
profile.ExtendProfile,
|
||||
prop.BIMModelProperties,
|
||||
prop.ConstrTypeInfo,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_authoring_architectural,
|
||||
ui.DisplayConstrTypesUI,
|
||||
ui.HelpConstrTypes,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
opening.BIM_OT_add_object,
|
||||
@@ -51,6 +54,7 @@ def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.Scene.ConstrTypeInfo = bpy.props.CollectionProperty(type=prop.ConstrTypeInfo)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
|
||||
@@ -68,6 +72,7 @@ def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.BimTool)
|
||||
del bpy.types.Scene.BIMModelProperties
|
||||
del bpy.types.Scene.ConstrTypeInfo
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
|
||||
|
||||
@@ -16,8 +16,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import functools
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
preview_icon_ids = {}
|
||||
attempts = 0
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -31,10 +37,29 @@ class AuthoringData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"ifc_classes": cls.ifc_classes(),
|
||||
"relating_types": cls.relating_types(),
|
||||
}
|
||||
if not hasattr(cls, "data"):
|
||||
cls.data = {}
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
cls.load_ifc_classes()
|
||||
cls.load_relating_types()
|
||||
cls.load_relating_types_browser()
|
||||
cls.load_preview_constr_types()
|
||||
|
||||
@classmethod
|
||||
def load_ifc_classes(cls):
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
|
||||
@classmethod
|
||||
def load_relating_types(cls):
|
||||
cls.data["relating_types_ids"] = cls.relating_types()
|
||||
|
||||
@classmethod
|
||||
def load_relating_types_browser(cls):
|
||||
cls.data["relating_types_browser_ids"] = cls.relating_types_browser()
|
||||
|
||||
@classmethod
|
||||
def load_preview_constr_types(cls):
|
||||
cls.data["preview_constr_types"] = preview_icon_ids
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
@@ -49,16 +74,123 @@ class AuthoringData:
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def relating_types(cls):
|
||||
ifc_classes = cls.ifc_classes()
|
||||
def constr_class_entities(cls, ifc_class=None):
|
||||
ifc_classes = cls.data["ifc_classes"]
|
||||
if not ifc_classes:
|
||||
return []
|
||||
results = []
|
||||
ifc_class = bpy.context.scene.BIMModelProperties.ifc_class
|
||||
if ifc_class is None:
|
||||
ifc_class = cls.props.ifc_class
|
||||
if not ifc_class and ifc_classes:
|
||||
ifc_class = ifc_classes[0][0]
|
||||
if ifc_class:
|
||||
elements = [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type(ifc_class)]
|
||||
results.extend(sorted(elements, key=lambda s: s[1]))
|
||||
elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name)
|
||||
results.extend(elements)
|
||||
return results
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def relating_types(cls, ifc_class=None):
|
||||
return [(str(e.id()), e.Name, e.Description or "") for e in cls.constr_class_entities(ifc_class=ifc_class)]
|
||||
|
||||
@classmethod
|
||||
def relating_types_browser(cls):
|
||||
return cls.relating_types(ifc_class=cls.props.ifc_class_browser)
|
||||
|
||||
@staticmethod
|
||||
def new_relating_type_info(ifc_class):
|
||||
relating_type_info = bpy.context.scene.ConstrTypeInfo.add()
|
||||
relating_type_info.name = ifc_class
|
||||
return relating_type_info
|
||||
|
||||
@classmethod
|
||||
def assetize_constr_class(cls, ifc_class=None):
|
||||
if ifc_class is None:
|
||||
ifc_class = cls.props.ifc_class
|
||||
relating_type_info = cls.relating_type_info(ifc_class)
|
||||
_ = cls.new_relating_type_info(ifc_class) if relating_type_info is None else relating_type_info
|
||||
constr_class_occurrences = cls.constr_class_entities(ifc_class)
|
||||
preview_constr_types = cls.data["preview_constr_types"]
|
||||
for constr_class_entity in constr_class_occurrences:
|
||||
|
||||
### handle asset regeneration when library entity is updated ¿?
|
||||
if (
|
||||
ifc_class not in preview_constr_types
|
||||
or str(constr_class_entity.id()) not in preview_constr_types[ifc_class]
|
||||
):
|
||||
obj = tool.Ifc.get_object(constr_class_entity)
|
||||
cls.assetize_object(obj, ifc_class, constr_class_entity)
|
||||
relating_type_info = cls.relating_type_info(ifc_class)
|
||||
relating_type_info.fully_loaded = True
|
||||
|
||||
@classmethod
|
||||
def assetize_object(cls, obj, ifc_class, ifc_class_entity, from_selection=False):
|
||||
relating_type_id = ifc_class_entity.id()
|
||||
to_be_deleted = False
|
||||
if obj.type == "EMPTY":
|
||||
kwargs = {}
|
||||
if not from_selection:
|
||||
kwargs.update({"ifc_class": ifc_class, "relating_type_id": relating_type_id})
|
||||
new_obj = cls.new_relating_type(**kwargs)
|
||||
if new_obj is not None:
|
||||
to_be_deleted = True
|
||||
obj = new_obj
|
||||
obj.asset_mark()
|
||||
obj.asset_generate_preview()
|
||||
icon_id = obj.preview.icon_id
|
||||
if ifc_class not in cls.data["preview_constr_types"]:
|
||||
cls.data["preview_constr_types"][ifc_class] = {}
|
||||
cls.data["preview_constr_types"][ifc_class][str(relating_type_id)] = {"icon_id": icon_id, "object": obj}
|
||||
if to_be_deleted:
|
||||
for col in obj.users_collection:
|
||||
col.objects.unlink(obj)
|
||||
|
||||
@classmethod
|
||||
def assetize_relating_type_from_selection(cls, browser=False):
|
||||
ifc_class = cls.props.ifc_class_browser if browser else cls.props.ifc_class
|
||||
relating_type_id = cls.props.relating_type_id_browser if browser else cls.props.relating_type_id
|
||||
constr_class_occurrences = cls.constr_class_entities(ifc_class=ifc_class)
|
||||
constr_class_occurrences = [
|
||||
entity for entity in constr_class_occurrences if entity.id() == int(relating_type_id)
|
||||
]
|
||||
if len(constr_class_occurrences) == 0:
|
||||
return False
|
||||
constr_class_entity = constr_class_occurrences[0]
|
||||
obj = tool.Ifc.get_object(constr_class_entity)
|
||||
if obj is None:
|
||||
return False
|
||||
cls.assetize_object(obj, ifc_class, constr_class_entity, from_selection=True)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def relating_type_info(ifc_class):
|
||||
relating_type_infos = [element for element in bpy.context.scene.ConstrTypeInfo if element.name == ifc_class]
|
||||
return None if len(relating_type_infos) == 0 else relating_type_infos[0]
|
||||
|
||||
@classmethod
|
||||
def new_relating_type(cls, ifc_class=None, relating_type_id=None):
|
||||
if ifc_class is None:
|
||||
bpy.ops.bim.add_constr_type_instance(
|
||||
ifc_class=cls.props.ifc_class, relating_type_id=int(cls.props.relating_type_id)
|
||||
)
|
||||
else:
|
||||
cls.props.updating = True
|
||||
cls.props.ifc_class = ifc_class
|
||||
cls.props.relating_type_id = str(relating_type_id)
|
||||
cls.props.updating = False
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
return bpy.context.selected_objects[-1]
|
||||
|
||||
@staticmethod
|
||||
def relating_type_name_by_id(ifc_class, relating_type_id):
|
||||
file = IfcStore.get_file()
|
||||
try:
|
||||
constr_class_entity = file.by_id(int(relating_type_id))
|
||||
except (RuntimeError, ValueError):
|
||||
return None
|
||||
return constr_class_entity.Name if constr_class_entity.is_a() == ifc_class else None
|
||||
|
||||
@classmethod
|
||||
def relating_type_id_by_name(cls, ifc_class, relating_type):
|
||||
relating_types = [ct[0] for ct in cls.relating_types(ifc_class=ifc_class) if ct[1] == relating_type]
|
||||
return None if len(relating_types) == 0 else relating_types[0]
|
||||
|
||||
@@ -54,7 +54,6 @@ class MepGenerator:
|
||||
self.width = dimensions.get("NominalDiameterOrWidth")
|
||||
self.height = dimensions.get("NominalHeight")
|
||||
self.length = 1
|
||||
self.rotation = 0
|
||||
|
||||
return self.derive_from_cursor()
|
||||
elif self.relating_type.is_a("IfcPipeSegmentType"):
|
||||
@@ -66,14 +65,14 @@ class MepGenerator:
|
||||
|
||||
def create_rectangle_segment(self):
|
||||
verts = [
|
||||
Vector((0, self.width, 0)),
|
||||
Vector((0, 0, 0)),
|
||||
Vector((0, self.width, self.height)),
|
||||
Vector((0, 0, self.height)),
|
||||
Vector((self.length, self.width, 0)),
|
||||
Vector((self.length, 0, 0)),
|
||||
Vector((self.length, self.width, self.height)),
|
||||
Vector((self.length, 0, self.height)),
|
||||
Vector((-self.width / 2, self.height / 2, 0)),
|
||||
Vector((-self.width / 2, -self.height / 2, 0)),
|
||||
Vector((-self.width / 2, self.height / 2, self.length)),
|
||||
Vector((-self.width / 2, -self.height / 2, self.length)),
|
||||
Vector((self.width / 2, self.height / 2, 0)),
|
||||
Vector((self.width / 2, -self.height / 2, 0)),
|
||||
Vector((self.width / 2, self.height / 2, self.length)),
|
||||
Vector((self.width / 2, -self.height / 2, self.length)),
|
||||
]
|
||||
faces = [
|
||||
[1, 3, 2, 0],
|
||||
@@ -91,7 +90,8 @@ class MepGenerator:
|
||||
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
|
||||
obj.location = self.location
|
||||
obj.rotation_euler[2] = self.rotation
|
||||
obj.rotation_euler[0] = math.pi / 2
|
||||
obj.rotation_euler[2] = math.pi / 2
|
||||
self.collection.objects.link(obj)
|
||||
|
||||
bpy.ops.bim.assign_class(
|
||||
@@ -100,9 +100,27 @@ class MepGenerator:
|
||||
ifc_representation_class="IfcExtrudedAreaSolid/IfcRectangleProfileDef",
|
||||
)
|
||||
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=self.relating_type)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=self.relating_type)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.Mep"})
|
||||
obj.select_set(True)
|
||||
|
||||
start_port_matrix = Matrix()
|
||||
end_port_matrix = Matrix.Translation((0, 0, self.length))
|
||||
|
||||
for mat in [start_port_matrix, end_port_matrix]:
|
||||
port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort")
|
||||
tool.Ifc.run("system.assign_port", element=element, port=port)
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
|
||||
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except RuntimeError:
|
||||
def msg(self, context):
|
||||
txt = "The created object could not be assigned to a collection. "
|
||||
txt += "Has any IfcSpatialElement been deleted?"
|
||||
self.layout.label(text=txt)
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return obj
|
||||
|
||||
@@ -20,16 +20,20 @@ import bpy
|
||||
import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
from . import wall, slab, profile, mep
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
from . import prop
|
||||
|
||||
|
||||
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
@@ -51,13 +55,17 @@ def add_empty_type_button(self, context):
|
||||
self.layout.operator(AddEmptyType.bl_idname, icon="FILE_3D")
|
||||
|
||||
|
||||
class AddTypeInstance(bpy.types.Operator):
|
||||
bl_idname = "bim.add_type_instance"
|
||||
bl_label = "Add Type Instance"
|
||||
class AddConstrTypeInstance(bpy.types.Operator):
|
||||
bl_idname = "bim.add_constr_type_instance"
|
||||
bl_label = "Add"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add the selected Type Instance to the model"
|
||||
bl_description = "Add Type Instance to the model"
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
relating_type: bpy.props.IntProperty()
|
||||
relating_type_id: bpy.props.IntProperty()
|
||||
from_invoke: bpy.props.BoolProperty(default=False)
|
||||
|
||||
def invoke(self, context, event):
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -65,11 +73,15 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
ifc_class = self.ifc_class or props.ifc_class
|
||||
relating_type_id = self.relating_type or props.relating_type
|
||||
relating_type_id = self.relating_type_id or props.relating_type_id
|
||||
|
||||
if not ifc_class or not relating_type_id:
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.from_invoke:
|
||||
props.ifc_class = self.ifc_class
|
||||
props.relating_type_id = str(self.relating_type_id)
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, self.file.schema)[0]
|
||||
relating_type = self.file.by_id(int(relating_type_id))
|
||||
@@ -81,7 +93,7 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
return {"FINISHED"}
|
||||
if relating_type.is_a("IfcFlowSegmentType"):
|
||||
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
|
||||
if mep.MepGenerator(relating_type).generate():
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -117,12 +129,8 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
blenderbim.core.type.assign_type(
|
||||
tool.Ifc,
|
||||
tool.Type,
|
||||
element=tool.Ifc.get_entity(obj),
|
||||
type=tool.Ifc.get().by_id(int(props.relating_type)),
|
||||
)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
|
||||
if building_obj:
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
@@ -136,12 +144,24 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
if collection_obj and collection_obj.BIMObjectProperties.ifc_definition_id:
|
||||
obj.location[2] = collection_obj.location[2] - min([v[2] for v in obj.bound_box])
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
for port in ifcopenshell.util.system.get_ports(relating_type):
|
||||
mat = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement)
|
||||
mat[0][3] *= unit_scale
|
||||
mat[1][3] *= unit_scale
|
||||
mat[2][3] *= unit_scale
|
||||
mat = obj.matrix_world @ mathutils.Matrix(mat)
|
||||
new_port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort")
|
||||
tool.Ifc.run("system.assign_port", element=element, port=new_port)
|
||||
tool.Ifc.run("geometry.edit_object_placement", product=new_port, matrix=mat, is_si=True)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
return {"FINISHED"}
|
||||
|
||||
def generate_layered_element(self, ifc_class, relating_type):
|
||||
@staticmethod
|
||||
def generate_layered_element(ifc_class, relating_type):
|
||||
layer_set_direction = None
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
@@ -163,6 +183,24 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
pass # Dumb block generator? Eh? :)
|
||||
|
||||
|
||||
class DisplayConstrTypes(bpy.types.Operator):
|
||||
bl_idname = "bim.display_constr_types"
|
||||
bl_label = "Browse Construction Types"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Display all available Construction Types to add new instances"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
props = context.scene.BIMModelProperties
|
||||
ifc_class = props.ifc_class
|
||||
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
bpy.ops.bim.display_constr_types_ui("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AlignProduct(bpy.types.Operator):
|
||||
bl_idname = "bim.align_product"
|
||||
bl_label = "Align Product"
|
||||
@@ -331,10 +369,13 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
if om is not None and om.BIMObjectProperties.ifc_definition_id
|
||||
]
|
||||
|
||||
if material[0].id() in object_material_ids:
|
||||
if material and material[0].id() in object_material_ids:
|
||||
continue
|
||||
|
||||
if len(obj.data.materials) == 1:
|
||||
obj.data.materials.clear()
|
||||
|
||||
if not material:
|
||||
continue
|
||||
|
||||
obj.data.materials.append(IfcStore.get_element(material[0].id()))
|
||||
|
||||
@@ -162,7 +162,16 @@ class DumbProfileGenerator:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
|
||||
MaterialData.load(self.file)
|
||||
obj.select_set(True)
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except RuntimeError:
|
||||
|
||||
def msg(self, context):
|
||||
txt = "The created object could not be assigned to a collection. "
|
||||
txt += "Has any IfcSpatialElement been deleted?"
|
||||
self.layout.label(text=txt)
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
@@ -17,21 +17,8 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.type
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def get_ifc_class(self, context):
|
||||
@@ -43,18 +30,104 @@ def get_ifc_class(self, context):
|
||||
def get_relating_type(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["relating_types"]
|
||||
return AuthoringData.data["relating_types_ids"]
|
||||
|
||||
|
||||
def get_relating_type_browser(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["relating_types_browser_ids"]
|
||||
|
||||
|
||||
def update_icon_id(self, context, browser=False):
|
||||
ifc_class = self.ifc_class_browser if browser else self.ifc_class
|
||||
relating_type_id = self.relating_type_id_browser if browser else self.relating_type_id
|
||||
relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id)
|
||||
if (
|
||||
ifc_class not in AuthoringData.data["preview_constr_types"]
|
||||
or relating_type_id not in AuthoringData.data["preview_constr_types"][ifc_class]
|
||||
) and relating_type is not None:
|
||||
if not AuthoringData.assetize_relating_type_from_selection(browser=browser):
|
||||
return
|
||||
if ifc_class not in AuthoringData.data["preview_constr_types"]:
|
||||
pass
|
||||
self.icon_id = AuthoringData.data["preview_constr_types"][ifc_class][relating_type_id]["icon_id"]
|
||||
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
AuthoringData.is_loaded = False
|
||||
AuthoringData.load_ifc_classes()
|
||||
AuthoringData.load_relating_types()
|
||||
if self.updating:
|
||||
return
|
||||
|
||||
|
||||
def update_ifc_class_browser(self, context):
|
||||
AuthoringData.load_ifc_classes()
|
||||
AuthoringData.load_relating_types_browser()
|
||||
if self.updating:
|
||||
return
|
||||
ifc_class = self.ifc_class_browser
|
||||
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
|
||||
|
||||
def update_relating_type(self, context):
|
||||
AuthoringData.load_relating_types()
|
||||
if not self.updating:
|
||||
update_icon_id(self, context)
|
||||
|
||||
|
||||
def update_relating_type_browser(self, context):
|
||||
AuthoringData.load_relating_types_browser()
|
||||
if not self.updating:
|
||||
update_icon_id(self, context, browser=True)
|
||||
|
||||
|
||||
def update_relating_type_by_name(self, context):
|
||||
AuthoringData.load_relating_types()
|
||||
relating_type_id = AuthoringData.relating_type_id_by_name(self.ifc_class, self.relating_type)
|
||||
if relating_type_id is not None:
|
||||
self.relating_type_id = relating_type_id
|
||||
|
||||
|
||||
def update_preview_multiple(self, context):
|
||||
if self.preview_multiple_constr_types:
|
||||
ifc_class = self.ifc_class
|
||||
relating_type_info = AuthoringData.relating_type_info(ifc_class)
|
||||
if relating_type_info is None or not relating_type_info.fully_loaded:
|
||||
AuthoringData.assetize_constr_class(ifc_class)
|
||||
else:
|
||||
update_relating_type(self, context)
|
||||
|
||||
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class)
|
||||
relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type")
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
ifc_class_browser: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class",
|
||||
update=update_ifc_class_browser)
|
||||
relating_type: bpy.props.StringProperty(update=update_relating_type_by_name)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
items=get_relating_type, name="Construction Type", update=update_relating_type
|
||||
)
|
||||
relating_type_id_browser: bpy.props.EnumProperty(
|
||||
items=get_relating_type_browser, name="Construction Type", update=update_relating_type_browser
|
||||
)
|
||||
icon_id: bpy.props.IntProperty()
|
||||
preview_multiple_constr_types: bpy.props.BoolProperty(default=False, update=update_preview_multiple)
|
||||
updating: bpy.props.BoolProperty(default=False)
|
||||
occurrence_name_style: bpy.props.EnumProperty(
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type}
|
||||
|
||||
|
||||
def get_relating_type_info(self, context):
|
||||
return AuthoringData.relating_types(ifc_class=self.name)
|
||||
|
||||
|
||||
class ConstrTypeInfo(PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="Construction class")
|
||||
relating_type: bpy.props.EnumProperty(name="Construction type", items=get_relating_type_info)
|
||||
fully_loaded: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@@ -166,7 +166,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
obj = settings["blender_object"]
|
||||
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
|
||||
if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer3":
|
||||
return
|
||||
qto = ifcopenshell.api.run(
|
||||
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_SlabBaseQuantities"
|
||||
@@ -306,7 +306,16 @@ class DumbSlabGenerator:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer3"})
|
||||
MaterialData.load(self.file)
|
||||
obj.select_set(True)
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except RuntimeError:
|
||||
|
||||
def msg(self, context):
|
||||
txt = "The created object could not be assigned to a collection. "
|
||||
txt += "Has any IfcSpatialElement been deleted?"
|
||||
self.layout.label(text=txt)
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return obj
|
||||
|
||||
|
||||
@@ -342,6 +351,23 @@ class DumbSlabPlaner:
|
||||
return
|
||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||
|
||||
relating_type = settings["relating_type"]
|
||||
if hasattr(relating_type, "HasPropertySets"):
|
||||
psets = relating_type.HasPropertySets
|
||||
if psets is not None:
|
||||
for pset in psets:
|
||||
if hasattr(pset, "HasProperties"):
|
||||
pset_props = pset.HasProperties
|
||||
if pset_props is not None:
|
||||
for prop in pset_props:
|
||||
if prop.Name == "LayerSetDirection":
|
||||
if hasattr(prop, "NominalValue"):
|
||||
nominal_value = prop.NominalValue
|
||||
if hasattr(nominal_value, "wrappedValue"):
|
||||
if nominal_value.wrappedValue == "AXIS2":
|
||||
return
|
||||
|
||||
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
|
||||
self.change_thickness(settings["related_object"], new_thickness)
|
||||
|
||||
|
||||
@@ -16,54 +16,17 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from bpy.types import Panel, Operator
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.helper import prop_with_search, close_operator_panel
|
||||
|
||||
|
||||
class BIM_PT_authoring(Panel):
|
||||
bl_idname = "BIM_PT_authoring"
|
||||
bl_label = "Authoring"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
props = context.scene.BIMModelProperties
|
||||
col = self.layout.column(align=True)
|
||||
enabled = True
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
col.prop(props, "ifc_class", text="", icon="FILE_VOLUME")
|
||||
else:
|
||||
col.label(text="No IFC Class", icon="FILE_VOLUME")
|
||||
enabled = False
|
||||
if AuthoringData.data["relating_types"]:
|
||||
col.prop(props, "relating_type", text="", icon="FILE_3D")
|
||||
else:
|
||||
col.label(text="No Relating Type", icon="FILE_3D")
|
||||
enabled = False
|
||||
row = col.row()
|
||||
row.operator("bim.add_type_instance", icon="ADD")
|
||||
row.enabled = enabled
|
||||
|
||||
|
||||
class BIM_PT_authoring_architectural(Panel):
|
||||
bl_label = "Architectural"
|
||||
bl_idname = "BIM_PT_authoring_architectural"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_idname = "BIM_PT_authoring"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_parent_id = "BIM_PT_authoring"
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -78,3 +41,108 @@ class BIM_PT_authoring_architectural(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.flip_wall", icon="ORIENTATION_NORMAL", text="Flip")
|
||||
row.operator("bim.split_wall", icon="MOD_PHYSICS", text="Split")
|
||||
|
||||
|
||||
class DisplayConstrTypesUI(Operator):
|
||||
bl_idname = "bim.display_constr_types_ui"
|
||||
bl_label = "Browse Construction Types"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Display all available Construction Types to add new instances"
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, props, "ifc_class_browser", text="")
|
||||
ifc_class = props.ifc_class_browser
|
||||
num_cols = 3
|
||||
self.layout.row().separator(factor=0.25)
|
||||
flow = self.layout.grid_flow(row_major=True, columns=num_cols, even_columns=True, even_rows=True, align=True)
|
||||
relating_types = AuthoringData.relating_types_browser()
|
||||
num_types = len(relating_types)
|
||||
for idx, (relating_type_id, name, desc) in enumerate(relating_types):
|
||||
outer_col = flow.column()
|
||||
box = outer_col.box()
|
||||
row = box.row()
|
||||
row.label(text=name, icon="FILE_3D")
|
||||
row.alignment = "CENTER"
|
||||
row = box.row()
|
||||
preview_constr_types = AuthoringData.data["preview_constr_types"]
|
||||
if ifc_class in preview_constr_types:
|
||||
preview_ifc_class = preview_constr_types[ifc_class]
|
||||
if relating_type_id in preview_ifc_class:
|
||||
icon_id = preview_ifc_class[relating_type_id]["icon_id"]
|
||||
row.template_icon(icon_value=icon_id, scale=6.0)
|
||||
row = box.row()
|
||||
op = row.operator("bim.add_constr_type_instance", icon="ADD")
|
||||
op.from_invoke = True
|
||||
op.ifc_class = ifc_class
|
||||
if relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(relating_type_id)
|
||||
last_row_cols = num_types % num_cols
|
||||
if last_row_cols != 0:
|
||||
for _ in range(num_cols - last_row_cols):
|
||||
flow.column()
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.help_relating_types", text="", icon="QUESTION")
|
||||
|
||||
|
||||
class HelpConstrTypes(Operator):
|
||||
bl_idname = "bim.help_relating_types"
|
||||
bl_label = "Construction Types Help"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Click to read some contextual help"
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=510)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.row().separator(factor=0.5)
|
||||
row = layout.row()
|
||||
row.alignment = "CENTER"
|
||||
row.label(text="BlenderBIM Help", icon="BLENDER")
|
||||
layout.row().separator(factor=0.5)
|
||||
|
||||
row = layout.row().row()
|
||||
row.label(text="Overview:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||
self.draw_lines(layout, self.message_summary)
|
||||
layout.row().separator()
|
||||
|
||||
row = layout.row().row()
|
||||
row.label(text="Further support:", icon="KEYTYPE_MOVING_HOLD_VEC")
|
||||
layout.row().separator(factor=0.5)
|
||||
row = layout.row()
|
||||
op = row.operator("bim.open_upstream", text="Homepage", icon="HOME")
|
||||
op.page = "home"
|
||||
op = row.operator("bim.open_upstream", text="Docs", icon="DOCUMENTS")
|
||||
op.page = "docs"
|
||||
op = row.operator("bim.open_upstream", text="Wiki", icon="CURRENT_FILE")
|
||||
op.page = "wiki"
|
||||
op = row.operator("bim.open_upstream", text="Community", icon="COMMUNITY")
|
||||
op.page = "community"
|
||||
layout.row().separator()
|
||||
|
||||
def draw_lines(self, layout, lines):
|
||||
box = layout.box()
|
||||
for line in lines:
|
||||
row = box.row()
|
||||
row.label(text=f" {line}")
|
||||
|
||||
@property
|
||||
def message_summary(self):
|
||||
return [
|
||||
"The Construction Type Browser allows to preview and add new instances to the model.",
|
||||
"For further support, please click on the Documentation link below.",
|
||||
]
|
||||
|
||||
@@ -688,23 +688,10 @@ class DumbWallGenerator:
|
||||
for stroke in layer.active_frame.strokes:
|
||||
if len(stroke.points) == 1:
|
||||
continue
|
||||
coords = (stroke.points[0].co, stroke.points[-1].co)
|
||||
direction = coords[1] - coords[0]
|
||||
length = direction.length
|
||||
if length < 0.1:
|
||||
continue
|
||||
data = {"coords": coords}
|
||||
|
||||
# Round to nearest 50mm (yes, metric for now)
|
||||
self.length = 0.05 * round(length / 0.05)
|
||||
self.rotation = math.atan2(direction[1], direction[0])
|
||||
# Round to nearest 5 degrees
|
||||
nearest_degree = (math.pi / 180) * 5
|
||||
self.rotation = nearest_degree * round(self.rotation / nearest_degree)
|
||||
self.location = coords[0]
|
||||
data["obj"] = self.create_wall()
|
||||
strokes.append(data)
|
||||
objs.append(data["obj"])
|
||||
data = self.create_wall_from_2_points((stroke.points[0].co, stroke.points[-1].co))
|
||||
if data:
|
||||
strokes.append(data)
|
||||
objs.append(data["obj"])
|
||||
|
||||
if len(objs) < 2:
|
||||
return objs
|
||||
@@ -726,6 +713,23 @@ class DumbWallGenerator:
|
||||
bpy.context.scene.grease_pencil.layers.remove(layer)
|
||||
return objs
|
||||
|
||||
def create_wall_from_2_points(self, coords):
|
||||
direction = coords[1] - coords[0]
|
||||
length = direction.length
|
||||
if length < 0.1:
|
||||
return
|
||||
data = {"coords": coords}
|
||||
|
||||
# Round to nearest 50mm (yes, metric for now)
|
||||
self.length = 0.05 * round(length / 0.05)
|
||||
self.rotation = math.atan2(direction[1], direction[0])
|
||||
# Round to nearest 5 degrees
|
||||
nearest_degree = (math.pi / 180) * 5
|
||||
self.rotation = nearest_degree * round(self.rotation / nearest_degree)
|
||||
self.location = coords[0]
|
||||
data["obj"] = self.create_wall()
|
||||
return data
|
||||
|
||||
def has_end_near_stroke(self, stroke, stroke2):
|
||||
point, distance = mathutils.geometry.intersect_point_line(stroke["coords"][0], *stroke2["coords"])
|
||||
if distance > 0 and distance < 1 and self.is_near(point, stroke["coords"][0]):
|
||||
@@ -813,7 +817,16 @@ class DumbWallGenerator:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbLayer2"})
|
||||
MaterialData.load(self.file)
|
||||
obj.select_set(True)
|
||||
try:
|
||||
obj.select_set(True)
|
||||
except RuntimeError:
|
||||
|
||||
def msg(self, context):
|
||||
txt = "The created object could not be assigned to a collection. "
|
||||
txt += "Has any IfcSpatialElement been deleted?"
|
||||
self.layout.label(text=txt)
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return obj
|
||||
|
||||
|
||||
@@ -857,7 +870,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
obj = settings["blender_object"]
|
||||
product = ifc_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
||||
if not parametric or "Engine" not in parametric or parametric["Engine"] != "BlenderBIM.DumbLayer2":
|
||||
return
|
||||
qto = ifcopenshell.api.run(
|
||||
"pset.add_qto", ifc_file, should_run_listeners=False, product=product, name="Qto_WallBaseQuantities"
|
||||
@@ -937,6 +950,23 @@ class DumbWallPlaner:
|
||||
return
|
||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||
|
||||
relating_type = settings["relating_type"]
|
||||
if hasattr(relating_type, "HasPropertySets"):
|
||||
psets = relating_type.HasPropertySets
|
||||
if psets is not None:
|
||||
for pset in psets:
|
||||
if hasattr(pset, "HasProperties"):
|
||||
pset_props = pset.HasProperties
|
||||
if pset_props is not None:
|
||||
for prop in pset_props:
|
||||
if prop.Name == "LayerSetDirection":
|
||||
if hasattr(prop, "NominalValue"):
|
||||
nominal_value = prop.NominalValue
|
||||
if hasattr(nominal_value, "wrappedValue"):
|
||||
if nominal_value.wrappedValue == "AXIS3":
|
||||
return
|
||||
|
||||
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS2":
|
||||
self.change_thickness(settings["related_object"], new_thickness)
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
import os
|
||||
import bpy
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
from blenderbim.bim.helper import prop_with_search, close_operator_panel
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model import prop
|
||||
|
||||
|
||||
class BimTool(WorkSpaceTool):
|
||||
@@ -52,31 +54,81 @@ class BimTool(WorkSpaceTool):
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
if not AuthoringData.is_loaded and IfcStore.get_file():
|
||||
AuthoringData.load()
|
||||
props = context.scene.BIMModelProperties
|
||||
is_tool_header = context.region.type == "TOOL_HEADER"
|
||||
is_sidebar = context.region.type == "UI"
|
||||
|
||||
row = layout.row(align=True)
|
||||
if not IfcStore.get_file():
|
||||
row.label(text="No IFC Project", icon="ERROR")
|
||||
return
|
||||
props = context.scene.BIMModelProperties
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
row.prop(props, "ifc_class", text="")
|
||||
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
ifc_classes = AuthoringData.data[
|
||||
"ifc_classes"] if "ifc_classes" in AuthoringData.data else False
|
||||
relating_types_ids = AuthoringData.data[
|
||||
"relating_types_ids"] if "relating_types_ids" in AuthoringData.data else False
|
||||
|
||||
if ifc_classes and relating_types_ids and not props.icon_id:
|
||||
# hack Dion won't like to show a preview also on the first time the sidebar is shown
|
||||
bpy.app.timers.register(lambda: prop.update_relating_type(props, context))
|
||||
|
||||
ifc_class = props.ifc_class
|
||||
relating_type_id = props.relating_type_id
|
||||
|
||||
if is_tool_header:
|
||||
row = layout.row(align=True)
|
||||
if ifc_classes:
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
row.prop(data=props, property="ifc_class", text="")
|
||||
else:
|
||||
row.label(text="No Construction Class", icon="FILE_VOLUME")
|
||||
row = layout.row(align=True)
|
||||
if relating_types_ids:
|
||||
row.label(text="", icon="FILE_3D")
|
||||
row.prop(data=props, property="relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
if ifc_classes:
|
||||
row = layout.row()
|
||||
row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
row.label(text=f" Add")
|
||||
else:
|
||||
row.label(text="No IFC Class")
|
||||
if AuthoringData.data["relating_types"]:
|
||||
row.prop(props, "relating_type", text="")
|
||||
else:
|
||||
row.label(text="No Relating Type")
|
||||
row = layout.row(align=True)
|
||||
if ifc_classes:
|
||||
row.label(text="", icon="FILE_VOLUME")
|
||||
prop_with_search(row, props, "ifc_class", text="")
|
||||
else:
|
||||
row.label(text="No Construction Class", icon="FILE_VOLUME")
|
||||
row = layout.row(align=True)
|
||||
if relating_types_ids:
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, props, "relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
if is_sidebar and ifc_classes and relating_types_ids:
|
||||
box = layout.box()
|
||||
box.template_icon(icon_value=props.icon_id, scale=8)
|
||||
row = layout.row()
|
||||
op = row.operator("bim.add_constr_type_instance", icon="ADD")
|
||||
op.from_invoke = True
|
||||
op.ifc_class = ifc_class
|
||||
if relating_type_id.isnumeric():
|
||||
op.relating_type_id = int(relating_type_id)
|
||||
|
||||
row.label(text="", icon="BLANK1")
|
||||
if ifc_classes:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_A")
|
||||
row.label(text=f" Add Type Instance")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Add Type Instance", icon="EVENT_A")
|
||||
|
||||
if AuthoringData.data["ifc_classes"]:
|
||||
if props.ifc_class == "IfcWallType":
|
||||
if ifc_classes:
|
||||
if ifc_class == "IfcWallType":
|
||||
row = layout.row()
|
||||
row.label(text="Join")
|
||||
row = layout.row(align=True)
|
||||
@@ -98,13 +150,23 @@ class BimTool(WorkSpaceTool):
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Split", icon="EVENT_S")
|
||||
|
||||
if props.ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
||||
if ifc_class in ("IfcColumnType", "IfcBeamType", "IfcMemberType"):
|
||||
row = layout.row()
|
||||
row.label(text="Join")
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
if props.ifc_class in (
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType",
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Extend", icon="EVENT_E")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="Opening", icon="EVENT_O")
|
||||
@@ -156,7 +218,7 @@ class Hotkey(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def hotkey_S_A(self):
|
||||
bpy.ops.bim.add_type_instance()
|
||||
bpy.ops.bim.add_constr_type_instance()
|
||||
|
||||
def hotkey_S_C(self):
|
||||
if self.has_ifc_class and self.props.ifc_class == "IfcWallType":
|
||||
|
||||
@@ -28,6 +28,7 @@ classes = (
|
||||
operator.AddPersonAndOrganisation,
|
||||
operator.AddPersonAttribute,
|
||||
operator.AddRole,
|
||||
operator.AssignActor,
|
||||
operator.ClearUser,
|
||||
operator.DisableEditingActor,
|
||||
operator.DisableEditingAddress,
|
||||
@@ -53,11 +54,13 @@ classes = (
|
||||
operator.RemovePersonAttribute,
|
||||
operator.RemoveRole,
|
||||
operator.SetUser,
|
||||
operator.UnassignActor,
|
||||
prop.BIMOwnerProperties,
|
||||
ui.BIM_PT_people,
|
||||
ui.BIM_PT_organisations,
|
||||
ui.BIM_PT_owner,
|
||||
ui.BIM_PT_actor,
|
||||
ui.BIM_PT_object_actor,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ def refresh():
|
||||
OrganisationsData.is_loaded = False
|
||||
OwnerData.is_loaded = False
|
||||
ActorData.is_loaded = False
|
||||
ObjectActorData.is_loaded = False
|
||||
|
||||
|
||||
class RolesAddressesData:
|
||||
@@ -93,15 +94,17 @@ class PeopleData(RolesAddressesData):
|
||||
def get_people(cls):
|
||||
people = []
|
||||
for person in tool.Ifc.get().by_type("IfcPerson"):
|
||||
roles = cls.get_roles(person)
|
||||
people.append(
|
||||
{
|
||||
"id": person.id(),
|
||||
"props": bpy.context.scene.BIMOwnerProperties.person_attributes,
|
||||
"name": cls.get_person_name(person),
|
||||
"roles_label": ", ".join([r["label"] for r in roles]),
|
||||
"is_editing": cls.get_person_is_editing(person),
|
||||
"is_engaged": bool(person.EngagedIn),
|
||||
"list_attributes": cls.get_person_list_attributes(person),
|
||||
"roles": cls.get_roles(person),
|
||||
"roles": roles,
|
||||
"addresses": cls.get_addresses(person),
|
||||
}
|
||||
)
|
||||
@@ -151,14 +154,16 @@ class OrganisationsData(RolesAddressesData):
|
||||
def get_organisations(cls):
|
||||
organisations = []
|
||||
for organisation in tool.Ifc.get().by_type("IfcOrganization"):
|
||||
roles = cls.get_roles(organisation)
|
||||
organisations.append(
|
||||
{
|
||||
"id": organisation.id(),
|
||||
"props": bpy.context.scene.BIMOwnerProperties.organisation_attributes,
|
||||
"name": organisation.Name,
|
||||
"roles_label": ", ".join([r["label"] for r in roles]),
|
||||
"is_editing": bpy.context.scene.BIMOwnerProperties.active_organisation_id == organisation.id(),
|
||||
"is_engaged": bool(organisation.Engages),
|
||||
"roles": cls.get_roles(organisation),
|
||||
"roles": roles,
|
||||
"addresses": cls.get_addresses(organisation),
|
||||
}
|
||||
)
|
||||
@@ -212,13 +217,13 @@ class ActorData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"actor": cls.actor(),
|
||||
"the_actor": cls.the_actor(),
|
||||
"actors": cls.actors(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def actor(cls):
|
||||
def the_actor(cls):
|
||||
ifc_class = bpy.context.scene.BIMOwnerProperties.actor_type
|
||||
return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type(ifc_class)]
|
||||
|
||||
@@ -228,5 +233,54 @@ class ActorData:
|
||||
props = bpy.context.scene.BIMOwnerProperties
|
||||
for actor in tool.Ifc.get().by_type(props.actor_class, include_subtypes=False):
|
||||
is_editing = props.active_actor_id == actor.id()
|
||||
actors.append({"id": actor.id(), "name": actor.Name or "Unnamed", "is_editing": is_editing})
|
||||
if actor.TheActor.is_a("IfcPerson"):
|
||||
the_actor = actor.TheActor.Identification or "N/A"
|
||||
elif actor.TheActor.is_a("IfcOrganization"):
|
||||
the_actor = actor.TheActor.Identification or "N/A"
|
||||
elif actor.TheActor.is_a("IfcPersonAndOrganization"):
|
||||
the_actor = actor.TheActor.ThePerson.Identification or "N/A"
|
||||
the_actor += "-" + actor.TheActor.TheOrganization.Identification or "N/A"
|
||||
actors.append(
|
||||
{"id": actor.id(), "name": actor.Name or "Unnamed", "the_actor": the_actor, "is_editing": is_editing}
|
||||
)
|
||||
return actors
|
||||
|
||||
|
||||
class ObjectActorData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"actor": cls.actor(), "actors": cls.actors()}
|
||||
|
||||
@classmethod
|
||||
def actor(cls):
|
||||
return [(str(p.id()), p.Name or "Unnamed", "") for p in tool.Ifc.get().by_type("IfcActor")]
|
||||
|
||||
@classmethod
|
||||
def actors(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return results
|
||||
for rel in getattr(element, "HasAssignments", []):
|
||||
if rel.is_a("IfcRelAssignsToActor"):
|
||||
actor = rel.RelatingActor
|
||||
if actor.TheActor.is_a("IfcPerson"):
|
||||
roles = cls.get_roles(actor.TheActor)
|
||||
elif actor.TheActor.is_a("IfcOrganization"):
|
||||
roles = cls.get_roles(actor.TheActor)
|
||||
elif actor.TheActor.is_a("IfcPersonAndOrganization"):
|
||||
roles = cls.get_roles(actor.TheActor.ThePerson)
|
||||
roles.extend(cls.get_roles(actor.TheActor.TheOrganization))
|
||||
role = ", ".join(roles)
|
||||
results.append(
|
||||
{"id": actor.id(), "name": actor.Name or "Unnamed", "role": role, "ifc_class": actor.is_a()}
|
||||
)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def get_roles(cls, parent):
|
||||
return [r.UserDefinedRole or r.Role for r in parent.Roles or []]
|
||||
|
||||
@@ -315,8 +315,8 @@ class AddActor(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMOwnerProperties
|
||||
if props.actor:
|
||||
core.add_actor(tool.Ifc, ifc_class=props.actor_class, actor=tool.Ifc.get().by_id(int(props.actor)))
|
||||
if props.the_actor:
|
||||
core.add_actor(tool.Ifc, ifc_class=props.actor_class, actor=tool.Ifc.get().by_id(int(props.the_actor)))
|
||||
|
||||
|
||||
class EnableEditingActor(bpy.types.Operator, Operator):
|
||||
@@ -355,3 +355,27 @@ class RemoveActor(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_actor(tool.Ifc, actor=tool.Ifc.get().by_id(self.actor))
|
||||
|
||||
|
||||
class AssignActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_actor"
|
||||
bl_label = "Assign Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_actor(
|
||||
tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object)
|
||||
)
|
||||
|
||||
|
||||
class UnassignActor(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_actor"
|
||||
bl_label = "Unassign Actor"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
actor: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_actor(
|
||||
tool.Ifc, actor=tool.Ifc.get().by_id(self.actor), element=tool.Ifc.get_entity(context.active_object)
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.owner.data import OwnerData, ActorData
|
||||
from blenderbim.bim.module.owner.data import OwnerData, ActorData, ObjectActorData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -44,14 +44,20 @@ def get_user_organisation(self, context):
|
||||
return OwnerData.data["user_organisation"]
|
||||
|
||||
|
||||
def get_actor(self, context):
|
||||
def get_the_actor(self, context):
|
||||
if not ActorData.is_loaded:
|
||||
ActorData.load()
|
||||
return ActorData.data["actor"]
|
||||
return ActorData.data["the_actor"]
|
||||
|
||||
|
||||
def get_actor(self, context):
|
||||
if not ObjectActorData.is_loaded:
|
||||
ObjectActorData.load()
|
||||
return ObjectActorData.data["actor"]
|
||||
|
||||
|
||||
def update_actor_type(self, context):
|
||||
ActorData.data["actor"] = ActorData.actor()
|
||||
ActorData.data["the_actor"] = ActorData.the_actor()
|
||||
|
||||
|
||||
def update_actor_class(self, context):
|
||||
@@ -94,4 +100,5 @@ class BIMOwnerProperties(PropertyGroup):
|
||||
name="Actor Type",
|
||||
update=update_actor_type,
|
||||
)
|
||||
the_actor: EnumProperty(items=get_the_actor, name="Actor")
|
||||
actor: EnumProperty(items=get_actor, name="Actor")
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData
|
||||
from blenderbim.bim.module.owner.data import PeopleData, OrganisationsData, OwnerData, ActorData, ObjectActorData
|
||||
|
||||
|
||||
def draw_roles(box, parent):
|
||||
@@ -127,7 +127,9 @@ class BIM_PT_people(bpy.types.Panel):
|
||||
draw_addresses(box, person)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=person["name"])
|
||||
row.label(text=person["name"], icon="OUTLINER_OB_ARMATURE")
|
||||
if person["roles_label"]:
|
||||
row.label(text=person["roles_label"])
|
||||
row.operator("bim.enable_editing_person", icon="GREASEPENCIL", text="").person = person["id"]
|
||||
if not person["is_engaged"]:
|
||||
row.operator("bim.remove_person", icon="X", text="").person = person["id"]
|
||||
@@ -171,7 +173,9 @@ class BIM_PT_organisations(bpy.types.Panel):
|
||||
draw_addresses(box, organisation)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=organisation["name"])
|
||||
row.label(text=organisation["name"], icon="COMMUNITY")
|
||||
if organisation["roles_label"]:
|
||||
row.label(text=organisation["roles_label"])
|
||||
op = row.operator("bim.enable_editing_organisation", icon="GREASEPENCIL", text="")
|
||||
op.organisation = organisation["id"]
|
||||
if not organisation["is_engaged"]:
|
||||
@@ -254,9 +258,9 @@ class BIM_PT_actor(bpy.types.Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "actor_class", text="")
|
||||
row.prop(self.props, "actor_type", text="")
|
||||
if ActorData.data["actor"]:
|
||||
if ActorData.data["the_actor"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "actor", text="")
|
||||
row.prop(self.props, "the_actor", text="")
|
||||
row.operator("bim.add_actor", icon="ADD", text="")
|
||||
else:
|
||||
self.layout.label(text="No users found.")
|
||||
@@ -273,6 +277,42 @@ class BIM_PT_actor(bpy.types.Panel):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.actor_attributes, box)
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=actor["name"])
|
||||
row.label(text=actor["name"], icon="USER")
|
||||
row.label(text=actor["the_actor"])
|
||||
row.operator("bim.enable_editing_actor", icon="GREASEPENCIL", text="").actor = actor["id"]
|
||||
row.operator("bim.remove_actor", icon="X", text="").actor = actor["id"]
|
||||
|
||||
|
||||
class BIM_PT_object_actor(bpy.types.Panel):
|
||||
bl_label = "IFC Actor"
|
||||
bl_idname = "BIM_PT_object_actor"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not ObjectActorData.is_loaded:
|
||||
ObjectActorData.load()
|
||||
|
||||
self.props = context.scene.BIMOwnerProperties
|
||||
|
||||
if not ObjectActorData.data["actor"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Actors Found", icon="USER")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "actor", text="")
|
||||
row.operator("bim.assign_actor", icon="ADD", text="").actor = int(self.props.actor)
|
||||
|
||||
for actor in ObjectActorData.data["actors"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=actor["name"], icon="USER")
|
||||
row.label(text=actor["role"])
|
||||
row.operator("bim.unassign_actor", icon="X", text="").actor = actor["id"]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
import bpy
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
|
||||
@@ -37,7 +38,7 @@ class BIM_PT_patch(bpy.types.Panel):
|
||||
scene = context.scene
|
||||
props = scene.BIMPatchProperties
|
||||
row = layout.row()
|
||||
row.prop(props, "ifc_patch_recipes")
|
||||
prop_with_search(row, props, "ifc_patch_recipes")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_patch_input")
|
||||
|
||||
@@ -20,27 +20,30 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CreateProject,
|
||||
operator.LoadProject,
|
||||
operator.UnloadProject,
|
||||
operator.LoadProjectElements,
|
||||
operator.LinkIfc,
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.LoadLink,
|
||||
operator.SelectLibraryFile,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.RefreshLibrary,
|
||||
operator.RewindLibrary,
|
||||
operator.AssignLibraryDeclaration,
|
||||
operator.UnassignLibraryDeclaration,
|
||||
operator.SaveLibraryFile,
|
||||
operator.AppendLibraryElement,
|
||||
operator.EnableEditingHeader,
|
||||
operator.AssignLibraryDeclaration,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.CreateProject,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadProject,
|
||||
operator.LoadProjectElements,
|
||||
operator.RefreshLibrary,
|
||||
operator.RewindLibrary,
|
||||
operator.SaveLibraryFile,
|
||||
operator.AppendEntireLibrary,
|
||||
operator.SelectLibraryFile,
|
||||
operator.ToggleFilterCategories,
|
||||
operator.ToggleLinkVisibility,
|
||||
operator.UnassignLibraryDeclaration,
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
prop.Link,
|
||||
@@ -56,7 +59,7 @@ classes = (
|
||||
|
||||
def menu_func_export(self, context):
|
||||
op = self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
|
||||
op.should_save_as = False
|
||||
op.should_save_as = True
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
|
||||
@@ -71,6 +71,7 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
||||
bl_description = "Select an IFC file that can be used as a library"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
append_all: bpy.props.BoolProperty(default=False)
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
@@ -87,6 +88,8 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
||||
bpy.ops.bim.refresh_library()
|
||||
if context.area:
|
||||
context.area.tag_redraw()
|
||||
if self.append_all:
|
||||
bpy.ops.bim.append_entire_library()
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -105,6 +108,9 @@ class SelectLibraryFile(bpy.types.Operator, IFCFileSelector):
|
||||
IfcStore.library_path = data["filepath"]
|
||||
IfcStore.library_file = ifcopenshell.open(data["filepath"])
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, "append_all", text="Append Entire Library")
|
||||
|
||||
|
||||
class RefreshLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_library"
|
||||
@@ -282,16 +288,43 @@ class SaveLibraryFile(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AppendEntireLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.append_entire_library"
|
||||
bl_label = "Append Entiry Library"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.library = IfcStore.library_file
|
||||
|
||||
lib_elements = ifcopenshell.util.selector.Selector().parse(
|
||||
self.library, ".IfcTypeProduct | .IfcMaterial | .IfcCostSchedule| .IfcProfileDef"
|
||||
)
|
||||
for element in lib_elements:
|
||||
bpy.ops.bim.append_library_element(definition=element.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AppendLibraryElement(bpy.types.Operator):
|
||||
bl_idname = "bim.append_library_element"
|
||||
bl_label = "Append Library Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Append element to the current project"
|
||||
definition: bpy.props.IntProperty()
|
||||
prop_index: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
poll = bool(IfcStore.get_file())
|
||||
if bpy.app.version > (3, 0, 0) and not poll:
|
||||
cls.poll_message_set("Please create or load a project first.")
|
||||
return poll
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -347,7 +380,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
ifc_importer.material_creator.load_existing_materials()
|
||||
self.import_type_materials(element, ifc_importer)
|
||||
self.import_type_styles(element, ifc_importer)
|
||||
ifc_importer.create_type_product(element)
|
||||
ifc_importer.create_element_type(element)
|
||||
ifc_importer.place_objects_in_collections()
|
||||
|
||||
def import_type_materials(self, element, ifc_importer):
|
||||
@@ -495,6 +528,7 @@ class LoadProject(bpy.types.Operator, IFCFileSelector):
|
||||
return {"FINISHED"}
|
||||
context.scene.BIMProperties.ifc_file = self.filepath
|
||||
context.scene.BIMProjectProperties.is_loading = True
|
||||
context.scene.BIMProjectProperties.total_elements = len(tool.Ifc.get().by_type("IfcElement"))
|
||||
if not self.is_advanced:
|
||||
bpy.ops.bim.load_project_elements()
|
||||
return {"FINISHED"}
|
||||
@@ -545,6 +579,8 @@ class LoadProjectElements(bpy.types.Operator):
|
||||
settings.elements = self.get_decomposition_elements()
|
||||
elif self.props.filter_mode == "IFC_CLASS":
|
||||
settings.elements = self.get_ifc_class_elements()
|
||||
elif self.props.filter_mode == "IFC_TYPE":
|
||||
settings.elements = self.get_ifc_type_elements()
|
||||
elif self.props.filter_mode == "WHITELIST":
|
||||
settings.elements = self.get_whitelist_elements()
|
||||
elif self.props.filter_mode == "BLACKLIST":
|
||||
@@ -595,6 +631,14 @@ class LoadProjectElements(bpy.types.Operator):
|
||||
elements.update(self.file.by_type(filter_category.name, include_subtypes=False))
|
||||
return elements
|
||||
|
||||
def get_ifc_type_elements(self):
|
||||
elements = set()
|
||||
for filter_category in self.props.filter_categories:
|
||||
if not filter_category.is_selected:
|
||||
continue
|
||||
elements.update(ifcopenshell.util.element.get_types(self.file.by_id(filter_category.ifc_definition_id)))
|
||||
return elements
|
||||
|
||||
def get_whitelist_elements(self):
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
return set(selector.parse(self.file, self.props.filter_query))
|
||||
@@ -604,22 +648,38 @@ class LoadProjectElements(bpy.types.Operator):
|
||||
return set(self.file.by_type("IfcElement")) - set(selector.parse(self.file, self.props.filter_query))
|
||||
|
||||
|
||||
class ToggleFilterCategories(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_filter_categories"
|
||||
bl_label = "Toggle Filter Categories"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_select: bpy.props.BoolProperty(name="Should Select", default=True)
|
||||
|
||||
def execute(self, context):
|
||||
for filter_category in context.scene.BIMProjectProperties.filter_categories:
|
||||
filter_category.is_selected = self.should_select
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LinkIfc(bpy.types.Operator):
|
||||
bl_idname = "bim.link_ifc"
|
||||
bl_label = "Link IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Link a Blender file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
files: bpy.props.CollectionProperty(name="Files", type=bpy.types.OperatorFileListElement)
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.blend;*.blend1", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.BIMProjectProperties.links.add()
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=self.filepath)
|
||||
files = [self.filepath] if self.filepath else [f.name for f in self.files]
|
||||
for filename in files:
|
||||
filepath = os.path.join(self.directory, filename)
|
||||
new = context.scene.BIMProjectProperties.links.add()
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, bpy.path.abspath("//"))
|
||||
new.name = filepath
|
||||
bpy.ops.bim.load_link(filepath=filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -689,6 +749,57 @@ class LoadLink(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleLinkVisibility(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_link_visibility"
|
||||
bl_label = "Toggle Link Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Toggle visibility between SOLID and WIREFRAME"
|
||||
link: bpy.props.StringProperty()
|
||||
mode: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
link = props.links.get(self.link)
|
||||
if self.mode == "WIREFRAME":
|
||||
self.toggle_wireframe(link)
|
||||
elif self.mode == "VISIBLE":
|
||||
self.toggle_visibility(link)
|
||||
return {"FINISHED"}
|
||||
|
||||
def toggle_wireframe(self, link):
|
||||
for collection in self.get_linked_collections():
|
||||
objs = filter(lambda obj: "IfcOpeningElement" not in obj.name, collection.all_objects)
|
||||
for i, obj in enumerate(objs):
|
||||
if i == 0:
|
||||
if obj.display_type == "WIRE":
|
||||
display_type = "TEXTURED"
|
||||
else:
|
||||
display_type = "WIRE"
|
||||
obj.display_type = display_type
|
||||
link.is_wireframe = display_type == "WIRE"
|
||||
|
||||
def toggle_visibility(self, link):
|
||||
linked_collections = self.get_linked_collections()
|
||||
queue = [bpy.context.view_layer.layer_collection]
|
||||
layer_collection = None
|
||||
|
||||
while queue:
|
||||
layer = queue.pop()
|
||||
if layer.collection in linked_collections:
|
||||
layer_collection = layer
|
||||
break
|
||||
queue.extend(list(layer.children))
|
||||
|
||||
if layer_collection:
|
||||
layer_collection.exclude = not layer_collection.exclude
|
||||
link.is_hidden = layer_collection.exclude
|
||||
|
||||
def get_linked_collections(self):
|
||||
return [
|
||||
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
|
||||
]
|
||||
|
||||
|
||||
class ExportIFC(bpy.types.Operator):
|
||||
bl_idname = "export_ifc.bim"
|
||||
bl_label = "Export IFC"
|
||||
|
||||
@@ -23,6 +23,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
BoolProperty,
|
||||
CollectionProperty,
|
||||
EnumProperty,
|
||||
@@ -63,10 +64,16 @@ def update_filter_mode(self, context):
|
||||
new.ifc_definition_id = element.id()
|
||||
new.total_elements = sum([len(r.RelatedElements) for r in element.ContainsElements])
|
||||
elif self.filter_mode == "IFC_CLASS":
|
||||
for ifc_class in sorted(list(set([e.is_a() for e in file.by_type("IfcElement")]))):
|
||||
for ifc_class in sorted(list({e.is_a() for e in file.by_type("IfcElement")})):
|
||||
new = self.filter_categories.add()
|
||||
new.name = ifc_class
|
||||
new.total_elements = len(file.by_type(ifc_class, include_subtypes=False))
|
||||
elif self.filter_mode == "IFC_TYPE":
|
||||
for ifc_type in sorted(file.by_type("IfcElementType"), key=lambda e: e.Name or "Unnamed"):
|
||||
new = self.filter_categories.add()
|
||||
new.name = ifc_type.is_a() + "/" + (ifc_type.Name or "Unnamed")
|
||||
new.ifc_definition_id = ifc_type.id()
|
||||
new.total_elements = len(ifcopenshell.util.element.get_types(ifc_type))
|
||||
|
||||
|
||||
class LibraryElement(PropertyGroup):
|
||||
@@ -86,6 +93,8 @@ class FilterCategory(PropertyGroup):
|
||||
class Link(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
||||
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
|
||||
is_hidden: BoolProperty(name="Is Hidden", default=False)
|
||||
|
||||
|
||||
class BIMProjectProperties(PropertyGroup):
|
||||
@@ -116,25 +125,40 @@ class BIMProjectProperties(PropertyGroup):
|
||||
("NONE", "None", "No filtering is performed"),
|
||||
("DECOMPOSITION", "Decomposition", "Filter objects by decomposition"),
|
||||
("IFC_CLASS", "IFC Class", "Filter objects by class"),
|
||||
("IFC_TYPE", "IFC Type", "Filter objects by type"),
|
||||
("WHITELIST", "Whitelist", "Filter objects using a custom whitelist query"),
|
||||
("BLACKLIST", "Blacklist", "Filter objects using a custom blacklist query"),
|
||||
],
|
||||
name="Filter Mode",
|
||||
update=update_filter_mode,
|
||||
)
|
||||
total_elements: IntProperty(name="Total Elements", default=0)
|
||||
filter_categories: CollectionProperty(name="Filter Categories", type=FilterCategory)
|
||||
active_filter_category_index: IntProperty(name="Active Filter Category Index")
|
||||
filter_query: StringProperty(name="Filter Query")
|
||||
should_filter_spatial_elements: BoolProperty(name="Filter Spatial Elements", default=False)
|
||||
should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True)
|
||||
should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False)
|
||||
should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False)
|
||||
should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True)
|
||||
deflection_tolerance: FloatProperty(name="Import Deflection Tolerance", default=0.001)
|
||||
angular_tolerance: FloatProperty(name="Import Angular Tolerance", default=0.5)
|
||||
should_offset_model: BoolProperty(name="Import and Offset Model", default=False)
|
||||
model_offset_coordinates: StringProperty(name="Model Offset Coordinates", default="0,0,0")
|
||||
should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True)
|
||||
merge_mode: bpy.props.EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "No objects are merged"),
|
||||
("IFC_CLASS", "IFC Class", "One object per IFC class"),
|
||||
("IFC_TYPE", "IFC Type", "One object per IFC construction type"),
|
||||
("MATERIAL", "Material", "One object per material"),
|
||||
],
|
||||
name="Merge Mode",
|
||||
default="NONE",
|
||||
)
|
||||
should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False)
|
||||
should_use_native_meshes: BoolProperty(name="Native Meshes", default=False)
|
||||
should_clean_mesh: BoolProperty(name="Clean Meshes", default=True)
|
||||
should_cache: BoolProperty(name="Cache", default=False)
|
||||
is_coordinating: BoolProperty(name="For Coordination Only", default=False)
|
||||
deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001)
|
||||
angular_tolerance: FloatProperty(name="Angular Tolerance", default=0.5)
|
||||
distance_limit: FloatProperty(name="Distance Limit", default=1000)
|
||||
false_origin: StringProperty(name="False Origin", default="0,0,0")
|
||||
element_offset: IntProperty(name="Element Offset", default=0)
|
||||
element_limit: IntProperty(name="Element Offset", default=30000)
|
||||
links: CollectionProperty(name="Links", type=Link)
|
||||
active_link_index: IntProperty(name="Active Link Index")
|
||||
export_schema: EnumProperty(items=get_export_schema, name="IFC Schema")
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.project.data import ProjectData
|
||||
@@ -48,11 +49,13 @@ class BIM_PT_project(Panel):
|
||||
|
||||
def draw_load_ui(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "collection_mode")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "filter_mode")
|
||||
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS"]:
|
||||
prop_with_search(self.layout, pprops, "collection_mode")
|
||||
prop_with_search(self.layout, pprops, "filter_mode")
|
||||
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"Total: {pprops.total_elements}")
|
||||
row.operator("bim.toggle_filter_categories", text="", icon="CHECKBOX_HLT").should_select = True
|
||||
row.operator("bim.toggle_filter_categories", text="", icon="CHECKBOX_DEHLT").should_select = False
|
||||
self.layout.template_list(
|
||||
"BIM_UL_filter_categories",
|
||||
"",
|
||||
@@ -70,21 +73,31 @@ class BIM_PT_project(Panel):
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_use_cpu_multiprocessing")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_by_class")
|
||||
row.prop(pprops, "should_clean_mesh")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_by_material")
|
||||
row.prop(pprops, "should_cache")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_use_native_meshes")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_merge_materials_by_colour")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_clean_mesh")
|
||||
row.prop(pprops, "is_coordinating")
|
||||
if pprops.is_coordinating:
|
||||
prop_with_search(self.layout, pprops, "merge_mode")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "deflection_tolerance")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "angular_tolerance")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "should_offset_model")
|
||||
row.prop(pprops, "distance_limit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "model_offset_coordinates")
|
||||
row.prop(pprops, "false_origin")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Element Range")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(pprops, "element_offset", text="")
|
||||
row.prop(pprops, "element_limit", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.load_project_elements")
|
||||
@@ -150,12 +163,12 @@ class BIM_PT_project(Panel):
|
||||
op.should_save_as = False
|
||||
op = row.operator("export_ifc.bim", icon="FILE_TICK", text="Save As")
|
||||
op.should_save_as = True
|
||||
row.operator("bim.unload_project", text="", icon="X")
|
||||
|
||||
def draw_create_project_ui(self, context):
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "export_schema")
|
||||
prop_with_search(self.layout, pprops, "export_schema")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.unit_settings, "system")
|
||||
row = self.layout.row()
|
||||
@@ -164,8 +177,7 @@ class BIM_PT_project(Panel):
|
||||
row.prop(props, "area_unit", text="Area Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "volume_unit", text="Volume Unit")
|
||||
row = self.layout.row()
|
||||
row.prop(pprops, "template_file", text="Template")
|
||||
prop_with_search(self.layout, pprops, "template_file", text="Template")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.create_project")
|
||||
@@ -286,6 +298,22 @@ class BIM_UL_links(UIList):
|
||||
row = layout.row(align=True)
|
||||
if item.is_loaded:
|
||||
row.label(text=item.name)
|
||||
op = row.operator(
|
||||
"bim.toggle_link_visibility",
|
||||
text="",
|
||||
icon="CUBE" if item.is_wireframe else "MESH_CUBE",
|
||||
emboss=False,
|
||||
)
|
||||
op.link = item.name
|
||||
op.mode = "WIREFRAME"
|
||||
op = row.operator(
|
||||
"bim.toggle_link_visibility",
|
||||
text="",
|
||||
icon="HIDE_ON" if item.is_hidden else "HIDE_OFF",
|
||||
emboss=False,
|
||||
)
|
||||
op.link = item.name
|
||||
op.mode = "VISIBLE"
|
||||
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
|
||||
op.filepath = item.name
|
||||
else:
|
||||
|
||||
@@ -35,6 +35,8 @@ classes = (
|
||||
operator.BIM_OT_rename_parameters,
|
||||
operator.BIM_OT_add_edit_custom_property,
|
||||
operator.BIM_OT_bulk_remove_psets,
|
||||
prop.IfcPropertyEnumeratedValue,
|
||||
prop.IfcProperty,
|
||||
prop.PsetProperties,
|
||||
prop.MaterialPsetProperties,
|
||||
prop.TaskPsetProperties,
|
||||
|
||||
@@ -41,7 +41,9 @@ class Data:
|
||||
@classmethod
|
||||
def psetqtos(cls, element, psets_only=False, qtos_only=False):
|
||||
results = []
|
||||
psetqtos = ifcopenshell.util.element.get_psets(element, psets_only=psets_only, qtos_only=qtos_only)
|
||||
psetqtos = ifcopenshell.util.element.get_psets(
|
||||
element, psets_only=psets_only, qtos_only=qtos_only, should_inherit=False
|
||||
)
|
||||
for name, data in psetqtos.items():
|
||||
results.append(
|
||||
{
|
||||
|
||||
@@ -122,57 +122,100 @@ class EnablePsetEditing(bpy.types.Operator):
|
||||
for prop_template in pset_template.HasPropertyTemplates:
|
||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||
continue # Other types not yet supported
|
||||
|
||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||
try:
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
||||
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||
)
|
||||
except:
|
||||
# TODO: Occurs if the data type is something that exists in
|
||||
# IFC4 and not in IFC2X3. To fully fix this we need to
|
||||
# generate the IFC2X3 pset template definitions.
|
||||
continue
|
||||
self.load_single_value(prop_template, data)
|
||||
elif prop_template.TemplateType.startswith("Q_"):
|
||||
self.load_single_value(prop_template, data)
|
||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||
data_type = "enum"
|
||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||
elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
data_type = "float"
|
||||
elif prop_template.TemplateType == "Q_COUNT":
|
||||
data_type = "integer"
|
||||
else:
|
||||
continue # Other types not yet supported
|
||||
self.load_enumerated_value(prop_template, data)
|
||||
|
||||
new = self.props.properties.add()
|
||||
new.name = prop_template.Name
|
||||
new.is_null = data.get(prop_template.Name, None) is None
|
||||
new.is_optional = True
|
||||
new.data_type = data_type
|
||||
def load_single_value(self, prop_template, data):
|
||||
prop = self.props.properties.add()
|
||||
prop.name = prop_template.Name
|
||||
prop.value_type = "IfcPropertySingleValue"
|
||||
metadata = prop.metadata
|
||||
metadata.name = prop_template.Name
|
||||
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||
metadata.is_optional = True
|
||||
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||
metadata.data_type = self.get_data_type(prop_template)
|
||||
|
||||
if data_type == "string":
|
||||
new.string_value = "" if new.is_null else data[prop_template.Name]
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else data[prop_template.Name]
|
||||
elif data_type == "float":
|
||||
new.float_value = 0.0 if new.is_null else data[prop_template.Name]
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else data[prop_template.Name]
|
||||
elif data_type == "enum":
|
||||
new.enum_items = json.dumps(enum_items)
|
||||
if data.get(prop_template.Name):
|
||||
new.enum_value = str(data[prop_template.Name])
|
||||
if metadata.data_type == "string":
|
||||
metadata.string_value = "" if metadata.is_null else data[prop_template.Name]
|
||||
elif metadata.data_type == "integer":
|
||||
metadata.int_value = 0 if metadata.is_null else data[prop_template.Name]
|
||||
elif metadata.data_type == "float":
|
||||
metadata.float_value = 0.0 if metadata.is_null else data[prop_template.Name]
|
||||
elif metadata.data_type == "boolean":
|
||||
metadata.bool_value = False if metadata.is_null else data[prop_template.Name]
|
||||
|
||||
def get_data_type(self, prop_template):
|
||||
if prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
return "float"
|
||||
elif prop_template.TemplateType == "Q_COUNT":
|
||||
return "integer"
|
||||
try:
|
||||
return ifcopenshell.util.attribute.get_primitive_type(
|
||||
IfcStore.get_schema().declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||
)
|
||||
except:
|
||||
# TODO: Occurs if the data type is something that exists in
|
||||
# IFC4 and not in IFC2X3. To fully fix this we need to
|
||||
# generate the IFC2X3 pset template definitions.
|
||||
pass
|
||||
|
||||
def load_enumerated_value(self, prop_template, data):
|
||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||
selected_enum_items = data.get(prop_template.Name, [])
|
||||
|
||||
prop = self.props.properties.add()
|
||||
prop.name = prop_template.Name
|
||||
prop.value_type = "IfcPropertyEnumeratedValue"
|
||||
metadata = prop.metadata
|
||||
metadata.name = prop_template.Name
|
||||
metadata.is_null = data.get(prop_template.Name, None) is None
|
||||
metadata.is_optional = True
|
||||
metadata.is_uri = prop_template.PrimaryMeasureType == "IfcURIReference"
|
||||
|
||||
# Cute hack to abuse the metadata to find the Blender data_type
|
||||
metadata.set_value(enum_items[0])
|
||||
data_type = metadata.get_value_name()
|
||||
|
||||
for enum in enum_items:
|
||||
new = prop.enumerated_value.enumerated_values.add()
|
||||
setattr(new, data_type, enum)
|
||||
new.is_selected = enum in selected_enum_items
|
||||
|
||||
def load_from_pset_data(self, pset_data):
|
||||
for prop_id in pset_data["Properties"]:
|
||||
prop = Data.properties[prop_id]
|
||||
|
||||
value = prop["NominalValue"]
|
||||
new = self.props.properties.add()
|
||||
new.set_value(value)
|
||||
new.name = prop["Name"]
|
||||
new.is_null = value is None
|
||||
new.is_optional = True
|
||||
new.set_value(new.get_value_default() if new.is_null else value)
|
||||
if prop["type"] == "IfcPropertyEnumeratedValue":
|
||||
simple_prop = self.props.properties.add()
|
||||
simple_prop.value_type = "IfcPropertyEnumeratedValue"
|
||||
metadata = simple_prop.metadata
|
||||
metadata.name = prop["Name"]
|
||||
metadata.is_null = len(simple_prop.enumerated_value.enumerated_values) == 0
|
||||
metadata.is_optional = True
|
||||
metadata.set_value(prop["EnumerationReference"].EnumerationValues[0].wrappedValue)
|
||||
|
||||
enum_items = [v.wrappedValue for v in prop["EnumerationReference"].EnumerationValues]
|
||||
selected_enum_items = [v.wrappedValue for v in prop["EnumerationValues"]]
|
||||
data_type = metadata.get_value_name()
|
||||
|
||||
for enum in enum_items:
|
||||
new = simple_prop.enumerated_value.enumerated_values.add()
|
||||
setattr(new, data_type, enum)
|
||||
new.is_selected = enum in selected_enum_items
|
||||
else:
|
||||
value = prop["NominalValue"]
|
||||
new_prop = self.props.properties.add()
|
||||
metadata = new_prop.metadata
|
||||
metadata.set_value(value)
|
||||
metadata.name = prop["Name"]
|
||||
metadata.is_null = value is None
|
||||
metadata.is_optional = True
|
||||
metadata.set_value(metadata.get_value_default() if metadata.is_null else value)
|
||||
|
||||
|
||||
class DisablePsetEditing(bpy.types.Operator, Operator):
|
||||
@@ -208,7 +251,13 @@ class EditPset(bpy.types.Operator, Operator):
|
||||
else:
|
||||
data = Data.psets if pset_id in Data.psets else Data.qtos
|
||||
for prop in props.properties:
|
||||
properties[prop.name] = prop.get_value()
|
||||
if prop.value_type == "IfcPropertySingleValue":
|
||||
properties[prop.metadata.name] = prop.metadata.get_value()
|
||||
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||
value_name = prop.metadata.get_value_name()
|
||||
properties[prop.metadata.name] = [
|
||||
e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected
|
||||
]
|
||||
|
||||
if pset_id in Data.psets:
|
||||
ifcopenshell.api.run(
|
||||
@@ -318,7 +367,7 @@ class GuessQuantity(bpy.types.Operator):
|
||||
self.qto_calculator = QtoCalculator()
|
||||
obj = context.active_object
|
||||
prop = obj.PsetProperties.properties.get(self.prop)
|
||||
prop.float_value = self.guess_quantity(obj, context)
|
||||
prop.metadata.float_value = self.guess_quantity(obj, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def guess_quantity(self, obj, context):
|
||||
@@ -362,7 +411,7 @@ class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
is_pset = tool.Ifc.get().by_id(context.active_object.PsetProperties.active_pset_id).is_a("IfcPropertySet")
|
||||
pset_name = context.active_object.PsetProperties.active_pset_name
|
||||
prop_value = context.active_object.PsetProperties.properties.get(self.name).get_value()
|
||||
prop_value = context.active_object.PsetProperties.properties.get(self.name).metadata.get_value()
|
||||
for obj in context.selected_objects:
|
||||
core.copy_property_to_selection(
|
||||
tool.Ifc,
|
||||
@@ -380,9 +429,13 @@ class BIM_OT_add_property_to_edit(bpy.types.Operator):
|
||||
bl_idname = "bim.add_property_to_edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
option: bpy.props.StringProperty()
|
||||
index: bpy.props.IntProperty(default=-1)
|
||||
|
||||
def execute(self, context):
|
||||
getattr(context.scene, self.option).add()
|
||||
if self.index == -1:
|
||||
getattr(context.scene, self.option).add()
|
||||
else:
|
||||
getattr(context.scene, self.option)[self.index].enum_values.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -391,10 +444,14 @@ class BIM_OT_remove_property_to_edit(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_property_to_edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
index2: bpy.props.IntProperty(default=-1)
|
||||
option: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
getattr(context.scene, self.option).remove(self.index)
|
||||
if self.index2 == -1:
|
||||
getattr(context.scene, self.option).remove(self.index)
|
||||
else:
|
||||
getattr(context.scene, self.option)[self.index].enum_values.remove(self.index2)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -470,16 +527,41 @@ class BIM_OT_add_edit_custom_property(bpy.types.Operator):
|
||||
for prop in props:
|
||||
value = getattr(prop, prop.get_value_name())
|
||||
primary_measure_type = prop.primary_measure_type
|
||||
value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value)
|
||||
|
||||
if prop.template_type == "IfcPropertyEnumeratedValue":
|
||||
value_ifc_entity = self.generate_enum_entity(prop)
|
||||
elif prop.template_type == "IfcPropertySingleValue":
|
||||
value_ifc_entity = getattr(self.file, f"create{primary_measure_type}")(value)
|
||||
|
||||
new_pset = ifcopenshell.api.run("pset.add_pset", self.file, product=ifc_element, name=prop.pset_name)
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", self.file, pset=new_pset, properties={prop.property_name: value_ifc_entity}
|
||||
)
|
||||
|
||||
Data.load(IfcStore.get_file(), ifc_definition_id)
|
||||
self.report({"INFO"}, "Finished applying changes")
|
||||
return {"FINISHED"}
|
||||
|
||||
def generate_enum_entity(self, prop):
|
||||
prop_type = prop.get_value_name()
|
||||
prop_enum = self.file.create_entity(
|
||||
"IFCPROPERTYENUMERATION",
|
||||
Name=prop.property_name,
|
||||
EnumerationValues=tuple(
|
||||
self.file.create_entity(prop.primary_measure_type, ev[prop_type]) for ev in prop.enum_values
|
||||
),
|
||||
)
|
||||
prop_enum_value = self.file.create_entity(
|
||||
"IFCPROPERTYENUMERATEDVALUE",
|
||||
Name=prop.property_name,
|
||||
EnumerationValues=tuple(
|
||||
self.file.create_entity(prop.primary_measure_type, ev[prop_type])
|
||||
for ev in prop.enum_values
|
||||
if ev.is_selected == True
|
||||
),
|
||||
EnumerationReference=prop_enum,
|
||||
)
|
||||
return prop_enum_value
|
||||
|
||||
|
||||
class BIM_OT_bulk_remove_psets(bpy.types.Operator):
|
||||
bl_label = "Bulk remove psets from selected objects"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.schema
|
||||
from blenderbim.bim.prop import Attribute
|
||||
from blenderbim.bim.prop import Attribute, StrProperty
|
||||
import ifcopenshell
|
||||
from ifcopenshell.api.pset.data import Data
|
||||
from blenderbim.bim.module.pset.data import AddEditCustomPropertiesData
|
||||
@@ -47,7 +47,7 @@ def purge():
|
||||
qtonames = {}
|
||||
|
||||
|
||||
def getPsetNames(self, context):
|
||||
def get_pset_names(self, context):
|
||||
global psetnames
|
||||
obj = context.active_object
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -122,7 +122,7 @@ def getWorkSchedulePsetNames(self, context):
|
||||
return psetnames[ifc_class]
|
||||
|
||||
|
||||
def getQtoNames(self, context):
|
||||
def get_qto_names(self, context):
|
||||
global qtonames
|
||||
if "/" in context.active_object.name:
|
||||
ifc_class = context.active_object.name.split("/")[0]
|
||||
@@ -139,18 +139,30 @@ def get_primary_measure_type(self, context):
|
||||
return AddEditCustomPropertiesData.data["primary_measure_type"]
|
||||
|
||||
|
||||
class IfcPropertyEnumeratedValue(PropertyGroup):
|
||||
enumerated_values: CollectionProperty(type=Attribute)
|
||||
|
||||
|
||||
class IfcProperty(PropertyGroup):
|
||||
metadata: PointerProperty(type=Attribute)
|
||||
value_type: EnumProperty(
|
||||
items=[(v, v, v) for v in ("IfcPropertySingleValue", "IfcPropertyEnumeratedValue")], name="Value Type"
|
||||
)
|
||||
enumerated_value: PointerProperty(type=IfcPropertyEnumeratedValue)
|
||||
|
||||
|
||||
class PsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
pset_name: EnumProperty(items=getPsetNames, name="Pset Name")
|
||||
qto_name: EnumProperty(items=getQtoNames, name="Qto Name")
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=get_pset_names, name="Pset Name")
|
||||
qto_name: EnumProperty(items=get_qto_names, name="Qto Name")
|
||||
|
||||
|
||||
class MaterialPsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=getMaterialPsetNames, name="Pset Name")
|
||||
|
||||
|
||||
@@ -172,14 +184,14 @@ class ResourcePsetProperties(PropertyGroup):
|
||||
class ProfilePsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=getProfilePsetNames, name="Pset Name")
|
||||
|
||||
|
||||
class WorkSchedulePsetProperties(PropertyGroup):
|
||||
active_pset_id: IntProperty(name="Active Pset ID")
|
||||
active_pset_name: StringProperty(name="Pset Name")
|
||||
properties: CollectionProperty(name="Properties", type=Attribute)
|
||||
properties: CollectionProperty(name="Properties", type=IfcProperty)
|
||||
pset_name: EnumProperty(items=getWorkSchedulePsetNames, name="Pset Name")
|
||||
|
||||
|
||||
@@ -197,6 +209,14 @@ class AddEditProperties(PropertyGroup):
|
||||
int_value: IntProperty(name="Value")
|
||||
float_value: FloatProperty(name="Value")
|
||||
primary_measure_type: EnumProperty(items=get_primary_measure_type, name="Primary Measure Type")
|
||||
template_type: EnumProperty(
|
||||
items=[
|
||||
("IfcPropertySingleValue", "IfcPropertySingleValue", "IfcPropertySingleValue"),
|
||||
("IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue", "IfcPropertyEnumeratedValue"),
|
||||
],
|
||||
name="Template Type",
|
||||
)
|
||||
enum_values: CollectionProperty(name="Enum Values", type=Attribute)
|
||||
|
||||
def get_value_name(self):
|
||||
ifc_data_type = IfcStore.get_schema().declaration_by_name(self.primary_measure_type)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attribute
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.pset.data import (
|
||||
ObjectPsetsData,
|
||||
ObjectQtosData,
|
||||
@@ -31,6 +31,45 @@ from blenderbim.bim.module.pset.data import (
|
||||
)
|
||||
|
||||
|
||||
def draw_property(prop, layout, copy_operator=None):
|
||||
if prop.value_type == "IfcPropertySingleValue":
|
||||
draw_single_property(prop, layout, copy_operator)
|
||||
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||
draw_enumerated_property(prop, layout, copy_operator)
|
||||
|
||||
|
||||
def draw_single_property(prop, layout, copy_operator=None):
|
||||
value_name = prop.metadata.get_value_name()
|
||||
if not value_name:
|
||||
layout.label(text=prop["Name"])
|
||||
return
|
||||
layout.prop(
|
||||
prop.metadata,
|
||||
value_name,
|
||||
text=prop.metadata.name,
|
||||
)
|
||||
if prop.metadata.is_optional:
|
||||
layout.prop(prop.metadata, "is_null", icon="RADIOBUT_OFF" if prop.metadata.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = prop.metadata.name
|
||||
if prop.metadata.is_uri:
|
||||
op = layout.operator("bim.select_uri_prop", text="", icon="FILE_FOLDER")
|
||||
op.data_path = prop.metadata.path_from_id("string_value")
|
||||
|
||||
|
||||
def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
value_name = prop.metadata.get_value_name()
|
||||
if not value_name:
|
||||
layout.label(text=prop.metadata.name)
|
||||
return
|
||||
if len(prop.enumerated_value.enumerated_values) != 0:
|
||||
layout.label(text=prop.metadata.name)
|
||||
grid = layout.column_flow(columns=3)
|
||||
for e in prop.enumerated_value.enumerated_values:
|
||||
grid.prop(e, "is_selected", text=str(e[value_name]))
|
||||
|
||||
|
||||
def get_active_pset_obj_name(context, obj_type):
|
||||
if obj_type == "Object":
|
||||
return context.active_object.name
|
||||
@@ -95,7 +134,7 @@ def draw_psetqto_ui(context, pset_id, pset, props, layout, obj_type):
|
||||
|
||||
def draw_psetqto_editable_ui(box, props, prop):
|
||||
row = box.row(align=True)
|
||||
draw_attribute(prop, row, copy_operator="bim.copy_property_to_selection")
|
||||
draw_property(prop, row, copy_operator="bim.copy_property_to_selection")
|
||||
if (
|
||||
"length" in prop.name.lower()
|
||||
or "width" in prop.name.lower()
|
||||
@@ -138,7 +177,7 @@ class BIM_PT_object_psets(Panel):
|
||||
|
||||
props = context.active_object.PsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "pset_name", text="")
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.obj_type = "Object"
|
||||
@@ -147,7 +186,7 @@ class BIM_PT_object_psets(Panel):
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
|
||||
|
||||
if ObjectPsetsData.data["inherited_psets"]:
|
||||
self.layout.label(text="Inherited Psets:")
|
||||
self.layout.label(text="Inherited Psets:", icon="FILE_PARENT")
|
||||
for pset in ObjectPsetsData.data["inherited_psets"]:
|
||||
draw_psetqto_ui(context, pset["id"], pset, props, self.layout, "Object")
|
||||
|
||||
@@ -177,7 +216,7 @@ class BIM_PT_object_qtos(Panel):
|
||||
|
||||
props = context.active_object.PsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "qto_name", text="")
|
||||
prop_with_search(row, props, "qto_name", text="")
|
||||
op = row.operator("bim.add_qto", icon="ADD", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.obj_type = "Object"
|
||||
@@ -213,7 +252,7 @@ class BIM_PT_material_psets(Panel):
|
||||
|
||||
props = context.active_object.active_material.PsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "pset_name", text="")
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj = context.active_object.active_material.name
|
||||
op.obj_type = "Material"
|
||||
@@ -309,7 +348,7 @@ class BIM_PT_resource_psets(Panel):
|
||||
|
||||
props = context.scene.ResourcePsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "pset_name", text="")
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj_type = "Resource"
|
||||
|
||||
@@ -342,7 +381,7 @@ class BIM_PT_profile_psets(Panel):
|
||||
|
||||
props = context.scene.ProfilePsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "pset_name", text="")
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj_type = "Profile"
|
||||
|
||||
@@ -371,7 +410,7 @@ class BIM_PT_work_schedule_psets(Panel):
|
||||
|
||||
props = context.scene.WorkSchedulePsetProperties
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "pset_name", text="")
|
||||
prop_with_search(row, props, "pset_name", text="")
|
||||
op = row.operator("bim.add_pset", icon="ADD", text="")
|
||||
op.obj_type = "WorkSchedule"
|
||||
|
||||
@@ -411,7 +450,7 @@ class BIM_PT_rename_parameters(Panel):
|
||||
if props:
|
||||
for index, prop in enumerate(props):
|
||||
row = layout.row(align=True)
|
||||
row.prop(prop, "pset_name", text="")
|
||||
prop_with_search(row, prop, "pset_name", text="")
|
||||
row.prop(prop, "existing_property_name", text="")
|
||||
row.prop(prop, "new_property_name", text="")
|
||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||
@@ -441,18 +480,36 @@ class BIM_PT_add_edit_custom_properties(Panel):
|
||||
row = layout.row()
|
||||
op = row.operator("bim.add_property_to_edit", icon="ADD")
|
||||
op.option = "AddEditProperties"
|
||||
op.index = -1
|
||||
|
||||
if props:
|
||||
for index, prop in enumerate(props):
|
||||
row = layout.row(align=True)
|
||||
row.prop(prop, "pset_name", text="")
|
||||
prop_with_search(row, prop, "pset_name", text="")
|
||||
row.prop(prop, "property_name", text="")
|
||||
row.prop(prop, prop.get_value_name(), text="")
|
||||
row.prop(prop, "primary_measure_type", text="")
|
||||
if prop.template_type == "IfcPropertySingleValue":
|
||||
row.prop(prop, prop.get_value_name(), text="")
|
||||
prop_with_search(row, prop, "primary_measure_type", text="")
|
||||
row.prop(prop, "template_type", text="")
|
||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||
op.index = index
|
||||
op.option = "AddEditProperties"
|
||||
|
||||
if prop.template_type == "IfcPropertyEnumeratedValue":
|
||||
op = row.operator("bim.add_property_to_edit", icon="ADD", text="Add Enum")
|
||||
op.option = "AddEditProperties"
|
||||
op.index = index
|
||||
for index2, prop2 in enumerate(prop.enum_values):
|
||||
row = layout.row()
|
||||
row.separator()
|
||||
row.separator()
|
||||
row.prop(prop2, prop.get_value_name(), text=f"#{index2}")
|
||||
row.prop(prop2, "is_selected")
|
||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||
op.index = index
|
||||
op.index2 = index2
|
||||
op.option = "AddEditProperties"
|
||||
|
||||
if props:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_edit_custom_property", icon="CHECKMARK", text="Apply Changes")
|
||||
@@ -480,7 +537,7 @@ class BIM_PT_delete_psets(Panel):
|
||||
if props:
|
||||
for index, prop in enumerate(props):
|
||||
row = layout.row(align=True)
|
||||
row.prop(prop, "pset_name", text="")
|
||||
prop_with_search(row, prop, "pset_name", text="")
|
||||
op = row.operator("bim.remove_property_to_edit", icon="X", text="")
|
||||
op.index = index
|
||||
op.option = "DeletePsets"
|
||||
|
||||
@@ -20,6 +20,8 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.RefreshPsetTemplates,
|
||||
operator.AddPsetFile,
|
||||
operator.SavePsetTemplateFile,
|
||||
operator.AddPsetTemplate,
|
||||
operator.EditPsetTemplate,
|
||||
@@ -30,8 +32,11 @@ classes = (
|
||||
operator.EnableEditingPsetTemplate,
|
||||
operator.DisableEditingPsetTemplate,
|
||||
operator.EnableEditingPropTemplate,
|
||||
operator.DeletePropEnum,
|
||||
operator.AddPropEnum,
|
||||
operator.DisableEditingPropTemplate,
|
||||
prop.PsetTemplate,
|
||||
prop.EnumerationValues,
|
||||
prop.PropTemplate,
|
||||
prop.BIMPsetTemplateProperties,
|
||||
ui.BIM_PT_pset_template,
|
||||
|
||||
@@ -16,16 +16,66 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.module.pset_template.prop import updatePsetTemplateFiles, updatePsetTemplates, getPsetTemplates
|
||||
from blenderbim.bim.module.pset_template.prop import purge as purge_templates, updatePsetTemplates, getPsetTemplates
|
||||
from blenderbim.bim.module.pset.prop import purge as purge_psets
|
||||
from ifcopenshell.api.pset_template.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
# This is just a temporary operator until
|
||||
# @Moult performs some of his refactor-magic ;) - vulevukusej
|
||||
class RefreshPsetTemplates(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_psettemplates"
|
||||
bl_label = "Refresh the data for PsetTemplates"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
purge_templates()
|
||||
purge_psets()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddPsetFile(bpy.types.Operator):
|
||||
bl_idname = "bim.add_pset_file"
|
||||
bl_label = "Add Pset File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self, width=250)
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMPsetTemplateProperties
|
||||
self.layout.prop(self.props, "new_template_filename", text="Filename:")
|
||||
|
||||
def execute(self, context):
|
||||
template = ifcopenshell.file()
|
||||
filepath = os.path.join(
|
||||
context.scene.BIMProperties.data_dir,
|
||||
"pset",
|
||||
self.props.new_template_filename + ".ifc",
|
||||
)
|
||||
|
||||
template.create_entity(
|
||||
"IFCPROPERTYSETTEMPLATE",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"TemplateType": "PSET_TYPEDRIVENONLY",
|
||||
"ApplicableEntity": "IfcTypeObject",
|
||||
}
|
||||
)
|
||||
template.write(filepath)
|
||||
self.props.new_template_filename = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddPsetTemplate(bpy.types.Operator):
|
||||
bl_idname = "bim.add_pset_template"
|
||||
bl_label = "Add Pset Template"
|
||||
@@ -41,7 +91,6 @@ class AddPsetTemplate(bpy.types.Operator):
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
ifcopenshell.api.run("pset_template.add_pset_template", IfcStore.pset_template_file)
|
||||
Data.load(IfcStore.pset_template_file)
|
||||
updatePsetTemplates(self, context)
|
||||
@@ -139,6 +188,40 @@ class EnableEditingPropTemplate(bpy.types.Operator):
|
||||
props.active_prop_template.name = template["Name"] or ""
|
||||
props.active_prop_template.description = template["Description"] or ""
|
||||
props.active_prop_template.primary_measure_type = template["PrimaryMeasureType"]
|
||||
props.active_prop_template.template_type = template["TemplateType"]
|
||||
props.active_prop_template.enum_values.clear()
|
||||
|
||||
if template["Enumerators"]:
|
||||
props.active_prop_template.enum_values.clear()
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
for e in template["Enumerators"].EnumerationValues:
|
||||
new = props.active_prop_template.enum_values.add()
|
||||
setattr(new, data_type, e.wrappedValue)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeletePropEnum(bpy.types.Operator):
|
||||
bl_idname = "bim.delete_prop_enum"
|
||||
bl_label = "delete property enumeration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||
active_prop.enum_values.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddPropEnum(bpy.types.Operator):
|
||||
bl_idname = "bim.add_prop_enum"
|
||||
bl_label = "add property enumeration"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||
active_prop.enum_values.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -285,6 +368,10 @@ class EditPropTemplate(bpy.types.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||
enumerator = self.generate_prop_enum(props)
|
||||
else:
|
||||
enumerator = None
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.edit_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
@@ -294,6 +381,8 @@ class EditPropTemplate(bpy.types.Operator):
|
||||
"Name": props.active_prop_template.name,
|
||||
"Description": props.active_prop_template.description,
|
||||
"PrimaryMeasureType": props.active_prop_template.primary_measure_type,
|
||||
"TemplateType": props.active_prop_template.template_type,
|
||||
"Enumerators": enumerator,
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -301,6 +390,21 @@ class EditPropTemplate(bpy.types.Operator):
|
||||
bpy.ops.bim.disable_editing_prop_template()
|
||||
return {"FINISHED"}
|
||||
|
||||
# TODO -This will need to go into the
|
||||
# api code at some point - vulevukusej
|
||||
def generate_prop_enum(self, props):
|
||||
self.file = IfcStore.pset_template_file
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
prop = props.active_prop_template
|
||||
prop_enum = self.file.create_entity(
|
||||
"IFCPROPERTYENUMERATION",
|
||||
Name=prop.name,
|
||||
EnumerationValues=tuple(
|
||||
self.file.create_entity(prop.primary_measure_type, ev[data_type]) for ev in prop.enum_values
|
||||
),
|
||||
)
|
||||
return prop_enum
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.pset_template_file.undo()
|
||||
|
||||
|
||||