Compare commits
561 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b8af680bc | |||
| 8a57a52949 | |||
| 720eda51c3 | |||
| 01a701007e | |||
| ec5368224f | |||
| 56e9488d3e | |||
| 23ec2fc48e | |||
| fbcb19fdbb | |||
| d9f3c9d012 | |||
| 91e236c42c | |||
| 03ab88b5bb | |||
| 42ef06aae1 | |||
| 691dfb45f0 | |||
| 760816b7f0 | |||
| fc04c6e41e | |||
| 99e67d528f | |||
| c113061742 | |||
| 174ff00a6d | |||
| 84a2d4266d | |||
| 64cba98c3d | |||
| 1aed2a6b3c | |||
| 4e1a956830 | |||
| 747a39b440 | |||
| 1b49515d9f | |||
| dabb85c466 | |||
| b74811b3f4 | |||
| 79eaf895f7 | |||
| 994c85a05a | |||
| 0faacccf6d | |||
| 89d27bdf5f | |||
| 76853db32c | |||
| 002c50e29d | |||
| d2efb2ab78 | |||
| 8bb78959ef | |||
| 48f0dc6a00 | |||
| 27aee31adf | |||
| 24aaefb927 | |||
| 7daec25454 | |||
| 8309b13db0 | |||
| 5e95985d19 | |||
| fefe325ece | |||
| b717abf639 | |||
| 32ae12b45a | |||
| 48b74ebb76 | |||
| 197d0b3258 | |||
| 2a1bc30f19 | |||
| 6dcbd98769 | |||
| 1be025ed86 | |||
| 6fa87a16ab | |||
| 8c19b695ea | |||
| 5831c56380 | |||
| 3dde1b80b9 | |||
| 08dd8f87ff | |||
| 1c3cf44122 | |||
| 868454c011 | |||
| edcfcd8850 | |||
| 33bbf9fc53 | |||
| 286e90e299 | |||
| 5aa7758795 | |||
| 6c47baf0de | |||
| 51018177cd | |||
| be9719f5ca | |||
| 9193ff7252 | |||
| 53c3019b73 | |||
| 6bbf3d3917 | |||
| 47a15011f6 | |||
| f26f6dfe5e | |||
| 1745a4fd39 | |||
| fcaac3787b | |||
| 2d3adce59e | |||
| 7e6607adcf | |||
| 46c62d7cdb | |||
| c521892ca2 | |||
| d4a0dd1527 | |||
| 527cdd3304 | |||
| 31e836cfb4 | |||
| 86f123b6b0 | |||
| fea8e3a5c9 | |||
| 3137904055 | |||
| f89dee7668 | |||
| c042d1afe5 | |||
| 42fc72bd4f | |||
| f72c128ba6 | |||
| 3ceeaa739f | |||
| 7a2e58944e | |||
| b9855b289f | |||
| c7a8fd9309 | |||
| daa6b31c5d | |||
| cd411e1f14 | |||
| 7a168069f7 | |||
| 97d2e08286 | |||
| f440a3ca17 | |||
| 81bdb551ab | |||
| 5fdad744d3 | |||
| ab6f408db4 | |||
| cdf8b9c038 | |||
| 918fb41a7e | |||
| 1d8e39e55f | |||
| b0b65e550c | |||
| a1527a7e31 | |||
| 2ca3f86fc9 | |||
| d80bcd1a94 | |||
| 60a06138d2 | |||
| e2001e82dd | |||
| 92f4cb0ffa | |||
| 9f00e8aef0 | |||
| 638d200a27 | |||
| 59717d6e92 | |||
| 009795f7dc | |||
| a0792a4b93 | |||
| 8534b6e154 | |||
| 7910a68028 | |||
| d44ee9874b | |||
| 8cd86581ff | |||
| 0a5b6a41b0 | |||
| 7bac468151 | |||
| b375579dcd | |||
| d3d1ca0169 | |||
| 2b0572af10 | |||
| ed6365b292 | |||
| e3b1e0773f | |||
| 3d38068b90 | |||
| a4a2497f33 | |||
| 1ef2fb89af | |||
| 902d777574 | |||
| 3bd0caade1 | |||
| 301cb4bbdc | |||
| f0338ffdda | |||
| 2100528cbe | |||
| ae4f681f73 | |||
| 53d7624fc4 | |||
| 951b2b76a6 | |||
| e9b4b6560e | |||
| cabffdc549 | |||
| 7ea60742d6 | |||
| 358c834427 | |||
| 5d6655cbc7 | |||
| 4d122d77dc | |||
| d4e863ed3b | |||
| b6ccf505e7 | |||
| ce9842983b | |||
| eb19b26a6e | |||
| 06602d0e39 | |||
| 00279b63de | |||
| 6418353119 | |||
| 3afbbe51d2 | |||
| ddb767674b | |||
| 5575ea8296 | |||
| e8a7a032aa | |||
| f23e419685 | |||
| b87e8136af | |||
| 473d0ae888 | |||
| 94f5137684 | |||
| 4e75945435 | |||
| e31019eb67 | |||
| c505630a20 | |||
| 860fa81f73 | |||
| 847b5d226e | |||
| ad77d09916 | |||
| 9b12cae8b4 | |||
| 84a8c5dfeb | |||
| 12a7b8a79e | |||
| ef04402ed8 | |||
| 45fed747c2 | |||
| 85229fc865 | |||
| 916f854905 | |||
| 1915541d20 | |||
| e37b84a95a | |||
| bcb2a37ce0 | |||
| 018d6847a0 | |||
| 35918265d0 | |||
| 4e48c35425 | |||
| d985fe9240 | |||
| 7c00021aaa | |||
| d2dc8b519f | |||
| 25fe025354 | |||
| 1aae710286 | |||
| 38e8130793 | |||
| 7e94388074 | |||
| ef835e43b8 | |||
| e5d0eb13bf | |||
| 4fcf23319f | |||
| 99154afdb1 | |||
| e83eb68fe1 | |||
| e4a4a33920 | |||
| e8848c9701 | |||
| cfd8044cb3 | |||
| 2d7866570e | |||
| eb0ed7fe6b | |||
| 67733c4449 | |||
| b3940cdc45 | |||
| 94e75aebb0 | |||
| ff203b2a13 | |||
| 10616df494 | |||
| 1dc6d4935e | |||
| 04088af2b6 | |||
| c477173096 | |||
| 2f4093f958 | |||
| 95fc51da76 | |||
| 50e629c02c | |||
| 4a178fca58 | |||
| aff5b238d9 | |||
| 315b6060b0 | |||
| b137197586 | |||
| d1d9db3ed8 | |||
| 736b50c6fa | |||
| 64b96633c2 | |||
| 8eae997053 | |||
| fee56fc120 | |||
| d1f87d5467 | |||
| 7539404c27 | |||
| 71ddc6384a | |||
| 4b402f8ea5 | |||
| c39fa80e09 | |||
| c89ad8c7da | |||
| 6dfe46d9bd | |||
| 1de51ecf77 | |||
| 8a55f18c95 | |||
| 7a1976cfc1 | |||
| 78c6401453 | |||
| e12838d93e | |||
| 7aa5b71a6a | |||
| 2ef3af5e1b | |||
| d399f7c6b7 | |||
| beb7f75368 | |||
| dc6fdc2a98 | |||
| 45be17c25a | |||
| 4f11b19b3d | |||
| f646cf4372 | |||
| 538859f60a | |||
| 75da994faf | |||
| 99827df89a | |||
| 054a900779 | |||
| e800f8b93d | |||
| 7163697362 | |||
| 29600711ae | |||
| 1d0ee61ba7 | |||
| fb909ace1a | |||
| 6ae5dbacf4 | |||
| 52bffa7f9c | |||
| e9a502ccec | |||
| f3d4eed19e | |||
| d260298ab2 | |||
| c7d0cff300 | |||
| 631deeb094 | |||
| 953a49f9ae | |||
| 2e94a7c3f1 | |||
| 68f10e1502 | |||
| 625a57c6de | |||
| 4348b9bff9 | |||
| 28ee1a7781 | |||
| d40119d617 | |||
| 774533911c | |||
| 7597b1b29c | |||
| bd8a2736d4 | |||
| aafb4b6147 | |||
| 2066ebcc11 | |||
| 795ebc0ac1 | |||
| 7612cf92c0 | |||
| db1f4d126a | |||
| b15820dd10 | |||
| f8f7884278 | |||
| 3318de7f6d | |||
| 726bf40d58 | |||
| 3860884db3 | |||
| 5784e3103d | |||
| 2a1d1d6e10 | |||
| 485643863f | |||
| c03777cfe6 | |||
| be442ff1be | |||
| 95e7910f69 | |||
| d5592ed2eb | |||
| a29806ef9e | |||
| 3a9d79dcd3 | |||
| b9c2e312e3 | |||
| f1db51df1a | |||
| 312a58ddce | |||
| 1e71708aba | |||
| ad6e00571e | |||
| c8d05951b0 | |||
| 0f8c3b3c83 | |||
| 25071dfec6 | |||
| cc28f5a92b | |||
| a57a7041ac | |||
| 17b90c3241 | |||
| 90089f91c6 | |||
| 37dd2a5607 | |||
| bc70130123 | |||
| 8d53141d5a | |||
| 0ffc62fbf3 | |||
| 4350e3df82 | |||
| 2ad601ed34 | |||
| f9e8d6d478 | |||
| 65cc5bf4c1 | |||
| 73e5985777 | |||
| de14e92720 | |||
| 09ad47d11b | |||
| debe63c1af | |||
| 6f2eb6c54a | |||
| 56ca431965 | |||
| 74e8b80fb4 | |||
| 7ecfe372ad | |||
| b017ab7596 | |||
| bd3905fe76 | |||
| 0c8048c14d | |||
| 77f72ecdfa | |||
| 723701f1b8 | |||
| bf96c63f7d | |||
| cef8251458 | |||
| 547981f32c | |||
| a3cc0245ca | |||
| 5d934a2baa | |||
| 4409a35441 | |||
| f637ef8016 | |||
| 5e2cefee12 | |||
| 67b01dcd0f | |||
| bc0efd7389 | |||
| 4b4e6fa7e2 | |||
| 17a0face6e | |||
| a3b2860789 | |||
| f8f22257b4 | |||
| 8284ed0cdb | |||
| d995e010cf | |||
| fcfc070825 | |||
| 55552770a6 | |||
| 350df4eee5 | |||
| 04a273c720 | |||
| 61641acf56 | |||
| 10035662dd | |||
| 056df7758b | |||
| 0522adac63 | |||
| 1f781e5cab | |||
| a30fd7cab4 | |||
| 94ca2423e9 | |||
| 2938c1fd0b | |||
| ab8710bd1e | |||
| a791abe23e | |||
| e70122f354 | |||
| 96fdced471 | |||
| 1c95f6cc3f | |||
| 0f03e412c3 | |||
| 6ca3e2a6db | |||
| 079446a4b6 | |||
| e706e568bd | |||
| 526a2a6a80 | |||
| 3e7a2c1b83 | |||
| 9bb04d3ffc | |||
| 7c4d45ab32 | |||
| dafec70097 | |||
| eca1d47d06 | |||
| 9671be1734 | |||
| 644f32d11c | |||
| 1626dd85f4 | |||
| 3c0103b4da | |||
| f9c4b6a98f | |||
| b01e93161e | |||
| ffbd3e50c5 | |||
| 98c4a1d110 | |||
| e2c4bcb217 | |||
| 7e60b6888e | |||
| bbf8ebcb52 | |||
| c1936b871e | |||
| 31d67b5e62 | |||
| 683e168ca1 | |||
| cbc0b4837f | |||
| 02ff6c00ef | |||
| 78f664e892 | |||
| d745265048 | |||
| 917991feab | |||
| 4877ab4426 | |||
| 48c5100d3b | |||
| 72caf7525e | |||
| 242d91bc17 | |||
| afb22ce7d5 | |||
| e45a670ff5 | |||
| 45042aa5e0 | |||
| 07aec0730f | |||
| 7e6da3edb3 | |||
| 4f0eafa92d | |||
| de75eded09 | |||
| f2b4bea4de | |||
| 4f28b2506d | |||
| 281da8ed77 | |||
| bdf4258895 | |||
| 385d9b9df7 | |||
| 0e40df4fa3 | |||
| b0c132f73a | |||
| 4fd802bf3d | |||
| e461040e17 | |||
| 43b574011e | |||
| 9bae914b51 | |||
| 945070bdff | |||
| d2b4afe1be | |||
| 3760f8fafe | |||
| b9e5cefddf | |||
| 9f5f757ccf | |||
| 3a147d7f62 | |||
| 9f1d14e5a7 | |||
| bce71f3797 | |||
| 253dca730d | |||
| e0f33a56ec | |||
| 2287931e9c | |||
| 1f7a498606 | |||
| a9e366b238 | |||
| 1ca0dfb1e0 | |||
| c11ba23a3d | |||
| 556c769b40 | |||
| 78173dd546 | |||
| 2196c16097 | |||
| 7af5db7cd4 | |||
| c58672cc25 | |||
| 85e022d76e | |||
| 872f40933f | |||
| 4491018f5e | |||
| a21c440b5b | |||
| 143e094db8 | |||
| 94e9e945d7 | |||
| 5e56115050 | |||
| 1ad654aab3 | |||
| 596ffb093b | |||
| 3576362fd8 | |||
| 2c949bb39a | |||
| 34faa0617b | |||
| 15f6b3803f | |||
| 8514a426bb | |||
| 2062324294 | |||
| 90c6218210 | |||
| 321bb7178d | |||
| 42dda0a07a | |||
| adb17d91f9 | |||
| 646cc59687 | |||
| 38759348cd | |||
| ff44f4f395 | |||
| a31051a937 | |||
| 5cfd69f337 | |||
| f4100f7951 | |||
| 785d5118d0 | |||
| fddd85817a | |||
| a689fc6377 | |||
| 8e5a7ed24e | |||
| 788217c73c | |||
| 1abc3a1422 | |||
| c31c34b7d2 | |||
| b22ecba179 | |||
| 22bc98b48b | |||
| cf70026570 | |||
| 3c4a865e0b | |||
| 8523bb317a | |||
| 4b00b268c0 | |||
| f0ca34d107 | |||
| 3834ecb06a | |||
| 047c9e3ee9 | |||
| 786b796f37 | |||
| 3fd8be72c2 | |||
| cb80081473 | |||
| 5ac84e2be7 | |||
| c8466e0606 | |||
| db5167bbaf | |||
| 7d6e26bfd5 | |||
| 2fd339ac6b | |||
| 6f6dc08696 | |||
| 6cb063ebd2 | |||
| fb95e3eb51 | |||
| 17a061f917 | |||
| 51db65b868 | |||
| faab9e04a3 | |||
| bea92455dd | |||
| 6be1b80a82 | |||
| 2927eadada | |||
| 4c5e90dc77 | |||
| f860973172 | |||
| 94ed0cd59c | |||
| 93ea1f98ac | |||
| b68a92f251 | |||
| 902da2e720 | |||
| d384aaeb9c | |||
| 133494af97 | |||
| 64fc9462b6 | |||
| 3d86e5fff3 | |||
| a24bb069aa | |||
| 159355d63a | |||
| 598cf4ba79 | |||
| c86a4c88a9 | |||
| 6a9e76c2af | |||
| 2070071c49 | |||
| 9bbf3bcaba | |||
| 0395c0c6c7 | |||
| a175ec4d28 | |||
| 139627b6ec | |||
| 6f2eb0a116 | |||
| d26561acee | |||
| de0374bdbd | |||
| 51463ab674 | |||
| 6012695cfe | |||
| 081b3fcf33 | |||
| 27a2c94b4a | |||
| b9f76042bb | |||
| e7ef307984 | |||
| 1f68e52ac2 | |||
| 87dfb75c21 | |||
| 8afb1b8b30 | |||
| 5572991a7c | |||
| 653b9288c0 | |||
| 233b0856af | |||
| ec29e3d60b | |||
| 70bc32b3df | |||
| ca781655b0 | |||
| 03e2cb70bb | |||
| c7c95e1f9d | |||
| b040c5e25b | |||
| 60b007d28a | |||
| 52130a3eb6 | |||
| 5e849d7b81 | |||
| a7a28f613b | |||
| 46b3bfecd1 | |||
| 11b62a0fdc | |||
| 732bb93826 | |||
| a0bda1afd3 | |||
| e0790601df | |||
| a9a4b0ccf0 | |||
| f8848051ca | |||
| b81031eecf | |||
| 95a7ea8dce | |||
| 98edb5a34c | |||
| 8ca2a55616 | |||
| 74141bec73 | |||
| f60c4fd3c5 | |||
| d9ee3522bc | |||
| 2538c47d01 | |||
| ec3dc71c0e | |||
| e5edd9a34d | |||
| f72359918d | |||
| dfd505a794 | |||
| 4a311fe4de | |||
| 9d4493e1da | |||
| a1d8fbdbe9 | |||
| 952abde48e | |||
| 168718063f | |||
| 4abcbbb4f9 | |||
| 1ac1b289c1 | |||
| c25173aa12 | |||
| ea8d303883 | |||
| 9d79337fa7 | |||
| 82a6ddb01c | |||
| a7152bdef0 | |||
| 6484a529b0 | |||
| 6e1e653ceb | |||
| 0488e4cccb | |||
| 3cf92fd508 | |||
| fb2bb7554e | |||
| be7661a83b | |||
| 9684e21d93 | |||
| ee5c401823 | |||
| 4aa3ffb765 | |||
| c9affcc622 | |||
| bddb8d1e56 | |||
| c4d3dad639 | |||
| adbf9093d3 | |||
| c8defbb77f | |||
| a14fc0a7f5 | |||
| 8d2717d25b |
@@ -1,85 +0,0 @@
|
||||
name: ci-blenderbim-daily
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-blenderbim-daily.yml'
|
||||
- 'src/blenderbim/**'
|
||||
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||
- 'src/bcf/bcf/**'
|
||||
- 'src/ifcclash/ifcclash/**'
|
||||
- 'src/ifccobie/**'
|
||||
- 'src/ifcdiff/**'
|
||||
- 'src/ifccsv/**'
|
||||
- 'src/ifcpatch/ifcpatch/**'
|
||||
- 'src/ifc4d/ifc4d/**'
|
||||
- 'src/ifc5d/ifc5d/**'
|
||||
- 'src/ifccityjson/**'
|
||||
branches:
|
||||
- v0.8.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
short_name: win,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux,
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos,
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
- name: Get current version
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/blenderbim && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/blenderbim/dist/blenderbim_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "blenderbim-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}} (unstable)"
|
||||
tag: "blenderbim-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish-blenderbim-chocolatey package
|
||||
name: ci-bonsai-choco
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -9,11 +9,12 @@ on:
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "30 0 * * *" # 30min past utc midnight
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: blenderbim
|
||||
name: bonsai
|
||||
choco_version: 1.1.0
|
||||
CHOCO_TOKEN: ${{ secrets.CHOCO_TOKEN }}
|
||||
|
||||
@@ -41,5 +42,6 @@ jobs:
|
||||
- name: Check in release tags if we should do a choco release and perform the release if needed
|
||||
id: do_choco_release
|
||||
run: |
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/ &&
|
||||
pip install pygithub
|
||||
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/bonsai/ &&
|
||||
python3 choco_release.py
|
||||
@@ -0,0 +1,155 @@
|
||||
name: ci-bonsai-daily
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-bonsai-daily.yml'
|
||||
- 'src/bonsai/**'
|
||||
- 'src/ifcopenshell-python/ifcopenshell/**'
|
||||
- 'src/bcf/bcf/**'
|
||||
- 'src/ifcclash/ifcclash/**'
|
||||
- 'src/ifccobie/**'
|
||||
- 'src/ifcdiff/**'
|
||||
- 'src/ifccsv/**'
|
||||
- 'src/ifcpatch/ifcpatch/**'
|
||||
- 'src/ifc4d/ifc4d/**'
|
||||
- 'src/ifc5d/ifc5d/**'
|
||||
- 'src/ifccityjson/**'
|
||||
branches:
|
||||
- v0.8.0
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
short_name: win,
|
||||
}
|
||||
- {
|
||||
name: "Linux Build",
|
||||
short_name: linux,
|
||||
}
|
||||
- {
|
||||
name: "MacOS Build",
|
||||
short_name: macos,
|
||||
}
|
||||
- {
|
||||
name: "MacOS ARM Build",
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
python-version: '3.11'
|
||||
- name: Get current version
|
||||
id: version
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%y%m%d')" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/bonsai && make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }}
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/bonsai/dist/bonsai_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}} (unstable)"
|
||||
tag: "bonsai-${{steps.version.outputs.version}}-alpha${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
body: "See README in https://github.com/IfcOpenShell/bonsai_unstable_repo/ on how to setup autoupdates for daily Bonsai builds."
|
||||
|
||||
update-extensions-repo-and-run-tests:
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout bonsai_unstable_repo repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: IfcOpenShell/bonsai_unstable_repo
|
||||
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
|
||||
path: bonsai_unstable_repo
|
||||
|
||||
- name: Update index.json on extensions repo
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
# Download Blender.
|
||||
wget -q -O blender.tar.xz https://ftp.nluug.nl/pub/graphics/blender/release/Blender4.2/blender-4.2.0-linux-x64.tar.xz
|
||||
tar -xf blender.tar.xz
|
||||
|
||||
# Setup Blender.
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
cd bonsai_unstable_repo
|
||||
pip install -r requirements.txt
|
||||
python setup_extensions_repo.py --last-tag
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
git add index.json
|
||||
git add readme.md
|
||||
git commit -m "Update index.json"
|
||||
git push
|
||||
- name: Run bonsai tests
|
||||
run: |
|
||||
set -x -e
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
bonsai_zip="$(pwd)/$(ls bonsai_unstable_repo/bonsai_py311*-linux-x64.zip)"
|
||||
blender --version
|
||||
blender --command extension install-file -r user_default -e $bonsai_zip
|
||||
blender --command extension list
|
||||
|
||||
wget -q -O sverchok.zip https://github.com/nortikin/sverchok/archive/refs/heads/master.zip
|
||||
# ifcsverchok expecting sverchok to be named "sverchok" and not "sverchok-master".
|
||||
unzip -q sverchok.zip
|
||||
mv sverchok-master sverchok
|
||||
zip -q -r sverchok.zip sverchok
|
||||
rm -r sverchok
|
||||
blender --command extension install-file -r user_default sverchok.zip
|
||||
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
|
||||
cd IfcOpenShell/src/ifcsverchok
|
||||
make dist
|
||||
sverchok_zip="$(pwd)/dist/$(ls dist)"
|
||||
blender --command extension install-file -r user_default $sverchok_zip
|
||||
|
||||
# Install Sun Position extension.
|
||||
blender --online-mode --command extension sync
|
||||
blender --online-mode --background --python-expr "import bpy; \
|
||||
bpy.ops.extensions.package_install(repo_index=0, pkg_id='sun_position'); \
|
||||
bpy.ops.preferences.addon_enable(module='bl_ext.blender_org.sun_position'); bpy.ops.wm.save_userpref()"
|
||||
|
||||
cd ../bonsai
|
||||
pip install pytest-blender
|
||||
blender --background --python scripts/setup_pytest.py
|
||||
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
|
||||
make test
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ci-blenderbim
|
||||
name: ci-bonsai
|
||||
|
||||
# Differences from ci-blenderbim-daily.yml:
|
||||
# Differences from ci-bonsai-daily.yml:
|
||||
# - make has IS_STABLE=TRUE
|
||||
# - action is never triggered and executed only manually
|
||||
# - doesn't add a current date to the release and tag
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py39, py310, py311, py312]
|
||||
pyver: [py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -53,12 +53,12 @@ jobs:
|
||||
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/blenderbim &&
|
||||
cd src/bonsai &&
|
||||
make dist PLATFORM=${{ matrix.config.short_name }} PYVERSION=${{ matrix.pyver }} IS_STABLE=TRUE
|
||||
- name: Find zip file name
|
||||
id: find_zip
|
||||
run: |
|
||||
filepath=$(ls src/blenderbim/dist/blenderbim_*.zip)
|
||||
filepath=$(ls src/bonsai/dist/bonsai_*.zip)
|
||||
echo "filepath=$filepath" >> $GITHUB_OUTPUT
|
||||
echo "filename=$(basename $filepath)" >> $GITHUB_OUTPUT
|
||||
- name: Upload zip file to release
|
||||
@@ -67,6 +67,6 @@ jobs:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ steps.find_zip.outputs.filepath }}
|
||||
asset_name: ${{ steps.find_zip.outputs.filename }}
|
||||
release_name: "blenderbim-${{steps.version.outputs.version}}"
|
||||
tag: "blenderbim-${{steps.version.outputs.version}}"
|
||||
release_name: "bonsai-${{steps.version.outputs.version}}"
|
||||
tag: "bonsai-${{steps.version.outputs.version}}"
|
||||
overwrite: true
|
||||
@@ -47,6 +47,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install mathutils
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
|
||||
@@ -106,7 +107,7 @@ jobs:
|
||||
|
||||
- name: Run IfcConvert on Sample files
|
||||
run: |
|
||||
(find test/input src/blenderbim/test/files -name '*.ifc' | while read i; do \
|
||||
(find test/input src/bonsai/test/files -name '*.ifc' | while read i; do \
|
||||
echo $i | tee -a log; \
|
||||
timeout 1m "$(which IfcConvert)" -yv "$i" "$i.obj" --validate >> log 2>&1; \
|
||||
echo $i $? >> statuses; \
|
||||
@@ -125,4 +126,11 @@ jobs:
|
||||
python tests.py
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
make test-safe
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
cd ../bsdd && make test
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -8,12 +8,12 @@ on:
|
||||
jobs:
|
||||
activate:
|
||||
if: github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- run: echo ok go
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -52,8 +52,8 @@ jobs:
|
||||
-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 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.10 \
|
||||
-DPYTHON_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libpython3.10.so \
|
||||
-DLIBXML2_INCLUDE_DIR=/usr/include/libxml2 \
|
||||
-DLIBXML2_LIBRARIES=/usr/lib/x86_64-linux-gnu/libxml2.so \
|
||||
-DGLTF_SUPPORT=On \
|
||||
|
||||
@@ -73,19 +73,16 @@ src/ifcopenshell-python/test/build
|
||||
# mypy cache
|
||||
.mypy_cache
|
||||
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
# bonsai i18n
|
||||
src/bonsai/bonsai/translations.py
|
||||
|
||||
# blenderbim i18n
|
||||
src/blenderbim/blenderbim/translations.py
|
||||
# bonsai test temp files
|
||||
src/bonsai/test/files/temp
|
||||
src/bonsai/test/files/basic.ifc.cache.blend
|
||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
src/blenderbim/test/files/basic.ifc.cache.blend
|
||||
src/blenderbim/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
src/blenderbim/drawings
|
||||
src/blenderbim/layouts
|
||||
src/bonsai/drawings
|
||||
src/bonsai/layouts
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
@@ -100,4 +97,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
.cache
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- mode: Dockerfile -*-
|
||||
|
||||
FROM ubuntu:focal
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG CHANNEL
|
||||
ENV CHANNEL=${CHANNEL:-latest}
|
||||
@@ -23,7 +23,7 @@ RUN echo "deb http://archive.ubuntu.com/ubuntu focal-proposed main restricted" |
|
||||
echo "deb http://archive.ubuntu.com/ubuntu focal-proposed multiverse" | tee -a /etc/apt/sources.list; \
|
||||
apt-get -qq update; \
|
||||
apt-get -y install tzdata dos2unix rsync; \
|
||||
apt-get -y install python3 libxml2 libpython3.8 \
|
||||
apt-get -y install python3 libxml2 libpython3.10 \
|
||||
libboost-all-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev \
|
||||
libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
|
||||
@@ -12,7 +12,7 @@ is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
In addition to a C++ and Python API, IfcOpenShell comes with an ecosystem of tools, notably including IfcConvert (an application
|
||||
to convert IFC models to other formats), the BlenderBIM Add-on (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
to convert IFC models to other formats), the ~~BlenderBIM~~ Bonsai Add-on (an add-on to Blender providing a graphical IFC authoring platform),
|
||||
and many other libraries, CLI apps, and more. Support is also provided for auxiliary standards such as BCF and IDS.
|
||||
|
||||
For more information, see:
|
||||
@@ -22,10 +22,10 @@ For more information, see:
|
||||
* [IfcOpenShell C++ Installation](https://docs.ifcopenshell.org/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://docs.ifcopenshell.org/ifcopenshell-python/hello_world.html)
|
||||
* [BlenderBIM Add-on Website](https://blenderbim.org)
|
||||
* [BlenderBIM Add-on Documentation](https://docs.blenderbim.org/index.html)
|
||||
* [Add-on Installation](https://docs.blenderbim.org/users/installation.html)
|
||||
* [Exploring an IFC model](https://docs.blenderbim.org/users/exploring_an_ifc_model.html)
|
||||
* [Bonsai Website](https://bonsaibim.org)
|
||||
* [Bonsai Documentation](https://docs.bonsaibim.org/index.html)
|
||||
* [Add-on Installation](https://docs.bonsaibim.org/users/installation.html)
|
||||
* [Exploring an IFC model](https://docs.bonsaibim.org/users/exploring_an_ifc_model.html)
|
||||
|
||||
Development is sponsored through your generous donations!
|
||||
|
||||
@@ -37,7 +37,7 @@ Contents
|
||||
| Name | Description | License | Service |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ------------------- | ------- |
|
||||
| bcf | Library to read and write BCF-XML and query OpenCDE BCF-API modules | LGPL-3.0-or-later | [](https://pypi.org/project/bcf-client/) |
|
||||
| blenderbim | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://blenderbim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=blenderbim&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| ~~blenderbim~~ Bonsai | Add-on to Blender providing a graphical native IFC authoring platform | GPL-3.0-or-later | [](https://bonsaibim.org/download.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=blenderbim&expanded=true) [](https://community.chocolatey.org/packages/blenderbim-nightly/) |
|
||||
| bsdd | Library to query the bSDD API | LGPL-3.0-or-later | [](https://pypi.org/project/bsdd/) |
|
||||
| ifc2ca | Utility to convert IFC structural analysis models to Code_Aster | LGPL-3.0-or-later |
|
||||
| ifc4d | Convert to and from IFC and project management software | LGPL-3.0-or-later | [](https://pypi.org/project/ifc4d/) |
|
||||
@@ -46,12 +46,12 @@ Contents
|
||||
| ifcblender | Historic Blender IFC import add-on | LGPL-3.0-or-later\* |
|
||||
| ifccityjson | Convert CityJSON to IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccityjson/) |
|
||||
| ifcclash | Clash detection library and CLI app | LGPL-3.0-or-later | [](https://pypi.org/project/ifcclash/) |
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html)
|
||||
| ifcconvert | CLI app to convert IFC to many other formats | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcconvert/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcconvert&expanded=true)
|
||||
| ifccsv | Library and CLI app to export and import schedules from IFC | LGPL-3.0-or-later | [](https://pypi.org/project/ifccsv/) |
|
||||
| ifcdiff | Compare changes between IFC models | LGPL-3.0-or-later | [](https://pypi.org/project/ifcdiff/) |
|
||||
| ifcfm | Extract IFC data for FM handover requirements | LGPL-3.0-or-later | [](https://pypi.org/project/ifcfm/) |
|
||||
| ifcmax | Historic extension for IFC support in 3DS Max | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcmax.html)
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcopenshell-python | Python library for IFC manipulation | LGPL-3.0-or-later\* | [](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html) [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcopenshell-python&expanded=true) [](https://pypi.org/project/ifcopenshell/) [](https://anaconda.org/conda-forge/ifcopenshell) [](https://anaconda.org/ifcopenshell/ifcopenshell) [](https://hub.docker.com/r/aecgeeks/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell) [](https://aur.archlinux.org/packages/ifcopenshell-git) |
|
||||
| ifcpatch | Utility to run pre-packaged scripts to manipulate IFCs | LGPL-3.0-or-later | [](https://pypi.org/project/ifcpatch/) |
|
||||
| ifcsverchok | Blender Add-on for visual node programming with IFC | GPL-3.0-or-later | [](https://github.com/IfcOpenShell/IfcOpenShell/releases?q=ifcsverchok&expanded=true)
|
||||
| ifctester | Library, CLI and webapp for IDS model auditing | LGPL-3.0-or-later | [](https://pypi.org/project/ifctester/) |
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||
<docsUrl>https://docs.blenderbim.org/</docsUrl>
|
||||
<docsUrl>https://docs.bonsaibim.org/</docsUrl>
|
||||
<!--<mailingListUrl></mailingListUrl>-->
|
||||
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
|
||||
<tags>blender bim blenderbim ifc python opensource foss</tags>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -14,9 +14,11 @@ import os
|
||||
import pathlib
|
||||
import re
|
||||
from urllib import request
|
||||
from github import Github
|
||||
from typing import NoReturn
|
||||
|
||||
|
||||
def get_repo_tag_names():
|
||||
def get_repo_tag_names() -> list[str]:
|
||||
git_return = os.popen("git tag -l").read()
|
||||
tag_names = [tag_name for tag_name in git_return.split("\n") if tag_name]
|
||||
print(f"{len(tag_names)} tag_names found in repo")
|
||||
@@ -43,7 +45,7 @@ def get_latest_choco_blender_version() -> list:
|
||||
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
|
||||
|
||||
|
||||
def get_file_sha256_hash(file_path):
|
||||
def get_file_sha256_hash(file_path: str) -> str:
|
||||
BLOCKSIZE = 65536
|
||||
hasher = hashlib.sha256()
|
||||
|
||||
@@ -56,16 +58,29 @@ def get_file_sha256_hash(file_path):
|
||||
return hasher.hexdigest()
|
||||
|
||||
|
||||
def quit_with_error_message(message: str):
|
||||
def quit_with_error_message(message: str) -> NoReturn:
|
||||
print(f"ERROR: {message}")
|
||||
quit(0)
|
||||
|
||||
|
||||
def get_release_zip(tag: str) -> tuple[str, str]:
|
||||
g = Github()
|
||||
repo = g.get_repo("IfcOpenShell/IfcOpenShell")
|
||||
release = repo.get_release(tag)
|
||||
for asset in release.get_assets():
|
||||
asset_name = asset.name
|
||||
if python_version not in asset_name:
|
||||
continue
|
||||
if TARGET_OS not in asset_name:
|
||||
continue
|
||||
return (asset_name, asset.browser_download_url)
|
||||
raise Exception(f"Couldn't find the release matching '{python_version}' and '{TARGET_OS}' in tag '{tag}'.")
|
||||
|
||||
|
||||
start = datetime.datetime.now()
|
||||
|
||||
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
||||
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases/download/"
|
||||
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
|
||||
@@ -79,7 +94,7 @@ os.chdir(BLENDERBIM_DIR)
|
||||
blenderbim_date_yesterday = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
should_release = False
|
||||
target_release_tag = ""
|
||||
target_os = "win"
|
||||
TARGET_OS = "windows-x64"
|
||||
|
||||
git_status = os.popen("git status").read()
|
||||
print(git_status)
|
||||
@@ -130,10 +145,7 @@ print(f"{python_version=}")
|
||||
blenderbim_build_version = target_release_tag.replace("blenderbim-", "")
|
||||
|
||||
# url_blenderbim_py3x_win_zip
|
||||
release_zip_file_name = f"{target_release_tag}-{python_version}-{target_os}.zip"
|
||||
|
||||
# download release
|
||||
url_blenderbim_py3x_win_zip = f"{URL_IFCOS_RELEASES}{target_release_tag}/{release_zip_file_name}"
|
||||
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
|
||||
os.popen(f"wget {url_blenderbim_py3x_win_zip} --no-verbose").read()
|
||||
|
||||
# sha256sum_blenderbim_py310_win_zip
|
||||
@@ -221,10 +221,11 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
find_file(json_hpp "json.hpp" ${JSON_INCLUDE_DIR}/nlohmann)
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_hpp)
|
||||
message(STATUS "JSON for Modern C++ header file found")
|
||||
if(json_header_path)
|
||||
message(STATUS "JSON for Modern C++ header file found in ${JSON_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
endif()
|
||||
|
||||
@@ -76,14 +76,14 @@ about:
|
||||
For more information, see:
|
||||
|
||||
* [IfcOpenShell Website](http://ifcopenshell.org)
|
||||
* [IfcOpenShell Documentation](http://blenderbim.org/docs-python)
|
||||
* [IfcOpenShell C++ Installation](https://blenderbim.org/docs-python/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://blenderbim.org/docs-python/ifcopenshell-python/hello_world.html)
|
||||
* [BlenderBIM Add-on Website](https://blenderbim.org)
|
||||
* [BlenderBIM Add-on Documentation](http://blenderbim.org/docs)
|
||||
* [Add-on Installation](https://blenderbim.org/docs/users/installation.html)
|
||||
* [Exploring an IFC model](https://blenderbim.org/docs/users/exploring_an_ifc_model.html)
|
||||
* [IfcOpenShell Documentation](http://bonsaibim.org/docs-python)
|
||||
* [IfcOpenShell C++ Installation](https://bonsaibim.org/docs-python/ifcopenshell/installation.html)
|
||||
* [IfcOpenShell Python Installation](https://bonsaibim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
* [IfcOpenShell Python Hello World Tutorial](https://bonsaibim.org/docs-python/ifcopenshell-python/hello_world.html)
|
||||
* [Bonsai Website](https://bonsaibim.org)
|
||||
* [Bonsai Documentation](http://bonsaibim.org/docs)
|
||||
* [Add-on Installation](https://bonsaibim.org/docs/users/installation.html)
|
||||
* [Exploring an IFC model](https://bonsaibim.org/docs/users/exploring_an_ifc_model.html)
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
@@ -81,7 +81,7 @@ ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.7.1"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
BOOST_VERSION = "1.80.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
|
||||
@@ -1,7 +1,34 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = 'src/(bcf|bcfserver|blenderbim|bsdd|foundationserver|ifc2ca|ifc4d|ifc5d|ifcbimtester|ifcblender|ifccityjson|ifcclash|ifccsv|ifcdiff|ifcfm|ifcpatch|ifctester|ifcopenshell-python|ifcsverchok|opencdeserver)/.*.py$'
|
||||
extend-exclude = '(src/ifcopenshell-python/ifcopenshell/express/*|src/ifc2ca/templates/*)'
|
||||
include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|blenderbim
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|ifc4d
|
||||
|ifc5d
|
||||
|ifcbimtester
|
||||
|ifcblender
|
||||
|ifccityjson
|
||||
|ifcclash
|
||||
|ifccsv
|
||||
|ifcdiff
|
||||
|ifcfm
|
||||
|ifcpatch
|
||||
|ifctester
|
||||
|ifcopenshell-python
|
||||
|ifcsverchok
|
||||
|opencdeserver
|
||||
)/.*.py$
|
||||
'''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifc2ca/templates/*
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
reportInvalidTypeForm = false
|
||||
|
||||
@@ -30,8 +30,12 @@ license:
|
||||
# TODO: make this based on xsd file presence
|
||||
.PHONY: models
|
||||
models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -p no:pytest-blender tests
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
A simple Python implementation of the BCF standard. Manipulation of BCF-XML is
|
||||
available via `bcfxml.py` and manipulation of BCF-API is available via
|
||||
`bcfapi.py`.
|
||||
|
||||
Python files in 'model' folder are automatically generated from .xsd files (located in 'xsd' folder).
|
||||
To regenerate them you can use `make models`.
|
||||
The only exception is 'v2/model/extensions.py', see it's note for the details.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import bcf.v2.model.extensions
|
||||
import bcf.v3.model.extensions
|
||||
from typing import NamedTuple, Union
|
||||
from dataclasses import fields
|
||||
|
||||
|
||||
class AttributeData(NamedTuple):
|
||||
attr_type: type
|
||||
subattr_name: str
|
||||
subattr_xsd_name: str
|
||||
|
||||
|
||||
Extensions = Union[bcf.v2.model.extensions.Extensions, bcf.v3.model.extensions.Extensions]
|
||||
|
||||
|
||||
def get_extensions_attributes(extensions: Extensions) -> dict[str, AttributeData]:
|
||||
"""Return mapping of xsd attribute name to a tuple that consists of:
|
||||
- Extensions attribute name
|
||||
- Extensions attribute type type
|
||||
- subattribute name"""
|
||||
possible_attributes = {}
|
||||
for field in fields(type(extensions)):
|
||||
field_type = field.type.__args__[0] # type: ignore [reportAttributeAccessIssue]
|
||||
subfield = next(iter(fields(field_type)))
|
||||
xsd_name = subfield.metadata["name"]
|
||||
possible_attributes[field.name] = AttributeData(field_type, subfield.name, xsd_name)
|
||||
|
||||
return possible_attributes
|
||||
@@ -0,0 +1,10 @@
|
||||
import bcf.v2.model
|
||||
import bcf.v3.model
|
||||
from typing import Union
|
||||
|
||||
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
|
||||
BitMap = Union[bcf.v2.model.VisualizationInfoBitmap, bcf.v3.model.Bitmap]
|
||||
DocumentReference = Union[bcf.v2.model.TopicDocumentReference, bcf.v3.model.DocumentReference]
|
||||
HeaderFile = Union[bcf.v2.model.HeaderFile, bcf.v3.model.File]
|
||||
Topic = Union[bcf.v2.model.Topic, bcf.v3.model.Topic]
|
||||
ViewPoint = Union[bcf.v2.model.ViewPoint, bcf.v3.model.ViewPoint]
|
||||
@@ -0,0 +1,101 @@
|
||||
import tempfile
|
||||
import bcf.v2.bcfxml
|
||||
import bcf.v2.model
|
||||
import bcf.v2.topic
|
||||
import bcf.v3.bcfxml
|
||||
import bcf.v3.model
|
||||
import bcf.v3.topic
|
||||
import bcf.agnostic.model as mdl
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import assert_never
|
||||
|
||||
TopicHandler = Union[bcf.v2.topic.TopicHandler, bcf.v3.topic.TopicHandler]
|
||||
|
||||
|
||||
def extract_file(
|
||||
topic: TopicHandler,
|
||||
entity: Union[mdl.HeaderFile, mdl.BimSnippet, mdl.DocumentReference, mdl.BitMap],
|
||||
bcfxml: Optional[Union[bcf.v2.bcfxml.BcfXml, bcf.v3.bcfxml.BcfXml]] = None,
|
||||
outfile: Optional[Path] = None,
|
||||
) -> Union[Path, str, None]:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:param bcfxml: The BCF XML file to use for resolving document references files.
|
||||
Required only for BCF v3 document references (in BCF v3 internal documents
|
||||
are stored at BCF root, not in the topic).
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
"""
|
||||
if isinstance(entity, mdl.DocumentReference):
|
||||
if isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
reference = entity.referenced_document
|
||||
else:
|
||||
reference = entity.document_guid
|
||||
else:
|
||||
reference = entity.reference
|
||||
|
||||
if not reference:
|
||||
return None
|
||||
|
||||
# For v3 document references external documents are detected by empty document_guid.
|
||||
# External bitmaps are not supported by bcf.
|
||||
if not isinstance(entity, (bcf.v3.model.DocumentReference, mdl.BitMap)) and entity.is_external:
|
||||
return reference
|
||||
|
||||
if isinstance(entity, bcf.v3.model.DocumentReference):
|
||||
# Extract document reference filename and contents.
|
||||
if not bcfxml:
|
||||
raise TypeError("bcfxml is required for BCF v3 document references.")
|
||||
assert isinstance(bcfxml, bcf.v3.bcfxml.BcfXml)
|
||||
error_msg = f"BCF XML is missing document with guid '{reference}'."
|
||||
if not bcfxml.documents:
|
||||
raise Exception(error_msg)
|
||||
definition_docs = bcfxml.documents.definition.documents
|
||||
if not definition_docs:
|
||||
raise Exception(error_msg)
|
||||
docs = next((doc for doc in definition_docs.document if doc.guid == reference), None)
|
||||
if not docs:
|
||||
raise Exception(error_msg)
|
||||
filename = docs.filename
|
||||
bytes_data = bcfxml.documents.documents[filename]
|
||||
else:
|
||||
if isinstance(entity, mdl.BimSnippet):
|
||||
bytes_data = topic.bim_snippet
|
||||
assert isinstance(bytes_data, bytes)
|
||||
elif isinstance(entity, mdl.HeaderFile):
|
||||
bytes_data = topic.reference_files[reference]
|
||||
elif isinstance(entity, mdl.BitMap):
|
||||
bytes_data = next(
|
||||
byte_data
|
||||
for vp in topic.viewpoints.values()
|
||||
for data_reference, byte_data in vp.bitmaps.items()
|
||||
if data_reference == reference
|
||||
)
|
||||
elif isinstance(entity, bcf.v2.model.TopicDocumentReference):
|
||||
assert isinstance(topic, bcf.v2.topic.TopicHandler)
|
||||
bytes_data = topic.document_references[reference]
|
||||
else:
|
||||
assert_never(entity)
|
||||
|
||||
# We don't really need it if 'outfile' is None, just keeping type checker happy.
|
||||
if isinstance(entity, mdl.HeaderFile) and entity.filename:
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = Path(reference).name
|
||||
|
||||
if not outfile:
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(bytes_data)
|
||||
|
||||
return outfile
|
||||
@@ -0,0 +1,5 @@
|
||||
import bcf.v2.visinfo
|
||||
import bcf.v3.visinfo
|
||||
from typing import Union
|
||||
|
||||
VisualizationInfoHandler = Union[bcf.v2.visinfo.VisualizationInfoHandler, bcf.v3.visinfo.VisualizationInfoHandler]
|
||||
@@ -30,9 +30,10 @@ from bcf.v3.model import Version as Version3
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
def load(
|
||||
filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None
|
||||
) -> Optional[Union[BcfXml2, BcfXml3]]:
|
||||
BcfXml = Union[BcfXml2, BcfXml3]
|
||||
|
||||
|
||||
def load(filepath: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
|
||||
"""
|
||||
Load a BCF file.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ from numpy.typing import NDArray
|
||||
|
||||
|
||||
def camera_vectors_from_element_placement(
|
||||
elem_placement: NDArray[np.float_],
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
elem_placement: NDArray[np.float64],
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to an element.
|
||||
|
||||
@@ -21,8 +21,8 @@ def camera_vectors_from_element_placement(
|
||||
|
||||
|
||||
def camera_vectors_from_target_position(
|
||||
target_position: NDArray[np.float_], offset: Optional[NDArray[np.float_]] = None
|
||||
) -> tuple[NDArray[np.float_], NDArray[np.float_], NDArray[np.float_]]:
|
||||
target_position: NDArray[np.float64], offset: Optional[NDArray[np.float64]] = None
|
||||
) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.float64]]:
|
||||
"""
|
||||
Calculate the vectors of a camera pointing to a target point.
|
||||
|
||||
@@ -50,7 +50,7 @@ def camera_vectors_from_target_position(
|
||||
# return camera_position, -mat[:3, 2], mat[:3, 1]
|
||||
|
||||
|
||||
def unit_vector(v: NDArray[np.float_]) -> NDArray[np.float_]:
|
||||
def unit_vector(v: NDArray[np.float64]) -> NDArray[np.float64]:
|
||||
"""
|
||||
Return the unit vector of a vector.
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional, TypeVar
|
||||
|
||||
import bcf.agnostic.extensions
|
||||
import bcf.v2.model as mdl
|
||||
import bcf.v2.model.extensions as mdl_extensions
|
||||
from bcf.inmemory_zipfile import InMemoryZipFile, ZipFileInterface
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
@@ -24,7 +26,8 @@ class BcfXml:
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._version: Optional[mdl.Version] = None
|
||||
self._project_info: Optional[mdl.ProjectExtension] = None
|
||||
self._topics: dict[str, TopicHandler] = {}
|
||||
self._extensions: Optional[mdl_extensions.Extensions] = None
|
||||
self._topics: Optional[dict[str, TopicHandler]] = None
|
||||
self._extension_schema: Optional[bytes] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
@@ -85,24 +88,63 @@ class BcfXml:
|
||||
def extension_schema(self, value: bytes) -> None:
|
||||
self._extension_schema = value
|
||||
|
||||
@property
|
||||
def extensions(self) -> Optional[mdl_extensions.Extensions]:
|
||||
"""BCF extensions."""
|
||||
|
||||
if not self._extensions and self.extension_schema:
|
||||
import io
|
||||
from xml.etree import ElementTree as etree
|
||||
|
||||
extensions = mdl_extensions.Extensions()
|
||||
|
||||
xs = "{http://www.w3.org/2001/XMLSchema}"
|
||||
root = etree.parse(io.BytesIO((self.extension_schema)))
|
||||
|
||||
attrs = bcf.agnostic.extensions.get_extensions_attributes(extensions)
|
||||
xsd_to_attrs = {v.subattr_xsd_name: k for k, v in attrs.items()}
|
||||
for node in root.findall(f".//{xs}restriction"):
|
||||
attr_type = node.attrib.get("base")
|
||||
if not attr_type:
|
||||
continue
|
||||
attr_name = xsd_to_attrs.get(attr_type)
|
||||
if attr_name is None:
|
||||
continue
|
||||
values = []
|
||||
for enum in node.findall(f".//{xs}enumeration"):
|
||||
values.append(enum.attrib["value"])
|
||||
if not values:
|
||||
continue
|
||||
attr_data = attrs[attr_name]
|
||||
attr = attr_data.attr_type()
|
||||
setattr(attr, attr_data.subattr_name, values)
|
||||
setattr(extensions, attr_name, attr)
|
||||
|
||||
self._extensions = extensions
|
||||
return self._extensions
|
||||
|
||||
@extensions.setter
|
||||
def extensions(self, value: Optional[mdl_extensions.Extensions]) -> None:
|
||||
self._extensions = value
|
||||
|
||||
@property
|
||||
def topics(self) -> dict[str, TopicHandler]:
|
||||
"""BCF topics."""
|
||||
if not self._topics and self._zip_file:
|
||||
self._topics = self._load_topics(self._zip_file, self._xml_handler)
|
||||
if self._topics is None:
|
||||
self._topics = self._load_topics()
|
||||
return self._topics
|
||||
|
||||
def _load_topics(
|
||||
self, zip_file: zipfile.ZipFile, xml_handler: AbstractXmlParserSerializer
|
||||
) -> dict[str, TopicHandler]:
|
||||
def _load_topics(self) -> dict[str, TopicHandler]:
|
||||
topics = {}
|
||||
for topic_dir in zipfile.Path(zip_file).iterdir():
|
||||
if self._zip_file is None:
|
||||
return topics
|
||||
for topic_dir in zipfile.Path(self._zip_file).iterdir():
|
||||
if not topic_dir.is_dir():
|
||||
continue
|
||||
markup_path = topic_dir.joinpath("markup.bcf")
|
||||
if not markup_path.exists():
|
||||
continue
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, xml_handler)
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
return topics
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# NOTE: This file is not generated from
|
||||
# https://github.com/buildingSMART/BCF-XML/blob/release_2_1/Extension%20Schemas/extensions.xsd
|
||||
# because in bcf 2.1 there is no extensions.xml - I guess, the schema assumes that each .bcf
|
||||
# will use their own schema patched by it's own extensions.xsd.
|
||||
#
|
||||
# To make things simpler we just mimic extensions structures from bcf 3, so they'll have common API,
|
||||
# and parse extensions.xsd inside .bcf as .xml and fill our structures.
|
||||
#
|
||||
# Preferably if we could generate some kind of extensions.xml from extensions.xsd
|
||||
# and leave all the handling to xsdata.
|
||||
#
|
||||
# We also don't add it to __init__.py not to mess with generator.
|
||||
#
|
||||
# Currently extensions support for v2 is only read-only.
|
||||
|
||||
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "UserIdType",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
"min_length": 1,
|
||||
"white_space": "collapse",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicTypes",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
topic_statuses: Optional[ExtensionsTopicStatuses] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicStatuses",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
priorities: Optional[ExtensionsPriorities] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Priorities",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
topic_labels: Optional[ExtensionsTopicLabels] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "TopicLabels",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
users: Optional[ExtensionsUsers] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Users",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
snippet_types: Optional[ExtensionsSnippetTypes] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "SnippetTypes",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
stages: Optional[ExtensionsStages] = field(
|
||||
default=None,
|
||||
metadata={
|
||||
"name": "Stages",
|
||||
"type": "Element",
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
@@ -5,7 +5,7 @@ import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Any, NoReturn, Optional
|
||||
from typing import Any, NoReturn, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
@@ -27,9 +27,9 @@ class TopicHandler:
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
|
||||
self._reference_files: dict[str, bytes] = {}
|
||||
self._document_references: dict[str, bytes] = {}
|
||||
self._viewpoints: Optional[dict[str, VisualizationInfoHandler]] = None
|
||||
self._reference_files: Optional[dict[str, bytes]] = None
|
||||
self._document_references: Optional[dict[str, bytes]] = None
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
@@ -63,11 +63,21 @@ class TopicHandler:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header if self.markup else None
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.markup.comment if self.markup else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
assert self.markup
|
||||
self.markup.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -80,14 +90,24 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if not self._viewpoints and self._topic_dir:
|
||||
if self._viewpoints is None:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self._topic_dir and self.markup and (viewpoints := self.markup.viewpoints):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files or not self.header:
|
||||
if self._reference_files is not None:
|
||||
return self._reference_files
|
||||
|
||||
self._reference_files = {}
|
||||
if not self.header:
|
||||
return self._reference_files
|
||||
|
||||
for ref in self.header.file:
|
||||
if ref.is_external:
|
||||
continue
|
||||
@@ -99,8 +119,13 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def document_references(self) -> dict[str, bytes]:
|
||||
if self._document_references or not self.topic:
|
||||
if self._document_references is not None:
|
||||
return self._document_references
|
||||
|
||||
self._document_references = {}
|
||||
if not self.topic:
|
||||
return self._document_references
|
||||
|
||||
for doc in self.topic.document_reference:
|
||||
if doc.is_external or not doc.referenced_document:
|
||||
continue
|
||||
@@ -118,11 +143,6 @@ class TopicHandler:
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self.markup and (viewpoints := self.markup.viewpoints):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
@@ -220,55 +240,7 @@ class TopicHandler:
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.document_references[doc.referenced_document])
|
||||
|
||||
def extract_file(self, entity, outfile: Optional[Path] = None) -> Path:
|
||||
"""Extracts an element with a file into a temporary directory
|
||||
|
||||
These include header files, bim snippets, document references, and
|
||||
viewpoint bitmaps. External reference are not downloaded. Instead, the
|
||||
URI reference is returned.
|
||||
|
||||
:param entity: The entity with a file reference to extract
|
||||
:type entity: bcf.v2.model.HeaderFile,bcf.v2.model.BimSnippet,bcf.v2.model.TopicDocumentReference
|
||||
:param outfile: If provided, save the header file to that location.
|
||||
Otherwise, a temporary directory is created and the filename is
|
||||
derived from the header's original filename.
|
||||
:type outfile: pathlib.Path,optional
|
||||
:return: The filepath of the extracted file. It may be a URL if the
|
||||
header file is external.
|
||||
:rtype: Path
|
||||
"""
|
||||
if hasattr(entity, "reference"):
|
||||
reference = entity.reference
|
||||
else:
|
||||
reference = entity.referenced_document
|
||||
|
||||
if not reference:
|
||||
return
|
||||
|
||||
if getattr(entity, "is_external", False):
|
||||
return entity.reference
|
||||
|
||||
resolved_reference = self._topic_dir
|
||||
|
||||
for part in Path(reference).parts:
|
||||
if part == "..":
|
||||
resolved_reference = resolved_reference.parent
|
||||
else:
|
||||
resolved_reference = resolved_reference.joinpath(part)
|
||||
|
||||
if not outfile:
|
||||
if getattr(entity, "filename", None):
|
||||
filename = entity.filename
|
||||
else:
|
||||
filename = resolved_reference.name
|
||||
outfile = Path(tempfile.mkdtemp()) / filename
|
||||
|
||||
with open(outfile, "wb") as f:
|
||||
f.write(resolved_reference.read_bytes())
|
||||
|
||||
return outfile
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""Add a viewpoint pointed at the placement of an IFC element to the topic.
|
||||
|
||||
Args:
|
||||
@@ -278,7 +250,9 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(
|
||||
self, position: NDArray[np.float64], *guids: str
|
||||
) -> VisualizationInfoHandler:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
|
||||
Args:
|
||||
@@ -291,9 +265,17 @@ class TopicHandler:
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
def add_visinfo_handler(
|
||||
self, new_viewpoint: VisualizationInfoHandler, snapshot_filename: Optional[str] = None
|
||||
) -> mdl.ViewPoint:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
self.markup.viewpoints.append(mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid))
|
||||
viewpoint = mdl.ViewPoint(
|
||||
viewpoint=new_viewpoint.guid + ".bcfv",
|
||||
snapshot=snapshot_filename,
|
||||
guid=new_viewpoint.guid,
|
||||
)
|
||||
self.markup.viewpoints.append(viewpoint)
|
||||
return viewpoint
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
@@ -134,6 +133,15 @@ class VisualizationInfoHandler:
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if bool(self.snapshot) ^ bool(filename):
|
||||
data = ["data (VisualizationInfoHandler.snapshot)", "filename (ViewPoint.snapshot)"]
|
||||
provided_data, missing_data = data if self.snapshot else data[::-1]
|
||||
print(
|
||||
f"WARNING. Snapshot with viewpoint guid '{self.guid}' won't be saved to bcf. "
|
||||
f"Only snapshot {provided_data} is provided but snapshot {missing_data} is missing."
|
||||
)
|
||||
return
|
||||
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
@@ -176,7 +184,7 @@ class VisualizationInfoHandler:
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
position: NDArray[np.float64],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
@@ -196,13 +204,110 @@ class VisualizationInfoHandler:
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
def get_selected_guids(self) -> Union[list[str], None]:
|
||||
"""
|
||||
Return viewpoint selected elements IFC guids.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no selection settings, return `None`.
|
||||
Otherwise return a list of selected elements IFC guids.
|
||||
"""
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
selection = components.selection
|
||||
if not selection:
|
||||
return None
|
||||
return [guid for c in selection.component if (guid := c.ifc_guid)]
|
||||
|
||||
def set_selected_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=True)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
selection = components.selection
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
if not selection:
|
||||
selection = mdl.ComponentSelection()
|
||||
components.selection = selection
|
||||
selection.component = components_list
|
||||
|
||||
def set_visible_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="VISIBLE")
|
||||
|
||||
def set_hidden_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="HIDDEN")
|
||||
|
||||
def set_visibility(
|
||||
self, elements: list[ifcopenshell.entity_instance], elements_visibility: Literal["VISIBLE", "HIDDEN"]
|
||||
) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
default_visibility = elements_visibility == "HIDDEN"
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components.visibility = visibility
|
||||
elif visibility.default_visibility != default_visibility:
|
||||
visibility.default_visibility = default_visibility
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
exceptions = mdl.ComponentVisibilityExceptions()
|
||||
visibility.exceptions = exceptions
|
||||
exceptions.component = components_list
|
||||
|
||||
def get_elements_visibility(self) -> Union[tuple[bool, list[str]], None]:
|
||||
"""
|
||||
Return viewpoint elements visibility settings.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no visibility settings, return `None`.
|
||||
Otherwise return a tuple containing the default visibility
|
||||
and a list of IFC element GUIDs listed as exceptions.
|
||||
|
||||
If default visibility is `True`, all elements are visible except the exceptions.
|
||||
|
||||
If default visibility is `False`, all elements are hidden except the exceptions.
|
||||
"""
|
||||
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
return None
|
||||
default_visibility = visibility.default_visibility or False
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
return default_visibility, []
|
||||
guids = [guid for c in exceptions.component if (guid := c.ifc_guid)]
|
||||
return default_visibility, guids
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
@@ -224,11 +329,11 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
@@ -249,7 +354,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
*guids: One or more selected IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
@@ -261,7 +366,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -275,7 +380,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -26,7 +26,7 @@ class BcfXml:
|
||||
self._version: Optional[mdl.Version] = None
|
||||
self._project_info: Optional[mdl.ProjectInfo] = None
|
||||
self._extensions: Optional[mdl.Extensions] = None
|
||||
self._topics: dict[str, TopicHandler] = {}
|
||||
self._topics: Optional[dict[str, TopicHandler]] = None
|
||||
self._documents: Optional[DocumentsHandler] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
@@ -91,18 +91,22 @@ class BcfXml:
|
||||
@property
|
||||
def topics(self) -> dict[str, TopicHandler]:
|
||||
"""BCF topics."""
|
||||
if not self._topics and self._zip_file:
|
||||
self._load_topics()
|
||||
if self._topics is None:
|
||||
self._topics = self._load_topics()
|
||||
return self._topics
|
||||
|
||||
def _load_topics(self) -> None:
|
||||
def _load_topics(self) -> dict[str, TopicHandler]:
|
||||
topics = {}
|
||||
if self._zip_file is None:
|
||||
return topics
|
||||
for topic_dir in zipfile.Path(self._zip_file).iterdir():
|
||||
if not topic_dir.is_dir():
|
||||
continue
|
||||
markup_path = topic_dir.joinpath("markup.bcf")
|
||||
if not markup_path.exists():
|
||||
continue
|
||||
self._topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
topics[topic_dir.name] = TopicHandler(topic_dir, self._xml_handler)
|
||||
return topics
|
||||
|
||||
@property
|
||||
def documents(self) -> Optional[DocumentsHandler]:
|
||||
|
||||
@@ -26,7 +26,8 @@ class TopicHandler:
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> None:
|
||||
self._markup: Optional[mdl.Markup] = None
|
||||
self._viewpoints: dict[str, VisualizationInfoHandler] = {}
|
||||
self._viewpoints: Optional[dict[str, VisualizationInfoHandler]] = None
|
||||
self._reference_files: Optional[dict[str, bytes]] = None
|
||||
self._bim_snippet: Optional[bytes] = None
|
||||
self._xml_handler = xml_handler or XmlParserSerializer()
|
||||
self._topic_dir = topic_dir
|
||||
@@ -49,22 +50,36 @@ class TopicHandler:
|
||||
return self.markup.topic
|
||||
|
||||
@property
|
||||
def guid(self) -> Optional[str]:
|
||||
def guid(self) -> str:
|
||||
"""Return the GUID of the topic."""
|
||||
if self._markup:
|
||||
return self.topic.guid
|
||||
return self._topic_dir.name if self._topic_dir else None
|
||||
return self._topic_dir.name if self._topic_dir else ""
|
||||
|
||||
@property
|
||||
def header(self) -> Optional[mdl.Header]:
|
||||
"""Return the header of the topic."""
|
||||
return self.markup.header
|
||||
|
||||
@header.setter
|
||||
def header(self, header: mdl.Header) -> None:
|
||||
"""Set the header of the topic."""
|
||||
self.markup.header = header
|
||||
|
||||
@property
|
||||
def comments(self) -> list[mdl.Comment]:
|
||||
"""Return the comments of the topic."""
|
||||
return self.topic.comments.comment if self.topic.comments else []
|
||||
|
||||
@comments.setter
|
||||
def comments(self, comments: list[mdl.Comment]) -> None:
|
||||
topic_comments = self.topic.comments
|
||||
if topic_comments is None:
|
||||
if not comments:
|
||||
return
|
||||
self.topic.comments = (topic_comments := mdl.TopicComments())
|
||||
topic_comments.comment = comments
|
||||
|
||||
@property
|
||||
def bim_snippet(self) -> Optional[bytes]:
|
||||
if not self._bim_snippet and self._topic_dir:
|
||||
@@ -77,15 +92,15 @@ class TopicHandler:
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
if (
|
||||
not self._viewpoints
|
||||
and self._topic_dir
|
||||
and self.topic.viewpoints
|
||||
and (viewpoints := self.topic.viewpoints.view_point)
|
||||
):
|
||||
self._viewpoints = VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
if self._viewpoints is None:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
if self._topic_dir and self.topic.viewpoints and (viewpoints := self.topic.viewpoints.view_point):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
|
||||
def _load_bim_snippet(self) -> Optional[bytes]:
|
||||
bim_snippet_obj = self.topic.bim_snippet
|
||||
if bim_snippet_obj and not bim_snippet_obj.is_external and self._topic_dir:
|
||||
@@ -94,6 +109,27 @@ class TopicHandler:
|
||||
return bim_snippet_path.read_bytes()
|
||||
return None
|
||||
|
||||
@property
|
||||
def reference_files(self) -> dict[str, bytes]:
|
||||
if self._reference_files is not None:
|
||||
return self._reference_files
|
||||
|
||||
self._reference_files = {}
|
||||
if not self.header:
|
||||
return self._reference_files
|
||||
|
||||
if not self.header.files:
|
||||
return self._reference_files
|
||||
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
self._reference_files[ref.reference] = real_path.read_bytes()
|
||||
return self._reference_files
|
||||
|
||||
@classmethod
|
||||
def create_new(
|
||||
cls,
|
||||
@@ -145,6 +181,7 @@ class TopicHandler:
|
||||
self._save_xml(destination_zip, self._markup, "markup.bcf")
|
||||
self._save_viewpoints(destination_zip, topic_dir)
|
||||
self._save_bim_snippet(destination_zip)
|
||||
self._save_reference_files(destination_zip)
|
||||
|
||||
def _save_viewpoints(self, destination_zip: ZipFileInterface, topic_dir: str) -> None:
|
||||
if not self.topic.viewpoints or not (viewpoints := self.topic.viewpoints.view_point):
|
||||
@@ -165,7 +202,20 @@ class TopicHandler:
|
||||
if self.bim_snippet:
|
||||
destination_zip.writestr(f"{self.topic.guid}/{ref_filename}", self.bim_snippet)
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> None:
|
||||
def _save_reference_files(self, destination_zip: ZipFileInterface) -> None:
|
||||
if not self.header:
|
||||
return
|
||||
if not self.header.files:
|
||||
return
|
||||
for ref in self.header.files.file:
|
||||
if ref.is_external or not ref.reference:
|
||||
continue
|
||||
real_path = self._topic_dir
|
||||
for path_part in ref.reference.split("/"):
|
||||
real_path = real_path.parent if path_part == ".." else real_path.joinpath(path_part)
|
||||
destination_zip.writestr(real_path.at, self.reference_files[ref.reference])
|
||||
|
||||
def add_viewpoint(self, element: entity_instance) -> VisualizationInfoHandler:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
@@ -174,8 +224,11 @@ class TopicHandler:
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
def add_viewpoint_from_point_and_guids(
|
||||
self, position: NDArray[np.float64], *guids: str
|
||||
) -> VisualizationInfoHandler:
|
||||
"""
|
||||
Add a viewpoint tergeting an IFC element to the topic.
|
||||
|
||||
@@ -186,14 +239,21 @@ class TopicHandler:
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
def add_visinfo_handler(
|
||||
self, new_viewpoint: VisualizationInfoHandler, snapshot_filename: Optional[str] = None
|
||||
) -> mdl.ViewPoint:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
if self.topic.viewpoints is None:
|
||||
self.topic.viewpoints = mdl.TopicViewpoints()
|
||||
self.topic.viewpoints.view_point.append(
|
||||
mdl.ViewPoint(viewpoint=new_viewpoint.guid + ".bcfv", guid=new_viewpoint.guid)
|
||||
viewpoint = mdl.ViewPoint(
|
||||
viewpoint=new_viewpoint.guid + ".bcfv",
|
||||
snapshot=snapshot_filename,
|
||||
guid=new_viewpoint.guid,
|
||||
)
|
||||
self.topic.viewpoints.view_point.append(viewpoint)
|
||||
return viewpoint
|
||||
|
||||
def __eq__(self, other: object) -> bool | NoReturn:
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from functools import lru_cache
|
||||
from typing import Any, Iterable, Optional
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.util import placement
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
@@ -133,6 +133,15 @@ class VisualizationInfoHandler:
|
||||
self._save_bitmaps(bcf_zip, topic_dir)
|
||||
|
||||
def _save_snapshot(self, bcf_zip: ZipFileInterface, topic_dir: str, filename: Optional[str]) -> None:
|
||||
if bool(self.snapshot) ^ bool(filename):
|
||||
data = ["data (VisualizationInfoHandler.snapshot)", "filename (ViewPoint.snapshot)"]
|
||||
provided_data, missing_data = data if self.snapshot else data[::-1]
|
||||
print(
|
||||
f"WARNING. Snapshot with viewpoint guid '{self.guid}' won't be saved to bcf. "
|
||||
f"Only snapshot {provided_data} is provided but snapshot {missing_data} is missing."
|
||||
)
|
||||
return
|
||||
|
||||
if self.snapshot and filename:
|
||||
bcf_zip.writestr(f"{topic_dir}/{filename}", self.snapshot)
|
||||
|
||||
@@ -175,7 +184,7 @@ class VisualizationInfoHandler:
|
||||
@classmethod
|
||||
def create_from_point_and_guids(
|
||||
cls,
|
||||
position: NDArray[np.float_],
|
||||
position: NDArray[np.float64],
|
||||
*guids: str,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "VisualizationInfoHandler":
|
||||
@@ -195,13 +204,110 @@ class VisualizationInfoHandler:
|
||||
visualization_info=build_viewpoint_from_position_and_guids(position, *guids), xml_handler=xml_handler
|
||||
)
|
||||
|
||||
def get_selected_guids(self) -> Union[list[str], None]:
|
||||
"""
|
||||
Return viewpoint selected elements IFC guids.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no selection settings, return `None`.
|
||||
Otherwise return a list of selected elements IFC guids.
|
||||
"""
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
selection = components.selection
|
||||
if not selection:
|
||||
return None
|
||||
return [guid for c in selection.component if (guid := c.ifc_guid)]
|
||||
|
||||
def set_selected_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=True)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
selection = components.selection
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
if not selection:
|
||||
selection = mdl.ComponentSelection()
|
||||
components.selection = selection
|
||||
selection.component = components_list
|
||||
|
||||
def set_visible_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="VISIBLE")
|
||||
|
||||
def set_hidden_elements(self, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
self.set_visibility(elements, elements_visibility="HIDDEN")
|
||||
|
||||
def set_visibility(
|
||||
self, elements: list[ifcopenshell.entity_instance], elements_visibility: Literal["VISIBLE", "HIDDEN"]
|
||||
) -> None:
|
||||
visualization_info = self.visualization_info
|
||||
default_visibility = elements_visibility == "HIDDEN"
|
||||
|
||||
guids = [e.GlobalId for e in elements]
|
||||
components_list = [mdl.Component(ifc_guid=guid) for guid in guids]
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components = mdl.Components(visibility=visibility)
|
||||
visualization_info.components = components
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
visibility = mdl.ComponentVisibility(default_visibility=default_visibility)
|
||||
components.visibility = visibility
|
||||
elif visibility.default_visibility != default_visibility:
|
||||
visibility.default_visibility = default_visibility
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
exceptions = mdl.ComponentVisibilityExceptions()
|
||||
visibility.exceptions = exceptions
|
||||
exceptions.component = components_list
|
||||
|
||||
def get_elements_visibility(self) -> Union[tuple[bool, list[str]], None]:
|
||||
"""
|
||||
Return viewpoint elements visibility settings.
|
||||
|
||||
Returns:
|
||||
If viewpoint has no visibility settings, return `None`.
|
||||
Otherwise return a tuple containing the default visibility
|
||||
and a list of IFC element GUIDs listed as exceptions.
|
||||
|
||||
If default visibility is `True`, all elements are visible except the exceptions.
|
||||
|
||||
If default visibility is `False`, all elements are hidden except the exceptions.
|
||||
"""
|
||||
|
||||
visualization_info = self.visualization_info
|
||||
components = visualization_info.components
|
||||
if not components:
|
||||
return None
|
||||
|
||||
visibility = components.visibility
|
||||
if not visibility:
|
||||
return None
|
||||
default_visibility = visibility.default_visibility or False
|
||||
|
||||
exceptions = visibility.exceptions
|
||||
if not exceptions:
|
||||
return default_visibility, []
|
||||
guids = [guid for c in exceptions.component if (guid := c.ifc_guid)]
|
||||
return default_visibility, guids
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
element: The IFC element to point at.
|
||||
@@ -209,7 +315,12 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
Returns:
|
||||
The BCF viewpoint definition.
|
||||
"""
|
||||
elem_placement = placement.get_local_placement(element.ObjectPlacement)
|
||||
ifc_file = element.wrapped_data.file
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
elem_placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
elem_placement[0][3] *= unit_scale
|
||||
elem_placement[1][3] *= unit_scale
|
||||
elem_placement[2][3] *= unit_scale
|
||||
|
||||
return mdl.VisualizationInfo(
|
||||
guid=str(uuid.uuid4()),
|
||||
@@ -218,11 +329,11 @@ def build_viewpoint(element: entity_instance) -> mdl.VisualizationInfo:
|
||||
)
|
||||
|
||||
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float_], *guids: str) -> mdl.VisualizationInfo:
|
||||
def build_viewpoint_from_position_and_guids(position: NDArray[np.float64], *guids: str) -> mdl.VisualizationInfo:
|
||||
"""
|
||||
Return a BCF viewpoint of an IFC element.
|
||||
|
||||
This function is cached to speedudp the creation of multiple BCF topics regarding the same element.
|
||||
This function is cached to speed up the creation of multiple BCF topics regarding the same element.
|
||||
|
||||
Args:
|
||||
position: target point coordinates.
|
||||
@@ -243,7 +354,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
Return the BCF components from an IFC element GUID.
|
||||
|
||||
Args:
|
||||
*guids: One or more IFC element GUID.
|
||||
*guids: One or more selected IFC element GUID.
|
||||
|
||||
Returns:
|
||||
The BCF components definition.
|
||||
@@ -255,7 +366,7 @@ def build_components(*guids: str) -> mdl.Components:
|
||||
)
|
||||
|
||||
|
||||
def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
def build_camera(elem_placement: NDArray[np.float64]) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -269,7 +380,7 @@ def build_camera(elem_placement: NDArray[np.float_]) -> mdl.PerspectiveCamera:
|
||||
|
||||
|
||||
def build_camera_from_vectors(
|
||||
camera_position: NDArray[np.float_], camera_dir: NDArray[np.float_], camera_up: NDArray[np.float_]
|
||||
camera_position: NDArray[np.float64], camera_dir: NDArray[np.float64], camera_up: NDArray[np.float64]
|
||||
) -> mdl.PerspectiveCamera:
|
||||
"""
|
||||
Return a BCF camera for an IFC element placement matrix.
|
||||
|
||||
@@ -55,9 +55,32 @@ def test_save_maximum_information() -> None:
|
||||
|
||||
def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.version.version_id == "2.1"
|
||||
assert bcf.project
|
||||
assert bcf.project.name == "BCF API Implementation"
|
||||
assert bcf.project_info
|
||||
assert bcf.project_info.extension_schema == "extensions.xsd"
|
||||
|
||||
assert bcf.extensions
|
||||
assert bcf.extensions.topic_types
|
||||
assert bcf.extensions.topic_types.topic_type == ["Architecture", "Hidden Type", "Structural"]
|
||||
assert bcf.extensions.topic_statuses
|
||||
assert bcf.extensions.topic_statuses.topic_status == ["Finished status", "Open", "Closed"]
|
||||
assert bcf.extensions.priorities
|
||||
assert bcf.extensions.priorities.priority == ["Low", "High", "Medium"]
|
||||
assert bcf.extensions.topic_labels
|
||||
assert bcf.extensions.topic_labels.topic_label == [
|
||||
"Architecture",
|
||||
"IT Development",
|
||||
"Management",
|
||||
"Mechanical",
|
||||
"Structural",
|
||||
]
|
||||
assert bcf.extensions.users
|
||||
assert bcf.extensions.users.user == ["dangl@iabi.eu", "linhard@iabi.eu"]
|
||||
assert bcf.extensions.snippet_types
|
||||
assert bcf.extensions.snippet_types.snippet_type == ["IFC2X3", "PDF", "XLSX"]
|
||||
assert bcf.extensions.stages is None
|
||||
|
||||
assert len(bcf.topics) == 2
|
||||
assert_first_topic_handler(bcf.topics["7ddc3ef0-0ab7-43f1-918a-45e38b42369c"])
|
||||
second_th = bcf.topics["d1068c81-af04-4546-b63c-348810f6c716"]
|
||||
|
||||
@@ -39,6 +39,22 @@ def assert_everything_in_place(bcf: BcfXml):
|
||||
assert bcf.project.name == "BCF 3.0 test cases"
|
||||
assert bcf.project.project_id == "de894a86-3a08-4ea0-b2d1-6c222b5602d1"
|
||||
|
||||
assert bcf.extensions
|
||||
assert bcf.extensions.topic_types
|
||||
assert bcf.extensions.topic_types.topic_type == ["ERROR", "WARNING", "INFORMATION", "CLASH", "OTHER"]
|
||||
assert bcf.extensions.topic_statuses
|
||||
assert bcf.extensions.topic_statuses.topic_status == ["OPEN", "IN_PROGRESS", "SOLVED", "CLOSED"]
|
||||
assert bcf.extensions.priorities
|
||||
assert bcf.extensions.priorities.priority == ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
|
||||
assert bcf.extensions.topic_labels
|
||||
assert bcf.extensions.topic_labels.topic_label == []
|
||||
assert bcf.extensions.users
|
||||
assert bcf.extensions.users.user == ["Architect@example.com", "Engineer@example.com", "MEPDesigner@example.com"]
|
||||
assert bcf.extensions.snippet_types
|
||||
assert bcf.extensions.snippet_types.snippet_type == []
|
||||
assert bcf.extensions.stages
|
||||
assert bcf.extensions.stages.stage == []
|
||||
|
||||
assert len(bcf.topics) == 1
|
||||
topic_handler = bcf.topics["8ac9822a-761a-4deb-9f39-f61286acbf6a"]
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# BlenderBIM Add-on
|
||||
|
||||
An add-on to Blender to allow BIM functionality.
|
||||
|
||||
More information on the [BlenderBIM Add-on website](https://blenderbim.org).
|
||||
@@ -1,147 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
blenderbim_lib_path = os.environ.get("blenderbim_lib_path")
|
||||
if blenderbim_lib_path:
|
||||
sys.path.insert(0, blenderbim_lib_path)
|
||||
|
||||
import argparse
|
||||
from aiohttp import web
|
||||
import socketio
|
||||
import pystache
|
||||
import json
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
sio = socketio.AsyncServer(
|
||||
cors_allowed_origins="*",
|
||||
async_mode="aiohttp",
|
||||
)
|
||||
|
||||
# sio.instrument(
|
||||
# auth={
|
||||
# "username": "admin",
|
||||
# "password": "admin",
|
||||
# }
|
||||
# )
|
||||
|
||||
app = web.Application()
|
||||
sio.attach(app)
|
||||
|
||||
|
||||
# represents a caching for blender messages
|
||||
blender_messages = {}
|
||||
|
||||
|
||||
# Web namespace
|
||||
class WebNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Web client connected: {sid}")
|
||||
if blender_messages:
|
||||
await self.send_cached_messages(sid)
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Web client disconnected: {sid}")
|
||||
|
||||
async def on_web_operator(self, sid, data):
|
||||
await sio.emit(
|
||||
"web_operator",
|
||||
data,
|
||||
namespace="/blender",
|
||||
room=data["blenderId"],
|
||||
)
|
||||
|
||||
async def send_cached_messages(self, sid):
|
||||
# Send cached messages to the connected web client
|
||||
for blenderId, messages in blender_messages.items():
|
||||
if "csv_data" in messages:
|
||||
await self.emit("csv_data", {"blenderId": blenderId, "data": messages["csv_data"]}, room=sid)
|
||||
if "gantt_data" in messages:
|
||||
await self.emit("gantt_data", {"blenderId": blenderId, "data": messages["gantt_data"]}, room=sid)
|
||||
|
||||
|
||||
# Blender namespace
|
||||
class BlenderNamespace(socketio.AsyncNamespace):
|
||||
def __init__(self, namespace):
|
||||
super().__init__(namespace)
|
||||
|
||||
async def on_connect(self, sid, environ):
|
||||
print(f"Blender client connected: {sid}")
|
||||
# Notify web client about new connection
|
||||
blender_messages[sid] = {}
|
||||
await sio.emit("blender_connect", sid, namespace="/web")
|
||||
|
||||
async def on_disconnect(self, sid):
|
||||
print(f"Blender client disconnected: {sid}")
|
||||
# Remove client message and notify web client about disconnection
|
||||
if sid in blender_messages:
|
||||
del blender_messages[sid]
|
||||
await sio.emit("blender_disconnect", sid, namespace="/web")
|
||||
|
||||
async def on_data(self, sid, data):
|
||||
print(f"Data from Blender client {sid}")
|
||||
# blender_messages[sid]["default_data"] = data
|
||||
await sio.emit("default_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_csv_data(self, sid, data):
|
||||
print(f"CSV data from Blender client {sid}")
|
||||
# Store the message and forward it to the web client
|
||||
blender_messages[sid]["csv_data"] = data
|
||||
await sio.emit("csv_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
async def on_gantt_data(self, sid, data):
|
||||
print(f"Gant Chart data from Blender client {sid}")
|
||||
blender_messages[sid]["gantt_data"] = data
|
||||
await sio.emit("gantt_data", {"blenderId": sid, "data": data}, namespace="/web")
|
||||
|
||||
|
||||
# Attach namespaces
|
||||
sio.register_namespace(WebNamespace("/web"))
|
||||
sio.register_namespace(BlenderNamespace("/blender"))
|
||||
|
||||
|
||||
# Define a route to render the index.html template
|
||||
async def index(request):
|
||||
with open("templates/index.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def gantt(request):
|
||||
with open("templates/gantt.html", "r") as f:
|
||||
template = f.read()
|
||||
html_content = pystache.render(template, {"port": sio_port})
|
||||
return web.Response(text=html_content, content_type="text/html")
|
||||
|
||||
|
||||
async def open_web_browser(app):
|
||||
webbrowser.open(f"http://127.0.0.1:{sio_port}/")
|
||||
|
||||
|
||||
app.router.add_get("/", index)
|
||||
app.router.add_get("/gantt", gantt)
|
||||
app.router.add_static("/jsgantt/", path="../gantt", name="jsgantt")
|
||||
app.router.add_static("/static/", path="./static", name="static")
|
||||
app.on_startup.append(open_web_browser)
|
||||
|
||||
|
||||
def main():
|
||||
global sio_port
|
||||
|
||||
parser = argparse.ArgumentParser(description="SocketIO server")
|
||||
parser.add_argument("--host", type=str, default="127.0.0.1", help="Host to run the server on")
|
||||
parser.add_argument("--port", type=int, default=8080, help="Port to run the server on")
|
||||
|
||||
args = parser.parse_args()
|
||||
sio_port = args.port
|
||||
web.run_app(app, host=args.host, port=sio_port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
# web.run_app(app, host="127.0.0.1", port=sio_port)
|
||||
@@ -1,319 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-small: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
--folder-collapse-font-size: 0.75rem;
|
||||
--folder-collapse-font-family: Courier, "Courier New", monospace;
|
||||
--box-shadow: 0 0 0.7rem #5f5f5f66;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--primary-text-color: #e0e0e0;
|
||||
--secondary-text-color: #c7c7c7;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
--border-color: #464444;
|
||||
--hover-bg-color: #3a3a3a;
|
||||
--highlight-color: #009136;
|
||||
--task-complete-bg-color: #777777;
|
||||
--milestone-border-color: #ffffff;
|
||||
--group-item-bg-color: #4b4b4b;
|
||||
--input-bg-color: #3b3b3b;
|
||||
--input-border-color: #000;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--primary-text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--primary-text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table-description {
|
||||
margin-bottom: var(--margin-medium);
|
||||
width: 50%;
|
||||
background-color: transparent;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border-color: var(--border-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: var(--box-shadow);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table-description tr {
|
||||
display: table-row;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.gantt-info {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin: 0.5rem;
|
||||
font-size: var(--font-size-small);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------ Overwriting JSGantt CSS rules ------------ */
|
||||
:root.dark div.gantt {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt table,
|
||||
:root.dark .gantt td {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Headings and cell defaults */
|
||||
:root.dark .gmajorheading,
|
||||
:root.dark .gminorheading,
|
||||
:root.dark .gminorheadingwkend,
|
||||
:root.dark .gtaskcell,
|
||||
:root.dark .gtaskcellcurrent,
|
||||
:root.dark .gtaskcellwkend,
|
||||
:root.dark .gname,
|
||||
:root.dark .ggroupitem,
|
||||
:root.dark .gtaskheading {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskheading,
|
||||
:root.dark .gname,
|
||||
:root.dark .gtaskname,
|
||||
:root.dark .gres,
|
||||
:root.dark .gdur,
|
||||
:root.dark .gcomp,
|
||||
:root.dark .gstartdate,
|
||||
:root.dark .gplanstartdate,
|
||||
:root.dark .gplanenddate,
|
||||
:root.dark .gcost,
|
||||
:root.dark .gchartlbl,
|
||||
:root.dark .gcontainercol,
|
||||
:root.dark .genddate {
|
||||
color: var(--secondary-text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname>div {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskbarcontainer.gplan,
|
||||
:root.dark .gchartlbl.gcontainercol {
|
||||
background: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gtaskname div,
|
||||
:root.dark .gtaskheading div,
|
||||
:root.dark .gtaskname span {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
:root.dark .gtasklist,
|
||||
:root.dark .gadditional {
|
||||
border: var(--border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark .gchartgrid {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
:root.dark .glistgrid,
|
||||
:root.dark .glistlbl {
|
||||
background-color: var(--bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* .gTaskInfo {
|
||||
background-color: #2e2e2e;
|
||||
color: #c7c7c7;
|
||||
border-color: #c7c7c7;;
|
||||
} */
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
:root.dark .frame::-webkit-scrollbar-thumb,
|
||||
:root.dark .frame::-webkit-scrollbar-track {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Highlight row */
|
||||
:root.dark .gitemhighlight td {
|
||||
background-color: var(--highlight-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Differentiate Group, Milestone and Ordinary task items (applied to row) */
|
||||
:root.dark .ggroupitem {
|
||||
background-color: var(--bg-color);
|
||||
font-weight: bold;
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark .gmileitem,
|
||||
:root.dark .glineitem {
|
||||
background-color: var(--bg-color);
|
||||
}
|
||||
|
||||
/* Task bar caption text styles */
|
||||
:root.dark .gmilecaption,
|
||||
:root.dark .ggroupcaption,
|
||||
:root.dark .gcaption {
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
/* Task complete %age bar */
|
||||
:root.dark .gtaskcomplete {
|
||||
background-color: var(--task-complete-bg-color);
|
||||
}
|
||||
|
||||
/* Milestones */
|
||||
:root.dark .gmdtop,
|
||||
:root.dark .gmdbottom {
|
||||
border-bottom: 5px solid var(--milestone-border-color);
|
||||
}
|
||||
|
||||
:root.dark .gfoldercollapse {
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
font-size: var(--folder-collapse-font-size);
|
||||
font-family: var(--folder-collapse-font-family);
|
||||
}
|
||||
|
||||
/* Highlight for collapsible row */
|
||||
:root.dark .gname.ggroupitem {
|
||||
background-color: var(--group-item-bg-color);
|
||||
}
|
||||
|
||||
/* Form label and selected highlighting */
|
||||
:root.dark .gformlabel {
|
||||
background-color: var(--formlabel-bg-color);
|
||||
color: var(--secondary-text-color);
|
||||
border: var(--formlabel-border-color) 1px solid;
|
||||
}
|
||||
|
||||
:root.dark span.gformlabel:hover {
|
||||
background-color: var(--hover-bg-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
:root.dark span.gselected {
|
||||
background-color: var(--formlabel-selected-bg-color);
|
||||
border-color: var(--task-complete-bg-color);
|
||||
color: var(--nav-link-color);
|
||||
}
|
||||
|
||||
:root.dark .gantt-inputtable {
|
||||
background-color: var(--input-bg-color);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--input-border-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--margin-small: 0.625rem;
|
||||
--margin-medium: 1.25rem;
|
||||
--margin-large: 2.5rem;
|
||||
--padding-small: 0.125rem;
|
||||
--padding-medium: 1rem;
|
||||
--font-size-large: 1.2rem;
|
||||
--logo-height: 2.5rem;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
--bg-color: #252525;
|
||||
--text-color: #e0e0e0;
|
||||
--nav-bg-color: #121212;
|
||||
--nav-border-color: #25682a;
|
||||
--nav-link-color: #fff;
|
||||
--nav-link-hover-color: #3fb449;
|
||||
--warning-color: #FFDB8F;
|
||||
}
|
||||
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #000000;
|
||||
--nav-bg-color: #f8f8f8;
|
||||
--nav-border-color: #cccccc;
|
||||
--nav-link-color: #000000;
|
||||
--nav-link-hover-color: #38a63d;
|
||||
--warning-color: #FF4500;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: var(--base-font-size);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
#container {
|
||||
margin-top: var(--margin-medium);
|
||||
margin-left: var(--margin-small);
|
||||
margin-right: var(--margin-small);
|
||||
margin-bottom: var(--margin-medium);
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-bottom: var(--margin-large);
|
||||
}
|
||||
|
||||
.csv-table {
|
||||
margin-top: var(--margin-small);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: var(--nav-bg-color);
|
||||
padding: var(--padding-medium) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-bottom: 2px solid var(--nav-border-color);
|
||||
}
|
||||
|
||||
nav .logo {
|
||||
margin-left: var(--margin-large);
|
||||
height: var(--logo-height);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
margin-left: 0.0625rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-right: 3.125rem;
|
||||
}
|
||||
|
||||
nav ul li a {
|
||||
text-decoration: none;
|
||||
color: var(--nav-link-color);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
nav ul li a:hover,
|
||||
nav ul li a.active {
|
||||
color: var(--nav-link-hover-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: var(--margin-small);
|
||||
padding: var(--padding-small);
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-theme {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-large);
|
||||
margin-right: var(--margin-medium);
|
||||
}
|
||||
|
||||
#toggle-theme:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* ------------ Overwriting Tabulator CSS rules ------------ */
|
||||
|
||||
:root.light .tabulator-header,
|
||||
:root.light .tabulator .tabulator-header .tabulator-col {
|
||||
background: var(--nav-bg-color) !important;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
:root.light .tabulator {
|
||||
border-top: 1px solid #222;
|
||||
border-bottom: 2px solid #222;
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// shown:bool, workSchedule: {}, ganttTasks: {},
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
// print options
|
||||
const pageSizes = [
|
||||
{ value: "210,297", text: "A4 Portrait" },
|
||||
{ value: "297,210", text: "A4 Landscape" },
|
||||
{ value: "297,420", text: "A3 Portrait" },
|
||||
{ value: "420,297", text: "A3 Landscape" },
|
||||
{ value: "420,594", text: "A2 Portrait" },
|
||||
{ value: "594,420", text: "A2 Landscape" },
|
||||
{ value: "594,841", text: "A1 Portrait" },
|
||||
{ value: "841,594", text: "A1 Landscape" },
|
||||
{ value: "841,1189", text: "A0 Portrait" },
|
||||
{ value: "1189,841", text: "A0 Landscape" },
|
||||
];
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
var theme = localStorage.getItem("theme") || systemTheme;
|
||||
setTheme(theme);
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("gantt_data", handleGanttData);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: false,
|
||||
workSchedule: {},
|
||||
ganttTasks: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeGanttElement(blenderId);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'gantt_data' event
|
||||
function handleGanttData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
|
||||
console.log(data);
|
||||
|
||||
const filename = data["data"]["ifc_file"];
|
||||
const ganttTasks = data["data"]["gantt_data"]["tasks"];
|
||||
const ganttWorkSched = data["data"]["gantt_data"]["work_schedule"];
|
||||
// const ganttWorkSched = {};
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
} else {
|
||||
updateGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
connectedClients[blenderId].workSchedule = ganttWorkSched;
|
||||
connectedClients[blenderId].ganttTasks = ganttTasks;
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ganttTasks: ganttTasks,
|
||||
workSchedule: ganttWorkSched,
|
||||
};
|
||||
addGanttElement(blenderId, ganttTasks, ganttWorkSched, filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDefaultData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const isDirty = data["data"]["is_dirty"];
|
||||
showWarning(blenderId, isDirty);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
// Function to add a new gantt with data and filename
|
||||
function addGanttElement(blenderId, tasks, workSched, filename) {
|
||||
const ganttContainer = $("<div></div>")
|
||||
.addClass("gantt-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
|
||||
const ganttTitle = $("<h3></h3>")
|
||||
.attr("id", "title-" + blenderId)
|
||||
.text(filename)
|
||||
.addClass("no-print")
|
||||
.css("margin-bottom", "10px");
|
||||
|
||||
const warning = $("<div></div>")
|
||||
.attr("id", "warning-" + blenderId)
|
||||
.html(
|
||||
"⚠ Warning: This Gantt Chart may contain outdated data due to recent changes in Blender."
|
||||
)
|
||||
.addClass("warning no-print");
|
||||
|
||||
const workSchedDiv = $("<div></div>").attr("id", "workSched" + blenderId);
|
||||
|
||||
const scheduleTable = $("<table></table>")
|
||||
.addClass("no-print table-description")
|
||||
.attr("id", "workSchedTable-" + blenderId)
|
||||
.hide();
|
||||
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(scheduleTable);
|
||||
});
|
||||
|
||||
const toggleButton = $("<button></button>")
|
||||
.text("Show Schedule Info")
|
||||
.addClass("btn no-print")
|
||||
.on("click", function () {
|
||||
scheduleTable.toggle();
|
||||
const buttonText = scheduleTable.is(":visible")
|
||||
? "Hide Schedule Info"
|
||||
: "Show Schedule Info";
|
||||
toggleButton.text(buttonText);
|
||||
});
|
||||
|
||||
var ganttInfoDiv = $("<div></div>")
|
||||
.addClass("gantt-info")
|
||||
.attr("id", "gantt-info-" + blenderId);
|
||||
|
||||
const scheduleName = $("<span></span>").text("Schedule: " + workSched.Name);
|
||||
|
||||
const createdOn = $("<span></span>")
|
||||
.text("Created: " + new Date(workSched.CreationDate).toLocaleDateString())
|
||||
.css("float", "right");
|
||||
|
||||
const ganttDiv = $("<div></div>")
|
||||
.addClass("gantt-chart")
|
||||
.attr("id", "gantt-" + blenderId);
|
||||
|
||||
ganttInfoDiv.append(scheduleName);
|
||||
ganttInfoDiv.append(createdOn);
|
||||
|
||||
workSchedDiv.append(toggleButton);
|
||||
workSchedDiv.append(scheduleTable);
|
||||
|
||||
ganttContainer.append(ganttTitle);
|
||||
ganttContainer.append(warning);
|
||||
ganttContainer.append(workSchedDiv);
|
||||
ganttContainer.append(ganttInfoDiv);
|
||||
ganttContainer.append(ganttDiv);
|
||||
|
||||
$("#container").append(ganttContainer);
|
||||
|
||||
let g = new JSGantt.GanttChart($("#gantt-" + blenderId)[0], "week");
|
||||
g.setOptions({
|
||||
vCaptionType: "Caption", // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: "day dd month yyyy", // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: "mon yyyy - Week ww", // Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: "dd mon", // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: "en",
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ["Day", "Week", "Month", "Quarter"], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vShowRes: true, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {
|
||||
ifcduration: { title: "Duration" },
|
||||
resourceUsage: { title: "Resource Usage" },
|
||||
},
|
||||
vUseToolTip: true, // Disable tooltips.
|
||||
vTooltipTemplate: generateTooltip,
|
||||
vTotalHeight: 900,
|
||||
|
||||
vEventsChange: {
|
||||
taskname: editValue, // if you need to use the this scope, do: editValue.bind(this)
|
||||
res: editValue,
|
||||
dur: editValue,
|
||||
comp: editValue,
|
||||
start: editValue,
|
||||
end: editValue,
|
||||
planstart: editValue,
|
||||
planend: editValue,
|
||||
cost: editValue,
|
||||
additional_category: editValue,
|
||||
},
|
||||
});
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.setEditable(true);
|
||||
g.Draw();
|
||||
|
||||
connectedClients[blenderId]["gantt"] = g;
|
||||
|
||||
let printButton = $("<button>", {
|
||||
id: "print-btn-" + blenderId,
|
||||
html: "Print",
|
||||
class: "btn no-print",
|
||||
});
|
||||
|
||||
let printOptions = $("<select>", {
|
||||
id: "print-options-" + blenderId,
|
||||
class: "no-print",
|
||||
});
|
||||
|
||||
$.each(pageSizes, function (index, size) {
|
||||
printOptions.append(
|
||||
$("<option>", {
|
||||
value: size.value,
|
||||
text: size.text,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
printButton.on("click", function () {
|
||||
// make it only the corresponding gantt chart is printed
|
||||
$(".gantt-chart").removeClass("no-print");
|
||||
$(".gantt-chart")
|
||||
.not("#gantt-" + blenderId)
|
||||
.addClass("your-css-class");
|
||||
|
||||
var values = $("#print-options-" + blenderId)
|
||||
.val()
|
||||
.split(",");
|
||||
|
||||
g.setEditable(false);
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
|
||||
addEventListener("afterprint", (event) => {
|
||||
g.setEditable(true);
|
||||
});
|
||||
|
||||
let css =
|
||||
"@media print {\n" +
|
||||
" @page {\n" +
|
||||
" size: " +
|
||||
values[0] +
|
||||
"mm " +
|
||||
values[1] +
|
||||
"mm;\n" +
|
||||
" }\n" +
|
||||
" /* Make all text black */\n" +
|
||||
" body, p, span, h1, h2, h3, h4, h5, h6, div, a, li, td, th, * {\n" +
|
||||
" color: black !important;\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
g.printChart(values[0], values[1], css);
|
||||
g.setTotalHeight(900);
|
||||
g.Draw();
|
||||
});
|
||||
|
||||
ganttContainer.append(printOptions);
|
||||
ganttContainer.append(printButton);
|
||||
}
|
||||
|
||||
// Function to update gantt and filename
|
||||
function updateGanttElement(blenderId, tasks, workSched, filename) {
|
||||
// update work schedule table
|
||||
const table = $("#workSchedTable-" + blenderId);
|
||||
table.empty();
|
||||
$.each(workSched, (key, value) => {
|
||||
value = value ? value : "null";
|
||||
$("<tr></tr>")
|
||||
.append($("<td></td>").text(key))
|
||||
.append($("<td></td>").text(value))
|
||||
.appendTo(table);
|
||||
});
|
||||
|
||||
// update gantt chart with new data
|
||||
let g = connectedClients[blenderId]["gantt"];
|
||||
g.ClearTasks();
|
||||
g.Draw();
|
||||
JSGantt.addJSONTask(g, tasks);
|
||||
g.Draw();
|
||||
|
||||
$("#title-" + blenderId).text(filename);
|
||||
$("#warning-" + blenderId).css("display", "none");
|
||||
}
|
||||
|
||||
// Function to remove gantt element
|
||||
function removeGanttElement(blenderId) {
|
||||
$("#container-" + blenderId).remove();
|
||||
}
|
||||
|
||||
function showWarning(blenderId, isDirty) {
|
||||
$("#warning-" + blenderId).css("display", "block");
|
||||
}
|
||||
|
||||
// Utility function to create a tooltip for the gantt chars
|
||||
function generateTooltip(task) {
|
||||
var dataObject = task.getDataObject();
|
||||
var numberResources = dataObject.resourceUsage
|
||||
? dataObject.resourceUsage
|
||||
: "NULL";
|
||||
return `
|
||||
<dl>
|
||||
<dt>Name:</dt><dd>{{pName}}</dd>
|
||||
<dt>Start:</dt><dd>{{pStart}}</dd>
|
||||
<dt>End:</dt><dd>{{pEnd}}</dd>
|
||||
<dt>Duration:</dt><dd>${dataObject.ifcduration}</dd>
|
||||
<dt>Number of Resources:</dt><dd>${numberResources}</dd>
|
||||
<dt>Resources:</dt><dd>{{pRes}}</dd>
|
||||
</dl>
|
||||
`;
|
||||
}
|
||||
|
||||
// Event handlers for editing gantt table data
|
||||
function editValue(list, task, event, cell, column) {
|
||||
console.log("editValue function called with the following parameters:");
|
||||
console.log("list:", list);
|
||||
console.log("task:", task);
|
||||
console.log("event:", event);
|
||||
console.log("cell:", cell);
|
||||
console.log("column:", column);
|
||||
|
||||
const ganttId = task.getGantt()["vDiv"].id;
|
||||
const index = ganttId.indexOf("-") + 1;
|
||||
const blenderId = ganttId.substring(index);
|
||||
const workSchedId = connectedClients[blenderId].workSchedule.id;
|
||||
|
||||
// update data object reprsenting the task
|
||||
const dataObj = task.getDataObject();
|
||||
dataObj[column] = event.target.value;
|
||||
task.setDataObject(dataObj);
|
||||
|
||||
const msg = {
|
||||
sourcePage: "gantt",
|
||||
blenderId: blenderId,
|
||||
operator: {
|
||||
type: "editTask",
|
||||
workScheduleId: workSchedId,
|
||||
taskId: task.getOriginalID(),
|
||||
column: column,
|
||||
value: event.target.value,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === "light") {
|
||||
$("html").removeClass("dark").addClass("light");
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else {
|
||||
$("html").removeClass("light").addClass("dark");
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("dark")) {
|
||||
setTheme("light");
|
||||
} else {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
// keeps track of blenders connected in form of
|
||||
// {shown:bool, filename:string, headers:array}
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
// Document ready function
|
||||
$(document).ready(function () {
|
||||
var systemTheme = window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
? "light"
|
||||
: "dark";
|
||||
var theme = localStorage.getItem("theme") || systemTheme;
|
||||
setTheme(theme);
|
||||
|
||||
connectSocket();
|
||||
});
|
||||
|
||||
// Function to connect to Socket.IO server
|
||||
function connectSocket() {
|
||||
const url = "ws://localhost:" + SOCKET_PORT + "/web";
|
||||
socket = io(url);
|
||||
console.log("socket: ", socket);
|
||||
|
||||
// Register socket event handlers
|
||||
socket.on("blender_connect", handleBlenderConnect);
|
||||
socket.on("blender_disconnect", handleBlenderDisconnect);
|
||||
socket.on("csv_data", handleCsvData);
|
||||
socket.on("default_data", handleDefaultData);
|
||||
}
|
||||
|
||||
// Function to handle 'blender_connect' event
|
||||
function handleBlenderConnect(blenderId) {
|
||||
console.log("blender_connect: ", blenderId);
|
||||
if (!connectedClients.hasOwnProperty(blenderId)) {
|
||||
connectedClients[blenderId] = { shown: false, ifc_file: "" };
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'blender_disconnect' event
|
||||
function handleBlenderDisconnect(blenderId) {
|
||||
console.log("blender_disconnect: ", blenderId);
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
delete connectedClients[blenderId];
|
||||
removeTableElement(blenderId);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle 'csv_data' event
|
||||
function handleCsvData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const csvData = data["data"]["csv_data"];
|
||||
const filename = data["data"]["ifc_file"];
|
||||
console.log(data);
|
||||
|
||||
if (connectedClients.hasOwnProperty(blenderId)) {
|
||||
if (!connectedClients[blenderId].shown) {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
} else {
|
||||
updateTableElement(blenderId, csvData, filename);
|
||||
}
|
||||
} else {
|
||||
connectedClients[blenderId] = {
|
||||
shown: true,
|
||||
ifc_file: filename,
|
||||
headers: [],
|
||||
};
|
||||
addTableElement(blenderId, csvData, filename);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDefaultData(data) {
|
||||
const blenderId = data["blenderId"];
|
||||
const isDirty = data["data"]["is_dirty"];
|
||||
showWarning(blenderId, isDirty);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
// Function to add a new table with data and filename
|
||||
function addTableElement(blenderId, csvData, filename) {
|
||||
// store headers of the csv data
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const csvHeaders = csvData.substring(0, firstLine).split(",");
|
||||
connectedClients[blenderId].headers = csvHeaders;
|
||||
|
||||
console.log(connectedClients[blenderId]);
|
||||
const tableContainer = $("<div></div>")
|
||||
.addClass("table-container")
|
||||
.attr("id", "container-" + blenderId);
|
||||
|
||||
const tableTitle = $("<h3></h3>")
|
||||
.attr("id", "title-" + blenderId)
|
||||
.text(filename)
|
||||
.css("margin-bottom", "10px");
|
||||
|
||||
const warning = $("<div></div>")
|
||||
.attr("id", "warning-" + blenderId)
|
||||
.html(
|
||||
"⚠ Warning: This table may contain outdated data due to recent changes in Blender."
|
||||
)
|
||||
.addClass("warning");
|
||||
|
||||
const tableDiv = $("<div></div>")
|
||||
.addClass("csv-table")
|
||||
.attr("id", "table-" + blenderId);
|
||||
|
||||
tableContainer.append(tableTitle);
|
||||
tableContainer.append(warning);
|
||||
tableContainer.append(tableDiv);
|
||||
$("#container").append(tableContainer);
|
||||
|
||||
function createHeaderMenu(definitions) {
|
||||
var menu = [];
|
||||
|
||||
for (let column of definitions) {
|
||||
// Create text element for checkbox
|
||||
if (!column.hasOwnProperty("key")) {
|
||||
column.visible = true;
|
||||
}
|
||||
let checkbox = document.createElement("span");
|
||||
checkbox.textContent = column.visible ? "\u2611" : "\u2610"; // ☑ or ☐
|
||||
|
||||
// Build label
|
||||
let label = document.createElement("span");
|
||||
let title = document.createElement("span");
|
||||
|
||||
title.textContent = " " + column.title;
|
||||
|
||||
label.appendChild(checkbox);
|
||||
label.appendChild(title);
|
||||
|
||||
// Create menu item
|
||||
menu.push({
|
||||
label: label,
|
||||
action: function (e) {
|
||||
// Prevent menu closing
|
||||
e.stopPropagation();
|
||||
|
||||
// Toggle current column visibility
|
||||
column.visible = !column.visible;
|
||||
console.log(column.title, column.visible);
|
||||
// Change menu item checkbox
|
||||
if (column.visible) {
|
||||
table.showColumn(column.title);
|
||||
checkbox.textContent = "\u2611"; // ☑
|
||||
} else {
|
||||
table.hideColumn(column.title);
|
||||
checkbox.textContent = "\u2610"; // ☐
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
var table = new Tabulator("#table-" + blenderId, {
|
||||
height: "400px",
|
||||
resizableColumnGuide: true,
|
||||
index: "GlobalId",
|
||||
data: csvData,
|
||||
importFormat: "csv",
|
||||
autoColumns: true,
|
||||
selectableRows: 1,
|
||||
layout: "fitColumns",
|
||||
autoColumnsDefinitions: function (definitions) {
|
||||
menu = createHeaderMenu(definitions, table);
|
||||
definitions.forEach((column) => {
|
||||
column.visible = true;
|
||||
column.headerMenu = menu;
|
||||
});
|
||||
return definitions;
|
||||
},
|
||||
});
|
||||
|
||||
table.on("rowSelected", function (row) {
|
||||
var index = row.getIndex(); // the guid of the object
|
||||
|
||||
if (!index) {
|
||||
console.log("No Global ID found");
|
||||
return;
|
||||
}
|
||||
|
||||
// table id is usually "table-BlenderId" so blender id is always
|
||||
// after the first 6 characters
|
||||
var tableId = row.getTable().element.id.substr(6);
|
||||
|
||||
const msg = {
|
||||
sourcePage: "csv",
|
||||
blenderId: tableId,
|
||||
operator: {
|
||||
type: "selection",
|
||||
globalId: index,
|
||||
},
|
||||
};
|
||||
socket.emit("web_operator", msg);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update table and filename
|
||||
function updateTableElement(blenderId, csvData, filename) {
|
||||
// check if the headers are the same
|
||||
// if yes, just replace the data
|
||||
// else, replace the whole data and column definations
|
||||
const firstLine = csvData.indexOf("\n");
|
||||
const newHeaders = csvData.substring(0, firstLine).split(",");
|
||||
const sameHeaders = compareHeaders(
|
||||
connectedClients[blenderId].headers,
|
||||
newHeaders
|
||||
);
|
||||
|
||||
const table = Tabulator.findTable("#table-" + blenderId)[0];
|
||||
if (table) {
|
||||
if (sameHeaders) {
|
||||
table.replaceData(csvData, { importFormat: "csv" });
|
||||
} else {
|
||||
table.setData(csvData);
|
||||
connectedClients[blenderId].headers = newHeaders;
|
||||
}
|
||||
$("#title-" + blenderId).text(filename);
|
||||
$("#warning-" + blenderId).css("display", "none");
|
||||
}
|
||||
}
|
||||
|
||||
// Function to remove table element
|
||||
function removeTableElement(blenderId) {
|
||||
$("#container-" + blenderId).remove();
|
||||
}
|
||||
|
||||
function showWarning(blenderId, isDirty) {
|
||||
$("#warning-" + blenderId).css("display", "block");
|
||||
}
|
||||
|
||||
// Utility function to compare two csv header
|
||||
function compareHeaders(headers1, headers2) {
|
||||
if (headers1.length !== headers2.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < headers1.length; i++) {
|
||||
if (headers1[i] !== headers2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
var stylesheet = $("#tabulator-stylesheet");
|
||||
if (theme === "light") {
|
||||
stylesheet.attr(
|
||||
"href",
|
||||
"https://unpkg.com/tabulator-tables/dist/css/tabulator_site.min.css"
|
||||
);
|
||||
$("html").removeClass("dark").addClass("light");
|
||||
$("#toggle-theme").html('<i class="fas fa-sun"></i>');
|
||||
} else {
|
||||
stylesheet.attr(
|
||||
"href",
|
||||
"https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
|
||||
);
|
||||
$("html").removeClass("light").addClass("dark");
|
||||
$("#toggle-theme").html('<i class="fas fa-moon"></i>');
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
if ($("html").hasClass("dark")) {
|
||||
setTheme("light");
|
||||
} else {
|
||||
setTheme("dark");
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css" />
|
||||
<link rel="stylesheet" href="/static/css/gantt.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script type="text/javascript" src="./jsgantt/jsgantt.js"></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/gantt.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="no-print">
|
||||
<img
|
||||
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li><a href="/">IFC Data</a></li>
|
||||
<li><a href="/gantt" class="active">Gantt Chart</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,51 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web Client</title>
|
||||
<link rel="stylesheet" href="/static/css/index.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
id="tabulator-stylesheet"
|
||||
href="https://unpkg.com/tabulator-tables/dist/css/tabulator_site_dark.min.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
|
||||
></script>
|
||||
<script>
|
||||
var SOCKET_PORT = {{port}};
|
||||
</script>
|
||||
<script defer src="./static/js/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<img
|
||||
src="https://blenderbim.org/assets/images/blender/blender-logo.png"
|
||||
alt="Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<ul>
|
||||
<li><a href="/" class="active">IFC Data</a></li>
|
||||
<li><a href="/gantt">Gantt Chart</a></li>
|
||||
</ul>
|
||||
<button id="toggle-theme" onclick="toggleTheme()">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,293 +0,0 @@
|
||||
# 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 gpu
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from math import sin, cos, radians
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from typing import Union
|
||||
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
|
||||
class ProfileDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
"""Note that operators that change mesh in `exit_edit_mode_callback` can freeze blender.
|
||||
The workaround is to move their code to function and use it for callback.
|
||||
|
||||
Example: https://devtalk.blender.org/t/calling-operator-that-saves-bmesh-freezes-blender-forever/28595"""
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(
|
||||
handler, (context, get_custom_bmesh, draw_faces, exit_edit_mode_callback), "WINDOW", "POST_VIEW"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(cls.installed, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_faces(self, bm, vertices_coords):
|
||||
"""mutates original bm (triangulates it)
|
||||
so the triangulation edges will be shown too
|
||||
"""
|
||||
traingulated_bm = bm
|
||||
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
|
||||
|
||||
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
|
||||
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
error_elements_color = self.addon_prefs.decorator_color_error
|
||||
background_elements_color = self.addon_prefs.decorator_color_background
|
||||
|
||||
obj = context.active_object
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
if exit_edit_mode_callback:
|
||||
ProfileDecorator.uninstall()
|
||||
exit_edit_mode_callback()
|
||||
return
|
||||
|
||||
if get_custom_bmesh:
|
||||
bm = get_custom_bmesh()
|
||||
else:
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
### Actually drawing
|
||||
all_vertices = []
|
||||
error_vertices = []
|
||||
selected_vertices = []
|
||||
unselected_vertices = []
|
||||
# special = associated with arcs/circles
|
||||
special_vertices = []
|
||||
special_vertex_indices = {}
|
||||
selected_edges = []
|
||||
unselected_edges = []
|
||||
arc_edges = []
|
||||
roof_angle_edges = []
|
||||
preview_edges = []
|
||||
|
||||
arc_groups = []
|
||||
circle_groups = []
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
arc_groups.append(i)
|
||||
elif "IFCCIRCLE" in group.name:
|
||||
circle_groups.append(i)
|
||||
|
||||
arcs = {}
|
||||
circles = {}
|
||||
|
||||
# https://docs.blender.org/api/blender_python_api_2_63_8/bmesh.html#CustomDataAccess
|
||||
# This is how we access vertex groups via bmesh, apparently, it's not very intuitive
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
angle_layer = bm.edges.layers.float.get("BBIM_gable_roof_angles")
|
||||
preview_layer = bm.edges.layers.int.get("BBIM_preview")
|
||||
|
||||
for vertex in bm.verts:
|
||||
co = tuple(obj.matrix_world @ vertex.co)
|
||||
all_vertices.append(co)
|
||||
if vertex.hide:
|
||||
continue
|
||||
|
||||
is_arc, is_circle = False, False
|
||||
# deform_layer is None if there are no verts assigned to vertex groups
|
||||
# even if there are vertex groups in the obj.vertex_groups
|
||||
if deform_layer:
|
||||
is_arc, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, arc_groups)
|
||||
if is_arc:
|
||||
arcs.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
is_circle, group_index = tool.Blender.bmesh_check_vertex_in_groups(vertex, deform_layer, circle_groups)
|
||||
if is_circle:
|
||||
circles.setdefault(group_index, []).append(vertex)
|
||||
special_vertex_indices[vertex.index] = group_index
|
||||
|
||||
if vertex.select:
|
||||
selected_vertices.append(co)
|
||||
else:
|
||||
if len(vertex.link_edges) > 1 and is_circle:
|
||||
error_vertices.append(co)
|
||||
elif is_circle:
|
||||
special_vertices.append(co)
|
||||
elif len(vertex.link_edges) != 2:
|
||||
error_vertices.append(co)
|
||||
elif is_arc:
|
||||
special_vertices.append(co)
|
||||
else:
|
||||
unselected_vertices.append(co)
|
||||
|
||||
for edge in bm.edges:
|
||||
edge_indices = [v.index for v in edge.verts]
|
||||
if edge.hide:
|
||||
continue
|
||||
if edge.select:
|
||||
selected_edges.append(edge_indices)
|
||||
else:
|
||||
i1, i2 = edge.verts[0].index, edge.verts[1].index
|
||||
# making sure that both vertices are in the same group
|
||||
if i1 in special_vertex_indices and special_vertex_indices[i1] == special_vertex_indices.get(i2, None):
|
||||
arc_edges.append(edge_indices)
|
||||
elif angle_layer and edge[angle_layer] > 0:
|
||||
roof_angle_edges.append(edge_indices)
|
||||
elif preview_layer and edge[preview_layer] == 1:
|
||||
preview_edges.append(edge_indices)
|
||||
else:
|
||||
unselected_edges.append(edge_indices)
|
||||
|
||||
### Actually drawing
|
||||
# POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind()
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
self.shader.bind()
|
||||
|
||||
# Draw faces
|
||||
if draw_faces:
|
||||
self.draw_faces(bm, all_vertices)
|
||||
|
||||
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
|
||||
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("LINES", all_vertices, background_elements_color, arc_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
|
||||
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
|
||||
|
||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
||||
self.draw_batch("POINTS", error_vertices, error_elements_color)
|
||||
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||
|
||||
# Draw arcs
|
||||
arc_centroids = []
|
||||
arc_segments = []
|
||||
for arc in arcs.values():
|
||||
if len(arc) != 3:
|
||||
continue
|
||||
sorted_arc = [None, None, None]
|
||||
for v1 in arc:
|
||||
connections = 0
|
||||
for link_edge in v1.link_edges:
|
||||
v2 = link_edge.other_vert(v1)
|
||||
if v2 in arc:
|
||||
connections += 1
|
||||
if connections == 2: # Midpoint
|
||||
sorted_arc[1] = v1
|
||||
else:
|
||||
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
|
||||
points = [tuple(obj.matrix_world @ v.co) for v in sorted_arc]
|
||||
centroid = tool.Cad.get_center_of_arc(points)
|
||||
if centroid:
|
||||
arc_centroids.append(tuple(centroid))
|
||||
arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
|
||||
|
||||
self.draw_batch("POINTS", arc_centroids, background_elements_color)
|
||||
for verts, edges in arc_segments:
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
# Draw circles
|
||||
circle_centroids = []
|
||||
circle_segments = []
|
||||
for circle in circles.values():
|
||||
if len(circle) != 2:
|
||||
continue
|
||||
p1 = obj.matrix_world @ circle[0].co
|
||||
p2 = obj.matrix_world @ circle[1].co
|
||||
radius = (p2 - p1).length / 2
|
||||
centroid = p1.lerp(p2, 0.5)
|
||||
circle_centroids.append(tuple(centroid))
|
||||
segments = self.create_circle_segments(360, 20, radius)
|
||||
matrix = obj.matrix_world.copy()
|
||||
matrix.translation = centroid
|
||||
segments = [[list(matrix @ Vector(v)) for v in segments[0]], segments[1]]
|
||||
circle_segments.append(segments)
|
||||
|
||||
self.draw_batch("POINTS", circle_centroids, background_elements_color)
|
||||
for verts, edges in circle_segments:
|
||||
self.draw_batch("LINES", verts, special_elements_color, edges)
|
||||
|
||||
def create_matrix(self, p, x, y, z):
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
|
||||
# This function is taken from Sverchok, licensed under GPL v2-or-later.
|
||||
# This is a combination of the make_verts and make_edges function.
|
||||
def create_circle_segments(self, Angle, Vertices, Radius):
|
||||
if Angle < 360:
|
||||
theta = Angle / (Vertices - 1)
|
||||
else:
|
||||
theta = Angle / Vertices
|
||||
listVertX = []
|
||||
listVertY = []
|
||||
for i in range(Vertices):
|
||||
listVertX.append(Radius * cos(radians(theta * i)))
|
||||
listVertY.append(Radius * sin(radians(theta * i)))
|
||||
|
||||
if Angle < 360 and self.mode_ == 0:
|
||||
sigma = radians(Angle)
|
||||
listVertX[-1] = Radius * cos(sigma)
|
||||
listVertY[-1] = Radius * sin(sigma)
|
||||
elif Angle < 360 and self.mode_ == 1:
|
||||
listVertX.append(0.0)
|
||||
listVertY.append(0.0)
|
||||
|
||||
points = list((x, y, 0) for x, y in zip(listVertX, listVertY))
|
||||
|
||||
listEdg = [(i, i + 1) for i in range(Vertices - 1)]
|
||||
|
||||
if Angle < 360 and self.mode_ == 1:
|
||||
listEdg.append((0, Vertices))
|
||||
listEdg.append((Vertices - 1, Vertices))
|
||||
else:
|
||||
listEdg.append((Vertices - 1, 0))
|
||||
|
||||
return points, listEdg
|
||||
@@ -1,113 +0,0 @@
|
||||
# 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 blenderbim.tool as tool
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.aggregate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class OpenPieClass(bpy.types.Operator):
|
||||
bl_idname = "bim.open_pie_class"
|
||||
bl_label = "Open Pie Class"
|
||||
bl_description = "Assign the IFC Class to the selected objects"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object and not context.selected_objects:
|
||||
cls.poll_message_set("No object selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_bim_class")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PieAddOpening(bpy.types.Operator):
|
||||
bl_idname = "bim.pie_add_opening"
|
||||
bl_label = "Add Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
if len(context.selected_objects) == 2:
|
||||
opening_name = None
|
||||
obj_name = None
|
||||
for obj in context.selected_objects:
|
||||
if "IfcOpeningElement" in obj.name or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.name
|
||||
elif len(obj.children) == 1 and not obj.children[0].BIMObjectProperties.ifc_definition_id:
|
||||
opening_name = obj.children[0].name
|
||||
else:
|
||||
obj_name = obj.name
|
||||
bpy.ops.bim.add_opening(obj=obj_name, opening=opening_name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PieUpdateContainer(bpy.types.Operator):
|
||||
bl_idname = "bim.pie_update_container"
|
||||
bl_label = "Update Spatial Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
bl_label = "Geometry"
|
||||
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_representation").ifc_representation_class = ""
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.aggregate_assign_object", text="Assign Aggregation")
|
||||
pie.operator("bim.aggregate_unassign_object", text="Unassign Aggregation")
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
|
||||
bl_label = "Class"
|
||||
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.assign_class", text="IfcWall").ifc_class = "IfcWall"
|
||||
pie.operator("bim.assign_class", text="IfcSlab").ifc_class = "IfcSlab"
|
||||
pie.operator("bim.assign_class", text="IfcStair").ifc_class = "IfcStair"
|
||||
pie.operator("bim.assign_class", text="IfcDoor").ifc_class = "IfcDoor"
|
||||
pie.operator("bim.assign_class", text="IfcWindow").ifc_class = "IfcWindow"
|
||||
pie.operator("bim.assign_class", text="IfcColumn").ifc_class = "IfcColumn"
|
||||
pie.operator("bim.assign_class", text="IfcBeam").ifc_class = "IfcBeam"
|
||||
@@ -1,104 +0,0 @@
|
||||
# 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/>.
|
||||
|
||||
|
||||
def add_georeferencing(georeference):
|
||||
georeference.add_georeferencing()
|
||||
|
||||
|
||||
def enable_editing_georeferencing(georeference):
|
||||
georeference.import_projected_crs()
|
||||
georeference.import_coordinate_operation()
|
||||
georeference.enable_editing()
|
||||
|
||||
|
||||
def remove_georeferencing(ifc):
|
||||
ifc.run("georeference.remove_georeferencing")
|
||||
|
||||
|
||||
def disable_editing_georeferencing(georeference):
|
||||
georeference.disable_editing()
|
||||
|
||||
|
||||
def edit_georeferencing(ifc, georeference):
|
||||
ifc.run(
|
||||
"georeference.edit_georeferencing",
|
||||
projected_crs=georeference.get_projected_crs_attributes(),
|
||||
coordinate_operation=georeference.get_coordinate_operation_attributes(),
|
||||
)
|
||||
georeference.disable_editing()
|
||||
georeference.set_model_origin()
|
||||
|
||||
|
||||
def get_cursor_location(georeference):
|
||||
georeference.set_coordinates("local", georeference.get_cursor_location())
|
||||
|
||||
|
||||
def convert_local_to_global(georeference):
|
||||
coordinates = georeference.xyz2enh(georeference.get_coordinates("local"))
|
||||
georeference.set_coordinates("map", coordinates)
|
||||
georeference.set_cursor_location()
|
||||
|
||||
|
||||
def convert_global_to_local(georeference):
|
||||
coordinates = georeference.enh2xyz(georeference.get_coordinates("map"))
|
||||
georeference.set_coordinates("local", coordinates)
|
||||
georeference.set_cursor_location()
|
||||
|
||||
|
||||
def convert_angle_to_coord(georeference, type):
|
||||
vector_coordinates = georeference.angle2coords(georeference.get_angle(type), type)
|
||||
georeference.set_vector_coordinates(vector_coordinates, type)
|
||||
|
||||
|
||||
def import_plot(georeference, filepath):
|
||||
georeference.import_plot(filepath)
|
||||
|
||||
|
||||
def enable_editing_wcs(georeference):
|
||||
georeference.import_wcs()
|
||||
georeference.enable_editing_wcs()
|
||||
|
||||
|
||||
def disable_editing_wcs(georeference):
|
||||
georeference.disable_editing_wcs()
|
||||
|
||||
|
||||
def edit_wcs(ifc, georeference):
|
||||
wcs = georeference.export_wcs()
|
||||
georeference.set_wcs(wcs)
|
||||
georeference.disable_editing_wcs()
|
||||
georeference.set_model_origin()
|
||||
|
||||
|
||||
def enable_editing_true_north(georeference):
|
||||
georeference.import_true_north()
|
||||
georeference.enable_editing_true_north()
|
||||
|
||||
|
||||
def disable_editing_true_north(georeference):
|
||||
georeference.disable_editing_true_north()
|
||||
|
||||
|
||||
def edit_true_north(ifc, georeference):
|
||||
ifc.run("georeference.edit_true_north", true_north=georeference.get_true_north_attributes())
|
||||
georeference.disable_editing_true_north()
|
||||
|
||||
|
||||
def remove_true_north(ifc):
|
||||
ifc.run("georeference.edit_true_north", true_north=None)
|
||||
@@ -1,64 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 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/>.
|
||||
|
||||
from blenderbim.tool.aggregate import Aggregate
|
||||
from blenderbim.tool.blender import Blender
|
||||
from blenderbim.tool.boundary import Boundary
|
||||
from blenderbim.tool.brick import Brick
|
||||
from blenderbim.tool.bsdd import Bsdd
|
||||
from blenderbim.tool.cad import Cad
|
||||
from blenderbim.tool.clash import Clash
|
||||
from blenderbim.tool.classification import Classification
|
||||
from blenderbim.tool.collector import Collector
|
||||
from blenderbim.tool.context import Context
|
||||
from blenderbim.tool.debug import Debug
|
||||
from blenderbim.tool.demo import Demo
|
||||
from blenderbim.tool.document import Document
|
||||
from blenderbim.tool.drawing import Drawing
|
||||
from blenderbim.tool.geometry import Geometry
|
||||
from blenderbim.tool.georeference import Georeference
|
||||
from blenderbim.tool.ifc import Ifc
|
||||
from blenderbim.tool.ifcgit import IfcGit
|
||||
from blenderbim.tool.ifcgit import IfcGitRepo
|
||||
from blenderbim.tool.library import Library
|
||||
from blenderbim.tool.loader import Loader
|
||||
from blenderbim.tool.material import Material
|
||||
from blenderbim.tool.misc import Misc
|
||||
from blenderbim.tool.model import Model
|
||||
from blenderbim.tool.nest import Nest
|
||||
from blenderbim.tool.owner import Owner
|
||||
from blenderbim.tool.patch import Patch
|
||||
from blenderbim.tool.project import Project
|
||||
from blenderbim.tool.profile import Profile
|
||||
from blenderbim.tool.pset import Pset
|
||||
from blenderbim.tool.qto import Qto
|
||||
from blenderbim.tool.resource import Resource
|
||||
from blenderbim.tool.root import Root
|
||||
from blenderbim.tool.sequence import Sequence
|
||||
from blenderbim.tool.spatial import Spatial
|
||||
from blenderbim.tool.covering import Covering
|
||||
from blenderbim.tool.structural import Structural
|
||||
from blenderbim.tool.style import Style
|
||||
from blenderbim.tool.surveyor import Surveyor
|
||||
from blenderbim.tool.system import System
|
||||
from blenderbim.tool.tester import Tester
|
||||
from blenderbim.tool.type import Type
|
||||
from blenderbim.tool.unit import Unit
|
||||
from blenderbim.tool.search import Search
|
||||
from blenderbim.tool.cost import Cost
|
||||
from blenderbim.tool.web import Web
|
||||
@@ -1,55 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 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 bmesh
|
||||
import shapely
|
||||
import ifcopenshell
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
import json
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
from shapely import Polygon, MultiPolygon
|
||||
|
||||
|
||||
class Covering(blenderbim.core.tool.Covering):
|
||||
@classmethod
|
||||
def get_z_from_ceiling_height(cls):
|
||||
props = bpy.context.scene.BIMCoveringProperties
|
||||
return props.ceiling_height
|
||||
|
||||
|
||||
# def toggle_spaces_visibility_wired_and_textured(cls, spaces):
|
||||
# first_obj = tool.Ifc.get_object(spaces[0])
|
||||
# if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = True
|
||||
# bpy.data.objects[obj.name].display_type = "WIRE"
|
||||
# return
|
||||
#
|
||||
# elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
||||
# for space in spaces:
|
||||
# obj = tool.Ifc.get_object(space)
|
||||
# bpy.data.objects[obj.name].show_wire = False
|
||||
# bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||
# return
|
||||
@@ -1,401 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.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 blenderbim.bim.module.web.data import WebData
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.api.sequence
|
||||
from typing import Any, Dict, Optional
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import asyncio
|
||||
import socketio
|
||||
import threading
|
||||
import queue
|
||||
import json
|
||||
from time import sleep
|
||||
from pathlib import Path
|
||||
|
||||
sio = None
|
||||
ws_process = None
|
||||
ws_thread = None
|
||||
web_operator_queue = queue.Queue()
|
||||
|
||||
RECONNECTION_ATTEMPTS = 3
|
||||
RECONNECTION_DELAY = 2
|
||||
IFC_TASK_ATTRIBUTE_MAP = {
|
||||
"pStart": "ScheduleStart",
|
||||
"pEnd": "ScheduleFinish",
|
||||
"pName": "Name",
|
||||
}
|
||||
|
||||
|
||||
class Web(blenderbim.core.tool.Web):
|
||||
@classmethod
|
||||
def generate_port_number(cls) -> int:
|
||||
"""
|
||||
Generate a free port number.
|
||||
|
||||
This method creates a temporary socket to bind to a free port.
|
||||
It then retrieves the port number, and returns it.
|
||||
|
||||
Returns:
|
||||
int: The port number that was generated.
|
||||
"""
|
||||
print("Generating port number")
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("localhost", 0)) # Bind to a free port
|
||||
port = s.getsockname()[1] # get the bound port
|
||||
print(f"Port number: {port}")
|
||||
return port
|
||||
|
||||
@classmethod
|
||||
def is_port_available(cls, port: int) -> bool:
|
||||
"""
|
||||
Attempts to connect to the specified port on localhost.
|
||||
|
||||
If the connection is refused, the port is available for use; otherwise, it is in use.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the port is available, False if it is in use.
|
||||
"""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# connect_ex returns errno.SUCCESS (0) if the connection succeeds
|
||||
# otherwise returns errno.ECONNREFUSED (111 or 10061) if no server is listening
|
||||
return s.connect_ex(("localhost", port)) == errno.ECONNREFUSED
|
||||
|
||||
@classmethod
|
||||
def start_websocket_server(cls, port: int) -> None:
|
||||
"""
|
||||
Starts a WebSocket server on the specified port.
|
||||
|
||||
This method sets up the environment, locates paths, and starts
|
||||
the WebSocket server process. It also handles the creation and updating of a PID file to keep track
|
||||
of running server instances.
|
||||
|
||||
Args:
|
||||
- port (int): The port number on which to start the WebSocket server.
|
||||
"""
|
||||
import addon_utils
|
||||
|
||||
global ws_process
|
||||
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
ws_path = os.path.join(webui_path, "sioserver.py")
|
||||
|
||||
py_version = sys.version_info
|
||||
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
blenderbim_lib_path = (
|
||||
Path(bpy.utils.user_resource("EXTENSIONS"))
|
||||
/ ".local"
|
||||
/ "lib"
|
||||
/ f"python{py_version.major}.{py_version.minor}"
|
||||
/ "site-packages"
|
||||
)
|
||||
else:
|
||||
addon = [a for a in addon_utils.modules() if a.bl_info["name"] == "BlenderBIM"][0]
|
||||
blenderbim_path = os.path.dirname(addon.__file__)
|
||||
blenderbim_lib_path = os.path.join(blenderbim_path, "libs", "site", "packages")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["blenderbim_lib_path"] = str(blenderbim_lib_path)
|
||||
|
||||
ws_process = subprocess.Popen(
|
||||
[sys.executable, ws_path, "--p", str(port), "--host", "127.0.0.1"],
|
||||
cwd=webui_path,
|
||||
env=env,
|
||||
)
|
||||
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
else:
|
||||
pids = {}
|
||||
|
||||
pids[str(ws_process.pid)] = port
|
||||
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
cls.set_is_running(True)
|
||||
|
||||
@classmethod
|
||||
def connect_websocket_server(cls, port: int) -> None:
|
||||
"""
|
||||
Connect to a WebSocket server on the specified port.
|
||||
|
||||
This method sets up an asynchronous Socket.IO client with
|
||||
reconnection attempts, starts an asyncio thread, connects to the WebSocket server, and sets the connection status.
|
||||
|
||||
Args:
|
||||
- port (int): The port number to connect to the WebSocket server.
|
||||
"""
|
||||
global ws_thread, sio
|
||||
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
print(f"Already connected to websocket server on port: {port}")
|
||||
return
|
||||
|
||||
sio = socketio.AsyncClient(
|
||||
reconnection=True,
|
||||
reconnection_attempts=RECONNECTION_ATTEMPTS,
|
||||
reconnection_delay=RECONNECTION_DELAY,
|
||||
logger=True,
|
||||
)
|
||||
|
||||
ws_thread = AsyncioThread()
|
||||
ws_thread.daemon = True
|
||||
ws_thread.start()
|
||||
|
||||
ws_url = f"ws://localhost:{port}/blender"
|
||||
ws_thread.run_coro(cls.sio_connect(ws_url))
|
||||
cls.set_is_connected(True)
|
||||
bpy.app.timers.register(cls.check_operator_queue)
|
||||
|
||||
@classmethod
|
||||
def disconnect_websocket_server(cls) -> None:
|
||||
"""
|
||||
Disconnects the WebSocket server and stops the associated thread.
|
||||
|
||||
This method is responsible for disconnecting the WebSocket server, stopping the asyncio thread,
|
||||
and resetting the global variables related to the WebSocket connection.
|
||||
"""
|
||||
global ws_thread, sio
|
||||
ws_thread.run_coro(cls.sio_disconnect())
|
||||
ws_thread.stop()
|
||||
ws_thread = None
|
||||
sio = None
|
||||
cls.set_is_connected(False)
|
||||
|
||||
@classmethod
|
||||
def kill_websocket_server(cls) -> None:
|
||||
"""
|
||||
Terminate the currently running WebSocket server.
|
||||
|
||||
This method checks if there is an active WebSocket server process. If so, it disconnects it (if connected),
|
||||
removes its PID from the PID file, terminates the process, and updates the server's running status.
|
||||
"""
|
||||
global ws_process
|
||||
|
||||
if ws_process is None:
|
||||
print("No Websocket server running")
|
||||
return
|
||||
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
cls.disconnect_websocket_server()
|
||||
|
||||
# sleep(0.5)
|
||||
webui_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "webui")
|
||||
pid_file = os.path.join(webui_path, "running_pid.json")
|
||||
with open(pid_file, "r") as f:
|
||||
pids = json.load(f)
|
||||
|
||||
if str(ws_process.pid) in pids:
|
||||
del pids[str(ws_process.pid)]
|
||||
|
||||
# Write the updated PIDs back to the file
|
||||
with open(pid_file, "w") as f:
|
||||
json.dump(pids, f, indent=4)
|
||||
|
||||
ws_process.terminate()
|
||||
ws_process.wait()
|
||||
ws_process = None
|
||||
|
||||
cls.set_is_running(False)
|
||||
print("Websocket server terminated successfully")
|
||||
|
||||
@classmethod
|
||||
def send_webui_data(
|
||||
cls,
|
||||
data: Optional[Any] = None,
|
||||
data_key: str = "data",
|
||||
event: str = "data",
|
||||
namespace: str = "/blender",
|
||||
use_web_data: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Sends data to the Web UI via Websocket connection.
|
||||
|
||||
Args:
|
||||
- data (Optional[Any]): The data to send. If None, just sends data from WebData.
|
||||
- data_key (str): The key under which to store the data in the payload. Defaults to "data".
|
||||
- event (str): The WebSocket event to emit. Defaults to "data".
|
||||
- namespace (str): The namespace for the WebSocket event. Defaults to "/blender".
|
||||
- use_web_data (bool): Whether to use data from WebData. Defaults to True.
|
||||
"""
|
||||
|
||||
global ws_thread
|
||||
payload = {}
|
||||
|
||||
if use_web_data:
|
||||
if not WebData.is_loaded:
|
||||
WebData.load()
|
||||
payload = WebData.data
|
||||
|
||||
if data is not None:
|
||||
payload[data_key] = data
|
||||
|
||||
if ws_thread is not None and bpy.context.scene.WebProperties.is_connected:
|
||||
ws_thread.run_coro(cls.sio_send(payload, event, namespace))
|
||||
|
||||
@classmethod
|
||||
def check_operator_queue(cls) -> None | float:
|
||||
if not bpy.context.scene.WebProperties.is_connected:
|
||||
with web_operator_queue.mutex:
|
||||
web_operator_queue.queue.clear()
|
||||
return None # unregister timer if not connected
|
||||
|
||||
while not web_operator_queue.empty():
|
||||
operator = web_operator_queue.get_nowait()
|
||||
if not operator:
|
||||
continue
|
||||
if operator["sourcePage"] == "csv":
|
||||
cls.handle_csv_operator(operator["operator"])
|
||||
elif operator["sourcePage"] == "gantt":
|
||||
cls.handle_gantt_operator(operator["operator"])
|
||||
return 1.0
|
||||
|
||||
@classmethod
|
||||
def handle_csv_operator(cls, operator_data: dict) -> None:
|
||||
if operator_data["type"] == "selection":
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
guid = operator_data["globalId"]
|
||||
ele = tool.Ifc.get().by_guid(guid)
|
||||
obj = tool.Ifc.get_object(ele)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
@classmethod
|
||||
def handle_gantt_operator(cls, operator_data: dict) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
if operator_data["type"] == "editTask":
|
||||
task_id = int(operator_data["taskId"])
|
||||
task = ifc_file.by_id(task_id)
|
||||
task_time = task.TaskTime
|
||||
column = operator_data["column"]
|
||||
new_value = operator_data["value"]
|
||||
|
||||
try:
|
||||
ifcopenshell.api.sequence.edit_task(
|
||||
ifc_file, task, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
|
||||
)
|
||||
except AttributeError:
|
||||
if task_time is None:
|
||||
ifcopenshell.api.sequence.add_task_time(ifc_file, task)
|
||||
task_time = task.TaskTime
|
||||
ifcopenshell.api.sequence.edit_task_time(
|
||||
ifc_file, task_time=task_time, attributes={IFC_TASK_ATTRIBUTE_MAP[column]: str(new_value)}
|
||||
)
|
||||
|
||||
bpy.ops.bim.load_task_properties()
|
||||
|
||||
# after updating, send new gantt data to handle the case where
|
||||
# changing a task cascades and changes other tasks. as this wouldn't
|
||||
# be reflected in the web ui
|
||||
work_schedule = ifc_file.by_id(operator_data["workScheduleId"])
|
||||
task_json = tool.Sequence.create_tasks_json(work_schedule)
|
||||
gantt_data = {"tasks": task_json, "work_schedule": work_schedule.get_info(recursive=True)}
|
||||
cls.send_webui_data(data=gantt_data, data_key="gantt_data", event="gantt_data")
|
||||
|
||||
@classmethod
|
||||
def open_web_browser(cls, port: int) -> None:
|
||||
webbrowser.open(f"http://127.0.0.1:{port}/")
|
||||
|
||||
@classmethod
|
||||
async def sio_connect(cls, url: str) -> None:
|
||||
await sio.connect(url, transports=["websocket"], namespaces="/blender")
|
||||
sio.on("web_operator", cls.sio_listen_web_operator, namespace="/blender")
|
||||
|
||||
@classmethod
|
||||
async def sio_disconnect(cls) -> None:
|
||||
await sio.disconnect()
|
||||
|
||||
@classmethod
|
||||
async def sio_send(cls, data: Any, event: str = "data", namespace: str = "/blender") -> None:
|
||||
await sio.emit(event, data, namespace=namespace)
|
||||
|
||||
@classmethod
|
||||
async def sio_listen_web_operator(cls, data: dict) -> None:
|
||||
try:
|
||||
web_operator_queue.put_nowait(data)
|
||||
except queue.Full:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def set_is_running(cls, is_running: bool) -> None:
|
||||
bpy.context.scene.WebProperties.is_running = is_running
|
||||
|
||||
@classmethod
|
||||
def set_is_connected(cls, is_connected: bool) -> None:
|
||||
bpy.context.scene.WebProperties.is_connected = is_connected
|
||||
|
||||
|
||||
class AsyncioThread(threading.Thread):
|
||||
def __init__(self, *args, loop=None, **kwargs):
|
||||
"""
|
||||
Initialize an instance of AsyncioThread.
|
||||
|
||||
This class represents a thread that runs an asyncio event loop. It is used to handle asynchronous tasks
|
||||
in a separate thread from the main thread.
|
||||
|
||||
Args:
|
||||
- *args: Variable length argument list. These arguments are passed to the superclass constructor.
|
||||
- loop: An existing asyncio event loop. If None, a new event loop is created.
|
||||
- **kwargs: Arbitrary keyword arguments. These arguments are passed to the superclass constructor.
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loop = loop or asyncio.new_event_loop()
|
||||
self.running = False
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Start the asyncio event loop and mark the thread as running.
|
||||
"""
|
||||
self.running = True
|
||||
self.loop.run_forever()
|
||||
|
||||
def run_coro(self, coro) -> Any:
|
||||
"""
|
||||
Run a coroutine in the asyncio event loop from a separate thread.
|
||||
|
||||
Args:
|
||||
- coro: The coroutine to be run.
|
||||
|
||||
Returns:
|
||||
The result of the coroutine.
|
||||
"""
|
||||
return asyncio.run_coroutine_threadsafe(coro, loop=self.loop).result()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Stop the asyncio event loop and join the thread.
|
||||
"""
|
||||
self.loop.call_soon_threadsafe(self.loop.stop)
|
||||
self.join()
|
||||
self.running = False
|
||||
@@ -1,20 +0,0 @@
|
||||
Code style
|
||||
============
|
||||
|
||||
|
||||
Black code formatter
|
||||
-------------------------------
|
||||
For Python code formatting, we use `Black code formatter <https://pypi.org/project/black/>`__ with ``--line-length 120``.
|
||||
|
||||
``black`` can be installed using ``pip install black`` and files can be formatted with the following example command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
black --line-length 120 src/blenderbim/blenderbim/bim/module/qto/operator.py
|
||||
|
||||
Using PowerShell, you can run the Black formatter on the last commit in the repository.
|
||||
You can change ``~1`` to ``~n`` to affect ``n`` commits.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
git diff HEAD HEAD~1 --name-only | where {$_ -like "*.py"} | foreach-object { start $_ && black --line-length 120 $_ }
|
||||
@@ -1,29 +0,0 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
BlenderBIM is an open-source project, and its development is driven by the contributions of a dedicated community of developers,
|
||||
architects, engineers, and enthusiasts. If you're interested in contributing to the project, whether by submitting bug reports,
|
||||
suggesting new features, or contributing code, your involvement is highly encouraged and appreciated.
|
||||
|
||||
This part of the documentation covers various aspects of the BlenderBIM development process, including:
|
||||
|
||||
- :doc:`Writing User Documentation </contribute/writing_docs>`
|
||||
- :doc:`Translations and Internationalisation </contribute/translations>`
|
||||
- Contributing Code
|
||||
- :doc:`Installation and Setting up a Development Environment </devs/installation>`
|
||||
- Understanding the Project Structure and Codebase
|
||||
- :doc:`Hello, World! </devs/hello_world>`
|
||||
- :doc:`Undo System </devs/undo_system>`
|
||||
- :doc:`Code Style Guidelines and Best Practices </devs/code_style>`
|
||||
|
||||
..
|
||||
- :doc:`Submitting Pull Requests and Contributing Code </devs/contributing_code>`
|
||||
|
||||
- :doc:`Testing and Quality Assurance </devs/running_tests>`
|
||||
- :doc:`Running Tests </devs/running_tests>`
|
||||
- :doc:`System Support and Multiplatform Compatibility </devs/system_support>`
|
||||
- :doc:`User Experience and User Interface Guidelines </devs/ux_guidelines>`
|
||||
|
||||
The BlenderBIM Developer Documentation is a living resource maintained by the core development team and the open-source community. It serves as a central hub for developers who want to get involved in the project, ensuring a consistent and efficient development process.
|
||||
|
||||
By contributing to BlenderBIM, you'll not only be helping to improve and expand the capabilities of this powerful open-source BIM authoring platform but also be part of a vibrant community driving innovation in the AEC industry.
|
||||
@@ -1,194 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
There are different methods of installation, depending on your situation.
|
||||
|
||||
1. **Unstable installation** is recommended for power users helping with testing.
|
||||
2. **Bundling for Blender** is recommended for distributing the add-on.
|
||||
3. **Live development environment** is recommended for developers who are actively coding.
|
||||
4. **Packaged installation** is recommended for those who use a package manager.
|
||||
|
||||
Unstable installation
|
||||
---------------------
|
||||
|
||||
**Unstable installation** is almost the same as **Stable installation**, except
|
||||
that they are typically updated every day. Simply download a daily build from
|
||||
the `GitHub releases page
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/releases>`__, then follow the
|
||||
usual :doc:`installation instructions</users/quickstart/installation>`.
|
||||
|
||||
You will need to choose which build to download.
|
||||
|
||||
- If you are on Blender >=4.1, choose py311
|
||||
- If you are on Blender >=3.1 and <=4.0, choose py310
|
||||
- If you are on Blender >=2.93 and <3.1, choose py39
|
||||
- Choose ``linux-x64``, ``macos-x64`` (Apple Intel), ``macos-arm64`` (Apple Silicon), or
|
||||
``windows-x64`` depending on your operating system
|
||||
|
||||
For users who don't follow the `VFX Platform <https://vfxplatform.com/>`_
|
||||
standard, we also provide py312 builds.
|
||||
|
||||
Sometimes, a build may be delayed, or contain broken code. We try to avoid this,
|
||||
but it happens.
|
||||
|
||||
Bundling for Blender
|
||||
--------------------
|
||||
|
||||
Instead of waiting for an official release on the BlenderBIM Add-on website, it
|
||||
is possible to make your own Blender add-on from the bleeding edge source code
|
||||
of BlenderBIM. BlenderBIM is coded in Python and doesn't require any
|
||||
compilation, so this is a relatively easy process.
|
||||
|
||||
Note that the BlenderBIM Add-on does depend on IfcOpenShell, and IfcOpenShell
|
||||
does require compilation. The following instructions will use a pre-built
|
||||
IfcOpenShell (using an IfcOpenBot build) for convenience. Instructions on how to
|
||||
compile IfcOpenShell is out of scope of this document.
|
||||
|
||||
You can create your own package by using the Makefile as shown below. You can
|
||||
choose between a ``PLATFORM`` of ``linux``, ``macos``, ``macosm1``, and ``win``.
|
||||
You can choose between a ``PYVERSION`` of ``py311``, ``py310``, or ``py39``.
|
||||
.. code-block:: bash
|
||||
|
||||
cd src/blenderbim
|
||||
make dist PLATFORM=linux PYVERSION=py311
|
||||
ls dist/
|
||||
|
||||
This will give you a fully packaged Blender add-on zip that you can distribute
|
||||
and install.
|
||||
|
||||
Live development environment
|
||||
----------------------------
|
||||
|
||||
One option for developers who want to actively develop from source is to follow
|
||||
the instructions from **Bundling for Blender**. However, creating a build,
|
||||
uninstalling the old add-on, and installing a new build is a slow process.
|
||||
Although it works, it is very slow, so we do not recommend it.
|
||||
|
||||
A more rapid approach is to follow the **Unstable installation** method, as this
|
||||
provides all dependencies for you out of the box.
|
||||
|
||||
Once you've done this, you can replace certain Python files that tend to be
|
||||
updated frequently with those from the Git repository. We're going to use
|
||||
symlinks (Windows users can use ``mklink``), so we can code in our Git
|
||||
repository, and see the changes in our Blender installation (you will need to
|
||||
restart Blender to see changes).
|
||||
|
||||
For Linux or Mac:
|
||||
|
||||
.. literalinclude:: ../../scripts/installation/dev_environment.sh
|
||||
:language: bash
|
||||
:caption: dev_environment.sh
|
||||
|
||||
Or, if you're on Windows, you can use the batch script below. You need to run
|
||||
it as an administrator. Before running it follow the instructions descibed
|
||||
in the `rem` tags.
|
||||
|
||||
.. literalinclude:: ../../scripts/installation/dev_environment.bat
|
||||
:language: bat
|
||||
:caption: dev_environment.bat
|
||||
|
||||
After you modify your code in the Git repository, you will need to restart
|
||||
Blender for the changes to take effect.
|
||||
|
||||
The downside with this approach is that if a new dependency is added, or a
|
||||
compiled dependency version requirement has changed, or the build system
|
||||
changes, you'll need to fix your setup manually. But this is relatively rare.
|
||||
Reviewing the Makefile history, `here <https://github.com/IfcOpenShell/IfcOpenShell/commits/v0.8.0/src/blenderbim/Makefile>`__, is one quick way to see if a dependency has changed.
|
||||
|
||||
.. seealso::
|
||||
|
||||
There is a `useful Blender Addon
|
||||
<https://blenderartists.org/uploads/short-url/yto1sjw7pqDRVNQzpVLmn51PEDN.zip>`__
|
||||
(see `forum thread
|
||||
<https://blenderartists.org/t/reboot-blender-addon/640465/13>`__) that adds
|
||||
a Reboot button in File menu. In this way, it's possible to directly
|
||||
restart Blender and test the modified source code. There is also a VS Code
|
||||
add-on called `Blender Development
|
||||
<https://marketplace.visualstudio.com/items?itemName=JacquesLucke.blender-development>`__
|
||||
that has a similar functionality.
|
||||
|
||||
|
||||
Packaged installation
|
||||
---------------------
|
||||
|
||||
- **Arch Linux**: `Direct from Git <https://aur.archlinux.org/packages/ifcopenshell-git/>`__.
|
||||
- **Chocolatey on Windows**: `Unstable <https://community.chocolatey.org/packages/blenderbim-nightly/>`__.
|
||||
|
||||
Tips for package managers
|
||||
-------------------------
|
||||
|
||||
If you are interested in packaging the BlenderBIM Add-on for a packaging
|
||||
manager, read on.
|
||||
|
||||
The BlenderBIM Add-on is fully contained in the ``blenderbim/`` subfolder of the
|
||||
Blender add-ons directory. This is typically distributed as a zipfile as per
|
||||
Blender add-on conventions. Within this folder, you'll find the following file
|
||||
structure:
|
||||
::
|
||||
|
||||
core/ (Blender agnostic core code)
|
||||
tool/ (Blender specific logic)
|
||||
bim/ (Blender specific UI)
|
||||
libs/ (dependencies)
|
||||
__init__.py
|
||||
|
||||
This corresponds to the structure found in the source code `here
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/blenderbim/blenderbim>`__.
|
||||
|
||||
The BlenderBIM Add-on is complex, and requires many dependencies, including
|
||||
Python modules, binaries, and static assets. When packaged for users, these
|
||||
dependencies are bundled with the add-on for convenience.
|
||||
|
||||
If you choose to install the BlenderBIM Add-on and use your own system
|
||||
dependencies, the source of truth for how dependencies are bundled are found in
|
||||
the `Makefile
|
||||
<https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.8.0/src/blenderbim/Makefile>`__.
|
||||
|
||||
Required Python modules to be stored in ``libs/site/packages/`` are:
|
||||
::
|
||||
|
||||
ifcopenshell
|
||||
bcf
|
||||
ifcclash
|
||||
bimtester
|
||||
ifccobie
|
||||
ifccsv
|
||||
ifcdiff
|
||||
ifc4d
|
||||
ifc5d
|
||||
ifcpatch
|
||||
ifctester
|
||||
pystache
|
||||
svgwrite
|
||||
dateutil
|
||||
isodate
|
||||
networkx
|
||||
https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
deepdiff
|
||||
jsonpickle
|
||||
ordered_set
|
||||
pyparsing
|
||||
xmlschema
|
||||
elementpath
|
||||
six
|
||||
lark-parser
|
||||
behave
|
||||
parse
|
||||
parse_type
|
||||
xlsxwriter
|
||||
odfpy
|
||||
defusedxml
|
||||
jmespath
|
||||
ifcjson
|
||||
|
||||
Notes:
|
||||
|
||||
1. ``ifcopenshell`` almost always requires the latest version due to the fast paced nature of the add-on development.
|
||||
2. ``behave`` requires `patches <https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.8.0/src/ifcbimtester/patch>`__.
|
||||
3. ``ifcjson`` can be found `here <https://github.com/IFCJSON-Team/IFC2JSON_python/tree/master/file_converters>`__.
|
||||
|
||||
Required static assets are:
|
||||
::
|
||||
|
||||
bim/data/gantt/jsgantt.js (from jsgantt-improved)
|
||||
bim/data/gantt/jsgantt.css (from jsgantt-improved)
|
||||
@@ -1,45 +0,0 @@
|
||||
System support
|
||||
==============
|
||||
|
||||
The BlenderBIM Add-on officially supports all major 64-bit platforms, as well as
|
||||
the Python version shipped by the Blender Foundation for the most recent three
|
||||
major Blender versions:
|
||||
|
||||
- 64-bit Linux on Python 3.10
|
||||
- 64-bit Linux on Python 3.11
|
||||
- 64-bit MacOS Intel on Python 3.10
|
||||
- 64-bit MacOS Intel on Python 3.11
|
||||
- 64-bit MacOS Silicon on Python 3.10
|
||||
- 64-bit MacOS Silicon on Python 3.11
|
||||
- 64-bit Windows on Python 3.10
|
||||
- 64-bit Windows on Python 3.11
|
||||
|
||||
Note that developer builds may exist for older versions of Python but there will
|
||||
be no guarantee of the uptime or stability of these builds.
|
||||
|
||||
Other system specifications match the `Blender Requirements
|
||||
<https://www.blender.org/download/requirements/>`_.
|
||||
|
||||
Add-on compatibility
|
||||
--------------------
|
||||
|
||||
The BlenderBIM Add-on is a non-trivial add-on. By turning Blender into a
|
||||
graphical front-end to a native IFC authoring platform, some fundamental Blender
|
||||
features (such as hotkeys for basic functionality like object deletion or
|
||||
duplication) have been patched and many dependencies have been introduced.
|
||||
|
||||
Other add-ons may no longer work as intended when the BlenderBIM Add-on is
|
||||
enabled, or vice versa, the BlenderBIM Add-on may no longer work as intended
|
||||
when other add-ons are enabled.
|
||||
|
||||
Known scenarios which will lead to add-on incompatibility include:
|
||||
|
||||
- The add-on also overrides the same hotkeys. For example, if an add-on
|
||||
overrides the "X" key to delete an object, you will need to manually trigger
|
||||
(either via menu or custom hotkey) the BlenderBIM Add-on equivalent operator
|
||||
(e.g. IFC Delete).
|
||||
- The add-on uses object deletion or duplication macros with dictionary
|
||||
override. Note that this is also deprecated in Blender, so the other add-on
|
||||
should be updated to fix this.
|
||||
- The add-on requires a conflicting dependency, or a conflicting version of the
|
||||
same dependency. Neither add-on may work simultaneously.
|
||||
@@ -1,178 +0,0 @@
|
||||
User Experience and User Interface guidelines
|
||||
=============================================
|
||||
|
||||
This is intended as a reference document for developers working on the BlenderBim addon.
|
||||
|
||||
Operators
|
||||
^^^^^^^^^
|
||||
|
||||
BIM will by definition contain a (big) number of submodules or subschemas. Granting access to the features of these submodules to the users needs to be considered with several things in minds :
|
||||
|
||||
1. Ease of use : How do I achieve my goal with the minimal number of clicks ? How many different menu items do I have to go through to get to my goal ? How long do I have to scroll a menu to get to the specific thing I have in mind ?
|
||||
|
||||
2. Clarity : How long do I have to visually parse the interface to find the item or button I'm looking for ? Are the buttons correctly labeled and are the icons descriptive enough ? If something is forbidden, can I easily know why ? Can I know what a button does before clicking on it ?
|
||||
|
||||
3. Completeness : I need to have access to all the items that are related to the topic I'm interested in. If it is tied to another submodule, I need to have access to it.
|
||||
|
||||
* **Blender**
|
||||
|
||||
These considerations must be adapted to the Python API, to the general paradigm we have chosen to access, modify or create IFC data, and to the general philosophy of Blender. There are many ways and places where an add-on creator can choose to present information to the user. BlenderBim currently works that way :
|
||||
|
||||
- Information about the current context is displayed inside the properties editor. We leverage the fact that it is already built around displaying different types of information whether we are in the Scene, Material, Mesh, Object, Curve, Texture, etc. subpanels. It fits really well with the way things are already organized in the IFC classification.
|
||||
|
||||
- Tools to create, modify or delete items are in the dedicated Toolbar (The left one) in the 3D viewport
|
||||
|
||||
- Some Misc tools and information are displayed in the N panel of the 3D viewport . They're shortcuts to things that are already present elsewhere in the UI, but are handy to have when modifying objects or geometry in the Viewport.
|
||||
|
||||
- Adding specific Import / Export IFC operators (arguably a bit misleading since BlenderBim does more than import / export in these operations)
|
||||
|
||||
* **Interface panels**
|
||||
|
||||
- The good : Every submodule is visually separated from each other. The user can customize its interface to only show what's relevant to them at any one point by folding in or out panels. Development / Debugging is facilitated because each submodule's interface is separated from the others.
|
||||
|
||||
- The bad : Having many interface panels clutters the interface, leading to an overwhelming experience for newcomers especially.
|
||||
|
||||
- Proposal : The addon should in the long run provide the user with dynamic UI depending on what's their usecase. Preferably accessed in the Addon Preferences interface, with pre-made options that dynamically enable or disable specific parts of the UI. It should also provide the option to selectively add or remove specific parts of the UI on top of that. Additionally, when it does not make sense to display a specific panel, it should be hidden from the UI. (eg if an object has no IFC Class, it can't be spatially contained, so the IFC Spatial Container panel should be hidden until the user sets the IFC class)
|
||||
In the long term it could also be interesting to think about some kind of tutorial mode where more information is conveyed through the interface for new users.
|
||||
|
||||
* **Item Lists**
|
||||
|
||||
An item list should provide right off the bat a few features :
|
||||
|
||||
- Add a new item.
|
||||
|
||||
- Modify an item, whether by adding a button to each item in the list, or adding a button on the list header to modify the selected item.
|
||||
|
||||
- Delete an item, whether by adding a button to each item in the list, or adding a button on the list header to delete the selected item.
|
||||
|
||||
- Modify an item's name by double-clicking on the field if applicable
|
||||
|
||||
- The currently selected item should be highlighted
|
||||
|
||||
- There should be a search field to filter through specific items in the list, with buttons to invert selection, sort alphabetically and revert display. (Features that are provided by the `UIList` class)
|
||||
|
||||
* **Operators**
|
||||
|
||||
- Some buttons should be dynamically disabled (grayed out and impossible to click on) when the context to execute them does not make sense. It is important to note there should not be any heavy computation to determine this since this is executed several times per second for each button where it is implemented. It should only test for simple things and not rely on retrieving information from other modules too much. eg : `Is an item selected ?` `Is the z position of the selected object > 0 ?` or `Is the selected object an IfcOpeningElement ?` and **not** `Is there an IfcOpeningElement in the current file ?` or `How many different classes of IfcBeam are implemented in the file ?`
|
||||
In Blender this is done in two ways :
|
||||
|
||||
- Directly in the operator `poll` method which is executed when the button is displayed in the interface. The drawback is that some operators rely on custom attributes which are provided only when the user clicks on the button, and cannot be test in the `poll` method. Note in V3.0 we can implement custom messages when the `poll` method fails, depending on the step where the context was not right.
|
||||
|
||||
- In the UI drawing code, where specific layout parts can be disabled.
|
||||
|
||||
- Some buttons or entire interface layout rows or columns may be disabled or hidden at once. It may be desirable when it relies on specific things or combinations of things not being met in the project. In order to avoid UI flicker or the user wondering where the button went, dynamically hiding UI elements should have a minimal interference with the rest of the UI by :
|
||||
|
||||
(1) Hiding the last row(s) of a panel
|
||||
|
||||
(2) Dynamically resizing the rest of the UI when it's not possible to (1)
|
||||
|
||||
The user should be advised why they are prevented to do things via either :
|
||||
|
||||
- A custom message using Blender's report system
|
||||
|
||||
- A label in the UI replacing the missing UI elements
|
||||
|
||||
- A helpful tooltip on an operator
|
||||
|
||||
Generally the context itself should be used to the best of its ability to convey why it's possible to do some things or not. The separation of concerns in multiple different dynamically hidden-or-shown sub-panels should help limit confusion.
|
||||
|
||||
- The user should be forbidden to click on buttons when its execution returns early and does nothing. If a button is clickable and the user clicks on it, there should always be some kind of feedback.
|
||||
|
||||
Icons
|
||||
^^^^^
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137270452-bf6c03ea-2d3e-4ae3-8121-6799e8beb335.png
|
||||
|
||||
**Icon name** : `"CANCEL"`
|
||||
|
||||
**Use** : To disable the edition state of a particular item.
|
||||
|
||||
**Expected result** : A part of the interface showing the item attributes is either shrunk down or hidden. The button is usually replaced by a button to delete this item.
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137271575-dcae9e56-3c64-4d8b-89b0-6cf103b999f2.png
|
||||
|
||||
**Icon name** : `"X"`
|
||||
|
||||
**Use** : To delete a particular item, for instance an element of a list.
|
||||
|
||||
**Expected result** : The interface element referencing the deleted item is hidden from the interface. If the item was in a list, and the deleted element was selected, the next element becomes selected, or if there is no next element, no element is selected.
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137271601-e79012e5-fc61-402e-830c-b34b4ca757a0.png
|
||||
|
||||
**Icon name** : `"GREASEPENCIL"`
|
||||
|
||||
**Use** : To Enable the edition of a particular item.
|
||||
|
||||
**Behaviour** : Usually only one element of a particular context can be edited at a time.
|
||||
|
||||
**Expected result** : The interface should either expand or spawn new elements to display the chosen item's editable attributes. If the editem item is part of a list and its interface elements contained an operator to delete it, it is swapped for an operator to disable the edition of that item.
|
||||
|
||||
**Proposal** : Currently all the edition buttons are hidden from the interface when editing a particular item. This results on buttons jumping to the right slightly. My proposal is to instead show but disable the other buttons to prevent interface flicker.
|
||||
|
||||
**Current** : https://user-images.githubusercontent.com/25156105/137273603-f9ac39e5-7616-4fb9-89e2-4dae453e650a.gif
|
||||
|
||||
**Proposal** : https://user-images.githubusercontent.com/25156105/137273329-532d6d59-e39d-483e-980b-b8405062eaa8.gif
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/25156105/137273981-26fa664a-a29e-402e-9d7e-67b8b71c2d5d.png
|
||||
|
||||
* **Icon name** : `"ADD"`
|
||||
|
||||
**Use** : To add an item to a particular context, either initializing it (eg. adding a coordinate system) or adding an element to a list.
|
||||
|
||||
**Expected result** : The interface should either expand to display the new item or a new line should be shown if the item is added in a list.
|
||||
|
||||
**Proposal** : In some instances adding an element in a list automatically enables the edition of this item's attributes. (eg IFC Groups) but in other instances it does not (eg IFC strucural load cases). It is arguable but I do not think the user should expect a new item to be in editable state right off the bat when they add it to a list.
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137274869-0d0b876a-545c-4a58-a386-90f85764b7cc.png
|
||||
|
||||
**Icon name** : `"RESTRICT_SELECT_OFF"`
|
||||
|
||||
**Use** : To select all the instances related to a particular item.
|
||||
|
||||
**Expected result** : All the objects relating to a particular item should be selected in the 3D viewport and/or in the outline.
|
||||
|
||||
**Proposal** : Whether or not previously selected objects should be deselected beforehand is up for debate I think, because both behaviours can be desirable. It also raises the question of which object should be set to active. (Random ? First one ? Biggest one ?...)
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137275582-98c1dfcc-fc96-41d6-80dd-fdb82796e66e.png
|
||||
|
||||
**Icon name** : `"IMPORT"`
|
||||
|
||||
**Use** : to import a particular file from the user's computer's directory to the current context
|
||||
|
||||
**Expected result** : A new file browser window should open, with sufficient information provided to the user to know which file type should be loaded in (eg adding a file extension, adding information in the filebrowser UI). When the file is loaded, the interface should expand to show the attributes or information or new context-sensitive operations provided by the imported file.
|
||||
|
||||
**Proposal** : Currently there are some instances where I don't know if the elements are supposed to require an external file, eg
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/25156105/137275887-e0f0b694-842b-422f-bc09-cdcefef64107.png
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137276333-4d083f6a-36b9-494f-b0de-ec1d321cbd3d.png
|
||||
|
||||
**Icon name** : `"EXPORT"`
|
||||
|
||||
**Use** : To export a particular file to the user's computer's directory
|
||||
|
||||
**Expected result** : A new file browser window should open, with sufficient information provided to the user to know which file type is going to be saved (eg adding a file extension, adding information in the filebrowser UI).
|
||||
|
||||
**Proposal** : When there is no actual file to be saved in the user's directory or the filepath is already set in another place in the UI :
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/25156105/137276840-3ceda0cd-87bd-45f5-8488-bfce86bf3d84.png
|
||||
|
||||
It may be better to use a save icon of some sort instead, eg
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/25156105/137276758-5d60f0ca-5a93-4af3-9c4f-30c5794e0657.png
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137277047-bd45df5b-3b47-4b2c-aa29-a340b0710345.png
|
||||
|
||||
**Icon name** : `"FILEBROWSER"`
|
||||
|
||||
**Use** : To Provide the current context with the path to a particular file or folder
|
||||
|
||||
**Expected result** : A new file browser window should open, with sufficient information provided to the user to know which file type is required (eg adding a file extension, adding information in the filebrowser UI). Upon file or folder selection, this should populate a field in the current interface context with the path to the selected file or folder, or at least let the user know that the required information is now correctly filled in.
|
||||
|
||||
* .. image:: https://user-images.githubusercontent.com/25156105/137277374-fa7ff310-0b41-49a9-98bd-7c4686da05fd.png
|
||||
|
||||
**Icon name** : `"EYEDROPPER"`
|
||||
|
||||
**Use** : To select a particular object in the interface to fill the field it is attached to
|
||||
|
||||
**Expected result** : The cursor should change to an eyedropper until the user clicks on the required item. The user should be made aware after selection if it was successful (associated field being filled) or unsuccessful (message ? or field staying empty)
|
||||
@@ -1,220 +0,0 @@
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
BlenderBIM Reference Manual
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
Welcome to the manual for `BlenderBIM <https://blenderbim.org>`__,
|
||||
the free and open source BIM add-on for Blender.
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/introduction_to_bim` (2-minute read)
|
||||
Brief overview of BIM concepts and BlenderBIM's role.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/installation` (5 to 10-minute process)
|
||||
Installation guide and system requirements.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/explore_model` (15-minute guide)
|
||||
Load and navigate an IFC model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/quickstart/create_model` (20-minute guide)
|
||||
Create a simple BIM project.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Quickstart
|
||||
:maxdepth: 1
|
||||
|
||||
users/quickstart/introduction_to_bim
|
||||
users/quickstart/installation
|
||||
users/quickstart/explore_model
|
||||
users/quickstart/create_model
|
||||
users/quickstart/next_steps
|
||||
|
||||
Sections
|
||||
========
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/modeling/interface`
|
||||
Guide to the BlenderBIM interface.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/modeling/ifc_modeling`
|
||||
IFC modeling techniques and concepts.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/git_support`
|
||||
Collaborate on projects using Git
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/drawing/index`
|
||||
2D drawing generation and annotation.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/structural_analysis/index`
|
||||
Structural analysis capabilities in BlenderBIM.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/costing_and_scheduling/index`
|
||||
Cost estimation and project scheduling features.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/advanced/index`
|
||||
Advanced topics and large-scale modeling.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/tutorials/index`
|
||||
Real-world BIM project tutorials.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/user_interface`
|
||||
Detailed UI reference.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/other_addons`
|
||||
Other complementary add-ons
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/users/troubleshooting`
|
||||
Troubleshooting when things go wrong
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Sections
|
||||
:maxdepth: 2
|
||||
|
||||
users/modeling/interface
|
||||
users/modeling/ifc_modeling
|
||||
users/git_support
|
||||
users/drawing/index
|
||||
users/structural_analysis/index
|
||||
users/costing_and_scheduling/index
|
||||
users/advanced/index
|
||||
users/tutorials/index
|
||||
users/user_interface
|
||||
users/other_addons
|
||||
users/troubleshooting
|
||||
|
||||
Get Involved
|
||||
============
|
||||
|
||||
This manual is maintained largely by volunteers.
|
||||
|
||||
Please consider joining the effort and :doc:`/contribute/writing_docs`.
|
||||
|
||||
You can also can help with :doc:`Translations and Internationalisation </contribute/translations>`
|
||||
|
||||
For users who want to contribute to the BlenderBIM codebase,
|
||||
please refer to the :doc:`Documentation for Developers </devs/getting_started>`.
|
||||
This part of the documentation covers various aspects of the BlenderBIM development process.
|
||||
|
||||
You can get in touch by `joining the chat <https://osarch.org/chat/>`__.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Get Involved
|
||||
:maxdepth: 1
|
||||
|
||||
contribute/writing_docs
|
||||
contribute/translations
|
||||
|
||||
|
||||
Developer Documentation
|
||||
=======================
|
||||
|
||||
BlenderBIM is an open-source project, and its development is driven by the contributions of a dedicated community of developers,
|
||||
architects, engineers, and enthusiasts. If you're interested in contributing to the project, whether by submitting bug reports,
|
||||
suggesting new features, or contributing code, your involvement is highly encouraged and appreciated.
|
||||
|
||||
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/getting_started`
|
||||
Introduction to contributing to BlenderBIM.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/installation`
|
||||
Setting up a development environment.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/hello_world`
|
||||
Understanding the project structure.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/undo_system`
|
||||
Working with BlenderBIM's undo system.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/code_style`
|
||||
Code style guidelines and best practices.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/running_tests`
|
||||
Testing and quality assurance.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/system_support`
|
||||
Multiplatform compatibility.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`/devs/ux_guidelines`
|
||||
User experience and interface guidelines.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Developer Documentation
|
||||
:maxdepth: 1
|
||||
|
||||
devs/getting_started
|
||||
devs/installation
|
||||
devs/hello_world
|
||||
devs/undo_system
|
||||
devs/code_style
|
||||
devs/running_tests
|
||||
devs/system_support
|
||||
devs/ux_guidelines
|
||||
@@ -1,10 +0,0 @@
|
||||
Advanced Use Cases
|
||||
==================
|
||||
|
||||
Advanced topics and large-scale modeling.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
georeferencing
|
||||
dealing_with_large_models
|
||||
@@ -1,51 +0,0 @@
|
||||
============================
|
||||
Advanded Modeling Techniques
|
||||
============================
|
||||
|
||||
Complex modeling techniques for advanced users.
|
||||
|
||||
.. note::
|
||||
This page is a stub. More detailed content will be added in future updates.
|
||||
|
||||
Custom Parametric Wall Types
|
||||
----------------------------
|
||||
|
||||
[Content about creating custom parametric wall types]
|
||||
|
||||
Material assignment
|
||||
-------------------
|
||||
|
||||
:doc:`material_assignment`
|
||||
|
||||
Modeling Furniture and Fixtures
|
||||
-------------------------------
|
||||
|
||||
[Content about modeling furniture and fixtures]
|
||||
|
||||
Structural Elements
|
||||
-------------------
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`/users/structural_analysis/index`
|
||||
|
||||
[Content about modeling structural elements]
|
||||
|
||||
Multi-Story Buildings
|
||||
---------------------
|
||||
|
||||
[Content about modeling multi-story buildings]
|
||||
|
||||
Complex Structures
|
||||
------------------
|
||||
|
||||
[Content about modeling complex structures]
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Sections
|
||||
:maxdepth: 2
|
||||
|
||||
material_assignment
|
||||
parametric_geometry
|
||||
@@ -1,44 +0,0 @@
|
||||
=========================
|
||||
Basic Modeling Techniques
|
||||
=========================
|
||||
|
||||
Fundamental techniques for IFC modeling.
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`creating_walls`
|
||||
Learn how to create and modify walls in your IFC model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`openings/index`
|
||||
Techniques for adding and customizing doors, windows and other openings.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`modeling_slabs_roofs`
|
||||
Guide to modeling slabs and different types of roofs.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`defining_rooms_spaces`
|
||||
Methods for defining and managing rooms and spaces in your model.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Basic Modeling Techniques
|
||||
:maxdepth: 1
|
||||
|
||||
creating_walls
|
||||
openings/index
|
||||
modeling_slabs_roofs
|
||||
defining_rooms_spaces
|
||||
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
Openings
|
||||
========
|
||||
|
||||
This section covers the creation and management of openings in BlenderBIM.
|
||||
Openings are crucial elements in building design, serving various purposes such as passage, ventilation, and lighting.
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`door`
|
||||
Learn how to add and customize doors in your BIM model.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`window`
|
||||
Discover the process of creating and modifying windows.
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`opening`
|
||||
Create openings without fillings for special architectural features.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
In the context of Building Information Modeling (BIM) and the Industry Foundation Classes (IFC)
|
||||
standard, openings are represented through a combination of elements:
|
||||
|
||||
1. Voids: Represented by IfcOpeningElement, these are the actual cut-outs in the wall.
|
||||
2. Fillings: These are the elements that occupy the voids, such as doors (IfcDoor) or windows (IfcWindow).
|
||||
3. Relationships: These are abstract objects that connect fillings to voids and voids to elements in which they're created.
|
||||
|
||||
BlenderBIM provides tools to create and manage these elements:
|
||||
|
||||
- Door Creation Tool: For adding doors to walls.
|
||||
- Window Creation Tool: For adding windows to walls.
|
||||
- Wall Creation Tool > Void Application: For creating openings without fillings
|
||||
(currently achieved by creating a door or window and removing it
|
||||
or by using "Add Void" feature of the Create Wall tool or any element).
|
||||
|
||||
These tools allow you to:
|
||||
|
||||
- Create openings with precise dimensions and positions.
|
||||
- Modify opening properties and geometries.
|
||||
- Manage the relationships between walls, voids, and fillings.
|
||||
|
||||
The following pages provide detailed guides on working with each type of wall opening in BlenderBIM.
|
||||
|
||||
.. container:: global-index-toc
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Wall Openings
|
||||
:maxdepth: 1
|
||||
|
||||
door
|
||||
window
|
||||
opening
|
||||
|
||||
See Also
|
||||
--------
|
||||
|
||||
- :doc:`../creating_walls`
|
||||
@@ -1,4 +0,0 @@
|
||||
Importing and Viewing IFC Models
|
||||
================================
|
||||
|
||||
[Content about importing and viewing IFC models]
|
||||
@@ -1,92 +0,0 @@
|
||||
============================
|
||||
BlenderBIM Interface Guide
|
||||
============================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The BlenderBIM interface extends Blender's powerful 3D environment with specialized tools for Building Information Modeling (BIM). This guide will help you navigate the BlenderBIM interface and understand its key components.
|
||||
|
||||
BlenderBIM Workspace
|
||||
====================
|
||||
|
||||
When you start Blender with BlenderBIM installed, you can switch to the BlenderBIM workspace
|
||||
by clicking on the BIM workspace tab at the top of the Blender window.
|
||||
|
||||
This workspace is preconfigured with the most commonly used panels and tools for BIM workflows.
|
||||
|
||||
|
||||
Key Interface Elements
|
||||
======================
|
||||
|
||||
Properties Extended
|
||||
-------------------
|
||||
|
||||
The Properties Editor is extended to provide IFC-specific properties and settings.
|
||||
|
||||
- Scene
|
||||
- Project Information
|
||||
- Tool
|
||||
- Object
|
||||
|
||||
BIM Toolbar
|
||||
-----------
|
||||
|
||||
The BIM toolbar, typically located at the left of the 3D Viewport, contains shortcuts to frequently used BIM tools:
|
||||
|
||||
- Explore tool
|
||||
- Create various building elements
|
||||
- Create annotations, measure distance and angles, calculate volumes, etc
|
||||
|
||||
|
||||
IFC Tree View
|
||||
-------------
|
||||
|
||||
Found in the Outliner, the IFC Tree View displays the hierarchical structure of your BIM model:
|
||||
|
||||
- Spatial Structure (Site, Building, Storey)
|
||||
- Building Elements
|
||||
- Types
|
||||
|
||||
Customizing the Interface
|
||||
=========================
|
||||
|
||||
BlenderBIM respects Blender's highly customizable interface. You can:
|
||||
|
||||
- Rearrange editors and panels
|
||||
- Save custom workspace layouts
|
||||
- Create custom shortcuts for BIM operations
|
||||
|
||||
Tips for Efficient Use
|
||||
======================
|
||||
|
||||
1. Familiarize yourself with IFC classes and their properties.
|
||||
2. Utilize Blender's search function (F3) to quickly access BlenderBIM tools.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
Now that you're familiar with the BlenderBIM interface, you have two main paths to continue your learning:
|
||||
|
||||
1. Proceed with IFC Modeling:
|
||||
If you're ready to start creating and working with BIM models, you can move on to:
|
||||
|
||||
- :doc:`IFC Modeling Basics </users/modeling/ifc_modeling>`
|
||||
|
||||
2. Dive deeper into the User Interface:
|
||||
If you want to explore more details about the BlenderBIM interface, you can refer to:
|
||||
|
||||
- :doc:`User Interface Reference </users/user_interface>`
|
||||
|
||||
Choose the path that best suits your current needs and learning style. You can always come back to explore the other option later.
|
||||
|
||||
Remember, as you become more comfortable with the interface and basic modeling, you can explore more advanced topics such as:
|
||||
|
||||
- :doc:`Generating Documentation </users/drawing/index>`
|
||||
- :doc:`Advanced BIM Techniques </users/advanced/index>`
|
||||
|
||||
The BlenderBIM interface is designed to integrate seamlessly with Blender while providing powerful BIM-specific functionality.
|
||||
As you progress, you'll find your BIM workflow becoming increasingly efficient and productive.
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,143 +0,0 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/videoseries?list=PLMDcOjMJYxUPHHvEHqAsOuBdSPsp6or32" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
||||
|
||||
1. **Download and install Blender**
|
||||
|
||||
Blender is a free and open-source program for 3D authoring. It works on
|
||||
Linux, Mac, and Windows. It is developed by the Blender community.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Download Blender <https://www.blender.org/download/>`__
|
||||
|
||||
.. tip::
|
||||
|
||||
No administrator rights on Windows? Choose the "Portable .zip" option when
|
||||
downloading from the Blender website.
|
||||
|
||||
2. **Download the BlenderBIM Add-on**
|
||||
|
||||
The BlenderBIM Add-on extends Blender with OpenBIM related capabilities.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Download BlenderBIM Add-on <https://blenderbim.org/download.html>`__
|
||||
|
||||
.. warning::
|
||||
|
||||
If you are not using Blender version >=4.1, please follow the **Unstable installation** instructions. :doc:`Read more </devs/installation>`
|
||||
|
||||
3. **Install the BlenderBIM Add-on**
|
||||
|
||||
Open up Blender, and click on ``Edit > Preferences``.
|
||||
|
||||
.. image:: images/install-blenderbim-1.png
|
||||
|
||||
Select the **Add-ons** tab, and press **Install...** on the top right. Navigate
|
||||
to the .zip you downloaded in Step 2, and press **Install Add-on**.
|
||||
|
||||
.. image:: images/install-blenderbim-2.png
|
||||
|
||||
.. warning::
|
||||
|
||||
You do not need to unzip the add-on file. You should install it as a zipped file.
|
||||
|
||||
You should now see **System: BlenderBIM** available in your add-ons list. Enable the add-on by pressing the checkbox.
|
||||
|
||||
.. image:: images/install-blenderbim-3.png
|
||||
|
||||
All done! Your interface will now look similar to below. If you check the ``File`` menu you should also see an option to ``Open IFC Project``.
|
||||
|
||||
.. image:: images/install-blenderbim-4.png
|
||||
|
||||
You can enable add-ons permanently by using ``Save User Settings`` from the Addons menu.
|
||||
|
||||
.. seealso::
|
||||
|
||||
If you are a poweruser, you may be interested in the **Unstable installation** to help with testing. :doc:`Read more </devs/installation>`
|
||||
|
||||
.. _where is the add-on installed:
|
||||
|
||||
Updating
|
||||
--------
|
||||
|
||||
First follow the `Uninstalling`_ section below, then install the latest version.
|
||||
|
||||
Uninstalling
|
||||
------------
|
||||
|
||||
Navigate to ``Edit > Preferences > Add-ons``. Due to a limitation in Blender,
|
||||
you have to **first disable the BlenderBIM Add-on in your Blender preferences**
|
||||
by pressing the checkbox next to the add-on, then **restart Blender**. It is
|
||||
critical to follow this sequence of disabling first, and then restarting.
|
||||
|
||||
After restarting, you can uninstall the BlenderBIM Add-on by pressing the
|
||||
``Remove`` button in the Blender preferences window.
|
||||
|
||||
Alternatively, you may uninstall manually by deleting the ``blenderbim``
|
||||
directory in :ref:`your Blender add-ons directory<where is the add-on
|
||||
installed>`.
|
||||
|
||||
.. warning::
|
||||
|
||||
It is important to follow the sequence of disabling, restarting, then removing.
|
||||
If you do not restart Blender, the add-on will fail to remove correctly, and you
|
||||
will need to uninstall manually.
|
||||
|
||||
|
||||
.. _installation_location:
|
||||
|
||||
Where is the add-on installed?
|
||||
------------------------------
|
||||
|
||||
Upon installation, the BlenderBIM Add-on is stored in the
|
||||
``scripts/addons/blenderbim/`` directory, within your Blender configuration
|
||||
folder. However, the location of your Blender configuration folder depends on
|
||||
how you have installed Blender.
|
||||
|
||||
If you downloaded Blender as a ``.zip`` file without running an installer, the
|
||||
BlenderBIM Add-on will be installed in the following directory, where ``X.XX``
|
||||
is the Blender version:
|
||||
|
||||
::
|
||||
|
||||
/path/to/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
Otherwise, if you installed Blender using an installation package, the Blender
|
||||
configuration folder depends on which operating system you use.
|
||||
|
||||
On Linux, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
~/.config/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Linux, if you are deploying the add-on system-wide (this may also depend on
|
||||
your Linux distribution):
|
||||
|
||||
::
|
||||
|
||||
/usr/share/blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are installing the add-on as a user:
|
||||
|
||||
::
|
||||
|
||||
/Users/{YOUR_USER}/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Mac, if you are deploying the add-on system-wide:
|
||||
|
||||
::
|
||||
|
||||
/Library/Application Support/Blender/X.XX/scripts/addons/blenderbim/
|
||||
|
||||
On Windows:
|
||||
|
||||
::
|
||||
|
||||
C:\Users\{YOUR_USER}\AppData\Roaming\Blender Foundation\X.XX\scripts\addons\blenderbim\
|
||||
@@ -1,98 +0,0 @@
|
||||
Introduction to BIM
|
||||
===================
|
||||
|
||||
**Building Information Modeling**, or **BIM**, is a way of digitally describing
|
||||
our built environment to computers. Aspects of our built environment that can be
|
||||
described are:
|
||||
|
||||
- **Products**, like walls, doors, and windows
|
||||
- **Processes**, like construction or maintenance tasks, and procedures
|
||||
- **Resources**, like labour, materials, and equipment
|
||||
- **Controls**, like permits, orders, costs, or calendar availability
|
||||
- **Actors**, like occupants, clients, architects, and liable parties
|
||||
- **Groups**, like systems, inventories, or zones
|
||||
|
||||
These objects may have lots of data and relationships. Examples of data might be
|
||||
classification systems, physical materials, associated documents, simulation
|
||||
results, and construction types. The data may be relevant to multiple
|
||||
disciplines, such as architecture, engineering, and construction.
|
||||
|
||||
.. note::
|
||||
|
||||
BIM data is very different from a regular 3D model. In fact, geometry is
|
||||
optional, and most data is non-geometric. This means that it is not simply a
|
||||
3D format that you can import or export from and expect meaningful results.
|
||||
|
||||
**Industry Foundation Classes**, or **IFC**, is an international standard for
|
||||
**BIM**. **IFC** is the most well-established open digital language for our
|
||||
built environment. Most software will be able to describe their **BIM** data
|
||||
using **IFC**. Most commonly, **IFC** models will be shared as a ``.ifc`` file.
|
||||
|
||||
For example, **IFC** will define a wall as an object that can have a name,
|
||||
construction type, and quantities. **IFC** will also describe that a wall that
|
||||
be associated with a location, like a building storey, or have an associated
|
||||
cost item in a schedule.
|
||||
|
||||
When you use the BlenderBIM Add-on, you will be able to view and create **BIM**
|
||||
objects and relationships using the **IFC** standard.
|
||||
|
||||
Things you can do
|
||||
-----------------
|
||||
|
||||
The BlenderBIM Add-on is designed to be a comprehensive and truly native
|
||||
BIM authoring platform. Its capabilities include a wide range of tasks
|
||||
and workflows typically found across various BIM and CAD software, costing
|
||||
programs, scheduling tools, and simulation applications. While not
|
||||
an exhaustive list, some of the key things you can do with BlenderBIM include:
|
||||
|
||||
- View and explore IFC models, including spaces, properties, and relationships
|
||||
- Edit and extract attributes, properties, and metadata directly from IFC data
|
||||
- Move, rotate, and modify the geometry of objects while preserving IFC semantics
|
||||
- Create new objects using predefined library elements or custom parametric types
|
||||
- Manage classification systems, document references, and link to external libraries
|
||||
- Generate 2D drawing views like plans, sections, and elevations with customizable annotations
|
||||
- Investigate and edit structural analysis models with support for various steel profiles
|
||||
- Model and manage complex distribution systems like HVAC, plumbing, and electrical (MEP)
|
||||
- Create construction schedules, perform critical path analysis, and generate sequence animations
|
||||
- Derive quantities from model elements and create cost schedules with formulas
|
||||
- Perform clash detection and coordinate models, managing issues across disciplines
|
||||
- Integrate non-geometric data like costing, scheduling, and asset management
|
||||
|
||||
|
||||
Roadmap and Upcoming Features
|
||||
-----------------------------
|
||||
|
||||
While BlenderBIM already offers a comprehensive set of BIM authoring capabilities,
|
||||
the development team and community are continuously working to expand its functionality and improve existing workflows.
|
||||
Some of the most ambitious features and enhancements on the roadmap include:
|
||||
|
||||
Usability and Workflow Enhancements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Improve user-friendliness to equip average "Joe" with capabilities to model simple projects like single family homes, making BlenderBIM a viable alternative to SketchUp.
|
||||
- Make BlenderBIM more approachable for users across different skill levels, from advanced BIM experts to architects working on smaller residential projects.
|
||||
|
||||
Improved Drawing and Documentation Workflows
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Continued enhancements to the drawing generation capabilities, including better dimensioning tools and support for associative dimensions linked to model geometry.
|
||||
- Advanced annotation tools with customizable tags, callouts, and the ability to define reusable annotation styles and templates.
|
||||
- Streamlined sheet layout management and improved integration with external tools like Inkscape for creating title blocks and sheet compositions.
|
||||
- Continued improvement of BlenderBIM's ability to generate comprehensive documentation sets, including drawing annotations, door schedules, wall schedules, and material tagging.
|
||||
|
||||
Expanded Parametric Modeling Capabilities
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Native modeling tools for complex building elements like curtain wall systems and facade panels.
|
||||
- Expanded libraries with support for country-specific and manufacturer-provided object types.
|
||||
- Enhanced support for multi-story modeling, enabling efficient duplication and coordination of building elements across various levels.
|
||||
- Expanded parametric relationships and automating more common BIM tasks to reduce manual effort and enhance productivity.
|
||||
|
||||
Enhanced Coordination and Collaboration Features
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Robust model merge and coordination workflows, with detailed clash detection capabilities across different disciplines and data sources.
|
||||
- Built-in support for version control and model sharing using Git repositories, enabling better team collaboration and change tracking.
|
||||
|
||||
|
||||
The development roadmap is continuously updated based on user feedback, industry requirements,
|
||||
and contributions from the open-source community. By embracing an open and collaborative approach,
|
||||
BlenderBIM aims to push the boundaries of what's possible in BIM authoring,
|
||||
ensuring that it remains at the forefront of innovation in the AEC industry.
|
||||
@@ -1,93 +0,0 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
The BlenderBIM Add-on is alpha software. There are many bugs! When something
|
||||
goes wrong, you may see some computer code flash up on your screen. You may
|
||||
also see an error message:
|
||||
|
||||
.. image:: images/error-message.png
|
||||
|
||||
**Don't panic!** Click on the button that says **Copy Error Message To
|
||||
Clipboard**. You will need to paste this text in a bug report.
|
||||
|
||||
If you do not have a GitHub account, you will need to sign up to report a bug.
|
||||
In addition to pasting the error message text, please also describe what you
|
||||
were doing, and attach your IFC file or screenshots if relevant.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Report a bug <https://github.com/IfcOpenShell/IfcOpenShell/issues/new>`__
|
||||
|
||||
If your issue is particularly complex, you can also chat live with developers
|
||||
or other powerusers.
|
||||
|
||||
.. container:: blockbutton
|
||||
|
||||
`Chat live with a developer <https://osarch.org/chat>`_
|
||||
|
||||
Installation issues
|
||||
-------------------
|
||||
|
||||
If you are unable to install the BlenderBIM Add-on, make sure you are using
|
||||
**Blender 4.1** installed from https://blender.org/ and are installing the
|
||||
latest version from https://blenderbim.org.
|
||||
|
||||
Other common solutions are listed below. If none of these fix the problem, you
|
||||
can `report a bug <https://github.com/ifcopenshell/ifcopenshell/issues>`_ or
|
||||
`live chat with a developer <https://osarch.org/chat/>`_.
|
||||
|
||||
1. **Some other error prevents me from installing or doing basic functions with
|
||||
the add-on. Is it specific to my environment?**
|
||||
|
||||
Try installing and using the BlenderBIM Add-on on a "clean environment". A
|
||||
clean environment is a fresh Blender installation with no other add-ons
|
||||
enabled with factory settings.
|
||||
|
||||
To quickly test in a clean environment, first :ref:`find your Blender
|
||||
configuration folder<installation_location>`.
|
||||
Rename the folder from ``X.XX`` to something else like ``X.XX_backup``, then
|
||||
restart Blender and try follow the :doc:`installation
|
||||
instructions</users/quickstart/installation>` again.
|
||||
|
||||
If this fixes your issue, consider disabling other add-ons one by one until
|
||||
you find a conflict as a next step to isolating the issue.
|
||||
|
||||
2. **I get an error similar to "ImportError: IfcOpenShell not built for 'linux/64bit/python3.10'"**
|
||||
|
||||
If you are using a Mac, be sure to use the Mac Silicon version if you have a
|
||||
newer Mac. The only exception is if you have installed Blender using Steam
|
||||
on a Mac, in which case you need to use the Mac Intel download.
|
||||
|
||||
For all other scenarios, check the BlenderBIM Add-on zip file which you
|
||||
downloaded. The zip will have either ``py39``, ``py310``, or ``py311`` in
|
||||
the name. See the instructions in the :ref:`devs/installation:unstable
|
||||
installation` section to check that you have installed the correct version.
|
||||
|
||||
3. **I am on Ubuntu and get an error similar to "ImportError:
|
||||
/lib/x86_64-linux-gnu/libm.so.6: version GLIBC_2.29 not found"**
|
||||
|
||||
Our latest package which uses IfcOpenShell v0.8.0 is built using Ubuntu 20 LTS.
|
||||
If you have an older Ubuntu version, you can either upgrade to 19.10 or above,
|
||||
or you'll need to compile IfcOpenShell yourself.
|
||||
|
||||
4. **I get an error saying "ModuleNotFoundError: No module named 'numpy'"**"
|
||||
|
||||
If you have installed Blender from another source instead of from
|
||||
`Blender.org <https://www.blender.org/download/>`__, such as from your
|
||||
distro's package repositories, then you may be missing some modules like
|
||||
``numpy``. Try installing it manually like ``apt install python-numpy``.
|
||||
|
||||
Common issues
|
||||
-------------
|
||||
|
||||
1. **I get an error similar to RuntimeError: Instance #1234 not found**
|
||||
|
||||
Blender saves and loads projects to a ``.blend`` file. However. the
|
||||
BlenderBIM Add-on works with native IFC, and this means instead of saving
|
||||
and loading ``.blend`` files, you should instead save and load the ``.ifc``
|
||||
project.
|
||||
|
||||
If you have opened a ``.blend`` file, there is a risk that the contents of
|
||||
the ``.blend`` session do not correlate to the contents of the ``.ifc``,
|
||||
which can cause this error. Unless you are an advanced user, only save and
|
||||
load ``.ifc`` files.
|
||||
@@ -1,98 +0,0 @@
|
||||
=======================
|
||||
Tutorials
|
||||
=======================
|
||||
|
||||
Project Series
|
||||
==============
|
||||
|
||||
Step-by-step guides for real-world BIM projects.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:hidden:
|
||||
|
||||
project0
|
||||
project1
|
||||
project2
|
||||
project3
|
||||
project4
|
||||
project5
|
||||
|
||||
.. only:: builder_html and (not singlehtml)
|
||||
|
||||
.. container:: toc-cards
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 0: Designing a Room <project0>`
|
||||
* Basic walls and openings
|
||||
* Slabs
|
||||
* MEP
|
||||
* Basic furniture
|
||||
* Basic floor plan drawings
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 1: Designing a Flat <project1>`
|
||||
* Advanced walls and openings
|
||||
* Profiles
|
||||
* Rooms and spaces
|
||||
* Furniture/fixture libraries
|
||||
* Elevation/section drawings
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 2: Building a Bungalow <project2>`
|
||||
* Roof
|
||||
* Advanced foundation
|
||||
* Structural elements
|
||||
* Roof openings
|
||||
* Underground services
|
||||
* Running services in subfloor space
|
||||
* Drawing details
|
||||
* Basic costing and scheduling
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 3: Multi-Storey Single-Family Home <project3>`
|
||||
* Templating
|
||||
* Types
|
||||
* Complex roofs
|
||||
* Complex fixtures (e.g. solar panels)
|
||||
* Complex HVAC, plumbing, electrical
|
||||
* Basic site landscaping
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 4: Terraced/Town Houses/Duplexes <project4>`
|
||||
* Shared walls
|
||||
* Shared roofs
|
||||
* Complex landscaping
|
||||
* Duplicating units
|
||||
* Basic parametric floor plans
|
||||
* Generating drawings parametrically
|
||||
* Clash detection
|
||||
* Coordination with other stakeholders
|
||||
|
||||
.. container:: card
|
||||
|
||||
:doc:`Project 5: Apartment Block <project5>`
|
||||
* Multiple levels
|
||||
* Typical floor
|
||||
* Shared amenities and common areas
|
||||
* Elevators
|
||||
* Parametric services
|
||||
* Complex structural elements
|
||||
* Advanced exporting
|
||||
* Merging
|
||||
* Coordination
|
||||
* Analysis
|
||||
* Advanced costing and scheduling
|
||||
* External tools
|
||||
|
||||
Quickstart Video Tutorial
|
||||
=========================
|
||||
|
||||
Learn how to model a small building and create simple architectural plans, sections, 2D details, and sheet layouts in this short tutorial series.
|
||||
|
||||
`View all tutorial videos <https://www.youtube.com/playlist?list=PLMDcOjMJYxUPHHvEHqAsOuBdSPsp6or32>`__
|
||||
@@ -1,23 +0,0 @@
|
||||
User Interface Reference
|
||||
========================
|
||||
|
||||
This section covers the general user interface of BlenderBIM. User interface documentation does not cover any specific use case but provides a general reference of BlenderBIM UI.
|
||||
|
||||
The documentation doesn't cover Blender UI, but makes references to `Blender documentation <https://docs.blender.org/manual/en/latest/interface/index.html>`__ where appropriate.
|
||||
|
||||
.. * Navigating the Project Browser and Outliner
|
||||
.. * Using the Property Panels
|
||||
.. * Toolbars and Menus
|
||||
.. * Customizing the Interface
|
||||
.. * Keyboard Shortcuts and Preferences
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
user_interface/topbar
|
||||
user_interface/workspace
|
||||
user_interface/toolbar/index
|
||||
user_interface/property-editor
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 513 KiB |
@@ -1,60 +0,0 @@
|
||||
Property Editor Scene Properties
|
||||
================================
|
||||
|
||||
.. container:: location-scene
|
||||
|
||||
|location| Scene Properties
|
||||
|
||||
.. |location| image:: /images/location-scene.svg
|
||||
|
||||
BlenderBIM adds new functionality to the `Property Editor` -> `Scene` tab.
|
||||
|
||||
.. figure:: images/interface_property-editor_project-overview_start-up.png
|
||||
:alt: Property editor on Blender start-up
|
||||
|
||||
The property editor on Blender startup.
|
||||
|
||||
Most of these sub-tabs become available with a created or loaded IFC file.
|
||||
Don't worry, the default Blender scene properties are still reachable under their own dedicated sub-tab.
|
||||
|
||||
.. figure:: images/interface_property-editor_icons.png
|
||||
:alt: Overview over the added property sub-tabs by BlenderBIM
|
||||
|
||||
Overview over the added property sub-tabs by BlenderBIM.
|
||||
|
||||
1. Project Overview
|
||||
2. Object Information
|
||||
3. Geometry and Materials
|
||||
4. Drawings and Documents
|
||||
5. Services and Systems
|
||||
6. Structural Analyses
|
||||
7. Costing and Scheduling
|
||||
8. Facility Management
|
||||
9. Quality and Coordination
|
||||
10. Blender Properties
|
||||
11. Switch Tab
|
||||
|
||||
You can also select the needed panel via the drop-down menue.
|
||||
|
||||
.. figure:: images/interface_property-editor_panel-dropdown.png
|
||||
:alt: BlenderBIM property editor sub-tabs drop-down menue
|
||||
|
||||
Switching between BlenderBIM property editor sub-tabs via the drop-down menue.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Contents:
|
||||
|
||||
property_editor/scene_editor/project_overview/index
|
||||
property_editor/scene_editor/object_information/index
|
||||
property_editor/scene_editor/geometry_and_materials/index
|
||||
property_editor/scene_editor/drawings_and_documents/index
|
||||
property_editor/scene_editor/services_and_systems/index
|
||||
property_editor/scene_editor/structural_analysis/index
|
||||
property_editor/scene_editor/costing_and_scheduling/index
|
||||
property_editor/scene_editor/facility_management/index
|
||||
property_editor/scene_editor/quality_and_coordination/index
|
||||
property_editor/scene_editor/blender
|
||||
property_editor/scene_editor/switch_tab
|
||||
@@ -1,44 +0,0 @@
|
||||
Topbar
|
||||
======
|
||||
|
||||
For general informations about the Blender Topbar see the respective
|
||||
`Workspaces <https://docs.blender.org/manual/en/latest/interface/window_system/topbar.html>`__ section in Blender documentation.
|
||||
|
||||
Menus
|
||||
-----
|
||||
|
||||
The `File` menu in the Topbar adds menu entries to Create/Open and Save an IFC project directly.
|
||||
|
||||
.. figure:: images/interface_window_file-menue.png
|
||||
:alt: Additional file menu options related to IFC projects
|
||||
|
||||
Additional file menu options related to IFC projects.
|
||||
|
||||
New IFC Project
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The New IFC Project submenu offers several options for creating a new IFC project:
|
||||
|
||||
.. figure:: images/file_new-ifc-project-submenu.png
|
||||
:alt: New IFC Project submenu options
|
||||
|
||||
New IFC Project submenu options
|
||||
|
||||
- New Metric (m) Project: Creates a new IFC project using meters as the base unit.
|
||||
- New Metric (mm) Project: Creates a new IFC project using millimeters as the base unit.
|
||||
- New Imperial (ft) Project: Creates a new IFC project using feet as the base unit.
|
||||
- New Demo Project: Creates a new IFC project with pre-populated demo content.
|
||||
- New Project Wizard: Creates a default Blender project (including the default cube)
|
||||
and opens the Scene Properties > Project Overview > Project Info tab.
|
||||
This allows users to customize their project settings before creation.
|
||||
Clicking the "Create Project" button in this panel will delete the default cube and set up the new IFC project with the specified settings.
|
||||
|
||||
These options provide flexibility in setting up new IFC projects,
|
||||
allowing users to choose between quick starts with predefined units
|
||||
or a more customizable approach through the Project Wizard.
|
||||
|
||||
|
||||
BIM Workspace
|
||||
-------------
|
||||
|
||||
BlenderBIM adds the **BIM** Workspace to the Topbar Workspaces tabs.
|
||||
@@ -1,26 +0,0 @@
|
||||
BIM Workspace
|
||||
=============
|
||||
|
||||
For general informations about the Blender Workspaces see the respective
|
||||
`Workspaces <https://docs.blender.org/manual/en/latest/interface/window_system/workspaces.html>`__ section in Blender documentation.
|
||||
|
||||
After installation of BlenderBIM the default Blender start-up screen looks different from the standard Blender interface.
|
||||
|
||||
BlenderBIM adds the **BIM** Workspace to the interface and by default activates this workspace on start-up.
|
||||
It also modifies the layout of views and sidebars and adds custom functionality to the toolbar.
|
||||
The left **Outliner** panel shows a tree of geometric objects. The centre main **Viewport** panel shows 3D geometry.
|
||||
The right **Properties** panel shows data and relationships.
|
||||
|
||||
.. figure:: images/interface_window_start-up.png
|
||||
:alt: Blender start-up window with installed BlenderBIM add-on
|
||||
|
||||
Blender start-up window with installed BlenderBIM add-on.
|
||||
|
||||
1. **BIM** Workspace tab
|
||||
2. Outliner with the document tree
|
||||
3. Main 3D viewport
|
||||
4. Property editor
|
||||
5. Toolbar with added BIM functionality
|
||||
6. Sidebar with BlenderBIM & 4D/5D Toolkit panels
|
||||
7. Status bar with information about the currently used Blender and BlenderBIM version
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
@echo off
|
||||
|
||||
echo SETUP BLENDERBIM ADD-ON LIVE DEVELOPMENT ENVIRONMENT
|
||||
echo Update BLENDER, BLENDER_VERSION, PYTHON_VERSION, BBIM_REPO, BLENDER_USER_NAME in the script bellow.
|
||||
echo Put the script at the root of your IfcOpenShell git repository
|
||||
echo This script needs to be run as administrator (to create symbolic links)
|
||||
echo Make sure you have followed these steps before proceeding :)
|
||||
|
||||
pause
|
||||
|
||||
SET BLENDER_VERSION=4.2
|
||||
SET PYTHON_VERSION=3.11
|
||||
SET BBIM_REPO=user_default
|
||||
rem Can be useful e.g. if you're running script from administrator account
|
||||
rem and need to setup dev environment for some other user.
|
||||
SET BLENDER_USER_NAME=%USERNAME%
|
||||
|
||||
SET BLENDER=%HOMEDRIVE%\Users\%BLENDER_USER_NAME%\AppData\Roaming\Blender Foundation\Blender\%BLENDER_VERSION%
|
||||
SET BLENDER_LOCAL=%BLENDER%\extensions\.local\lib\python%PYTHON_VERSION%\site-packages
|
||||
SET BLENDERBIM=%BLENDER_LOCAL%\blenderbim
|
||||
SET BLENDER_EXT=%BLENDER%\extensions\%BBIM_REPO%\blenderbim
|
||||
|
||||
echo Removing the Blender add-on Python code...
|
||||
del "%BLENDER_EXT%\__init__.py"
|
||||
rd /S /Q "%BLENDERBIM%"
|
||||
|
||||
echo Replacing them with links to the Git repository...
|
||||
mklink "%BLENDER_EXT%\__init__.py" "%CD%\src\blenderbim\blenderbim\__init__.py"
|
||||
mklink /D "%BLENDERBIM%" "%CD%\src\blenderbim\blenderbim"
|
||||
|
||||
echo Copy over compiled IfcOpenShell files...
|
||||
copy "%BLENDER_LOCAL%\ifcopenshell\*_wrapper*" "%CD%\src\ifcopenshell-python\ifcopenshell\"
|
||||
|
||||
echo Remove the IfcOpenShell dependency...
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifcopenshell"
|
||||
|
||||
echo Replace them with links to the Git repository...
|
||||
mklink /D "%BLENDER_LOCAL%\ifcopenshell" "%CD%\src\ifcopenshell-python\ifcopenshell"
|
||||
|
||||
echo Remove and link other IfcOpenShell utilities...
|
||||
del "%BLENDER_LOCAL%\ifccsv.py"
|
||||
del "%BLENDER_LOCAL%\ifcdiff.py"
|
||||
del "%BLENDER_LOCAL%\bsdd.py"
|
||||
rd /S /Q "%BLENDER_LOCAL%\bcf"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifc4d"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifc5d"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifccityjson"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifcclash"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifcpatch"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifctester"
|
||||
rd /S /Q "%BLENDER_LOCAL%\ifcfm"
|
||||
|
||||
mklink "%BLENDER_LOCAL%\ifccsv.py" "%CD%\src\ifccsv\ifccsv.py"
|
||||
mklink "%BLENDER_LOCAL%\ifcdiff.py" "%CD%\src\ifcdiff\ifcdiff.py"
|
||||
mklink "%BLENDER_LOCAL%\bsdd.py" "%CD%\src\bsdd\bsdd.py"
|
||||
mklink /D "%BLENDER_LOCAL%\bcf" "%CD%\src\bcf\bcf"
|
||||
mklink /D "%BLENDER_LOCAL%\ifc4d" "%CD%\src\ifc4d\ifc4d"
|
||||
mklink /D "%BLENDER_LOCAL%\ifc5d" "%CD%\src\ifc5d\ifc5d"
|
||||
mklink /D "%BLENDER_LOCAL%\ifccityjson" "%CD%\src\ifccityjson\ifccityjson"
|
||||
mklink /D "%BLENDER_LOCAL%\ifcclash" "%CD%\src\ifcclash\ifcclash"
|
||||
mklink /D "%BLENDER_LOCAL%\ifcpatch" "%CD%\src\ifcpatch\ifcpatch"
|
||||
mklink /D "%BLENDER_LOCAL%\ifctester" "%CD%\src\ifctester\ifctester"
|
||||
mklink /D "%BLENDER_LOCAL%\ifcfm" "%CD%\src\ifcfm\ifcfm"
|
||||
|
||||
echo Manually downloading some third party dependencies...
|
||||
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js -o "%BLENDERBIM%\bim\data\gantt\jsgantt.js"
|
||||
curl https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css -o "%BLENDERBIM%\bim\data\gantt\jsgantt.css"
|
||||
curl -L https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl -o "%BLENDERBIM%\bim\schema\Brick.ttl"
|
||||
|
||||
pause
|
||||
@@ -1,60 +0,0 @@
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git
|
||||
cd IfcOpenShell
|
||||
|
||||
# path to BlenderBIM addon
|
||||
# default path on Mac: "/Users/$USER/Library/Application Support/Blender/X.X/scripts/addons/blenderbim"
|
||||
# default path on Linux: "$HOME/.config/blender/X.X/"
|
||||
BLENDER_ADDON_PATH="/path/to/blender/X.XX/scripts/addons/blenderbim"
|
||||
|
||||
# Remove the Blender add-on Python code
|
||||
rm -r $BLENDER_ADDON_PATH/core/
|
||||
rm -r $BLENDER_ADDON_PATH/tool/
|
||||
rm -r $BLENDER_ADDON_PATH/bim/
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
ln -s $PWD/src/blenderbim/blenderbim/core $BLENDER_ADDON_PATH/core
|
||||
ln -s $PWD/src/blenderbim/blenderbim/tool $BLENDER_ADDON_PATH/tool
|
||||
ln -s $PWD/src/blenderbim/blenderbim/bim $BLENDER_ADDON_PATH/bim
|
||||
|
||||
# Copy over compiled IfcOpenShell files
|
||||
cp $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell/*_wrapper* $PWD/src/ifcopenshell-python/ifcopenshell/
|
||||
|
||||
# Remove the IfcOpenShell dependency
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
|
||||
|
||||
# Replace them with links to the Git repository
|
||||
ln -s $PWD/src/ifcopenshell-python/ifcopenshell $BLENDER_ADDON_PATH/libs/site/packages/ifcopenshell
|
||||
|
||||
# Remove and link other IfcOpenShell utilities
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/bcf
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifctester
|
||||
rm -r $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
|
||||
rm -r $BLENDER_ADDON_PATH/libs/Desktop
|
||||
|
||||
ln -s $PWD/src/ifccsv/ifccsv.py $BLENDER_ADDON_PATH/libs/site/packages/ifccsv.py
|
||||
ln -s $PWD/src/ifcdiff/ifcdiff.py $BLENDER_ADDON_PATH/libs/site/packages/ifcdiff.py
|
||||
ln -s $PWD/src/bsdd/bsdd.py $BLENDER_ADDON_PATH/libs/site/packages/bsdd.py
|
||||
ln -s $PWD/src/bcf/bcf $BLENDER_ADDON_PATH/libs/site/packages/bcf
|
||||
ln -s $PWD/src/ifc4d/ifc4d $BLENDER_ADDON_PATH/libs/site/packages/ifc4d
|
||||
ln -s $PWD/src/ifc5d/ifc5d $BLENDER_ADDON_PATH/libs/site/packages/ifc5d
|
||||
ln -s $PWD/src/ifccityjson/ifccityjson $BLENDER_ADDON_PATH/libs/site/packages/ifccityjson
|
||||
ln -s $PWD/src/ifcclash/ifcclash $BLENDER_ADDON_PATH/libs/site/packages/ifcclash
|
||||
ln -s $PWD/src/ifcpatch/ifcpatch $BLENDER_ADDON_PATH/libs/site/packages/ifcpatch
|
||||
ln -s $PWD/src/ifctester/ifctester $BLENDER_ADDON_PATH/libs/site/packages/ifctester
|
||||
ln -s $PWD/src/ifcfm/ifcfm $BLENDER_ADDON_PATH/libs/site/packages/ifcfm
|
||||
ln -s $PWD/src/blenderbim/blenderbim/libs/desktop $BLENDER_ADDON_PATH/libs/Desktop
|
||||
|
||||
# Manually download some third party dependencies
|
||||
cd $BLENDER_ADDON_PATH/bim/data/gantt
|
||||
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
cd $BLENDER_ADDON_PATH/bim/schema
|
||||
wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
@@ -1,122 +0,0 @@
|
||||
@aggregate
|
||||
Feature: Aggregate
|
||||
Covers element and spatial aggregation
|
||||
|
||||
Scenario: Enable editing aggregate
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
When I press "bim.enable_editing_aggregate"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable editing aggregate
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And I press "bim.enable_editing_aggregate"
|
||||
When I press "bim.disable_editing_aggregate"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Assign object
|
||||
Given an empty IFC project
|
||||
And the object "IfcSite/My Site" is selected
|
||||
And the object "IfcBuildingStorey/My Storey" is selected
|
||||
And I press "bim.enable_editing_aggregate"
|
||||
And the variable "relating_object" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
When I press "bim.aggregate_assign_object(relating_object={relating_object})"
|
||||
Then the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
|
||||
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcSite/My Site"
|
||||
|
||||
Scenario: Unassign object
|
||||
Given an empty IFC project
|
||||
And the object "IfcBuildingStorey/My Storey" is selected
|
||||
And I press "bim.enable_editing_aggregate"
|
||||
And the variable "relating_object" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
|
||||
And I press "bim.aggregate_assign_object(relating_object={relating_object})"
|
||||
And the object "IfcBuildingStorey/My Storey" is selected
|
||||
When I press "bim.aggregate_unassign_object"
|
||||
Then the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
|
||||
And the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the collection "IfcBuildingStorey/My Storey" is in the collection "IfcProject/My Project"
|
||||
|
||||
Scenario: Unassign object - multiple objects are contained again to their indirect container
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcMember"
|
||||
And I press "bim.assign_class"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcCovering"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
And I press "bim.add_aggregate(ifc_class='IfcWall')"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
When I press "bim.aggregate_unassign_object"
|
||||
Then the object "IfcMember/Cube" is in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcCovering/Cube" is in the collection "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate - with the aggregate inheriting the existing spatial collection
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the object "IfcElementAssembly/Assembly" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate - add a nested aggregate
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is placed in the collection "IfcBuildingStorey/My Storey"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
When I press "bim.add_aggregate"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.add_aggregate"
|
||||
Then the object "IfcWall/Cube" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the object "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly.001"
|
||||
And the collection "IfcElementAssembly/Assembly.001" is in the collection "IfcElementAssembly/Assembly"
|
||||
And the collection "IfcElementAssembly/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
|
||||
Scenario: Add aggregate - add multiple elements to a custom aggregate class
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcMember"
|
||||
And I press "bim.assign_class"
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcCovering"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcMember/Cube" is selected
|
||||
And additionally the object "IfcCovering/Cube" is selected
|
||||
When I press "bim.add_aggregate(ifc_class='IfcWall')"
|
||||
Then the object "IfcMember/Cube" is in the collection "IfcWall/Assembly"
|
||||
And the object "IfcCovering/Cube" is in the collection "IfcWall/Assembly"
|
||||
And the object "IfcWall/Assembly" is in the collection "IfcWall/Assembly"
|
||||
And the collection "IfcWall/Assembly" is in the collection "IfcBuildingStorey/My Storey"
|
||||
@@ -1,9 +0,0 @@
|
||||
@bimtester
|
||||
Feature: Bimtester
|
||||
|
||||
Scenario: Execute Bimtester
|
||||
Given an empty IFC project
|
||||
When I set "scene.BimTesterProperties.should_load_from_memory" to "True"
|
||||
And I set "scene.BimTesterProperties.feature" to "{cwd}/test/files/sample.feature"
|
||||
And I press "bim.execute_bim_tester"
|
||||
Then nothing happens
|
||||
@@ -1,15 +0,0 @@
|
||||
@covering
|
||||
Feature: Covering
|
||||
Covers covering tool.
|
||||
|
||||
Scenario: Execute generate flooring coverings from walls
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWall/Wall" is selected
|
||||
When I press "bim.add_instance_flooring_coverings_from_walls"
|
||||
Then nothing happens
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
@georeference
|
||||
Feature: Georeference
|
||||
|
||||
Scenario: Add georeferencing
|
||||
Given an empty IFC project
|
||||
When I press "bim.add_georeferencing"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Enable editing georeferencing
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
When I press "bim.enable_editing_georeferencing"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Remove georeferencing
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
When I press "bim.remove_georeferencing"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Edit georeferencing
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
And I press "bim.enable_editing_georeferencing"
|
||||
When I press "bim.edit_georeferencing"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable editing georeferencing
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
And I press "bim.enable_editing_georeferencing"
|
||||
When I press "bim.disable_editing_georeferencing"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Set IFC grid north
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
And I press "bim.enable_editing_georeferencing"
|
||||
When I press "bim.set_ifc_grid_north"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Set Blender grid north
|
||||
Given an empty IFC project
|
||||
And I press "bim.add_georeferencing"
|
||||
And I press "bim.enable_editing_georeferencing"
|
||||
And I press "bim.set_ifc_grid_north"
|
||||
When I press "bim.set_blender_grid_north"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Get cursor location
|
||||
Given an empty IFC project
|
||||
When I press "bim.get_cursor_location"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Set IFC true north
|
||||
Given an empty IFC project
|
||||
When I press "bim.set_ifc_true_north"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Set Blender true north
|
||||
Given an empty IFC project
|
||||
And I press "bim.set_ifc_true_north"
|
||||
When I press "bim.set_blender_true_north"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Convert local to global
|
||||
Given an empty IFC project
|
||||
When I set "scene.BIMGeoreferenceProperties.local_coordinates" to "0,0,0"
|
||||
And I press "bim.convert_local_to_global"
|
||||
Then "scene.BIMGeoreferenceProperties.map_coordinates" is "0.0,0.0,0.0"
|
||||
|
||||
Scenario: Convert global to local
|
||||
Given an empty IFC project
|
||||
When I set "scene.BIMGeoreferenceProperties.map_coordinates" to "0,0,0"
|
||||
And I press "bim.convert_global_to_local"
|
||||
Then "scene.BIMGeoreferenceProperties.local_coordinates" is "0.0,0.0,0.0"
|
||||
@@ -1,25 +0,0 @@
|
||||
@patch
|
||||
Feature: Patch
|
||||
|
||||
Scenario: Execute IFC Patch - file in memory
|
||||
Given an empty IFC project
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements"
|
||||
And I set "scene.BIMPatchProperties.should_load_from_memory" to "True"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/temp/basic-patched.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
|
||||
When I press "bim.execute_ifc_patch(use_json_for_args=True)"
|
||||
Then the file "{cwd}/test/files/temp/basic-patched.ifc" should contain "123454321"
|
||||
|
||||
Scenario: Execute IFC Patch - file from disk
|
||||
Given an empty IFC project
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/temp/basic-patched.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
|
||||
When I press "bim.execute_ifc_patch(use_json_for_args=True)"
|
||||
Then the file "{cwd}/test/files/temp/basic-patched.ifc" should contain "123454321"
|
||||
|
||||
Scenario: Run migrate patch
|
||||
Given an empty Blender session
|
||||
When I press "bim.run_migrate_patch(infile='{cwd}/test/files/ifc2x3.ifc', outfile='{cwd}/test/files/temp/ifc2x3-migrated.ifc', schema='IFC4')"
|
||||
Then the file "{cwd}/test/files/temp/ifc2x3-migrated.ifc" should contain "IFC4"
|
||||
@@ -1,68 +0,0 @@
|
||||
@qto
|
||||
Feature: Qto
|
||||
|
||||
Scenario: Calculate circle radius
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I press "bim.calculate_circle_radius"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "1.732"
|
||||
|
||||
Scenario: Calculate edge lengths
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I press "bim.calculate_edge_lengths"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "24.0"
|
||||
|
||||
Scenario: Calculate face areas
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I press "bim.calculate_face_areas"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "24.0"
|
||||
|
||||
Scenario: Calculate object volumes
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I press "bim.calculate_object_volumes"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "8.0"
|
||||
|
||||
Scenario: Execute qto method - formwork areas
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And I add a cube of size "1" at "1,0,0"
|
||||
And the object "Cube" is selected
|
||||
And additionally the object "Cube.001" is selected
|
||||
When I set "scene.BIMQtoProperties.qto_methods" to "FORMWORK"
|
||||
And I press "bim.execute_qto_method"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "21.5"
|
||||
|
||||
Scenario: Execute qto method - side formwork areas
|
||||
Given an empty Blender session
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
When I set "scene.BIMQtoProperties.qto_methods" to "SIDE_FORMWORK"
|
||||
And I press "bim.execute_qto_method"
|
||||
Then "scene.BIMQtoProperties.qto_result" is "16.0"
|
||||
|
||||
Scenario: Assign objects base qto
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||
When I press "bim.assign_objects_base_qto"
|
||||
Then "active_object.PsetProperties.qto_name" is "Qto_WallBaseQuantities"
|
||||
|
||||
Scenario: Calculate all quantities
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I press "bim.assign_class(ifc_class='IfcWall', predefined_type='SOLIDWALL')"
|
||||
When I press "bim.calculate_all_quantities"
|
||||
And the variable "qset_id" is "{ifc}.by_type('IfcElementQuantity')[0].id()"
|
||||
And I press "bim.enable_pset_editing(pset_id={qset_id}, obj='IfcWall/Cube', obj_type='Object')"
|
||||
And I press "bim.disable_pset_editing(obj='IfcWall/Cube', obj_type='Object')"
|
||||
Then "active_object.PsetProperties.qto_name" is "Qto_WallBaseQuantities"
|
||||
Then "active_object.PsetProperties.properties['Length'].metadata.float_value" is "2000.0"
|
||||
@@ -1,16 +0,0 @@
|
||||
@search
|
||||
Feature: Search
|
||||
|
||||
Scenario: Select all walls
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And I press "bim.add_filter_group(module='search')"
|
||||
And I set "scene.BIMSearchProperties.facet" to "entity"
|
||||
And I press "bim.add_filter(index=0, type='entity', module='search')"
|
||||
And I set "scene.BIMSearchProperties.filter_groups[0].filters[0].value" to "IfcWall"
|
||||
When I press "bim.search"
|
||||
Then the object "IfcWall/Cube" is selected
|
||||
@@ -1,94 +0,0 @@
|
||||
@style
|
||||
Feature: Style
|
||||
|
||||
Scenario: Update style colours
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
When I press "bim.update_style_colours"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Remove style
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
|
||||
When I press "bim.remove_style(style={style})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Add style to current blender material
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
When I press "bim.add_style"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Unlink style
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
When I press "bim.unlink_style"
|
||||
Then the material "Material" is not an IFC style
|
||||
|
||||
Scenario: Add a new style
|
||||
Given an empty IFC project
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
And I press "bim.enable_adding_presentation_style"
|
||||
When I press "bim.add_presentation_style"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable adding a new style
|
||||
Given an empty IFC project
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
When I press "bim.disable_adding_presentation_style"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Edit style
|
||||
Given an empty IFC project
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
And I press "bim.enable_adding_presentation_style"
|
||||
And I press "bim.add_presentation_style"
|
||||
And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
|
||||
And I press "bim.enable_editing_style(style={style})"
|
||||
When I press "bim.edit_style"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable editing style
|
||||
Given an empty IFC project
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
And I press "bim.enable_adding_presentation_style"
|
||||
And I press "bim.add_presentation_style"
|
||||
And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
|
||||
And I press "bim.enable_editing_style(style={style})"
|
||||
When I press "bim.disable_editing_style"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Load styles
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
When I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Disable editing styles
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
When I press "bim.disable_editing_styles"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Select by style
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I add a material
|
||||
And I press "bim.add_style"
|
||||
And I press "bim.load_styles(style_type='IfcSurfaceStyle')"
|
||||
And the variable "style" is "{ifc}.by_type('IfcSurfaceStyle')[0].id()"
|
||||
When I press "bim.select_by_style(style={style})"
|
||||
Then nothing happens
|
||||
@@ -1,17 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 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/>.
|
||||
@@ -1,17 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 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/>.
|
||||
@@ -1,112 +0,0 @@
|
||||
# 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 blenderbim.core.georeference as subject
|
||||
from test.core.bootstrap import ifc, georeference
|
||||
|
||||
|
||||
class TestAddGeoreferencing:
|
||||
def test_run(self, ifc):
|
||||
ifc.run("georeference.add_georeferencing").should_be_called()
|
||||
subject.add_georeferencing(ifc)
|
||||
|
||||
|
||||
class TestEnableGeoreferencing:
|
||||
def test_run(self, georeference):
|
||||
georeference.import_projected_crs().should_be_called()
|
||||
georeference.import_map_conversion().should_be_called()
|
||||
georeference.import_true_north().should_be_called()
|
||||
georeference.enable_editing().should_be_called()
|
||||
subject.enable_editing_georeferencing(georeference)
|
||||
|
||||
|
||||
class TestRemoveGeoreferencing:
|
||||
def test_run(self, ifc):
|
||||
ifc.run("georeference.remove_georeferencing").should_be_called()
|
||||
subject.remove_georeferencing(ifc)
|
||||
|
||||
|
||||
class TestEditGeoreferencing:
|
||||
def test_run(self, ifc, georeference):
|
||||
georeference.get_projected_crs_attributes().should_be_called().will_return("projected_crs_attributes")
|
||||
georeference.get_map_conversion_attributes().should_be_called().will_return("map_conversion_attributes")
|
||||
georeference.get_true_north_attributes().should_be_called().will_return("true_north_attributes")
|
||||
ifc.run(
|
||||
"georeference.edit_georeferencing",
|
||||
projected_crs="projected_crs_attributes",
|
||||
map_conversion="map_conversion_attributes",
|
||||
true_north="true_north_attributes",
|
||||
).should_be_called()
|
||||
georeference.disable_editing().should_be_called()
|
||||
subject.edit_georeferencing(ifc, georeference)
|
||||
|
||||
|
||||
class TestSetIfcGridNorth:
|
||||
def test_run(self, georeference):
|
||||
georeference.set_ifc_grid_north().should_be_called()
|
||||
subject.set_ifc_grid_north(georeference)
|
||||
|
||||
|
||||
class TestSetBlenderGridNorth:
|
||||
def test_run(self, georeference):
|
||||
georeference.set_blender_grid_north().should_be_called()
|
||||
subject.set_blender_grid_north(georeference)
|
||||
|
||||
|
||||
class TestSetIfcTrueNorth:
|
||||
def test_run(self, georeference):
|
||||
georeference.set_ifc_true_north().should_be_called()
|
||||
subject.set_ifc_true_north(georeference)
|
||||
|
||||
|
||||
class TestSetBlenderTrueNorth:
|
||||
def test_run(self, georeference):
|
||||
georeference.set_blender_true_north().should_be_called()
|
||||
subject.set_blender_true_north(georeference)
|
||||
|
||||
|
||||
class TestGetCursorLocation:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_cursor_location().should_be_called().will_return("coordinates")
|
||||
georeference.set_coordinates("input", "coordinates").should_be_called()
|
||||
subject.get_cursor_location(georeference)
|
||||
|
||||
|
||||
class TestSetCursorLocation:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("output").should_be_called().will_return("coordinates")
|
||||
georeference.set_cursor_location("coordinates").should_be_called()
|
||||
subject.set_cursor_location(georeference)
|
||||
|
||||
|
||||
class TestConvertLocalToGlobal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.xyz2enh("coordinates").should_be_called().will_return("enh")
|
||||
georeference.set_coordinates("output", "enh").should_be_called()
|
||||
georeference.set_cursor_location("enh").should_be_called()
|
||||
subject.convert_local_to_global(georeference)
|
||||
|
||||
|
||||
class TestConvertGlobalToLocal:
|
||||
def test_run(self, georeference):
|
||||
georeference.get_coordinates("input").should_be_called().will_return("coordinates")
|
||||
georeference.enh2xyz("coordinates").should_be_called().will_return("xyz")
|
||||
georeference.set_coordinates("output", "xyz").should_be_called()
|
||||
georeference.set_cursor_location("xyz").should_be_called()
|
||||
subject.convert_global_to_local(georeference)
|
||||
@@ -1,17 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 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/>.
|
||||