mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 04:32:23 +00:00
Compare commits
558 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4efe284338 | |||
| 34a4e3f37a | |||
| 889c9a9e9f | |||
| 9e79499532 | |||
| 589b98053e | |||
| fc5bb230a9 | |||
| 3ef823db23 | |||
| 60a70f5236 | |||
| 757a010172 | |||
| 447625bffd | |||
| 25edaa9b89 | |||
| 9da4215bac | |||
| 6ddf91fb52 | |||
| 28eb2cd628 | |||
| fa4239159d | |||
| ea6f0b054a | |||
| 3b637a0234 | |||
| 45dacc3612 | |||
| f93d79dce0 | |||
| 6f8dcc637f | |||
| a748dda378 | |||
| b158a83644 | |||
| 31cd20c61b | |||
| 0a542e6957 | |||
| 89d29cf133 | |||
| 6ee7502920 | |||
| 708ee7350b | |||
| 836293504c | |||
| b458aea6b9 | |||
| e1e169ece8 | |||
| aeea9dfb5e | |||
| 9a9be821d1 | |||
| c808382154 | |||
| 81608b0cee | |||
| 02ae838c45 | |||
| f1f8b3da6b | |||
| 8ac0d22a95 | |||
| 3b6f79cc75 | |||
| 1e41561fe8 | |||
| 23abf674f3 | |||
| ac64d0c400 | |||
| 0983f9e66d | |||
| 5725ed867f | |||
| 4174a78bb5 | |||
| 96b0a241d9 | |||
| ff76850a54 | |||
| 9954e073fa | |||
| 5da68721d6 | |||
| 097f44284f | |||
| 1f605484d9 | |||
| 81dcad6a14 | |||
| 03af1013a0 | |||
| 144831f9ee | |||
| d6e69bd6e9 | |||
| 7d6fd03eb4 | |||
| 75c854da01 | |||
| 01ddf8e769 | |||
| 0a9c9f2a28 | |||
| 919dcbe2d9 | |||
| 5e237a1670 | |||
| acf6628f23 | |||
| 8a62558575 | |||
| 0e0861c84b | |||
| 5a8209e2ad | |||
| 50b41f5af6 | |||
| fc6678f5ce | |||
| ee1183481f | |||
| 6ddf7dae01 | |||
| 8232c9fa24 | |||
| 07b6c95f54 | |||
| ca2f683af3 | |||
| eb46de9658 | |||
| 59cc9c0a16 | |||
| 32d2f1d629 | |||
| 38a73d6510 | |||
| 59b496d4b5 | |||
| e56aa45a82 | |||
| a3c99b8362 | |||
| e66ac3d7db | |||
| 6b48e8b4b9 | |||
| 3d243b5eee | |||
| fa0dbac914 | |||
| 49e7abb4dc | |||
| 5098550399 | |||
| e12c05eea0 | |||
| 39433fb5a5 | |||
| 1ea80016cd | |||
| 58b39d3926 | |||
| 946c868388 | |||
| df4943205a | |||
| ba4eb2a891 | |||
| 90bb2f0e16 | |||
| c2840d92c2 | |||
| 81ff9c168e | |||
| 239ab5b587 | |||
| 37a33d62d5 | |||
| 13c6fdd3c6 | |||
| e69597f72b | |||
| 9c633c8b6c | |||
| 4d906578c7 | |||
| de744dddda | |||
| 0a04b88a52 | |||
| bee1e21f0e | |||
| f5c5fba151 | |||
| 4f9badc063 | |||
| b61d870922 | |||
| 153389fe4a | |||
| 965a45d65b | |||
| 06b84cfa24 | |||
| 97db95085a | |||
| d1bcec7a6a | |||
| 7b46c6a443 | |||
| 6db0c14fa8 | |||
| d03f9237e2 | |||
| 24716a0089 | |||
| 437d19e1f5 | |||
| bd5fd3539a | |||
| bcc67ecb31 | |||
| c50810a24a | |||
| 7f9ce37a98 | |||
| ac23d08717 | |||
| a8211ed592 | |||
| e38f3beb91 | |||
| f14bf8557a | |||
| cd11d2de27 | |||
| d764c0af23 | |||
| d24e5df7c1 | |||
| de5075aafe | |||
| 9474d17867 | |||
| abe6dbdfc9 | |||
| 7ecec973dd | |||
| f34aef1ebf | |||
| 3253bdf7aa | |||
| 8e2f9d2bb5 | |||
| 7dd0d7601a | |||
| 5bd53b973f | |||
| 911cfe010e | |||
| 35dd03664a | |||
| c89b84ff57 | |||
| cda13af7a3 | |||
| 6e84eb0acc | |||
| b9ede5c739 | |||
| 44b980c1e0 | |||
| 08f4dea47f | |||
| 8f2a7be903 | |||
| 63b541917e | |||
| c8706bddc9 | |||
| e969d407d3 | |||
| 066b198ac9 | |||
| 5d6119e542 | |||
| 845e229a9c | |||
| cefa088b59 | |||
| 109787d869 | |||
| 78abca16e6 | |||
| 02aaf1b409 | |||
| 02e102f2d2 | |||
| ec0dcb7f8c | |||
| ba63ed81a1 | |||
| fb6660361d | |||
| ecae9943e2 | |||
| 104e896531 | |||
| 6555e56647 | |||
| f9a590a22c | |||
| ad4a1576d3 | |||
| c35c3f06b5 | |||
| fada808a2f | |||
| e221e6fa6a | |||
| 725dac04d0 | |||
| 5c365990b9 | |||
| 797e2e41a5 | |||
| 57fe40839b | |||
| b4c5ad1e35 | |||
| 635ddce93d | |||
| 24e807cac7 | |||
| 33ceba38a8 | |||
| 90ed1f9b28 | |||
| e431cb205c | |||
| 09bb45f5bc | |||
| 7087452cde | |||
| af4b58444d | |||
| 711423cae5 | |||
| eb8b6062b4 | |||
| 5ac96cb5b9 | |||
| c462fc9337 | |||
| 95f7a2bcde | |||
| 04cf9c870b | |||
| bf9bddc867 | |||
| 20a2fda066 | |||
| f03afb20f3 | |||
| e0df22ffec | |||
| 96302a5c07 | |||
| 38afce1628 | |||
| cd5afacc97 | |||
| c2623e5dce | |||
| 42ecd7e127 | |||
| c80a7cfcc9 | |||
| 6d8f1a3980 | |||
| 5c4c61bb15 | |||
| d8af8b58bb | |||
| 3c0405249b | |||
| 7f57649d10 | |||
| fbde23870e | |||
| 7b968030ca | |||
| b5d672ba42 | |||
| efa8da83ba | |||
| 64cf7237db | |||
| cf9a94a799 | |||
| 46c1a8c8ff | |||
| ac7f2aeb62 | |||
| 9378a763f3 | |||
| 0bc83fedae | |||
| f7c03db752 | |||
| b6b8c21741 | |||
| c8af51cfd2 | |||
| 77de3820e7 | |||
| adc5bea4e9 | |||
| 1eddcb9ee0 | |||
| eae542f90f | |||
| fab0d05279 | |||
| d2e495a69f | |||
| 02058efa4d | |||
| ed79cbb1cc | |||
| 0065c5fba8 | |||
| 1ab24e8362 | |||
| 232b4ec34d | |||
| 75e6147dbc | |||
| 8d7b293381 | |||
| 19c8afb652 | |||
| 84dcee6b36 | |||
| dadcf03f55 | |||
| 413886ac6b | |||
| 377f68c020 | |||
| b718a37db2 | |||
| e21043b038 | |||
| 395598d8e3 | |||
| 813518bcb5 | |||
| 4ce7a898c9 | |||
| 78f235ea7c | |||
| bda2f50f9d | |||
| 5e5ba5b3d4 | |||
| 22b0e9dcc5 | |||
| 68420d43ea | |||
| 924e337c90 | |||
| 04599a10d2 | |||
| 84c931bb5f | |||
| 572aedb3eb | |||
| 1db86c24e3 | |||
| 74adde3030 | |||
| f68e8f0e00 | |||
| 98386984f3 | |||
| 7b35ed97ca | |||
| b3302ed679 | |||
| e12f060af8 | |||
| 6d4f47a2ca | |||
| e49ab7e0cc | |||
| ccfe7ab80a | |||
| 7b95358975 | |||
| bcc50aaadc | |||
| 30f63b7d67 | |||
| ead13e23bc | |||
| 24257d2a85 | |||
| 5c4dbb924d | |||
| 88e1b3d2f9 | |||
| f67b80c6a6 | |||
| c7fd42f2cf | |||
| bed03bf83c | |||
| 1e49400e93 | |||
| 608c0dc05d | |||
| 921dbe9dae | |||
| a7e0837f96 | |||
| 071c80f845 | |||
| 06d823b9d8 | |||
| b15e33e5e9 | |||
| 55885f7a24 | |||
| e5fd6d9152 | |||
| 4dda7e84a0 | |||
| bb6be4d52d | |||
| 01d487f585 | |||
| 67a17ddd1b | |||
| f916edce1e | |||
| b0d810199b | |||
| 515a72cec9 | |||
| 7464f7519f | |||
| f0dde4f825 | |||
| 54fefb3684 | |||
| 628fcb2999 | |||
| 22f830b053 | |||
| 579c72fc69 | |||
| 642a0f7084 | |||
| 7cd5aab1c5 | |||
| 4b15535c9c | |||
| b978da24d9 | |||
| 7132c4e9bf | |||
| 5aa6123806 | |||
| e48299cb3f | |||
| c65088a7a7 | |||
| 4f53d8c5f1 | |||
| 424b2589d9 | |||
| 05f6ed1084 | |||
| 9ec80cee0d | |||
| a5daf4177a | |||
| 1b1eccb0c1 | |||
| ece9e5c834 | |||
| 6005ff641b | |||
| fa603244b9 | |||
| 6d6010be3b | |||
| 3691052f06 | |||
| e38eafd034 | |||
| 976eeaef20 | |||
| 09da42d649 | |||
| 97b23a9ac6 | |||
| 5e8a57f089 | |||
| 7f34d18bbf | |||
| 97cc19ed43 | |||
| d49faf7412 | |||
| fcaac6bd98 | |||
| c35cdcfcad | |||
| 205da63d8e | |||
| 3f529ae21b | |||
| 89f7105e9c | |||
| e5dd06c461 | |||
| ec7af4e719 | |||
| a755ec99f9 | |||
| 812389bce2 | |||
| b67b5e8d9f | |||
| e2a06e10b6 | |||
| 7fa26b2d60 | |||
| cdd332d1ee | |||
| 22f21a3f0b | |||
| ee27bc3b74 | |||
| 8307bf5877 | |||
| 09e473a0dc | |||
| abeefeeee7 | |||
| d949180461 | |||
| 708bd3207e | |||
| 68d9d04e5b | |||
| 7c7bd3dc53 | |||
| 24d2b59a2e | |||
| ac2684af8d | |||
| 8291a0ca6a | |||
| b72b23741e | |||
| 858692f3f5 | |||
| c74bfa24d2 | |||
| 66c29f0da1 | |||
| f20fd3357f | |||
| e4eb76d4e0 | |||
| 22c95828ce | |||
| e87ca960e8 | |||
| 6881e7d220 | |||
| 28b54c869a | |||
| 9596117953 | |||
| 8dbeb928e3 | |||
| 4fdb78aebc | |||
| 01724913e1 | |||
| 44dd07547e | |||
| cc157239a2 | |||
| 0576914e07 | |||
| a064173ffe | |||
| 8e502c9bb7 | |||
| 4d23706145 | |||
| 6e8de292c6 | |||
| d209cc9ff4 | |||
| ddfaf3308b | |||
| 301ddab1ee | |||
| 1160d20d33 | |||
| 28099ddc8f | |||
| c1bca22638 | |||
| 3d5d868812 | |||
| b015647543 | |||
| c54f2131af | |||
| 2a832202fa | |||
| a3d970e014 | |||
| 24912bb827 | |||
| 1cd0bc9e7d | |||
| 8880ab0fe9 | |||
| 1c052d1417 | |||
| 20cb6ab007 | |||
| e854447714 | |||
| 7f394a5807 | |||
| c80b437682 | |||
| b793ab63cc | |||
| 8c28f52e6a | |||
| 9d71b7483f | |||
| e6e71adba7 | |||
| 7ab8a5921b | |||
| d68d4d54a9 | |||
| bce0f0d8d4 | |||
| 50648fd350 | |||
| b7e26bacea | |||
| a2fdd93e6b | |||
| aef00a4dfc | |||
| efae8ebdcd | |||
| 61403fcf42 | |||
| badee8fdc5 | |||
| 36e779e32f | |||
| c50ac3db4f | |||
| b17c261344 | |||
| 956aae37a8 | |||
| 48d6cc8a28 | |||
| cf9d3026f4 | |||
| e2f0b1ed5c | |||
| b833cffbd3 | |||
| 7a7c89e475 | |||
| 01b0164e0d | |||
| 13f26d9b70 | |||
| 8a88b0b3a1 | |||
| a15e293db5 | |||
| 9509f07da8 | |||
| 37b15de3e4 | |||
| 66b4509b06 | |||
| 41dcb83bdb | |||
| 0bda41710c | |||
| cbd85fafda | |||
| dc7ebab0d1 | |||
| 0c2a3ff400 | |||
| 6af6fda799 | |||
| 2c9e443425 | |||
| 2bede1cdf0 | |||
| 4f3397a04e | |||
| 9b8adad8c8 | |||
| c48bccf727 | |||
| 5d41336342 | |||
| b8628cc9df | |||
| 519cb86163 | |||
| 0008d8bd2a | |||
| bc7946091a | |||
| ac2db7b4c9 | |||
| 0b57d5fc5f | |||
| 82af25d09a | |||
| 4a82525bdb | |||
| 002112dd50 | |||
| 844a9842cf | |||
| 3a098e55c4 | |||
| e186b81f0c | |||
| 527d16c5f0 | |||
| 8c1b33e1d5 | |||
| e46f18f686 | |||
| 931fd9cc9f | |||
| aebc073416 | |||
| 56e00d059b | |||
| 8a6b4aa5fb | |||
| 46bee8aed9 | |||
| eaa2aa05a4 | |||
| abfa6edc5c | |||
| e3a1c29593 | |||
| e6691989dc | |||
| 83845bdd6d | |||
| 0f4c19fd2d | |||
| ce8bdffe57 | |||
| 53e052eb77 | |||
| f21b472cfe | |||
| 324b440f5c | |||
| 37a7049a61 | |||
| 1c1035e251 | |||
| d83924b73e | |||
| a04542dfd1 | |||
| 066616707a | |||
| c1cd228911 | |||
| 515830e52d | |||
| a971860094 | |||
| f872906e0c | |||
| 0597100356 | |||
| d5feaaa1af | |||
| caca209c8f | |||
| 2e45d4a57e | |||
| 0d8c86a0f6 | |||
| 7f0df72fec | |||
| 863a8248af | |||
| bdc991856c | |||
| 1a38b7f5c6 | |||
| 91199308a9 | |||
| 1f7930c5c8 | |||
| 54c7f355ff | |||
| 551f3b0d8d | |||
| 049c796629 | |||
| 412700c8fd | |||
| 96a8c1e8ae | |||
| 602e18a262 | |||
| 9e30e1ad31 | |||
| b6f939f1b1 | |||
| d964f924d7 | |||
| 36b702630d | |||
| ccf70c7aad | |||
| a2ff63f9ec | |||
| a82b0f30b0 | |||
| bc2fd232d0 | |||
| 5d7b6ad998 | |||
| 0e0678b926 | |||
| 1d4dfd0eb3 | |||
| 65179db444 | |||
| 5457ee3a40 | |||
| 4f3ba7ae22 | |||
| a33a1700dc | |||
| 8a48769760 | |||
| 7e55872ca8 | |||
| edbe1d80bf | |||
| bd093303c4 | |||
| c5c7363a93 | |||
| 5014fef129 | |||
| d710a01df5 | |||
| 23c65742e1 | |||
| d8f52afa6c | |||
| 29ce8d5d80 | |||
| 5e5de0bbd8 | |||
| b9b58f5726 | |||
| 5c7271ddc8 | |||
| 2db35556e9 | |||
| 2449656311 | |||
| c66cf0a22b | |||
| e1a1ba62c5 | |||
| 6489f4dc1d | |||
| beab99bb0d | |||
| 8b50f1bcd6 | |||
| 4617834578 | |||
| 426dcbee43 | |||
| 2a12151c07 | |||
| c08958c1bd | |||
| e355a33d48 | |||
| ece9e2fdd1 | |||
| c0d84713bc | |||
| ca9f165b6a | |||
| 8f9c97a663 | |||
| 4ed8310b93 | |||
| 31be899dc9 | |||
| 4463dd8af9 | |||
| 95c0ac901f | |||
| 6d7a6eeda7 | |||
| c520e0eeb5 | |||
| 73817acdff | |||
| 3d7e8d7c6c | |||
| a701840826 | |||
| 5de2ac9042 | |||
| 573e2626ad | |||
| 9de59afd49 | |||
| 74a9671075 | |||
| 799658ff41 | |||
| bd64f42a54 | |||
| 8eab7f155e | |||
| 5351966430 | |||
| 2663c079f4 | |||
| 6747bd9c8f | |||
| 6ece295be4 | |||
| 64a9f85f25 | |||
| d8fa853308 | |||
| ce37c49624 | |||
| 0da3270c7f | |||
| fda6fc6c48 | |||
| 3865f01df9 | |||
| 6bf61d7163 | |||
| b8ad73668f | |||
| b7a3dd4164 | |||
| 566fd443db | |||
| 51b5a507cf | |||
| a44357dad5 | |||
| 8e792ebb50 | |||
| 352321364c | |||
| 2ab32e41b2 | |||
| 3fbd9d7cdf |
@@ -16,7 +16,7 @@ jobs:
|
||||
- run: echo ok go
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
path: build/assets/Ifc*
|
||||
|
||||
deliver:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
name: Docker Build, Tag, Push
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
name: ci-bsdd-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/bsdd &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/bsdd/dist
|
||||
@@ -0,0 +1,48 @@
|
||||
name: ci-ifcclash-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifcclash &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifcclash/dist
|
||||
@@ -0,0 +1,48 @@
|
||||
name: ci-ifccsv-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifccsv &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifccsv/dist
|
||||
@@ -1,6 +1,7 @@
|
||||
name: ci-ifcopenshell-pypi
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py36, py37, py38, py39, py310, py311]
|
||||
pyver: [py39, py310, py311, py312]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
@@ -57,7 +58,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
name: ci-ifcpatch-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifcpatch &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/ifcpatch/dist
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Publish-ifcsverchok
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifcsverchok-build.yml'
|
||||
- 'src/ifcsverchok/*'
|
||||
branches:
|
||||
- v0.7.0
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcsverchok
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ifcsverchok
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
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'
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%y%m%d')"
|
||||
- name: Compile
|
||||
run: |
|
||||
cd src/ifcsverchok
|
||||
make dist
|
||||
- name: Upload Zip file to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: src/ifcsverchok/dist/ifcsverchok-${{steps.date.outputs.date}}.zip
|
||||
asset_name: ifcsverchok-${{steps.date.outputs.date}}.zip
|
||||
tag: "ifcsverchok-${{steps.date.outputs.date}}"
|
||||
overwrite: true
|
||||
body: "ifcsverchok build for ${{steps.date.outputs.date}}"
|
||||
@@ -9,9 +9,6 @@ on:
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifctester-pypi.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -30,7 +27,6 @@ jobs:
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -38,12 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%y%m%d')"
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
|
||||
@@ -76,8 +76,14 @@ src/ifcopenshell-python/test/build
|
||||
# blenderbim libs
|
||||
src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim i18n
|
||||
src/blenderbim/blenderbim/translations.py
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ and many other libraries, CLI apps, and more. Support is also provided for auxil
|
||||
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)
|
||||
* [IfcOpenShell Documentation](https://docs.ifcopenshell.org)
|
||||
* [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](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)
|
||||
* [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)
|
||||
|
||||
| Service | Status |
|
||||
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN apt-get -y update && apt-get -y install unzip curl
|
||||
# Install AWS Lambda runtime interface client
|
||||
RUN pip install --target ${FUNCTION_DIR} awslambdaric
|
||||
|
||||
# Set the IfcOpenShell build version (check available builds at: https://blenderbim.org/docs-python/ifcopenshell-python/installation.html)
|
||||
# Set the IfcOpenShell build version (check available builds at: https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
|
||||
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
|
||||
|
||||
# Download and extract IfcOpenShell
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<licenseUrl>https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/COPYING</licenseUrl>
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
<projectSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</projectSourceUrl>
|
||||
<docsUrl>https://blenderbim.org/docs/</docsUrl>
|
||||
<docsUrl>https://docs.blenderbim.org/</docsUrl>
|
||||
<!--<mailingListUrl></mailingListUrl>-->
|
||||
<bugTrackerUrl>https://github.com/IfcOpenShell/IfcOpenShell/issues</bugTrackerUrl>
|
||||
<tags>blender bim blenderbim ifc python opensource foss</tags>
|
||||
|
||||
+45
-15
@@ -17,7 +17,7 @@
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.1.3)
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
@@ -27,6 +27,9 @@ cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
cmake_policy(SET CMP0078 OLD)
|
||||
cmake_policy(SET CMP0086 NEW)
|
||||
if (POLICY CMP0144)
|
||||
cmake_policy(SET CMP0144 NEW) # find_package() uses upper-case <PACKAGENAME>_ROOT variables.
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
@@ -186,7 +189,7 @@ if(GLTF_SUPPORT AND BUILD_CONVERT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
# Add USD support to serializers
|
||||
if(USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
@@ -209,13 +212,13 @@ if(USD_SUPPORT)
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
@@ -373,13 +376,34 @@ if(BUILD_IFCGEOM)
|
||||
message(STATUS "Looking for Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAJOR
|
||||
REGEX "#define OCC_VERSION_MAJOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAJOR ${OCC_MAJOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MINOR
|
||||
REGEX "#define OCC_VERSION_MINOR.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MINOR ${OCC_MINOR})
|
||||
file(STRINGS ${OCC_INCLUDE_DIR}/Standard_Version.hxx OCC_MAINT
|
||||
REGEX "#define OCC_VERSION_MAINTENANCE.*"
|
||||
)
|
||||
string(REGEX MATCH "[0-9]+" OCC_MAINT ${OCC_MAINT})
|
||||
set(OCC_VERSION_STRING "${OCC_MAJOR}.${OCC_MINOR}.${OCC_MAINT}")
|
||||
endif(OCC_INCLUDE_DIR)
|
||||
|
||||
set(OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
TKFillet TKXSBase TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
if(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKIGES TKSTEPBase TKSTEPAttr TKSTEP209 TKSTEP)
|
||||
else(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
list(APPEND OPENCASCADE_LIBRARY_NAMES TKDESTEP TKDEIGES)
|
||||
endif(OCC_VERSION_STRING VERSION_LESS 7.8.0)
|
||||
|
||||
if("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
find_library(OCC_LIBRARY TKernel
|
||||
@@ -435,7 +459,7 @@ if(BUILD_IFCGEOM)
|
||||
|
||||
if(OCCT_STATIC)
|
||||
find_package(Threads)
|
||||
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
else()
|
||||
@@ -447,7 +471,7 @@ if(BUILD_IFCGEOM)
|
||||
set(OPENCASCADE_LIBRARIES -Wl,--start-group ${OPENCASCADE_LIBRARIES} -Wl,--end-group ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if (NOT APPLE AND NOT WIN32)
|
||||
set(OPENCASCADE_LIBRARIES ${OPENCASCADE_LIBRARIES} "rt")
|
||||
endif()
|
||||
@@ -656,6 +680,10 @@ if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Od")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
@@ -755,6 +783,8 @@ if(NOT SCHEMA_VERSIONS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS "IFC SCHEMA_VERSIONS that will be used for the build: ${SCHEMA_VERSIONS}.")
|
||||
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_definitions(-DHAS_SCHEMA_${schema})
|
||||
endforeach()
|
||||
@@ -837,7 +867,7 @@ if (BUILD_IFCGEOM)
|
||||
if (WASM_BUILD)
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
else()
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
set(IFCOPENSHELL_LIBRARIES ${IFCOPENSHELL_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES} IfcGeom ${IFCGEOM_SCHEMA_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -900,7 +930,7 @@ if(BUILD_IFCGEOM)
|
||||
add_library(IfcGeom_ifc${schema} STATIC ${IFCGEOM_FILES})
|
||||
set_target_properties(IfcGeom_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
if (NOT WASM_BUILD)
|
||||
target_link_libraries(IfcGeom_ifc${schema} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
target_link_libraries(IfcGeom_ifc${schema} IfcParse ${OPENCASCADE_LIBRARIES})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
@@ -936,7 +966,7 @@ if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
foreach(schema ${SCHEMA_VERSIONS})
|
||||
add_library(Serializers_ifc${schema} STATIC ${SERIALIZERS_S_FILES})
|
||||
set_target_properties(Serializers_ifc${schema} PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS -DIfcSchema=Ifc${schema}")
|
||||
|
||||
|
||||
if (WASM_BUILD)
|
||||
target_link_libraries(Serializers_ifc${schema} ${HDF5_LIBRARIES})
|
||||
else()
|
||||
@@ -1142,7 +1172,7 @@ endforeach(COMPONENT)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_CONTACT}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3.8, python3-pytest ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "python3, libxml2, libocct-foundation-dev, libocct-modeling-algorithms-dev, libocct-modeling-data-dev, libocct-ocaf-dev, libocct-visualization-dev, libocct-data-exchange-dev, libhdf5-serial-dev, libpython3-dev, python3-pytest ${BOOST_DEPS}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}")
|
||||
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
|
||||
|
||||
+9
-3
@@ -76,7 +76,7 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.7", "3.12.1"]
|
||||
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.5.3"
|
||||
@@ -254,6 +254,11 @@ if not os.path.exists(LOG_FILE):
|
||||
open(LOG_FILE, "w").close()
|
||||
logger.info(f"using command log file '{LOG_FILE}'")
|
||||
|
||||
# Causing havoc in python 3.11 build
|
||||
try:
|
||||
del os.environ['__PYVENV_LAUNCHER__']
|
||||
except: pass
|
||||
|
||||
def run(cmds, cwd=None, can_fail=False):
|
||||
|
||||
"""
|
||||
@@ -284,8 +289,9 @@ if platform.system() == "Darwin":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
|
||||
if run(["sw_vers", "-productVersion"]) < "10.16":
|
||||
# Apparently not supported
|
||||
PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
|
||||
# This is now solved with the '__PYVENV_LAUNCHER__' hack
|
||||
# PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]
|
||||
pass
|
||||
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
|
||||
+10
-258
@@ -61,26 +61,6 @@ PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
|
||||
LXML_ARCH:=manylinux_2_28_x86_64
|
||||
SHAPELY_ARCH:=manylinux_2_17_x86_64.manylinux2014_x86_64
|
||||
PILLOW_ARCH:=manylinux_2_28_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py39h359ec1e_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py39hb1babde_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py310hd002f03_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py310hcfe0b05_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/linux-64/hpp-fcl-2.4.0-py311h04f086a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/linux-64/eigenpy-3.2.0-py311h995a085_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py311h59ea3da_3.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
@@ -89,27 +69,13 @@ SHAPELY_ARCH:=macosx_10_9_x86_64
|
||||
PILLOW_ARCH:=macosx_10_10_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
LXML_ARCH:=macosx_11_0_x86_64
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py39h26350b9_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py39h3323527_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
LXML_ARCH:=macosx_11_0_x86_64
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py310ha216469_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py310h33a8c9b_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
LXML_ARCH:=macosx_11_0_universal2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-64/hpp-fcl-2.4.0-py311hebdd1a2_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-64/eigenpy-3.2.0-py311h62e90b6_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py311h349b758_4.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
@@ -117,26 +83,6 @@ PYPI_PLATFORM:=--platform macosx_11_0_arm64
|
||||
LXML_ARCH:=macosx_11_0_universal2
|
||||
SHAPELY_ARCH:=macosx_11_0_arm64
|
||||
PILLOW_ARCH:=macosx_11_0_arm64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py39h82b2ff2_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py39h3a9e884_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py310hdb6c3eb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py310h96e9823_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/osx-arm64/hpp-fcl-2.4.0-py311hc0c29b9_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/osx-arm64/eigenpy-3.2.0-py311h24fa84d_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py311h5c64717_4.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
@@ -144,26 +90,6 @@ PYPI_PLATFORM:=--platform win_amd64
|
||||
LXML_ARCH:=win_amd64
|
||||
SHAPELY_ARCH:=win_amd64
|
||||
PILLOW_ARCH:=win_amd64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py39hf3ebc58_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py39ha1f6a47_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py310h1bf3f0a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py310h7fb871d_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.tar.bz2
|
||||
endif
|
||||
ifeq ($(PYVERSION), py311)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.4.0/download/win-64/hpp-fcl-2.4.0-py311hd263b2c_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.2.0/download/win-64/eigenpy-3.2.0-py311hd354db1_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py311h12feb9d_3.tar.bz2
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
endif
|
||||
|
||||
LXML_NAME:=lxml
|
||||
@@ -211,7 +137,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-6c9e130-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-f7c03db-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -427,6 +353,8 @@ endif
|
||||
cd dist/working && mkdir site-packages
|
||||
# Required by IfcCSV
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
|
||||
# Pandas ships with very heavy tests
|
||||
cd dist/working/site-packages/pandas && rm -rf tests
|
||||
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
|
||||
# Required by IFCCityJSON
|
||||
@@ -434,161 +362,12 @@ endif
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "brickschema[persistence]==0.7.6a2" --target=./site-packages
|
||||
# Required for SVG to DXF conversion
|
||||
cd dist/working && . env/bin/activate && $(PIP) install "ezdxf" --target=./site-packages
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
cd dist/working && unzip hpp-fcl*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/$(PYLIBDIR)/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Lib/site-packages/hppfcl dist/blenderbim/libs/site/packages/
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ASSIMP_URL)
|
||||
cd dist/working && tar -xf assimp*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && unzip eigenpy*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(OCTOMAP_URL)
|
||||
cd dist/working && tar -xf octomap*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOST_URL)
|
||||
cd dist/working && tar -xf boost*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(BOOSTCPP_URL)
|
||||
cd dist/working && unzip boost-cpp*
|
||||
cd dist/working && tar -I zstd -xvf pkg*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/libboost_serialization.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/libboost_serialization.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/boost_serialization.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(ZLIB_URL)
|
||||
cd dist/working && tar -xf zlib*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by BIMTester
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c8/4b/d0a8c23b6c8985e5544ea96d27105a273ea22051317f850c2cdbf2029fe4/behave-1.2.6.tar.gz
|
||||
@@ -636,34 +415,6 @@ endif
|
||||
cd dist/working/parse_type-0.5.2/ && cp -r parse_type ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
cd dist/working && tar -xzvf boto3*
|
||||
cd dist/working/boto3-1.17.17/ && cp -r boto3 ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7f/2e/e02fdfd0f0377c4e44e61de27b05d5cfe93575770661ef9ded80ed90fa88/botocore-1.20.17.tar.gz
|
||||
cd dist/working && tar -xzvf botocore*
|
||||
cd dist/working/botocore-1.20.17/ && cp -r botocore ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz
|
||||
cd dist/working && tar -xzvf jmespath*
|
||||
cd dist/working/jmespath-0.10.0/ && cp -r jmespath ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by boto3
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/08/e1/3ee2096ebaeeb8c186d20ed16c8faf4a503913e5c9a0e14cd6b8ffc405a3/s3transfer-0.3.4.tar.gz
|
||||
cd dist/working && tar -xzvf s3transfer*
|
||||
cd dist/working/s3transfer-0.3.4/ && cp -r s3transfer ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://github.com/IFCJSON-Team/IFC2JSON_python/archive/master.zip
|
||||
@@ -681,7 +432,7 @@ endif
|
||||
# Required by brickschema
|
||||
# This is a bit of a dodgy one, it should be provided by setuptools which Blender doesn't ship.
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://blenderbim.org/builds/pkg_resources.tar.gz
|
||||
cd dist/working && wget https://github.com/IfcOpenShell/IfcOpenShell/raw/v0.7.0/src/blenderbim/scripts/pkg_resources.tar.gz
|
||||
cd dist/working && tar -xzvf pkg_resources*
|
||||
cd dist/working/ && cp -r pkg_resources ../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
@@ -696,9 +447,9 @@ endif
|
||||
# Required for Desktop icon and file association
|
||||
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
|
||||
|
||||
# generate translations module for BBIM build
|
||||
# Generate translations module for BBIM build
|
||||
git clone https://github.com/IfcOpenShell/blenderbim-translations.git dist/working
|
||||
python scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
|
||||
$(PYTHON) scripts/bbim_translations.py -i "dist/working" -o "dist/blenderbim"
|
||||
rm -rf dist/working
|
||||
|
||||
# Remove dependencies also bundled with Blender
|
||||
@@ -706,6 +457,7 @@ endif
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" blender_manifest.toml
|
||||
cd dist/blenderbim/bim && $(SED) "s/8888888/$(LAST_COMMIT_HASH)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -27,7 +27,7 @@ bl_info = {
|
||||
"blender": (3, 1, 0),
|
||||
"version": (0, 0, 999999),
|
||||
"location": "File Menu, Scene Properties Tab. See documentation for more.",
|
||||
"doc_url": "https://blenderbim.org/docs",
|
||||
"doc_url": "https://docs.blenderbim.org/",
|
||||
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import bpy.utils.previews
|
||||
import blenderbim
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
from blenderbim.translations import translations_dict
|
||||
@@ -96,9 +97,12 @@ for name in modules.keys():
|
||||
classes = [
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_delete_object,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
@@ -106,6 +110,7 @@ classes = [
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
@@ -128,6 +133,7 @@ classes = [
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMFacet,
|
||||
prop.BIMFilterGroup,
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
@@ -175,6 +181,7 @@ classes = [
|
||||
ui.BIM_PT_tab_sandbox,
|
||||
# TODO: move this somewhere else and clean it up
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_PT_section_with_cappings,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
@@ -184,7 +191,7 @@ addon_keymaps = []
|
||||
icons = None
|
||||
is_registering = False
|
||||
last_commit_hash = "8888888"
|
||||
overridden_scene_panels = dict()
|
||||
original_scene_panels_polls: dict[bpy.types.Panel, Union[Callable, None]] = dict()
|
||||
|
||||
|
||||
def on_register(scene):
|
||||
@@ -292,10 +299,10 @@ def unregister():
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
|
||||
for panel in tuple(overridden_scene_panels.keys()):
|
||||
original_panel, override_panel = overridden_scene_panels[panel]
|
||||
bpy.utils.unregister_class(override_panel)
|
||||
bpy.utils.register_class(original_panel)
|
||||
del overridden_scene_panels[panel]
|
||||
import blenderbim.tool as tool
|
||||
|
||||
# use tuple() as method will be removing keys from dict
|
||||
for panel in tuple(original_scene_panels_polls.keys()):
|
||||
tool.Blender.remove_scene_panel_override(panel)
|
||||
|
||||
bpy.app.translations.unregister("blenderbim")
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* (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
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* (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
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
|
||||
FILE_NAME('/dev/null','2024-03-03T16:03:58+11:00',(),(),'IfcOpenShell v0.7.0-eaa2aa05a','IfcOpenShell v0.7.0-eaa2aa05a','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
|
||||
#1=IFCPROJECT('3NJUxHyTzEv96oezyDYEqf',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('0D39k7hbT5ce$jWaCd2niG',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('2RWVohyP57PBHT9zCs4H2O',$,$,$,#1,(#2));
|
||||
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#7=IFCUNITASSIGNMENT((#5,#6,#4));
|
||||
#7=IFCUNITASSIGNMENT((#6,#4,#5));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCDIRECTION((0.,0.,1.));
|
||||
#10=IFCDIRECTION((1.,0.,0.));
|
||||
@@ -23,84 +23,84 @@ DATA;
|
||||
#16=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#15,$);
|
||||
#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#12,$,.MODEL_VIEW.,$);
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#16,$,.MODEL_VIEW.,$);
|
||||
#20=IFCMATERIAL('Unknown',$,$);
|
||||
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#21=IFCWALLTYPE('2F1k78lI53fwS0rg3rqrRb',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#22=IFCMATERIALLAYERSET((#24),$,$);
|
||||
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
|
||||
#23=IFCRELASSOCIATESMATERIAL('0khP_Smdj12f2JrOTRbRKj',$,$,$,(#21),#22);
|
||||
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
|
||||
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
|
||||
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#25=IFCRELDECLARES('31JriXdev6BP2sC9lcxdC1',$,$,$,#2,(#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#210,#153,#34,#67,#38,#82,#77,#42));
|
||||
#26=IFCWALLTYPE('3R5onkTtX1IxdCMPoThRaw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#27=IFCMATERIALLAYERSET((#29),$,$);
|
||||
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
|
||||
#28=IFCRELASSOCIATESMATERIAL('0$mXRPCT16nP6Q70XFNvZv',$,$,$,(#26),#27);
|
||||
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
|
||||
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#30=IFCWALLTYPE('1juanculbDJPqvqT2FCm8m',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#31=IFCMATERIALLAYERSET((#33),$,$);
|
||||
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
|
||||
#32=IFCRELASSOCIATESMATERIAL('0FsB2A$frFn9AZW2IG38ny',$,$,$,(#30),#31);
|
||||
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#34=IFCWALLTYPE('05UYduR9r1vRxZZgP64Rdw',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#35=IFCMATERIALLAYERSET((#37),$,$);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2FYhYrvmv4SBoZHVWDTTte',$,$,$,(#34),#35);
|
||||
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#38=IFCCOVERINGTYPE('0iLxgfHB9F2hRH2vMcw7Yv',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#39=IFCMATERIALLAYERSET((#41),$,$);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
|
||||
#40=IFCRELASSOCIATESMATERIAL('0yvFX0zgb9uvJ3ARnf4mfe',$,$,$,(#38),#39);
|
||||
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
|
||||
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#42=IFCCOVERINGTYPE('15Riw2WTrAUPwvfLyKXJ2g',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#43=IFCMATERIALLAYERSET((#45),$,$);
|
||||
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
|
||||
#44=IFCRELASSOCIATESMATERIAL('1iAOG6NK9FBxitFtzN9qP5',$,$,$,(#42),#43);
|
||||
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
|
||||
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
|
||||
#46=IFCPROPERTYSET('18ICCZ0fjDG9UXMyzrsFBA',$,'EPset_Parametric',$,(#47));
|
||||
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
|
||||
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#48=IFCCOVERINGTYPE('0gEOtYULD0F9KD9zPoA6cJ',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#49=IFCMATERIALLAYERSET((#51),$,$);
|
||||
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
|
||||
#50=IFCRELASSOCIATESMATERIAL('0xd6dsjf13JA98q9fnAMpQ',$,$,$,(#48),#49);
|
||||
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
|
||||
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
|
||||
#52=IFCPROPERTYSET('3qCbs1tuDCvuOXam5RrA$a',$,'EPset_Parametric',$,(#53));
|
||||
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
|
||||
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#54=IFCRAMPTYPE('2yW1ePlyb8cOR6HdEntpRh',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#55=IFCMATERIALLAYERSET((#57),$,$);
|
||||
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
|
||||
#56=IFCRELASSOCIATESMATERIAL('1x9ZyVim19AQtJhfEE06XR',$,$,$,(#54),#55);
|
||||
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
|
||||
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#59=IFCPILETYPE('25MTkMtaH5QeCf$gFbvSSw',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#60=IFCMATERIALPROFILESET($,$,(#62),$);
|
||||
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
|
||||
#61=IFCRELASSOCIATESMATERIAL('09FXUZCnrFEffxBrtg16gx',$,$,$,(#59),#60);
|
||||
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
|
||||
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#63=IFCSLABTYPE('1Akbal7tP5DAPW0ti$Qp30',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#64=IFCMATERIALLAYERSET((#66),$,$);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0jzkXlkBL4HhmQ_FTWW6Cj',$,$,$,(#63),#64);
|
||||
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#67=IFCSLABTYPE('0$gpYNi3T08BrxaR67CxgI',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#68=IFCMATERIALLAYERSET((#70),$,$);
|
||||
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
|
||||
#69=IFCRELASSOCIATESMATERIAL('3a6kjEM29DleYTlSkA8_DK',$,$,$,(#67),#68);
|
||||
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCCOLUMNTYPE('278sjptdDDzgOZseERj390',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#73=IFCMATERIALPROFILESET($,$,(#75),$);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1ppYXw39v6kBwi3rrIm4ZE',$,$,$,(#72),#73);
|
||||
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
|
||||
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCCOLUMNTYPE('3aSZOvGmr7jggSNwsq5PJE',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#78=IFCMATERIALPROFILESET($,$,(#80),$);
|
||||
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
|
||||
#79=IFCRELASSOCIATESMATERIAL('1AtNJPaD14DgINbAjXvbU4',$,$,$,(#77),#78);
|
||||
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
|
||||
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCCOLUMNTYPE('28iv3Kru12yQ7R7RRwNjvD',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#83=IFCMATERIALPROFILESET($,$,(#85),$);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
|
||||
#84=IFCRELASSOCIATESMATERIAL('2M_oL$n3j6rBRnOW5RSk87',$,$,$,(#82),#83);
|
||||
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
|
||||
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
|
||||
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#87=IFCBEAMTYPE('2E$V5l4b54dxuToJ1A6IHp',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#88=IFCMATERIALPROFILESET($,$,(#90),$);
|
||||
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
|
||||
#89=IFCRELASSOCIATESMATERIAL('3PUNnY7cj8LhyXbHKVqZnf',$,$,$,(#87),#88);
|
||||
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
|
||||
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
|
||||
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#92=IFCBEAMTYPE('3qutoZTvP0lvLWNXyjPhPm',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#93=IFCMATERIALPROFILESET($,$,(#95),$);
|
||||
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
|
||||
#94=IFCRELASSOCIATESMATERIAL('3dGyQf42H2ahch2bSYPPHP',$,$,$,(#92),#93);
|
||||
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
|
||||
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#96=IFCWINDOWTYPE('0bK3c4PWL3eOMNwkPN$rlg',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
|
||||
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
|
||||
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
|
||||
@@ -157,7 +157,7 @@ DATA;
|
||||
#150=IFCDIRECTION((0.,0.,1.));
|
||||
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
|
||||
#152=IFCREPRESENTATIONMAP(#151,#147);
|
||||
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#153=IFCDOORTYPE('2j0vIBgkH0ERnBCP0cpcCz',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
|
||||
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
|
||||
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
|
||||
@@ -214,7 +214,7 @@ DATA;
|
||||
#207=IFCDIRECTION((0.,0.,1.));
|
||||
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
|
||||
#209=IFCREPRESENTATIONMAP(#208,#204);
|
||||
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#210=IFCFURNITURETYPE('02XxQ_3oT0SPrmFPATrt7o',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
|
||||
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
|
||||
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
|
||||
@@ -957,35 +957,35 @@ DATA;
|
||||
#950=IFCDIRECTION((0.,0.,1.));
|
||||
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
|
||||
#952=IFCREPRESENTATIONMAP(#951,#947);
|
||||
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
|
||||
#953=IFCTYPEPRODUCT('0UrP0fLdD5OwzwD41aRKao',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('27lmSbeAXC08EWkEq8XdUG',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
|
||||
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
|
||||
#956=IFCTYPEPRODUCT('3IQrQOSFP0WfkuLk0ak7_0',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('23aN9DsdjFcfWq8KGSIVoN',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
|
||||
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
|
||||
#959=IFCTYPEPRODUCT('3wvbaiaIz8agQgDRzt01vz',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('1j7aFM1Rb9BRuKgUEn1U10',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
|
||||
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
|
||||
#962=IFCTYPEPRODUCT('0p5ZTfTnX9ZOywroa7Ffql',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('27smSHyiv39vhBRSwfDVCD',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
|
||||
#965=IFCTYPEPRODUCT('2EvrG9Vuf6t9etgMeWFuJ2',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('0gVTuDcZ5ByRdR3sZnEZUR',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
|
||||
#968=IFCTYPEPRODUCT('0IeM1ywXn1qhR_6NhB6N4s',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0t6c$uCeT9092GYkbm8hDS',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
|
||||
#971=IFCTYPEPRODUCT('1jZVbCwrTCGhZdKbH4uqTP',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('2uTJ11lF98GeN8pPIf340N',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
|
||||
#974=IFCTYPEPRODUCT('38zW9E1uH2ae$zat9KrieS',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2XQxgO16v9vxjvuIJpaPpG',$,'EPset_Annotation',$,(#976));
|
||||
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
|
||||
#977=IFCTYPEPRODUCT('3G2s7ZLzfDJh5J$2iIzHw9',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('0TcB8Gal96vebTrLWa5CEw',$,'EPset_Annotation',$,(#979));
|
||||
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
|
||||
#980=IFCTYPEPRODUCT('3pq2B$0k52ZOv2bFuRYkxO',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('39$oNFI052cBhtpZCVVLfj',$,'EPset_Annotation',$,(#982));
|
||||
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
|
||||
#983=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#984=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1005,8 +1005,8 @@ DATA;
|
||||
#998=IFCDIRECTION((0.,0.,1.));
|
||||
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
|
||||
#1000=IFCREPRESENTATIONMAP(#999,#995);
|
||||
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
|
||||
#1001=IFCTYPEPRODUCT('11M3ahhrr9NBdB29YEazzw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('3xUNoLKPT9kB4LMdoctm5k',$,'EPset_Annotation',$,(#1003));
|
||||
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#1004=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1005=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1026,8 +1026,8 @@ DATA;
|
||||
#1019=IFCDIRECTION((0.,0.,1.));
|
||||
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
|
||||
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
|
||||
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
|
||||
#1022=IFCTYPEPRODUCT('1eE8Y$BVDFDgG8Fj6d9wiV',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2MqzSGkXDBcPSolaAQvYO4',$,'EPset_Annotation',$,(#1024));
|
||||
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#1025=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1026=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1041,8 +1041,8 @@ DATA;
|
||||
#1034=IFCDIRECTION((0.,0.,1.));
|
||||
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
|
||||
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
|
||||
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
|
||||
#1037=IFCTYPEPRODUCT('0vvfHSiaPBxewA$32C4LdW',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('2zKuFdTPj3DhNdQ0U2kd5u',$,'EPset_Annotation',$,(#1039));
|
||||
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#1040=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1041=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1068,8 +1068,8 @@ DATA;
|
||||
#1061=IFCDIRECTION((0.,0.,1.));
|
||||
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
|
||||
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
|
||||
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
|
||||
#1064=IFCTYPEPRODUCT('36lTcd9yT8UBNDejQUNWrq',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('1x86VNVk1Drwe5XOj$GZX3',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1067=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1068=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1083,8 +1083,8 @@ DATA;
|
||||
#1076=IFCDIRECTION((0.,0.,1.));
|
||||
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
|
||||
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
|
||||
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
|
||||
#1079=IFCTYPEPRODUCT('1rxhEX6I16oPqglXaGZ7Sw',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('3BeqEbDzX0xORamqqSx_kZ',$,'EPset_Annotation',$,(#1081));
|
||||
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1082=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1083=IFCDIRECTION((0.,0.,1.));
|
||||
@@ -1098,8 +1098,8 @@ DATA;
|
||||
#1091=IFCDIRECTION((0.,0.,1.));
|
||||
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
|
||||
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
|
||||
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
|
||||
#1094=IFCTYPEPRODUCT('0sws1hxNb2Og1etXqyoEh1',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('11L$PtdnX8LfYigzRZ$g0a',$,'EPset_Annotation',$,(#1096));
|
||||
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1097=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1098=IFCDIRECTION((0.,0.,1.));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -27,5 +27,6 @@ DATA;
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('28mJ$GDxb7kBbKFH_rACMQ',$,'AngleDecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.aggregate
|
||||
@@ -34,6 +35,7 @@ import blenderbim.core.spatial
|
||||
import blenderbim.core.style
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
from typing import Union
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -85,8 +87,8 @@ class IfcExporter:
|
||||
self.get_application_name(), tool.Blender.get_blenderbim_version()
|
||||
)
|
||||
|
||||
def sync_all_objects(self):
|
||||
results = []
|
||||
def sync_all_objects(self, skip_unlinking=False) -> list[ifcopenshell.entity_instance]:
|
||||
results: list[ifcopenshell.entity_instance] = []
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
for ifc_definition_id in list(IfcStore.id_map.keys()):
|
||||
obj = IfcStore.id_map[ifc_definition_id]
|
||||
@@ -95,19 +97,21 @@ class IfcExporter:
|
||||
continue
|
||||
if obj.library:
|
||||
continue
|
||||
tool.Collector.sync(obj)
|
||||
tool.Collector.sync(obj, skip_unlinking)
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
result = self.sync_object_material(obj)
|
||||
# TODO: sync_object_material always returns None
|
||||
# so it's never really appended
|
||||
if result:
|
||||
results.append(result)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
return results
|
||||
|
||||
def sync_edited_objects(self):
|
||||
results = []
|
||||
def sync_edited_objects(self) -> list[ifcopenshell.entity_instance]:
|
||||
results: list[ifcopenshell.entity_instance] = []
|
||||
for obj in IfcStore.edited_objs.copy():
|
||||
if not obj:
|
||||
continue
|
||||
@@ -125,7 +129,7 @@ class IfcExporter:
|
||||
IfcStore.edited_objs.clear()
|
||||
return results
|
||||
|
||||
def sync_object_material(self, obj):
|
||||
def sync_object_material(self, obj: bpy.types.Object) -> None:
|
||||
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
if not self.has_changed_materials(obj):
|
||||
@@ -136,11 +140,10 @@ class IfcExporter:
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
return element
|
||||
@@ -151,7 +154,7 @@ class IfcExporter:
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
return element
|
||||
|
||||
def sync_grid_axis_object_placement(self, obj, element):
|
||||
def sync_grid_axis_object_placement(self, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
|
||||
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
if grid_obj:
|
||||
|
||||
@@ -31,6 +31,7 @@ from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
|
||||
from mathutils import Vector
|
||||
from math import cos, degrees
|
||||
from typing import Union
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -191,6 +192,8 @@ def refresh_ui_data():
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
@@ -254,60 +257,6 @@ def viewport_shading_changed_callback(area):
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
@classmethod
|
||||
def poll_check_blender_tab(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
def get_override_scene_panel(panel_name):
|
||||
original_panel = getattr(bpy.types, panel_name)
|
||||
|
||||
override_panel = type(f"Override_{panel_name}", (original_panel,), {})
|
||||
override_panel.bl_idname = f"{panel_name}_override"
|
||||
|
||||
# some blender panel depend on other and register_class will throw an error
|
||||
# if we won't override their `bl_parent_id`
|
||||
bl_parent_id = getattr(override_panel, "bl_parent_id", None)
|
||||
if bl_parent_id is not None:
|
||||
override_panel.bl_parent_id = f"{bl_parent_id}_override"
|
||||
|
||||
# override poll method
|
||||
poll = getattr(override_panel, "poll", None)
|
||||
if poll is None:
|
||||
override_panel.poll = poll_check_blender_tab
|
||||
else:
|
||||
|
||||
@classmethod
|
||||
def wrapped_poll(cls, context):
|
||||
return super(override_panel, cls).poll(context) and poll_check_blender_tab.__func__(cls, context)
|
||||
|
||||
override_panel.poll = wrapped_poll
|
||||
|
||||
return override_panel
|
||||
|
||||
|
||||
# TODO: possibly override scene panels from other addons too?
|
||||
# list can be updated from
|
||||
# https://projects.blender.org/blender/blender/src/branch/main/scripts/startup/bl_ui/properties_scene.py#L421
|
||||
OVERRIDE_SCENE_PANELS = (
|
||||
"SCENE_PT_scene",
|
||||
"SCENE_PT_unit",
|
||||
"SCENE_PT_physics",
|
||||
"SCENE_PT_rigid_body_world",
|
||||
"SCENE_PT_rigid_body_world_settings",
|
||||
"SCENE_PT_rigid_body_cache",
|
||||
"SCENE_PT_rigid_body_field_weights",
|
||||
"SCENE_PT_audio",
|
||||
"SCENE_PT_keying_sets",
|
||||
"SCENE_PT_custom_props",
|
||||
# after SCENE_PT_keying_sets
|
||||
"SCENE_PT_keying_set_paths",
|
||||
"SCENE_PT_keyframing_settings",
|
||||
)
|
||||
if bpy.app.version >= (4, 0):
|
||||
OVERRIDE_SCENE_PANELS += ("SCENE_PT_simulation",)
|
||||
|
||||
|
||||
@persistent
|
||||
def load_post(scene):
|
||||
global global_subscription_owner
|
||||
@@ -343,15 +292,11 @@ def load_post(scene):
|
||||
else:
|
||||
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
|
||||
|
||||
# To improve usability for new users, we hijack the scene properties
|
||||
# tab. We override default scene properties panels with our own poll
|
||||
# to hide them unless the user has chosen to view Blender properties.
|
||||
for panel in OVERRIDE_SCENE_PANELS:
|
||||
if panel in blenderbim.bim.overridden_scene_panels:
|
||||
continue
|
||||
override_panel = get_override_scene_panel(panel)
|
||||
original_panel = getattr(bpy.types, panel)
|
||||
bpy.utils.register_class(override_panel)
|
||||
bpy.utils.unregister_class(original_panel)
|
||||
blenderbim.bim.overridden_scene_panels[panel] = (original_panel, override_panel)
|
||||
# To improve usability for new users, we hijack the scene properties
|
||||
# tab. We override default scene properties panels with our own poll
|
||||
# to hide them unless the user has chosen to view Blender properties.
|
||||
for panel in tool.Blender.get_scene_panels_list():
|
||||
if panel in blenderbim.bim.original_scene_panels_polls:
|
||||
continue
|
||||
tool.Blender.override_scene_panel(panel)
|
||||
tool.Blender.setup_tabs()
|
||||
|
||||
@@ -29,6 +29,7 @@ from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from typing import Optional, Callable, Any
|
||||
|
||||
|
||||
def draw_attributes(props, layout, copy_operator=None, popup_active_attribute=None):
|
||||
@@ -75,6 +76,10 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
if attribute.name == "GlobalId":
|
||||
layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = attribute.name
|
||||
@@ -116,7 +121,7 @@ def import_attribute(attribute, props, data, callback=None):
|
||||
elif is_handled_by_callback is False:
|
||||
props.remove(len(props) - 1)
|
||||
elif data_type == "string":
|
||||
new.string_value = "" if new.is_null else str(data[attribute.name()])
|
||||
new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
elif data_type == "boolean":
|
||||
@@ -178,7 +183,7 @@ def add_attribute_description(attribute_blender, attribute_ifc=None):
|
||||
attribute_blender.description = description
|
||||
|
||||
|
||||
def export_attributes(props, callback=None):
|
||||
def export_attributes(props, callback: Optional[Callable] = None) -> dict[str, Any]:
|
||||
attributes = {}
|
||||
for prop in props:
|
||||
is_handled_by_callback = callback(attributes, prop) if callback else False
|
||||
@@ -216,39 +221,6 @@ def get_enum_items(data, prop_name, context=None):
|
||||
return items
|
||||
|
||||
|
||||
def get_obj_ifc_definition_id(context, obj, obj_type):
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "Material":
|
||||
return bpy.data.materials.get(obj).BIMObjectProperties.ifc_definition_id
|
||||
elif obj_type == "MaterialSet":
|
||||
return ifcopenshell.util.element.get_material(
|
||||
tool.Ifc.get_entity(bpy.data.objects.get(obj)), should_skip_usage=True
|
||||
).id()
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Cost":
|
||||
return context.scene.BIMCostProperties.cost_items[
|
||||
context.scene.BIMCostProperties.active_cost_item_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Resource":
|
||||
return context.scene.BIMResourceTreeProperties.resources[
|
||||
context.scene.BIMResourceProperties.active_resource_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "Profile":
|
||||
return context.scene.BIMProfileProperties.profiles[
|
||||
context.scene.BIMProfileProperties.active_profile_index
|
||||
].ifc_definition_id
|
||||
elif obj_type == "WorkSchedule":
|
||||
return context.scene.BIMWorkScheduleProperties.active_work_schedule_id
|
||||
elif obj_type == "Group":
|
||||
prop = context.scene.BIMGroupProperties
|
||||
return prop.groups[prop.active_group_index].ifc_definition_id
|
||||
|
||||
# hack to close popup
|
||||
# https://blender.stackexchange.com/a/202576/130742
|
||||
def close_operator_panel(event):
|
||||
@@ -304,6 +276,9 @@ def draw_filter(layout, filter_groups, data, module):
|
||||
op.type = sprops.facet
|
||||
op.index = i
|
||||
op.module = module
|
||||
op = row.operator("bim.remove_filter_group", text="", icon="X")
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
for j, ifc_filter in enumerate(filter_group.filters):
|
||||
if ifc_filter.type == "entity":
|
||||
@@ -350,11 +325,6 @@ def draw_filter(layout, filter_groups, data, module):
|
||||
op.index = j
|
||||
op.module = module
|
||||
|
||||
row = box.row()
|
||||
op = row.operator("bim.remove_filter_group", icon="X")
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
@@ -23,33 +23,39 @@ import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
from typing import Set, Union, Optional
|
||||
|
||||
|
||||
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
|
||||
|
||||
class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
schema = None
|
||||
cache = None
|
||||
cache_path = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
classification_path = ""
|
||||
classification_file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
path: str = ""
|
||||
file: ifcopenshell.file = None
|
||||
schema: ifcopenshell.ifcopenshell_wrapper.schema_definition = None
|
||||
cache: ifcopenshell.ifcopenshell_wrapper.HdfSerializer = None
|
||||
cache_path: str = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: ifcopenshell.file = None
|
||||
classification_path: str = ""
|
||||
classification_file: ifcopenshell.file = None
|
||||
library_path: str = ""
|
||||
library_file: ifcopenshell.file = None
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3", "IFC4X3"]
|
||||
session_files = {}
|
||||
session_files: dict[str, ifcopenshell.file] = {}
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
@@ -80,8 +86,8 @@ class IfcStore:
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Failed to load file {IfcStore.path}. Error details: {e}")
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
@@ -117,7 +123,7 @@ class IfcStore:
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
def load_file(path) -> None:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
@@ -146,7 +152,7 @@ class IfcStore:
|
||||
return IfcStore.schema
|
||||
|
||||
@staticmethod
|
||||
def get_element(id_or_guid):
|
||||
def get_element(id_or_guid: Union[int, str]) -> IFC_CONNECTED_TYPE:
|
||||
if isinstance(id_or_guid, int):
|
||||
map_object = IfcStore.id_map
|
||||
else:
|
||||
@@ -159,7 +165,7 @@ class IfcStore:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
def relink_all_objects() -> None:
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
for obj in bpy.data.objects:
|
||||
@@ -172,7 +178,7 @@ class IfcStore:
|
||||
IfcStore.relink_object(obj)
|
||||
|
||||
@staticmethod
|
||||
def relink_object(obj):
|
||||
def relink_object(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if not obj:
|
||||
return
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -193,7 +199,7 @@ class IfcStore:
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
def link_element(element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
# Please use tool.Ifc.link() instead of this method. We want to
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
@@ -259,7 +265,7 @@ class IfcStore:
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def rollback_unlink_element(data):
|
||||
def rollback_unlink_element(data) -> None:
|
||||
if "id" not in data or "obj" not in data:
|
||||
return
|
||||
obj = bpy.data.objects.get(data["obj"])
|
||||
@@ -268,13 +274,15 @@ class IfcStore:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
|
||||
@staticmethod
|
||||
def commit_unlink_element(data):
|
||||
def commit_unlink_element(data) -> None:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if data["guid"]:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(element=None, obj=None):
|
||||
def unlink_element(
|
||||
element: Optional[ifcopenshell.entity_instance] = None, obj: Optional[IFC_CONNECTED_TYPE] = None
|
||||
) -> None:
|
||||
if element is None:
|
||||
try:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -320,7 +328,7 @@ class IfcStore:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator, context, is_invoke=False):
|
||||
def execute_ifc_operator(operator: bpy.types.Operator, context: bpy.types.Context, is_invoke=False):
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
@@ -354,17 +362,17 @@ class IfcStore:
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
def begin_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@staticmethod
|
||||
def end_transaction(operator):
|
||||
def end_transaction(operator: bpy.types.Operator) -> None:
|
||||
IfcStore.current_transaction = ""
|
||||
operator.transaction_key = ""
|
||||
|
||||
@staticmethod
|
||||
def add_transaction_operation(operator, rollback=None, commit=None):
|
||||
def add_transaction_operation(operator: bpy.types.Operator, rollback=None, commit=None) -> None:
|
||||
key = getattr(operator, "transaction_key", None)
|
||||
data = getattr(operator, "transaction_data", None)
|
||||
bpy.context.scene.BIMProperties.last_transaction = key
|
||||
@@ -380,7 +388,7 @@ class IfcStore:
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo(until_key=None):
|
||||
def undo(until_key=None) -> None:
|
||||
BrickStore.undo()
|
||||
if not IfcStore.history:
|
||||
return
|
||||
@@ -395,7 +403,7 @@ class IfcStore:
|
||||
IfcStore.future.append(event)
|
||||
|
||||
@staticmethod
|
||||
def redo(until_key=None):
|
||||
def redo(until_key=None) -> None:
|
||||
BrickStore.redo()
|
||||
|
||||
if not IfcStore.future:
|
||||
|
||||
@@ -32,23 +32,28 @@ import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from blenderbim.tool.loader import OBJECT_DATA_TYPE
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
from typing import Dict, Union, Optional, Any
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
|
||||
self.mesh = None
|
||||
self.obj = None
|
||||
self.materials = {}
|
||||
self.styles = {}
|
||||
self.parsed_meshes = set()
|
||||
self.mesh: bpy.types.Mesh = None
|
||||
self.obj: bpy.types.Object = None
|
||||
self.materials: Dict[int, bpy.types.Material] = {}
|
||||
self.styles: Dict[int, bpy.types.Material] = {}
|
||||
self.parsed_meshes: set[str] = set()
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.ifc_importer = ifc_importer
|
||||
|
||||
def create(self, element, obj, mesh):
|
||||
def create(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object, mesh: bpy.types.Mesh) -> None:
|
||||
self.mesh = mesh
|
||||
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
|
||||
self.obj = obj
|
||||
@@ -64,7 +69,7 @@ class MaterialCreator:
|
||||
self.assign_material_slots_to_faces()
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
def add_default_material(self, element):
|
||||
def add_default_material(self, element: ifcopenshell.entity_instance) -> None:
|
||||
element_material = ifcopenshell.util.element.get_material(element)
|
||||
if not element_material:
|
||||
return
|
||||
@@ -82,14 +87,17 @@ class MaterialCreator:
|
||||
self.mesh.materials.append(self.materials[material.id()])
|
||||
return
|
||||
|
||||
def load_existing_materials(self):
|
||||
def load_existing_materials(self) -> None:
|
||||
for material in bpy.data.materials:
|
||||
if material.BIMObjectProperties.ifc_definition_id:
|
||||
self.materials[material.BIMObjectProperties.ifc_definition_id] = material
|
||||
if material.BIMMaterialProperties.ifc_style_id:
|
||||
self.styles[material.BIMMaterialProperties.ifc_style_id] = material
|
||||
|
||||
def parse_representations(self, element):
|
||||
def parse_representations(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
"""Search for styles in in all `element`'s representation items
|
||||
and adds them to `self.mesh.materials`.\n
|
||||
Returns `True` if any styles were found and added, returns `False` otherwise."""
|
||||
has_parsed = False
|
||||
if hasattr(element, "Representation"):
|
||||
for representation in element.Representation.Representations:
|
||||
@@ -103,7 +111,7 @@ class MaterialCreator:
|
||||
has_parsed = True
|
||||
return has_parsed
|
||||
|
||||
def parse_representation(self, representation):
|
||||
def parse_representation(self, representation: ifcopenshell.entity_instance) -> bool:
|
||||
has_parsed = False
|
||||
representation_items = self.resolve_all_stylable_representation_items(representation)
|
||||
for item in representation_items:
|
||||
@@ -111,9 +119,9 @@ class MaterialCreator:
|
||||
has_parsed = True
|
||||
return has_parsed
|
||||
|
||||
def parse_representation_item(self, item):
|
||||
def parse_representation_item(self, item: ifcopenshell.entity_instance) -> bool:
|
||||
if not item.StyledByItem:
|
||||
return
|
||||
return False
|
||||
style_ids = []
|
||||
styles = list(item.StyledByItem[0].Styles)
|
||||
while styles:
|
||||
@@ -123,11 +131,12 @@ class MaterialCreator:
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
styles.extend(style.Styles)
|
||||
if not style_ids:
|
||||
return
|
||||
return False
|
||||
for style_id in style_ids:
|
||||
material = self.styles[style_id]
|
||||
|
||||
def get_ifc_coordinate(material):
|
||||
def get_ifc_coordinate(material: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""returns IfcTextureCoordinate"""
|
||||
texture_style = tool.Style.get_texture_style(material)
|
||||
if not texture_style:
|
||||
return
|
||||
@@ -148,7 +157,7 @@ class MaterialCreator:
|
||||
self.mesh.materials.append(material)
|
||||
return True
|
||||
|
||||
def assign_material_slots_to_faces(self):
|
||||
def assign_material_slots_to_faces(self) -> None:
|
||||
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
|
||||
return
|
||||
if len(self.mesh.materials) == 1:
|
||||
@@ -156,8 +165,12 @@ class MaterialCreator:
|
||||
material_to_slot = {}
|
||||
|
||||
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
|
||||
for i, material in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.mesh.materials.find(self.styles[material].name)
|
||||
for i, style_or_material_id in enumerate(self.mesh["ios_materials"]):
|
||||
if style_or_material_id in self.styles:
|
||||
blender_material = self.styles[style_or_material_id]
|
||||
else:
|
||||
blender_material = self.materials[style_or_material_id]
|
||||
slot_index = self.mesh.materials.find(blender_material.name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
material_index = [
|
||||
@@ -165,7 +178,10 @@ class MaterialCreator:
|
||||
]
|
||||
self.mesh.polygons.foreach_set("material_index", material_index)
|
||||
|
||||
def resolve_all_stylable_representation_items(self, representation):
|
||||
def resolve_all_stylable_representation_items(
|
||||
self, representation: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""returns list of resolved IfcRepresentationItems"""
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
@@ -183,24 +199,30 @@ class MaterialCreator:
|
||||
|
||||
|
||||
class IfcImporter:
|
||||
def __init__(self, ifc_import_settings):
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings):
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.diff = None
|
||||
self.file = None
|
||||
self.context_settings = []
|
||||
self.gross_context_settings = []
|
||||
self.file: ifcopenshell.file = None
|
||||
self.context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.contexts = []
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
self.elements = set()
|
||||
self.type_collection = None
|
||||
# element guids to blender collections mapping
|
||||
self.collections: dict[str, bpy.types.Collection] = {}
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.annotations: set[ifcopenshell.entity_instance] = set()
|
||||
self.gross_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.element_types: set[ifcopenshell.entity_instance] = set()
|
||||
self.spatial_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.type_collection: bpy.types.Collection = None
|
||||
self.type_products = {}
|
||||
self.meshes = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
self.unit_scale = 1
|
||||
self.added_data = {}
|
||||
# ifc definition ids to blender elements mapping
|
||||
self.added_data: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
self.native_elements = set()
|
||||
self.native_data = {}
|
||||
self.progress = 0
|
||||
@@ -439,7 +461,9 @@ class IfcImporter:
|
||||
for element in self.gross_elements:
|
||||
print(element)
|
||||
|
||||
def get_spatial_elements_filtered_by_elements(self, elements):
|
||||
def get_spatial_elements_filtered_by_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance]
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
leaf_spatial_elements = set([ifcopenshell.util.element.get_container(e) for e in elements])
|
||||
results = set()
|
||||
for spatial_element in leaf_spatial_elements:
|
||||
@@ -701,7 +725,7 @@ class IfcImporter:
|
||||
if len(subelement.Coordinates) == 3 and self.is_point_far_away(subelement, is_meters=False):
|
||||
return True
|
||||
|
||||
def apply_blender_offset_to_matrix_world(self, obj, matrix):
|
||||
def apply_blender_offset_to_matrix_world(self, obj: bpy.types.Object, matrix: np.ndarray) -> mathutils.Matrix:
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
if obj.data and obj.data.get("has_cartesian_point_offset", None):
|
||||
@@ -775,7 +799,7 @@ class IfcImporter:
|
||||
continue
|
||||
self.create_element_type(element_type)
|
||||
|
||||
def create_element_type(self, element):
|
||||
def create_element_type(self, element: ifcopenshell.entity_instance) -> None:
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
mesh = None
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
@@ -832,17 +856,23 @@ class IfcImporter:
|
||||
self.create_product(element, mesh=mesh)
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self):
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
def create_spatial_elements(self) -> None:
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.spatial_elements_unselectable:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=True)
|
||||
else:
|
||||
self.create_generic_elements(self.spatial_elements, unselectable=False)
|
||||
|
||||
def create_elements(self):
|
||||
|
||||
def create_elements(self) -> None:
|
||||
self.create_generic_elements(self.elements)
|
||||
tmp = self.context_settings
|
||||
self.context_settings = self.gross_context_settings
|
||||
self.create_generic_elements(self.gross_elements)
|
||||
self.context_settings = tmp
|
||||
|
||||
def create_generic_shape(self, element):
|
||||
def create_generic_shape(
|
||||
self, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell_wrapper.TriangulationElement, None]:
|
||||
for settings in self.context_settings:
|
||||
try:
|
||||
result = ifcopenshell.geom.create_shape(settings, element)
|
||||
@@ -851,7 +881,7 @@ class IfcImporter:
|
||||
except:
|
||||
pass
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
def create_generic_elements(self, elements: set[ifcopenshell.entity_instance], unselectable=False) -> None:
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
@@ -865,12 +895,17 @@ class IfcImporter:
|
||||
elements -= products
|
||||
|
||||
total = len(elements)
|
||||
objects = set()
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
self.create_product(element)
|
||||
objects.add(self.create_product(element))
|
||||
|
||||
def create_generic_sqlite_elements(self, elements):
|
||||
if unselectable:
|
||||
for obj in objects:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
for geometry_id, geometry in self.geometry_cache["geometry"].items():
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
@@ -917,7 +952,11 @@ class IfcImporter:
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
def create_products(
|
||||
self,
|
||||
products: set[ifcopenshell.entity_instance],
|
||||
settings: Optional[ifcopenshell.geom.main.settings] = None,
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
results = set()
|
||||
if not products:
|
||||
return results
|
||||
@@ -1040,7 +1079,7 @@ class IfcImporter:
|
||||
return mapped_representation
|
||||
return None
|
||||
|
||||
def create_pointclouds(self, products):
|
||||
def create_pointclouds(self, products: set[ifcopenshell.entity_instance]) -> set[ifcopenshell.entity_instance]:
|
||||
result = set()
|
||||
for product in products:
|
||||
representation = self.get_pointcloud_representation(product)
|
||||
@@ -1051,7 +1090,9 @@ class IfcImporter:
|
||||
|
||||
return result
|
||||
|
||||
def create_pointcloud(self, product, representation):
|
||||
def create_pointcloud(
|
||||
self, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
@@ -1079,7 +1120,12 @@ class IfcImporter:
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
|
||||
def create_product(self, element, shape=None, mesh=None):
|
||||
def create_product(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Optional[Any] = None,
|
||||
mesh: Optional[OBJECT_DATA_TYPE] = None,
|
||||
) -> Union[bpy.types.Object, None]:
|
||||
if element is None:
|
||||
return
|
||||
|
||||
@@ -1124,6 +1170,9 @@ class IfcImporter:
|
||||
|
||||
return obj
|
||||
|
||||
def load_existing_meshes(self) -> None:
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes})
|
||||
|
||||
def get_representation_item_material_name(self, item):
|
||||
if not item.StyledByItem:
|
||||
return
|
||||
@@ -1530,18 +1579,19 @@ class IfcImporter:
|
||||
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
obj.hide_select = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
|
||||
|
||||
def create_collections(self):
|
||||
def create_collections(self) -> None:
|
||||
self.create_spatial_decomposition_collections()
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_aggregate_and_nest_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
pass
|
||||
|
||||
def create_spatial_decomposition_collections(self):
|
||||
def create_spatial_decomposition_collections(self) -> None:
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
|
||||
|
||||
@@ -1554,7 +1604,9 @@ class IfcImporter:
|
||||
tool.Loader.create_project_collection("Views")
|
||||
self.type_collection = tool.Loader.create_project_collection("Types")
|
||||
|
||||
def create_spatial_decomposition_collection(self, parent, related_objects):
|
||||
def create_spatial_decomposition_collection(
|
||||
self, parent: Union[bpy.types.Collection, bpy.types.Object], related_objects: list[ifcopenshell.entity_instance]
|
||||
) -> None:
|
||||
for element in related_objects:
|
||||
if element not in self.spatial_elements:
|
||||
continue
|
||||
@@ -1577,24 +1629,28 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.has_filter:
|
||||
rel_aggregates = set()
|
||||
for element in self.elements:
|
||||
if element.IsDecomposedBy:
|
||||
rel_aggregates.add(element.IsDecomposedBy[0])
|
||||
elif element.Decomposes:
|
||||
rel_aggregates.add(element.Decomposes[0])
|
||||
elif getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
|
||||
if [e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")]:
|
||||
rel_aggregates.add(element.IsNestedBy[0])
|
||||
elif getattr(element, "Nests", []):
|
||||
rel_aggregates.add(element.Nests[0])
|
||||
if decomposed_by := element.IsDecomposedBy:
|
||||
rel_aggregates.add(decomposed_by[0])
|
||||
elif decomposes := element.Decomposes:
|
||||
rel_aggregates.add(decomposes[0])
|
||||
elif nested_by := getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy
|
||||
if next((e for e in nested_by[0].RelatedObjects if not e.is_a("IfcPort")), None):
|
||||
rel_aggregates.add(nested_by[0])
|
||||
elif nests := getattr(element, "Nests", []):
|
||||
rel_aggregates.add(nests[0])
|
||||
else:
|
||||
rel_aggregates = [
|
||||
r
|
||||
for r in self.file.by_type("IfcRelAggregates")
|
||||
if r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType")
|
||||
if (relating_obj := r.RelatingObject).is_a("IfcElement") or relating_obj.is_a("IfcElementType")
|
||||
] + [
|
||||
r
|
||||
for r in self.file.by_type("IfcRelNests")
|
||||
if (r.RelatingObject.is_a("IfcElement") or r.RelatingObject.is_a("IfcElementType"))
|
||||
if (
|
||||
(relating_obj := r.RelatingObject).is_a("IfcElement")
|
||||
or relating_obj.is_a("IfcElementType")
|
||||
or (relating_obj.is_a("IfcPositioningElement") and not relating_obj.is_a("IfcGrid"))
|
||||
)
|
||||
and [e for e in r.RelatedObjects if not e.is_a("IfcPort")]
|
||||
]
|
||||
|
||||
@@ -1604,9 +1660,9 @@ class IfcImporter:
|
||||
self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION"
|
||||
return
|
||||
|
||||
aggregates = {}
|
||||
aggregates: dict[str, dict] = {}
|
||||
for rel_aggregate in rel_aggregates:
|
||||
element = rel_aggregate.RelatingObject
|
||||
element: ifcopenshell.entity_instance = rel_aggregate.RelatingObject
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
aggregates[element.GlobalId] = {"element": element, "collection": collection}
|
||||
self.collections[element.GlobalId] = collection
|
||||
@@ -1658,7 +1714,9 @@ class IfcImporter:
|
||||
continue
|
||||
self.create_style(style)
|
||||
|
||||
def create_style(self, style, blender_material=None):
|
||||
def create_style(
|
||||
self, style: ifcopenshell.entity_instance, blender_material: Optional[bpy.types.Material] = None
|
||||
) -> None:
|
||||
if not blender_material:
|
||||
name = style.Name or str(style.id())
|
||||
blender_material = bpy.data.materials.new(name)
|
||||
@@ -1675,18 +1733,20 @@ class IfcImporter:
|
||||
else:
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
def place_objects_in_collections(self) -> None:
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
if isinstance(obj, bpy.types.Object):
|
||||
self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj)
|
||||
|
||||
def place_object_in_collection(self, element, obj):
|
||||
def place_object_in_collection(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None:
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.place_object_in_decomposition_collection(element, obj)
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_decomposition_collection(self, element, obj):
|
||||
def place_object_in_decomposition_collection(
|
||||
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
|
||||
) -> None:
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
@@ -1703,10 +1763,12 @@ class IfcImporter:
|
||||
elif getattr(element, "Nests", None) and not element.is_a("IfcPort"):
|
||||
nest = ifcopenshell.util.element.get_nest(element)
|
||||
return self.collections[nest.GlobalId].objects.link(obj)
|
||||
else:
|
||||
return self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_spatial_decomposition_collection(self, element, obj):
|
||||
return self.place_object_in_spatial_decomposition_collection(element, obj)
|
||||
|
||||
def place_object_in_spatial_decomposition_collection(
|
||||
self, element: ifcopenshell.entity_instance, obj: bpy.types.Object
|
||||
) -> None:
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
@@ -1750,7 +1812,7 @@ class IfcImporter:
|
||||
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING":
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
def get_element_matrix(self, element: ifcopenshell.entity_instance) -> np.ndarray:
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
@@ -1760,7 +1822,7 @@ class IfcImporter:
|
||||
result[2][3] *= self.unit_scale
|
||||
return result
|
||||
|
||||
def scale_matrix(self, matrix):
|
||||
def scale_matrix(self, matrix: np.array) -> np.array:
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
matrix[2][3] *= self.unit_scale
|
||||
@@ -1792,7 +1854,7 @@ class IfcImporter:
|
||||
):
|
||||
return representation.Items[0].MappingTarget
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
def create_curve(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Curve:
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
@@ -1817,12 +1879,13 @@ class IfcImporter:
|
||||
polyline.points[-1].co = mathutils.Vector(v2)
|
||||
return curve
|
||||
|
||||
def create_mesh(self, element, shape):
|
||||
def create_mesh(self, element: ifcopenshell.entity_instance, shape) -> bpy.types.Mesh:
|
||||
try:
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
# shape is ifcopenshell_wrapper.TriangulationElement
|
||||
geometry: ifcopenshell_wrapper.Triangulation = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
geometry: ifcopenshell_wrapper.Triangulation = shape
|
||||
|
||||
mesh = bpy.data.meshes.new(tool.Loader.get_mesh_name(geometry))
|
||||
|
||||
@@ -1898,14 +1961,14 @@ class IfcImporter:
|
||||
|
||||
print(traceback.format_exc())
|
||||
|
||||
def a2p(self, o, z, x):
|
||||
def a2p(self, o: mathutils.Vector, z: mathutils.Vector, x: mathutils.Vector) -> mathutils.Matrix:
|
||||
y = z.cross(x)
|
||||
r = mathutils.Matrix((x, y, z, o))
|
||||
r.resize_4x4()
|
||||
r.transpose()
|
||||
return r
|
||||
|
||||
def get_axis2placement(self, plc):
|
||||
def get_axis2placement(self, plc: ifcopenshell.entity_instance) -> mathutils.Matrix:
|
||||
if plc.is_a("IfcAxis2Placement3D"):
|
||||
z = mathutils.Vector(plc.Axis.DirectionRatios if plc.Axis else (0, 0, 1))
|
||||
x = mathutils.Vector(plc.RefDirection.DirectionRatios if plc.RefDirection else (1, 0, 0))
|
||||
@@ -1925,7 +1988,7 @@ class IfcImporter:
|
||||
o = plc.LocalOrigin.Coordinates
|
||||
return self.a2p(o, z, x)
|
||||
|
||||
def get_local_placement(self, plc):
|
||||
def get_local_placement(self, plc: Optional[ifcopenshell.entity_instance] = None) -> mathutils.Matrix:
|
||||
if plc is None:
|
||||
return mathutils.Matrix()
|
||||
if plc.PlacementRelTo is None:
|
||||
@@ -1940,11 +2003,11 @@ class IfcImporter:
|
||||
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
|
||||
break
|
||||
|
||||
def link_element(self, element, obj):
|
||||
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
self.added_data[element.id()] = obj
|
||||
tool.Ifc.link(element, obj)
|
||||
|
||||
def set_matrix_world(self, obj, matrix_world):
|
||||
def set_matrix_world(self, obj: bpy.types.Object, matrix_world: mathutils.Matrix) -> None:
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
@@ -1988,7 +2051,7 @@ class IfcImportSettings:
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.should_setup_viewport_camera = True
|
||||
self.elements = set()
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.collection_mode = "DECOMPOSITION"
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -28,8 +28,11 @@ classes = (
|
||||
operator.BIM_OT_select_aggregate,
|
||||
operator.BIM_OT_select_parts,
|
||||
operator.BIM_OT_aggregate_unassign_object,
|
||||
operator.BIM_OT_break_link_to_other_aggregates,
|
||||
operator.BIM_OT_select_linked_aggregates,
|
||||
prop.BIMObjectAggregateProperties,
|
||||
ui.BIM_PT_aggregate,
|
||||
ui.BIM_PT_linked_aggregate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class AggregateData:
|
||||
"has_related_objects": cls.has_related_objects(),
|
||||
"total_parts": cls.total_parts(),
|
||||
"ifc_class": cls.ifc_class(),
|
||||
"total_linked_aggregate": cls.total_linked_aggregate(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -72,3 +73,26 @@ class AggregateData:
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
return element.is_a()
|
||||
|
||||
@classmethod
|
||||
def total_linked_aggregate(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return []
|
||||
if not element:
|
||||
return []
|
||||
|
||||
product_linked_agg_group = [
|
||||
r
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
|
||||
if not product_linked_agg_group:
|
||||
return []
|
||||
|
||||
total = len(product_linked_agg_group[0].RelatedObjects)
|
||||
return total
|
||||
|
||||
|
||||
@@ -44,7 +44,12 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
relating_obj = None
|
||||
if self.relating_object:
|
||||
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
|
||||
element = tool.Ifc.get().by_id(self.relating_object)
|
||||
if element.IsDecomposedBy:
|
||||
relating_obj = tool.Ifc.get_object(element)
|
||||
else:
|
||||
assembly = element.Decomposes[0].RelatingObject
|
||||
relating_obj = tool.Ifc.get_object(assembly)
|
||||
elif context.active_object:
|
||||
relating_obj = context.active_object
|
||||
if not relating_obj:
|
||||
@@ -90,6 +95,12 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, Operator):
|
||||
related_obj=tool.Ifc.get_object(element),
|
||||
)
|
||||
|
||||
# Removes Pset related to Linked Aggregates
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator, Operator):
|
||||
"""Enable editing aggregation relationship"""
|
||||
@@ -179,12 +190,17 @@ class BIM_OT_select_parts(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
|
||||
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
|
||||
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
|
||||
for selectable_part_obj in selectable_parts_objs:
|
||||
selectable_part_obj.select_set(True)
|
||||
# obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
|
||||
for obj in context.selected_objects:
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(obj))
|
||||
if parts:
|
||||
parts_objs = set(tool.Ifc.get_object(part) for part in parts)
|
||||
selectable_parts_objs = set(context.selectable_objects).intersection(parts_objs)
|
||||
for selectable_part_obj in selectable_parts_objs:
|
||||
selectable_part_obj.select_set(True)
|
||||
else:
|
||||
obj.select_set(False)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -195,15 +211,54 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
bl_label = "Select Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
select_parts: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.select_parts:
|
||||
return "Select Aggregate and Parts"
|
||||
else:
|
||||
return "Select Aggregate"
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
if aggregate_obj in context.selectable_objects:
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
|
||||
# obj = bpy.data.objects.get(self.obj) or context.active_object
|
||||
# aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(obj))
|
||||
# aggregate_obj = tool.Ifc.get_object(aggregate)
|
||||
|
||||
all_parts = []
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
all_parts.append(aggregate)
|
||||
obj.select_set(False)
|
||||
else:
|
||||
pass
|
||||
if not element:
|
||||
obj.select_set(False)
|
||||
|
||||
if self.select_parts:
|
||||
all_objs = []
|
||||
for part in all_parts:
|
||||
if part.IsDecomposedBy:
|
||||
for subpart in part.IsDecomposedBy[0].RelatedObjects:
|
||||
all_parts.append(subpart)
|
||||
all_objs.append(part)
|
||||
|
||||
for element in all_objs:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
|
||||
else:
|
||||
for aggregate_element in all_parts:
|
||||
aggregate_obj = tool.Ifc.get_object(aggregate_element)
|
||||
aggregate_obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = aggregate_obj
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -230,3 +285,88 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
|
||||
part_class=self.part_class,
|
||||
part_name=self.part_name,
|
||||
)
|
||||
|
||||
|
||||
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.break_link_to_other_aggregates"
|
||||
bl_label = "Break link to other aggregates"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
return []
|
||||
if not element:
|
||||
return []
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(aggregate)
|
||||
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group[0], products=[aggregate])
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_select_linked_aggregates(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_linked_aggregates"
|
||||
bl_label = "Select linked aggregates"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
select_parts: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.select_parts:
|
||||
return "Select all aggregates, subaggregates and all their parts"
|
||||
else:
|
||||
return "Select all aggregates"
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
obj.select_set(False)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
continue
|
||||
if not element:
|
||||
continue
|
||||
|
||||
linked_aggregate_group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
]
|
||||
|
||||
group_rel = linked_aggregate_group[0].IsGroupedBy or []
|
||||
for group_link in group_rel:
|
||||
parts = list(group_link.RelatedObjects)
|
||||
if self.select_parts:
|
||||
parts_objs = []
|
||||
for part in parts:
|
||||
if part.IsDecomposedBy:
|
||||
for subpart in part.IsDecomposedBy[0].RelatedObjects:
|
||||
parts.append(subpart)
|
||||
parts_objs.append(part)
|
||||
|
||||
for element in parts_objs:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
else:
|
||||
for element in parts:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.aggregate.data import AggregateData
|
||||
from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -61,8 +62,12 @@ class BIM_PT_aggregate(Panel):
|
||||
row = layout.row(align=True)
|
||||
if AggregateData.data["has_relating_object"]:
|
||||
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
|
||||
op = row.operator("bim.select_aggregate", icon="OBJECT_DATA", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.select_parts = False
|
||||
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.select_parts = True
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
op = row.operator("bim.aggregate_unassign_object", icon="X", text="")
|
||||
@@ -96,3 +101,52 @@ class BIM_PT_aggregate(Panel):
|
||||
op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc"))
|
||||
op.part_class = part_class
|
||||
op.obj = context.active_object.name
|
||||
|
||||
|
||||
class BIM_PT_linked_aggregate(Panel):
|
||||
bl_label = "Linked Aggregates"
|
||||
bl_idname = "BIM_PT_linked_aggregate"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 2
|
||||
bl_parent_id = "BIM_PT_aggregate"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Advanced Users Only", icon="ERROR")
|
||||
row = layout.row(align=True)
|
||||
|
||||
if type(AggregateData.data['total_linked_aggregate']) is int:
|
||||
if AggregateData.data['total_linked_aggregate'] > 0:
|
||||
row.label(text=f"{AggregateData.data['total_linked_aggregate']} Linked Aggregates")
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_DATA_POINTCLOUD")
|
||||
op.select_parts = False
|
||||
op = row.operator("bim.select_linked_aggregates", text="", icon="OUTLINER_OB_POINTCLOUD")
|
||||
op.select_parts = True
|
||||
row.operator("bim.refresh_linked_aggregate", text="", icon="FILE_REFRESH")
|
||||
op = row.operator("bim.break_link_to_other_aggregates", text="", icon="X")
|
||||
else:
|
||||
row.label(text="No Linked Aggregates")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,10 +47,18 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
elif self.obj_type == "Material":
|
||||
obj = bpy.data.materials.get(self.obj)
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
has_inherited_predefined_type = False
|
||||
if not element.is_a("IfcTypeObject") and (element_type := ifcopenshell.util.element.get_type(element)):
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
has_inherited_predefined_type = ifcopenshell.util.element.get_predefined_type(element_type) not in (
|
||||
"NOTDEFINED",
|
||||
None,
|
||||
)
|
||||
|
||||
def callback(name, prop, data):
|
||||
if name in ("RefLatitude", "RefLongitude"):
|
||||
new = props.attributes.add()
|
||||
@@ -62,10 +70,11 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
new.string_value = "" if new.is_null else json.dumps(data[name])
|
||||
blenderbim.bim.helper.add_attribute_description(new)
|
||||
new.description += " The degrees, minutes and seconds should follow this format : [12,34,56]"
|
||||
if name in ("PredefinedType", "ObjectType") and has_inherited_predefined_type:
|
||||
props.attributes.remove(len(props.attributes) - 1)
|
||||
return True
|
||||
|
||||
blenderbim.bim.helper.import_attributes2(
|
||||
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
|
||||
)
|
||||
blenderbim.bim.helper.import_attributes2(element, props.attributes, callback=callback)
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -120,20 +129,46 @@ class EditAttributes(bpy.types.Operator, Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GenerateGlobalId(bpy.types.Operator):
|
||||
class GenerateGlobalId(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.generate_global_id"
|
||||
bl_label = "Regenerate GlobalId"
|
||||
bl_description = "Regenerate GlobalId\n\nSHIFT+CLICK to regenerate GlobalIds for all selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
index = context.active_object.BIMAttributeProperties.attributes.find("GlobalId")
|
||||
if index >= 0:
|
||||
global_id = context.active_object.BIMAttributeProperties.attributes[index]
|
||||
use_selected: bpy.props.BoolProperty(name="Use All Selected Objects", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# using all selected objects on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.use_selected = True
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
if self.use_selected:
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcRoot"):
|
||||
continue
|
||||
element.GlobalId = ifcopenshell.guid.new()
|
||||
|
||||
obj = context.active_object
|
||||
if not obj or not obj.BIMAttributeProperties.is_editing_attributes:
|
||||
return {"FINISHED"}
|
||||
|
||||
props = obj.BIMAttributeProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if not element.is_a("IfcRoot"):
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.use_selected and obj in context.selected_objects:
|
||||
# guid value was already regenerated, just update the ui prop
|
||||
guid_value = element.GlobalId
|
||||
else:
|
||||
global_id = context.active_object.BIMAttributeProperties.attributes.add()
|
||||
global_id.name = "GlobalId"
|
||||
global_id.data_type = "string"
|
||||
global_id.string_value = ifcopenshell.guid.new()
|
||||
guid_value = ifcopenshell.guid.new()
|
||||
|
||||
props.attributes["GlobalId"].string_value = guid_value
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from . import ui, operator, prop
|
||||
classes = (
|
||||
operator.AddBoundary,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.DecorateBoundaries,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.DisableEditingBoundaryGeometry,
|
||||
operator.EditBoundaryAttributes,
|
||||
|
||||
@@ -24,6 +24,7 @@ import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.element
|
||||
@@ -37,6 +38,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.boundary.decorator import BoundaryDecorator
|
||||
import blenderbim.core
|
||||
import blenderbim.core.geometry
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
@@ -147,7 +149,9 @@ class Loader:
|
||||
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
self.ifc_importer.file = self.ifc_file
|
||||
|
||||
def load_boundary(self, boundary, blender_space=None):
|
||||
def load_boundary(
|
||||
self, boundary: ifcopenshell.entity_instance, blender_space: bpy.types.Object = None
|
||||
) -> bpy.types.Object:
|
||||
if not blender_space:
|
||||
if not boundary.RelatingSpace:
|
||||
self.operator.report(
|
||||
@@ -509,10 +513,10 @@ class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.show_boundaries"
|
||||
bl_label = "Show Boundaries"
|
||||
bl_description = "Load selected objects boundaries if they are not loaded"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMBoundaryProperties
|
||||
loader = Loader(self)
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -524,7 +528,7 @@ class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for rel in element.BoundedBy or []:
|
||||
boundary_obj = loader.load_boundary(rel, obj)
|
||||
tool.Boundary.decorate_boundary(boundary_obj)
|
||||
BoundaryDecorator.install(bpy.context)
|
||||
BoundaryDecorator.install(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -557,6 +561,36 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.decorate_boundaries"
|
||||
bl_label = "Toggle Boundaries Decorations"
|
||||
bl_description = "Add special decorations for all boundaries in the scene or hide them if they are already added"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBoundaryProperties
|
||||
# filter not decorated boundaries and add decorations for them
|
||||
decorated_boundaries = set([i.obj for i in props.boundaries])
|
||||
active_boundaries = set()
|
||||
for element in tool.Ifc.get().by_type("IfcRelSpaceBoundary"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj is not None:
|
||||
active_boundaries.add(obj)
|
||||
|
||||
boundaries_to_decorate = active_boundaries - decorated_boundaries
|
||||
if boundaries_to_decorate:
|
||||
for obj in boundaries_to_decorate:
|
||||
tool.Boundary.decorate_boundary(obj)
|
||||
BoundaryDecorator.install(context)
|
||||
else:
|
||||
for obj in decorated_boundaries:
|
||||
tool.Boundary.undecorate_boundary(obj)
|
||||
props.boundaries.clear()
|
||||
BoundaryDecorator.uninstall()
|
||||
tool.Blender.update_viewport()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boundary"
|
||||
bl_label = "Add Boundary"
|
||||
|
||||
@@ -37,7 +37,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.CadTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMCadProperties = bpy.props.PointerProperty(type=prop.BIMCadProperties)
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ from math import pi
|
||||
|
||||
class BIMCadProperties(PropertyGroup):
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
|
||||
x: bpy.props.FloatProperty(name="X", default=0.2)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0.1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE")
|
||||
x: bpy.props.FloatProperty(name="X", default=0.2, subtype="DISTANCE")
|
||||
y: bpy.props.FloatProperty(name="Y", default=0.1, subtype="DISTANCE")
|
||||
gable_roof_edge_angle: bpy.props.FloatProperty(
|
||||
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ import os
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.type.prop as type_prop
|
||||
import ifcopenshell.util.unit
|
||||
from bpy.types import WorkSpaceTool
|
||||
from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
|
||||
@@ -239,8 +240,9 @@ class CadHotkey(bpy.types.Operator):
|
||||
row.prop(props, "resolution")
|
||||
|
||||
def hotkey_S_C(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_2_points()
|
||||
|
||||
@@ -248,13 +250,15 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_F(self):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
|
||||
|
||||
def hotkey_S_O(self):
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
@@ -270,7 +274,8 @@ class CadHotkey(bpy.types.Operator):
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
|
||||
@@ -25,15 +25,12 @@ classes = (
|
||||
operator.ExecuteIfcClash,
|
||||
operator.ExportClashSets,
|
||||
operator.ImportClashSets,
|
||||
operator.LoadIfcClashes,
|
||||
operator.SaveIfcClashes,
|
||||
operator.LoadSmartGroupsForActiveClashSet,
|
||||
operator.RemoveClashSet,
|
||||
operator.RemoveClashSource,
|
||||
operator.SelectClash,
|
||||
operator.SelectClashResults,
|
||||
operator.SelectClashSource,
|
||||
operator.SelectIfcClashResults,
|
||||
operator.SelectSmartGroup,
|
||||
operator.SelectSmartGroupedClashesPath,
|
||||
operator.SmartClashGroup,
|
||||
@@ -44,7 +41,6 @@ classes = (
|
||||
prop.BIMClashProperties,
|
||||
ui.BIM_PT_ifcclash,
|
||||
ui.BIM_PT_clash_manager,
|
||||
ui.BIM_PT_dumb_clash_manager,
|
||||
ui.BIM_PT_smart_clash_manager,
|
||||
ui.BIM_UL_clashes,
|
||||
ui.BIM_UL_clash_sets,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
ClashData.is_loaded = False
|
||||
|
||||
|
||||
class ClashData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {}
|
||||
cls.data["saved_searches"] = cls.saved_searches()
|
||||
|
||||
@classmethod
|
||||
def saved_searches(cls):
|
||||
if not tool.Ifc.get():
|
||||
return []
|
||||
groups = tool.Ifc.get().by_type("IfcGroup")
|
||||
results = []
|
||||
for group in groups:
|
||||
try:
|
||||
data = json.loads(group.Description)
|
||||
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
|
||||
results.append(group)
|
||||
except:
|
||||
pass
|
||||
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
|
||||
@@ -0,0 +1,102 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 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 blf
|
||||
import gpu
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
|
||||
|
||||
class ClashDecorator:
|
||||
is_installed = False
|
||||
handlers = []
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.is_installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_geometry, (context,), "WINDOW", "POST_VIEW"))
|
||||
cls.is_installed = True
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
for handler in cls.handlers:
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.is_installed = False
|
||||
|
||||
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_text(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].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
|
||||
|
||||
text = context.scene.BIMClashProperties.active_clash_text
|
||||
p = context.scene.BIMClashProperties.p1.lerp(context.scene.BIMClashProperties.p2, 0.5)
|
||||
|
||||
font_id = 0
|
||||
blf.size(font_id, 12)
|
||||
coords_2d = location_3d_to_region_2d(context.region, context.region_data, p)
|
||||
color = self.addon_prefs.decorations_colour
|
||||
blf.color(font_id, *color)
|
||||
if coords_2d:
|
||||
w, h = blf.dimensions(font_id, text)
|
||||
coords_2d -= Vector((w * .5, 0))
|
||||
blf.position(font_id, coords_2d[0], coords_2d[1], 0)
|
||||
blf.draw(font_id, text) # Set your text here
|
||||
|
||||
def draw_geometry(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].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
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# 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")
|
||||
|
||||
selected_vertices = [context.scene.BIMClashProperties.p1, context.scene.BIMClashProperties.p2]
|
||||
selected_edges = []
|
||||
if selected_vertices[0] != selected_vertices[1]:
|
||||
selected_edges = [[0, 1]]
|
||||
|
||||
self.draw_batch("POINTS", selected_vertices, special_elements_color)
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, special_elements_color, selected_edges)
|
||||
@@ -23,10 +23,11 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
from math import radians
|
||||
from mathutils import Matrix, Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.clash.decorator import ClashDecorator
|
||||
|
||||
|
||||
class ExportClashSets(bpy.types.Operator):
|
||||
@@ -45,18 +46,7 @@ class ExportClashSets(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
clash_sets = []
|
||||
for clash_set in context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
for data in getattr(clash_set, ab):
|
||||
clash_source = {"file": data.name}
|
||||
if data.selector:
|
||||
clash_source["selector"] = data.selector
|
||||
clash_source["mode"] = data.mode
|
||||
getattr(self, ab).append(clash_source)
|
||||
clash_sets.append({"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a, "b": self.b})
|
||||
clash_sets = tool.Clash.export_clash_sets()
|
||||
with open(self.filepath, "w") as destination:
|
||||
destination.write(json.dumps(clash_sets, indent=4))
|
||||
return {"FINISHED"}
|
||||
@@ -78,25 +68,34 @@ class ImportClashSets(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
for clash_set in clash_sets:
|
||||
tool.Clash.load_clash_sets(self.filepath)
|
||||
context.scene.BIMClashProperties.clash_sets.clear()
|
||||
for clash_set in tool.Clash.get_clash_sets():
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = clash_set["name"]
|
||||
new.tolerance = clash_set["tolerance"]
|
||||
new.mode = clash_set["mode"]
|
||||
if new.mode == "intersection":
|
||||
new.tolerance = clash_set["tolerance"]
|
||||
new.check_all = clash_set["check_all"]
|
||||
elif new.mode == "collision":
|
||||
new.allow_touching = clash_set["allow_touching"]
|
||||
elif new.mode == "clearance":
|
||||
new.clearance = clash_set["clearance"]
|
||||
new.check_all = clash_set["check_all"]
|
||||
for clash_source in clash_set["a"]:
|
||||
new_source = new.a.add()
|
||||
new_source.name = clash_source["file"]
|
||||
if "selector" in clash_source:
|
||||
new_source.selector = clash_source["selector"]
|
||||
tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
|
||||
new_source.mode = clash_source["mode"]
|
||||
if clash_set["b"]:
|
||||
if "b" in clash_set and clash_set["b"]:
|
||||
for clash_source in clash_set["b"]:
|
||||
new_source = new.b.add()
|
||||
new_source.name = clash_source["file"]
|
||||
if "selector" in clash_source:
|
||||
new_source.selector = clash_source["selector"]
|
||||
tool.Search.import_filter_query(clash_source["selector"], new_source.filter_groups)
|
||||
new_source.mode = clash_source["mode"]
|
||||
tool.Clash.import_active_clashes()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -109,7 +108,6 @@ class AddClashSet(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = "New Clash Set"
|
||||
new.tolerance = 0.01
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -211,8 +209,11 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
if extension != ".bcf":
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
# TODO Temporarily until BCF support comes back
|
||||
# if extension != ".json":
|
||||
# self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -220,7 +221,12 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
from ifcclash import ifcclash
|
||||
|
||||
self.props = context.scene.BIMClashProperties
|
||||
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".bcf":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
# TODO Temporarily until BCF support comes back
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
|
||||
@@ -230,7 +236,7 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
clasher = ifcclash.Clasher(settings)
|
||||
|
||||
if context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
if self.props.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
@@ -271,23 +277,13 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
|
||||
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
clasher.clash_sets = []
|
||||
for clash_set in context.scene.BIMClashProperties.clash_sets:
|
||||
self.a = []
|
||||
self.b = []
|
||||
for ab in ["a", "b"]:
|
||||
for data in getattr(clash_set, ab):
|
||||
clash_source = {"file": data.name}
|
||||
if data.selector:
|
||||
clash_source["selector"] = data.selector
|
||||
clash_source["mode"] = data.mode
|
||||
getattr(self, ab).append(clash_source)
|
||||
clash_set_data = {"name": clash_set.name, "tolerance": clash_set.tolerance, "a": self.a}
|
||||
if self.b:
|
||||
clash_set_data["b"] = self.b
|
||||
clasher.clash_sets.append(clash_set_data)
|
||||
clasher.clash_sets = tool.Clash.export_clash_sets()
|
||||
clasher.clash()
|
||||
clasher.export()
|
||||
|
||||
if extension == ".json":
|
||||
tool.Clash.load_clash_sets(self.filepath)
|
||||
tool.Clash.import_active_clashes()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -306,6 +302,7 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
# TODO refactor into new clash results system
|
||||
self.file = IfcStore.get_file()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
@@ -352,83 +349,6 @@ class SelectIfcClashResults(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadIfcClashes(bpy.types.Operator):
|
||||
bl_idname = "bim.load_ifc_clashes"
|
||||
bl_label = "Load IFC Clashes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load the clashing IFC geometry stored in a file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
clash_sets_json = json.load(f)
|
||||
active_clash_set = context.scene.BIMClashProperties.active_clash_set
|
||||
|
||||
for clash_set in clash_sets_json:
|
||||
if not "clashes" in clash_set.keys():
|
||||
self.report({"WARNING"}, "No clashes found for the selected Clash Set.")
|
||||
return {"CANCELLED"}
|
||||
active_clash_set.clashes.clear()
|
||||
for clash in clash_set["clashes"].values():
|
||||
blender_clash = active_clash_set.clashes.add()
|
||||
blender_clash.a_global_id = clash["a_global_id"]
|
||||
blender_clash.b_global_id = clash["b_global_id"]
|
||||
blender_clash.a_name = "{}/{}".format(clash["a_ifc_class"], clash["a_name"])
|
||||
blender_clash.b_name = "{}/{}".format(clash["b_ifc_class"], clash["b_name"])
|
||||
blender_clash.status = False if not "status" in clash.keys() else clash["status"]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveIfcClashes(bpy.types.Operator):
|
||||
bl_idname = "bim.save_ifc_clashes"
|
||||
bl_label = "Save IFC Clashes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save the clashing IFC geometry stored in a file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
clash_sets_json = self.load_json()
|
||||
active_clash_set = context.scene.BIMClashProperties.active_clash_set
|
||||
self.update_clash_sets(clash_sets_json, active_clash_set)
|
||||
self.save_json(clash_sets_json)
|
||||
return {"FINISHED"}
|
||||
|
||||
def load_json(self):
|
||||
with open(self.filepath) as f:
|
||||
return json.load(f)
|
||||
|
||||
def update_clash_sets(self, clash_sets_json, active_clash_set):
|
||||
for clash_set in clash_sets_json:
|
||||
if clash_set["name"] != active_clash_set.name or "clashes" not in clash_set.keys():
|
||||
continue
|
||||
for clash in active_clash_set.clashes:
|
||||
clash_key = clash.a_global_id + "-" + clash.b_global_id
|
||||
if clash_set.get("clashes", {}).get(clash_key, None) is not None:
|
||||
clash_set["clashes"][clash_key]["status"] = clash.status
|
||||
|
||||
def save_json(self, clash_sets_json):
|
||||
with open(self.filepath, "w") as destination:
|
||||
json.dump(clash_sets_json, destination, indent=4)
|
||||
|
||||
|
||||
class SelectClash(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash"
|
||||
bl_label = "Select Clash"
|
||||
@@ -437,31 +357,29 @@ class SelectClash(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
clash = context.scene.BIMClashProperties.active_clash
|
||||
self.props = context.scene.BIMClashProperties
|
||||
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
|
||||
active_clash = self.props.active_clash
|
||||
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
|
||||
|
||||
if not clash:
|
||||
return {"FINISHED"}
|
||||
|
||||
products = []
|
||||
linked_objects = []
|
||||
try:
|
||||
products.append(tool.Ifc.get().by_guid(clash.a_global_id))
|
||||
except:
|
||||
linked_objects.append(clash.a_name)
|
||||
try:
|
||||
products.append(tool.Ifc.get().by_guid(clash.b_global_id))
|
||||
except:
|
||||
linked_objects.append(clash.b_name)
|
||||
|
||||
for global_id in (clash["a_global_id"], clash["b_global_id"]):
|
||||
try:
|
||||
products.append(tool.Ifc.get().by_guid(global_id))
|
||||
except:
|
||||
pass
|
||||
|
||||
tool.Spatial.select_products(products, unhide=True)
|
||||
print(linked_objects)
|
||||
for obj_name in linked_objects:
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
else:
|
||||
print("Object not found:", obj_name)
|
||||
if context.scene.BIMClashProperties.sould_focus_on_clash:
|
||||
context_override = tool.Blender.get_viewport_context()
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.view3d.view_selected()
|
||||
ClashDecorator.install(bpy.context)
|
||||
target = Vector(clash["p1"])
|
||||
tool.Clash.look_at(target, target + Vector((5, 5, 5)))
|
||||
self.props.p1 = clash["p1"]
|
||||
self.props.p2 = clash["p2"]
|
||||
self.props.active_clash_text = clash["type"].title() + " " + str(round(clash["distance"] * 1000)) + "mm"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.prop import StrProperty, Attribute, BIMFilterGroup
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -33,11 +33,12 @@ from bpy.props import (
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
name: StringProperty(name="File")
|
||||
selector: StringProperty(name="Selector")
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
("i", "Include", "Only the selected objects are included for clashing"),
|
||||
("e", "Exclude", "All objects except the selected objects are included for clashing"),
|
||||
("a", "All Elements", "All elements will be used for clashing"),
|
||||
("i", "Include", "Only the selected elements are included for clashing"),
|
||||
("e", "Exclude", "All elements except the selected elements are included for clashing"),
|
||||
],
|
||||
name="Mode",
|
||||
)
|
||||
@@ -53,7 +54,24 @@ class Clash(PropertyGroup):
|
||||
|
||||
class ClashSet(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
tolerance: FloatProperty(name="Tolerance")
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
(
|
||||
"intersection",
|
||||
"Intersection",
|
||||
"Detect objects that protrude or pierce another object",
|
||||
"PIVOT_MEDIAN",
|
||||
1,
|
||||
),
|
||||
("collision", "Collision", "Detect touching objects with any surface collision", "PIVOT_INDIVIDUAL", 2),
|
||||
("clearance", "Clearance", "Detect objects within a proximity threshold", "PIVOT_ACTIVE", 3),
|
||||
],
|
||||
name="Mode",
|
||||
)
|
||||
tolerance: FloatProperty(name="Tolerance", default=0.002)
|
||||
clearance: FloatProperty(name="Clearance", default=0.01)
|
||||
allow_touching: BoolProperty(name="Allow Touching", default=False)
|
||||
check_all: BoolProperty(name="Check All", default=False)
|
||||
a: CollectionProperty(name="Group A", type=ClashSource)
|
||||
b: CollectionProperty(name="Group B", type=ClashSource)
|
||||
clashes: CollectionProperty(name="Clashes", type=Clash)
|
||||
@@ -78,22 +96,21 @@ class BIMClashProperties(PropertyGroup):
|
||||
smart_clash_grouping_max_distance: IntProperty(
|
||||
name="Smart Clash Grouping Max Distance", default=3, soft_min=1, soft_max=10
|
||||
)
|
||||
sould_focus_on_clash: BoolProperty(name="Show Focus Clash", default=False)
|
||||
p1: FloatVectorProperty(name="P1", default=(0.0, 0.0, 0.0), subtype="XYZ")
|
||||
p2: FloatVectorProperty(name="P2", default=(0.0, 0.0, 0.0), subtype="XYZ")
|
||||
active_clash_text: StringProperty(name="Active Clash Text")
|
||||
|
||||
@property
|
||||
def active_clash_set(self):
|
||||
if not self.clash_sets:
|
||||
return None
|
||||
return self.clash_sets[self.active_clash_set_index]
|
||||
if self.active_clash_set_index < len(self.clash_sets):
|
||||
return self.clash_sets[self.active_clash_set_index]
|
||||
|
||||
@property
|
||||
def active_smart_group(self):
|
||||
if not self.smart_clash_groups:
|
||||
return None
|
||||
return self.smart_clash_groups[self.active_smart_group_index]
|
||||
if self.active_smart_group_index < len(self.smart_clash_groups):
|
||||
return self.smart_clash_groups[self.active_smart_group_index]
|
||||
|
||||
@property
|
||||
def active_clash(self):
|
||||
if not self.active_clash_set:
|
||||
return None
|
||||
return self.active_clash_set.clashes[self.active_clash_index]
|
||||
if self.active_clash_index < len(self.active_clash_set.clashes):
|
||||
return self.active_clash_set.clashes[self.active_clash_index]
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.clash.data import ClashData
|
||||
|
||||
|
||||
class BIM_PT_ifcclash(Panel):
|
||||
@@ -30,6 +32,9 @@ class BIM_PT_ifcclash(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_clash_detection"
|
||||
|
||||
def draw(self, context):
|
||||
if not ClashData.is_loaded:
|
||||
ClashData.load()
|
||||
|
||||
layout = self.layout
|
||||
|
||||
scene = context.scene
|
||||
@@ -45,57 +50,83 @@ class BIM_PT_ifcclash(Panel):
|
||||
|
||||
layout.template_list("BIM_UL_clash_sets", "", props, "clash_sets", props, "active_clash_set_index")
|
||||
|
||||
if props.active_clash_set_index < len(props.clash_sets):
|
||||
clash_set = props.active_clash_set
|
||||
if not props.active_clash_set:
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(clash_set, "name")
|
||||
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
|
||||
clash_set = props.active_clash_set
|
||||
|
||||
row = layout.row(align=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(clash_set, "name")
|
||||
row.operator("bim.remove_clash_set", icon="X", text="").index = props.active_clash_set_index
|
||||
|
||||
row = layout.row()
|
||||
row.prop(clash_set, "mode")
|
||||
|
||||
if clash_set.mode == "intersection":
|
||||
row = layout.row()
|
||||
row.prop(clash_set, "tolerance")
|
||||
|
||||
layout.label(text="Group A:")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_clash_source").group = "a"
|
||||
|
||||
for index, source in enumerate(clash_set.a):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "mode", text="")
|
||||
row.prop(source, "selector", text="")
|
||||
|
||||
layout.label(text="Group B:")
|
||||
row.prop(clash_set, "check_all")
|
||||
elif clash_set.mode == "collision":
|
||||
row = layout.row()
|
||||
row.operator("bim.add_clash_source").group = "b"
|
||||
|
||||
for index, source in enumerate(clash_set.b):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "mode", text="")
|
||||
row.prop(source, "selector", text="")
|
||||
|
||||
row.prop(clash_set, "allow_touching")
|
||||
elif clash_set.mode == "clearance":
|
||||
row = layout.row()
|
||||
row.prop(props, "should_create_clash_snapshots")
|
||||
row.prop(clash_set, "clearance")
|
||||
row = layout.row()
|
||||
row.prop(clash_set, "check_all")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Group A:", icon="OUTLINER_OB_POINTCLOUD")
|
||||
row.operator("bim.add_clash_source", icon="ADD", text="").group = "a"
|
||||
|
||||
for index, source in enumerate(clash_set.a):
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.execute_ifc_clash")
|
||||
row.operator("bim.select_ifc_clash_results")
|
||||
row.prop(source, "name", text="")
|
||||
row.prop(source, "mode", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
|
||||
if source.mode != "a":
|
||||
blenderbim.bim.helper.draw_filter(
|
||||
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}"
|
||||
)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD")
|
||||
row.operator("bim.add_clash_source", icon="ADD", text="").group = "b"
|
||||
|
||||
for index, source in enumerate(clash_set.b):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
row.prop(source, "mode", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
|
||||
if source.mode != "a":
|
||||
blenderbim.bim.helper.draw_filter(
|
||||
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}"
|
||||
)
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_create_clash_snapshots")
|
||||
row = layout.row()
|
||||
row.operator("bim.execute_ifc_clash")
|
||||
|
||||
row = layout.row()
|
||||
row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
|
||||
|
||||
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
|
||||
row = layout.row()
|
||||
row.operator("bim.select_clash")
|
||||
|
||||
|
||||
class BIM_PT_clash_manager(Panel):
|
||||
@@ -111,29 +142,6 @@ class BIM_PT_clash_manager(Panel):
|
||||
pass
|
||||
|
||||
|
||||
class BIM_PT_dumb_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_dumb_clash_manager"
|
||||
bl_label = "Dumb Manager"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_clash_manager"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMClashProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.load_ifc_clashes", text="LOAD IFC CLASHES", icon="IMPORT")
|
||||
row.operator("bim.save_ifc_clashes", text="SAVE CLASH RESULTS", icon="EXPORT")
|
||||
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
|
||||
# bim.select_clash
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.select_clash", text="SELECT CLASH", icon="IMPORT")
|
||||
row.prop(props, "sould_focus_on_clash", icon="RESTRICT_VIEW_OFF")
|
||||
|
||||
|
||||
class BIM_PT_smart_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_smart_clash_manager"
|
||||
bl_label = "Smart Clash Manager"
|
||||
|
||||
@@ -20,6 +20,8 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.helper
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -87,7 +89,7 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
product=product,
|
||||
products=[product],
|
||||
classification=classification,
|
||||
identification="X",
|
||||
name="Unnamed",
|
||||
@@ -292,20 +294,25 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
active_reference = tool.Ifc.get().by_id(self.reference)
|
||||
identification = active_reference[1]
|
||||
|
||||
elements_by_references: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]] = {}
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
references = ifcopenshell.util.classification.get_references(element, should_inherit=False)
|
||||
for reference in references:
|
||||
if (identification and reference[1] == identification) or (
|
||||
not identification and reference == active_reference
|
||||
):
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_reference",
|
||||
tool.Ifc.get(),
|
||||
reference=reference,
|
||||
product=element,
|
||||
)
|
||||
elements_by_references.setdefault(reference, []).append(element)
|
||||
|
||||
if elements_by_references:
|
||||
for reference, products in elements_by_references.items():
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_reference",
|
||||
tool.Ifc.get(),
|
||||
reference=reference,
|
||||
products=products,
|
||||
)
|
||||
|
||||
|
||||
class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -357,15 +364,18 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
classification = element
|
||||
break
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
ifc_file = tool.Ifc.get()
|
||||
products = [
|
||||
ifc_file.by_id(ifc_definition_id)
|
||||
for obj in objects
|
||||
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
|
||||
]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
reference=IfcStore.classification_file.by_id(self.reference),
|
||||
product=tool.Ifc.get().by_id(ifc_definition_id),
|
||||
products=products,
|
||||
classification=classification,
|
||||
)
|
||||
|
||||
@@ -406,14 +416,14 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
classification.Location = bsdd_classification.domain_namespace_uri
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = blenderbim.bim.helper.get_obj_ifc_definition_id(context, obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
products=[element],
|
||||
classification=classification,
|
||||
identification=bsdd_classification.reference_code,
|
||||
name=bsdd_classification.name,
|
||||
|
||||
@@ -114,7 +114,7 @@ class EditObjective(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"constraint.edit_objective",
|
||||
self.file,
|
||||
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes}
|
||||
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes},
|
||||
)
|
||||
bpy.ops.bim.load_objectives()
|
||||
return {"FINISHED"}
|
||||
@@ -179,19 +179,17 @@ class AssignConstraint(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.file = tool.Ifc.get()
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"constraint.assign_constraint",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"products": products,
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
}
|
||||
},
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -207,18 +205,16 @@ class UnassignConstraint(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
self.file = tool.Ifc.get()
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
obj_id = obj.BIMObjectProperties.ifc_definition_id
|
||||
if not obj_id:
|
||||
continue
|
||||
products = [self.file.by_id(obj_id) for obj in objs if (obj_id := obj.BIMObjectProperties.ifc_definition_id)]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"constraint.unassign_constraint",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(obj_id),
|
||||
"products": products,
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
}
|
||||
},
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -191,11 +191,11 @@ class CoveringToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
row = cls.layout.row()
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
|
||||
@@ -24,22 +24,24 @@ classes = (
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.DebugActiveDrawing,
|
||||
operator.InspectFromObject,
|
||||
operator.InspectFromStepId,
|
||||
operator.OverrideDisplayType,
|
||||
operator.ParseExpress,
|
||||
operator.PipInstall,
|
||||
operator.PrintIfcFile,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.SelectHighestPolygonMeshes,
|
||||
operator.ValidateIfcFile,
|
||||
operator.PrintUnusedElementStats,
|
||||
operator.PurgeUnusedElementsByClass,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import bpy
|
||||
import time
|
||||
import random
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
@@ -623,3 +627,140 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
print("Finished 20 batches. Manually stopping in case of infinite loop.")
|
||||
self.report({"INFO"}, f"Auto purged {total_purged} orphaned elements")
|
||||
tool.Ifc.get().write(self.filepath)
|
||||
|
||||
|
||||
class PipInstall(bpy.types.Operator):
|
||||
bl_idname = "bim.pip_install"
|
||||
bl_label = "Pip Install"
|
||||
bl_description = "Installs a package from PyPI"
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
import blenderbim.bim
|
||||
|
||||
target = [p for p in sys.path if p.endswith("packages") and "addons" in p][0]
|
||||
py_exec = str(sys.executable)
|
||||
|
||||
print("Detected executable:", py_exec)
|
||||
print("Installing to:", target)
|
||||
|
||||
subprocess.call([py_exec, "-m", "ensurepip", "--user"])
|
||||
subprocess.call([py_exec, "-m", "pip", "install", "--upgrade", "pip"])
|
||||
# Trusted host to make things simpler due to some enterprise network restrictions
|
||||
subprocess.call(
|
||||
[
|
||||
py_exec,
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
f"--target={target}",
|
||||
"--upgrade",
|
||||
self.name,
|
||||
"--trusted-host",
|
||||
"pypi.org",
|
||||
"--trusted-host",
|
||||
"files.pythonhosted.org",
|
||||
]
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DebugActiveDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.debug_active_drawing"
|
||||
bl_label = "Search Active Drawing For Failing Elements"
|
||||
bl_description = (
|
||||
"Will iterate over all visible drawing's objects, trying to narrow down the list of possible failing objects"
|
||||
)
|
||||
|
||||
def execute(self, context: bpy.types.Context):
|
||||
props = context.scene.DocProperties
|
||||
drawing_item = props.drawings[props.active_drawing_index]
|
||||
drawing = tool.Ifc.get().by_id(drawing_item.ifc_definition_id)
|
||||
|
||||
GREEN = "\033[92m"
|
||||
CYAN = "\033[96m"
|
||||
END = "\033[0m"
|
||||
ATTEMPS = 10
|
||||
|
||||
# run create drawing with sync for once
|
||||
# to make sure everything is actually in sync
|
||||
try:
|
||||
bpy.ops.bim.create_drawing(sync=True)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
self.report({"INFO"}, "No errors creating drawing, nothing to investigate.")
|
||||
return {"FINISHED"}
|
||||
|
||||
all_elements = [e for obj in context.visible_objects if (e := tool.Ifc.get_entity(obj))]
|
||||
all_elements = set(all_elements)
|
||||
|
||||
original_exclude = ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "Exclude")
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
|
||||
def drawing_fails_to_load(chunk_to_include: set) -> bool:
|
||||
current_elements = all_elements - chunk_to_include
|
||||
excluded_guids = ", ".join([e.GlobalId for e in current_elements if hasattr(e, "GlobalId")])
|
||||
tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": f"{original_exclude}, {excluded_guids}"})
|
||||
|
||||
try:
|
||||
bpy.ops.bim.create_drawing(sync=False)
|
||||
result = False
|
||||
except Exception as e:
|
||||
# print(e)
|
||||
result = True
|
||||
|
||||
tool.Ifc.run("pset.edit_pset", pset=pset, properties={"Exclude": original_exclude})
|
||||
return result
|
||||
|
||||
def test_elements(elements: list[ifcopenshell.entity_instance], attempts: int = ATTEMPS) -> None:
|
||||
print(f"{CYAN}processing {len(elements)} elements{END}")
|
||||
if not elements:
|
||||
print(f"Empty list of elements, will stop...")
|
||||
return
|
||||
|
||||
n_elements = len(elements)
|
||||
middle = int(n_elements / 2)
|
||||
chunk1, chunk2 = elements[:middle], elements[middle:]
|
||||
test_chunk_1 = drawing_fails_to_load(set(chunk1))
|
||||
test_chunk_2 = drawing_fails_to_load(set(chunk2))
|
||||
|
||||
if (
|
||||
# both chunks do not fail anymore
|
||||
(not test_chunk_1 and not test_chunk_2)
|
||||
# or we have 1 element chunk that is still failing
|
||||
or (test_chunk_1 and not chunk2)
|
||||
or (test_chunk_2 and not chunk1)
|
||||
):
|
||||
if attempts == 0 or n_elements in (1, 2):
|
||||
print(f"{GREEN}Couldn't narrow it down any further.{END}")
|
||||
print(f"It's some of the {n_elements} elements:")
|
||||
print(elements)
|
||||
|
||||
print("Let's test excluding them...")
|
||||
for element in elements:
|
||||
test = drawing_fails_to_load(all_elements - {element})
|
||||
if test:
|
||||
print(f"{CYAN}Excluding element didn't fixed the drawing: {END}")
|
||||
print(element)
|
||||
else:
|
||||
print(f"{GREEN}Excluding element fixed the drawing: {END}")
|
||||
print(element)
|
||||
else:
|
||||
print(f"{CYAN}Will try to reshuffle elements and try again, attempt {ATTEMPS-attempts+1}/{ATTEMPS}")
|
||||
attempts -= 1
|
||||
random.shuffle(elements)
|
||||
test_elements(elements, attempts)
|
||||
return
|
||||
|
||||
# if chunk fails we need to investigate it further
|
||||
if test_chunk_1:
|
||||
test_elements(chunk1)
|
||||
|
||||
if test_chunk_2:
|
||||
test_elements(chunk2)
|
||||
|
||||
test_elements(list(all_elements))
|
||||
|
||||
self.report({"INFO"}, "See system console for the results")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -51,3 +51,4 @@ class BIMDebugProperties(PropertyGroup):
|
||||
default="BOUNDS",
|
||||
)
|
||||
ifc_class_purge: StringProperty(name="Unused Elements IFC Class", default="")
|
||||
package_name: StringProperty(name="Package Name", default="")
|
||||
|
||||
@@ -44,6 +44,10 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.parse_express", icon="IMPORT", text="")
|
||||
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "package_name", text="")
|
||||
row.operator("bim.pip_install", icon="EVENT_PAGEDOWN").name = props.package_name
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.reload_ifc_file", text="Incrementally Reload Changes")
|
||||
|
||||
@@ -71,6 +75,9 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.profile_import_ifc")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.debug_active_drawing")
|
||||
|
||||
row = layout.split(factor=0.5, align=True)
|
||||
row.operator("bim.create_shape_from_step_id").should_include_curves = False
|
||||
row.operator("bim.create_shape_from_step_id", text="", icon="IPO_ELASTIC").should_include_curves = True
|
||||
|
||||
@@ -38,6 +38,7 @@ class DiffData:
|
||||
cls.data["total_deleted"] = cls.total_deleted()
|
||||
cls.data["total_changed"] = cls.total_changed()
|
||||
cls.data["changes"] = cls.changes()
|
||||
cls.data["saved_searches"] = cls.saved_searches()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -84,3 +85,18 @@ class DiffData:
|
||||
elif element.GlobalId in diff["deleted"]:
|
||||
changes["Deleted"] = True
|
||||
return changes
|
||||
|
||||
@classmethod
|
||||
def saved_searches(cls):
|
||||
if not tool.Ifc.get():
|
||||
return []
|
||||
groups = tool.Ifc.get().by_type("IfcGroup")
|
||||
results = []
|
||||
for group in groups:
|
||||
try:
|
||||
data = json.loads(group.Description)
|
||||
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
|
||||
results.append(group)
|
||||
except:
|
||||
pass
|
||||
return [(str(g.id()), g.Name or "Unnamed", "") for g in sorted(results, key=lambda x: x.Name or "Unnamed")]
|
||||
|
||||
@@ -135,6 +135,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
bl_label = "Execute IFC Diff"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
@@ -162,7 +163,7 @@ class ExecuteIfcDiff(bpy.types.Operator):
|
||||
new = ifcopenshell.open(self.props.new_file)
|
||||
|
||||
relationships = [r.relationship for r in self.props.diff_relationships]
|
||||
query = self.props.diff_filter_elements
|
||||
query = tool.Search.export_filter_query(self.props.filter_groups) or None
|
||||
|
||||
ifc_diff = ifcdiff.IfcDiff(old, new, relationships=relationships, filter_elements=query)
|
||||
ifc_diff.diff()
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from blenderbim.bim.prop import StrProperty, BIMFilterGroup
|
||||
from blenderbim.bim.module.diff.data import DiffData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -48,7 +48,7 @@ class DiffProperties(PropertyGroup):
|
||||
old_file: StringProperty(default="", name="Old IFC File")
|
||||
new_file: StringProperty(default="", name="New IFC File")
|
||||
diff_relationships: CollectionProperty(type=Relationships, name="Relationships")
|
||||
diff_filter_elements: StringProperty(default="", name="Filter")
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
should_load_changed_elements: BoolProperty(name="Load Changed Elements", default=True)
|
||||
active_file: EnumProperty(
|
||||
items=[
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.diff.data import DiffData
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import json
|
||||
|
||||
|
||||
class BIM_PT_diff(Panel):
|
||||
@@ -93,9 +93,7 @@ class BIM_PT_diff(Panel):
|
||||
remove.collection = "diff_relationships"
|
||||
remove.index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "diff_filter_elements")
|
||||
row.operator("bim.ifc_selector", icon="FILTER", text="")
|
||||
blenderbim.bim.helper.draw_filter(self.layout, props.filter_groups, DiffData, "diff")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.execute_ifc_diff")
|
||||
|
||||
@@ -41,6 +41,7 @@ classes = (
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
operator.ContractTargetView,
|
||||
operator.ConvertSVGToDXF,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableAddAnnotationType,
|
||||
@@ -126,7 +127,7 @@ def register():
|
||||
bpy.types.Object.BIMAssignedProductProperties = bpy.props.PointerProperty(type=prop.BIMAssignedProductProperties)
|
||||
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
||||
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
|
||||
|
||||
@@ -140,6 +141,6 @@ def unregister():
|
||||
del bpy.types.Object.BIMAssignedProductProperties
|
||||
del bpy.types.Object.BIMTextProperties
|
||||
del bpy.types.TextCurve.BIMTextProperties
|
||||
bpy.app.handlers.load_post.remove(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(handler.depsgraph_update_pre_handler)
|
||||
bpy.types.VIEW3D_MT_image_add.remove(ui.add_object_button)
|
||||
|
||||
@@ -23,6 +23,7 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Any, Union
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -234,9 +235,9 @@ class DecoratorData:
|
||||
cut_cache = {}
|
||||
layerset_cache = {}
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "BATTING"
|
||||
@classmethod
|
||||
def get_batting_thickness(cls, obj):
|
||||
"""used by IfcAnnotations with ObjectType = "BATTING" """
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -248,9 +249,9 @@ class DecoratorData:
|
||||
cls.data[obj.name] = thickness
|
||||
return thickness
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "SECTION"
|
||||
@classmethod
|
||||
def get_section_markers_display_data(cls, obj):
|
||||
"""used by IfcAnnotations with ObjectType = "SECTION" """
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -290,10 +291,10 @@ class DecoratorData:
|
||||
cls.data[obj.name] = display_data
|
||||
return display_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj):
|
||||
"""returns font size in mm for current ifc text object"""
|
||||
def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
"""used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
|
||||
returns font size in mm for current ifc text object"""
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
@@ -317,7 +318,7 @@ class DecoratorData:
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = pset_data.get("Symbol", None)
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
|
||||
# other attributes
|
||||
props_literals = props.literals
|
||||
@@ -340,6 +341,11 @@ class DecoratorData:
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
@classmethod
|
||||
def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]:
|
||||
"""used by IfcAnnotations with ObjectType MULTI_SYMBOL"""
|
||||
return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj):
|
||||
"""used by Ifc Annotations with ObjectType:
|
||||
@@ -412,10 +418,12 @@ class AnnotationData:
|
||||
relating_types = []
|
||||
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append({
|
||||
"id": relating_type.id(),
|
||||
"name": relating_type.Name or "Unnamed",
|
||||
"description": relating_type.Description or "No Description",
|
||||
})
|
||||
relating_types.append(
|
||||
{
|
||||
"id": relating_type.id(),
|
||||
"name": relating_type.Name or "Unnamed",
|
||||
"description": relating_type.Description or "No Description",
|
||||
}
|
||||
)
|
||||
|
||||
return sorted(relating_types, key=lambda x: x["name"])
|
||||
|
||||
@@ -37,6 +37,7 @@ from blenderbim.bim.module.drawing.shaders import add_verts_sequence, add_offset
|
||||
from blenderbim.bim.module.drawing.helper import format_distance
|
||||
from timeit import default_timer as timer
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Iterator, Type, Union
|
||||
|
||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||
|
||||
@@ -69,7 +70,7 @@ class profile_consequential:
|
||||
cls.lines = []
|
||||
|
||||
|
||||
def worldspace_to_winspace(verts, context):
|
||||
def worldspace_to_winspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
|
||||
"""Convert world space verts to window space"""
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
@@ -80,7 +81,7 @@ def worldspace_to_winspace(verts, context):
|
||||
return winspace_verts
|
||||
|
||||
|
||||
def winspace_to_worldspace(verts, context):
|
||||
def winspace_to_worldspace(verts: list[Vector], context: bpy.types.Context) -> list[Vector]:
|
||||
"""Convert winspace verts to world space"""
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
@@ -121,17 +122,18 @@ def get_callout_head(edge_dir, edge_side, callout_size, callout_gap):
|
||||
return head
|
||||
|
||||
|
||||
def get_circle_head(size, segments=20):
|
||||
def get_circle_head(size: float, segments: int = 20, rotation: float = 0.0) -> list[Vector]:
|
||||
"""`rotation` value in radians"""
|
||||
angle_d = 2 * pi / segments
|
||||
head = []
|
||||
for i in range(segments):
|
||||
angle = angle_d * i
|
||||
angle = angle_d * i + rotation
|
||||
head.append(Vector([cos(angle), sin(angle), 0]) * size)
|
||||
return head
|
||||
|
||||
|
||||
def get_circle_head_asterisk(size, segments=6):
|
||||
circle_head = get_circle_head(size, segments)
|
||||
def get_circle_head_asterisk(size: float, segments: int = 6, rotation: float = 0.0) -> Iterator[tuple[Vector, Vector]]:
|
||||
circle_head = get_circle_head(size, segments, rotation)
|
||||
middle = segments // 2
|
||||
return zip(circle_head[:middle], circle_head[middle:])
|
||||
|
||||
@@ -264,7 +266,7 @@ class BaseDecorator:
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
return vertices, indices
|
||||
|
||||
def decorate(self, context, obj):
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
|
||||
"""perform actual drawing stuff"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@@ -310,13 +312,28 @@ class BaseDecorator:
|
||||
|
||||
self.draw_lines(context, obj, output_verts, output_edges)
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
def draw_batch(
|
||||
self,
|
||||
shader_type: str,
|
||||
content_pos: list[Vector],
|
||||
color: tuple[float, float, float, float],
|
||||
indices: Optional[list[tuple[int, int]]] = None,
|
||||
) -> None:
|
||||
shader = self.line_shader if shader_type == "LINES" else self.base_shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_lines(self, context, obj, vertices, indices, color=None, line_width=1.0):
|
||||
def draw_lines(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
obj: Optional[bpy.types.Object],
|
||||
vertices: list[Vector],
|
||||
indices: list[tuple[int, int]],
|
||||
color: Optional[tuple[float, float, float, float]] = None,
|
||||
line_width: float = 1.0,
|
||||
) -> None:
|
||||
# TODO: `obj` is actually never used, need to remove it
|
||||
"""`verts` should be in winspace with `(0,0,0)` in the screen left bottom corner, not in the center"""
|
||||
region = context.region
|
||||
if not color:
|
||||
@@ -329,7 +346,7 @@ class BaseDecorator:
|
||||
gpu.state.blend_set("ALPHA")
|
||||
self.draw_batch("LINES", vertices, color, indices)
|
||||
|
||||
def get_viewport_drawing_scale(self, context):
|
||||
def get_viewport_drawing_scale(self, context: bpy.types.Context) -> float:
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
@@ -485,14 +502,16 @@ class BaseDecorator:
|
||||
decimal_places = drawing_pset_data.get("DecimalPlaces", None)
|
||||
return format_distance(value, precision=precision, decimal_places=decimal_places)
|
||||
|
||||
def draw_asterisk(self, context, obj):
|
||||
def draw_asterisk(self, context: bpy.types.Context, pos: Vector, rotation: float = 0.0, scale: float = 1.0) -> None:
|
||||
"""`pos` is a world space position\n
|
||||
`rotation` value in radians"""
|
||||
# gather geometry data and convert to winspace
|
||||
verts = [obj.location]
|
||||
verts = [pos]
|
||||
viewportDrawingScale = self.get_viewport_drawing_scale(context)
|
||||
winspace_verts = worldspace_to_winspace(verts, context)
|
||||
|
||||
# setup geometry parameters
|
||||
circle_size = viewportDrawingScale * 4
|
||||
circle_size = viewportDrawingScale * 4 * scale
|
||||
|
||||
output_verts = []
|
||||
output_edges = []
|
||||
@@ -502,20 +521,13 @@ class BaseDecorator:
|
||||
}
|
||||
|
||||
v0 = winspace_verts[0]
|
||||
asterisk_head = get_circle_head_asterisk(circle_size)
|
||||
asterisk_head = get_circle_head_asterisk(circle_size, rotation=rotation)
|
||||
start_i = 0
|
||||
for segment in asterisk_head:
|
||||
start_i = add_verts_sequence(add_offsets(v0, segment), start_i, **out_kwargs)
|
||||
self.draw_lines(context, obj, output_verts, output_edges)
|
||||
self.draw_lines(context, None, output_verts, output_edges)
|
||||
|
||||
def draw_text(self, context, obj, text_world_position=None, reverse_lines_order=False):
|
||||
"""if `text_world_position` is not provided, the object's location will be used"""
|
||||
|
||||
if not text_world_position:
|
||||
text_world_position = obj.location
|
||||
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
def get_annotation_direction(self, context: bpy.types.Context, obj: bpy.types.Object) -> Vector:
|
||||
camera = context.scene.camera
|
||||
|
||||
def get_basis_vector(matrix, i=0):
|
||||
@@ -523,10 +535,57 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(camera)
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
return text_dir
|
||||
|
||||
def draw_symbol(self, context: bpy.types.Context, obj: bpy.types.Object, annotation_dir: Vector) -> None:
|
||||
if obj.type not in ("MESH", "EMPTY"):
|
||||
return
|
||||
|
||||
if obj.type == "MESH":
|
||||
mesh: bpy.types.Mesh = obj.data
|
||||
|
||||
if len(mesh.vertices) == 0:
|
||||
return
|
||||
|
||||
if mesh.edges:
|
||||
MiscDecorator.decorate(self, context, obj)
|
||||
return
|
||||
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
return
|
||||
|
||||
for vert in mesh.vertices:
|
||||
self.draw_asterisk(context, obj.matrix_world @ vert.co)
|
||||
|
||||
return
|
||||
|
||||
# EMPTY objects
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
return
|
||||
|
||||
rotation = -Vector((1, 0)).angle_signed(annotation_dir)
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
self.draw_asterisk(context, obj.location, rotation, obj.scale.x)
|
||||
|
||||
def draw_text(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
obj: bpy.types.Object,
|
||||
text_world_position: Optional[Vector] = None,
|
||||
reverse_lines_order=False,
|
||||
) -> None:
|
||||
"""if `text_world_position` is not provided, the object's location will be used"""
|
||||
|
||||
if not text_world_position:
|
||||
text_world_position = obj.location
|
||||
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
text_dir = self.get_annotation_direction(context, obj)
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
@@ -539,8 +598,7 @@ class BaseDecorator:
|
||||
|
||||
# draw asterisk symbol to indicate that there is some symbol that's not shown in viewport
|
||||
if symbol:
|
||||
self.draw_asterisk(context, obj)
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
self.draw_symbol(context, obj, text_dir)
|
||||
text_scale = obj.scale.x
|
||||
|
||||
line_i = 0
|
||||
@@ -548,7 +606,7 @@ class BaseDecorator:
|
||||
for literal_data in literals_data:
|
||||
box_alignment = literal_data["BoxAlignment"]
|
||||
|
||||
for line in literal_data["CurrentValue"].split("\\n"):
|
||||
for line in literal_data["CurrentValue"].split("\n"):
|
||||
self.draw_label(
|
||||
context,
|
||||
line,
|
||||
@@ -812,8 +870,9 @@ class AngleDecorator(BaseDecorator):
|
||||
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
|
||||
label_position = p1 + text_offset
|
||||
|
||||
text = f"{int(angle)}deg"
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
|
||||
text = f"{round(angle, decimal_places)}°"
|
||||
label_dir = Vector((1, 0))
|
||||
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
|
||||
|
||||
@@ -1003,7 +1062,7 @@ class StairDecorator(BaseDecorator):
|
||||
class MiscDecorator(BaseDecorator):
|
||||
objecttype = "MISC"
|
||||
|
||||
def decorate(self, context, obj):
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object):
|
||||
if obj.data.is_editmode:
|
||||
verts, idxs = self.get_editmesh_geom(obj)
|
||||
else:
|
||||
@@ -1505,6 +1564,14 @@ class TextDecorator(BaseDecorator):
|
||||
self.draw_text(context, obj)
|
||||
|
||||
|
||||
class SymbolDecorator(BaseDecorator):
|
||||
objecttype = "SYMBOL"
|
||||
|
||||
def decorate(self, context: bpy.types.Context, obj: bpy.types.Object) -> None:
|
||||
annotation_dir = self.get_annotation_direction(context, obj)
|
||||
self.draw_symbol(context, obj, annotation_dir)
|
||||
|
||||
|
||||
class CutDecorator:
|
||||
installed = None
|
||||
cache = {}
|
||||
@@ -1901,7 +1968,7 @@ class CutDecorator:
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
decorators_classes = [
|
||||
decorators_classes: list[Type[BaseDecorator]] = [
|
||||
DimensionDecorator,
|
||||
AngleDecorator,
|
||||
GridDecorator,
|
||||
@@ -1918,6 +1985,7 @@ class DecorationsHandler:
|
||||
SectionDecorator,
|
||||
ElevationDecorator,
|
||||
TextDecorator,
|
||||
SymbolDecorator,
|
||||
BattingDecorator,
|
||||
FallDecorator,
|
||||
]
|
||||
@@ -1945,6 +2013,7 @@ class DecorationsHandler:
|
||||
self.decorators = {cls.objecttype: cls() for cls in self.decorators_classes}
|
||||
for object_type in ("SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT"):
|
||||
self.decorators[object_type] = self.decorators["FALL"]
|
||||
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
@@ -1962,7 +2031,7 @@ class DecorationsHandler:
|
||||
if not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
|
||||
object_type = element.ObjectType
|
||||
object_type: Union[str, None] = element.ObjectType
|
||||
if object_type == "DRAWING":
|
||||
continue
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def toggleDecorationsOnLoad(*args):
|
||||
def load_post(*args):
|
||||
if bpy.context.scene.DocProperties.should_draw_decorations:
|
||||
decoration.DecorationsHandler.install(bpy.context)
|
||||
else:
|
||||
|
||||
@@ -188,7 +188,6 @@ def format_distance(
|
||||
feet += 1
|
||||
inches = 0
|
||||
|
||||
|
||||
if not isArea:
|
||||
add_inches = bool(inches) or not suppress_zero_inches
|
||||
tx_dist = ""
|
||||
@@ -299,7 +298,9 @@ def parse_diagram_scale(camera):
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
|
||||
def ortho_view_frame(camera, margin=0.015):
|
||||
def ortho_view_frame(
|
||||
camera: bpy.types.Camera, margin: float = 0.015
|
||||
) -> tuple[float, float, float, float, float, float]:
|
||||
"""Calculates 2d bounding box of camera view area.
|
||||
|
||||
Similar to `bpy.types.Camera.view_frame`
|
||||
|
||||
@@ -44,6 +44,7 @@ import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.export_ifc
|
||||
from blenderbim.bim.module.drawing.decoration import CutDecorator
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
from typing import NamedTuple, List, Union, Optional
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Color, Matrix
|
||||
from timeit import default_timer as timer
|
||||
@@ -77,6 +78,11 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LineworkContexts(NamedTuple):
|
||||
body: List[List[int]]
|
||||
annotation: List[List[int]]
|
||||
|
||||
|
||||
class AddAnnotationType(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_annotation_type"
|
||||
bl_label = "Add Annotation Type"
|
||||
@@ -193,6 +199,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
+ "SHIFT+CLICK to print all selected drawings"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
|
||||
sync: bpy.props.BoolProperty(
|
||||
name="Sync Before Creating Drawing",
|
||||
description="Could save some time if you're sure IFC and current Blender session are already in sync",
|
||||
default=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -214,7 +225,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
else:
|
||||
drawings_to_print = [self.props.active_drawing_id]
|
||||
|
||||
for drawing_id in drawings_to_print:
|
||||
for drawing_i, drawing_id in enumerate(drawings_to_print):
|
||||
self.drawing_index = drawing_i
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
|
||||
|
||||
@@ -329,6 +341,13 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
return
|
||||
svg_path = self.get_svg_path(cache_type="underlay")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_underlay_cache:
|
||||
return svg_path
|
||||
|
||||
visible_object_names = {obj.name for obj in bpy.context.visible_objects}
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
obj.hide_render = obj.name not in visible_object_names
|
||||
|
||||
context.scene.render.filepath = svg_path[0:-4] + ".png"
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[self.cprops.active_drawing_style_index]
|
||||
|
||||
@@ -367,20 +386,129 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
|
||||
return svg_path
|
||||
|
||||
def generate_linework(self, context):
|
||||
def get_linework_contexts(self, ifc, target_view) -> LineworkContexts:
|
||||
plan_body_target_contexts = []
|
||||
plan_body_model_contexts = []
|
||||
model_body_target_contexts = []
|
||||
model_body_model_contexts = []
|
||||
|
||||
plan_annotation_target_contexts = []
|
||||
plan_annotation_model_contexts = []
|
||||
model_annotation_target_contexts = []
|
||||
model_annotation_model_contexts = []
|
||||
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.ContextType == "Plan":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
model_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
model_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
# You should never purely assign to a "Model" context, but
|
||||
# if you do, this is what we assume your intention is.
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
body_contexts = (
|
||||
[
|
||||
plan_body_target_contexts,
|
||||
plan_body_model_contexts,
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
annotation_contexts = (
|
||||
[
|
||||
plan_annotation_target_contexts,
|
||||
plan_annotation_model_contexts,
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
return LineworkContexts(body_contexts, annotation_contexts)
|
||||
|
||||
def serialize_contexts_elements(
|
||||
self,
|
||||
ifc: ifcopenshell.file,
|
||||
tree: ifcopenshell.geom.tree,
|
||||
contexts: LineworkContexts,
|
||||
context_type: str,
|
||||
drawing_elements: set[ifcopenshell.entity_instance],
|
||||
target_view: str,
|
||||
) -> None:
|
||||
drawing_elements = drawing_elements.copy()
|
||||
contexts = getattr(contexts, context_type)
|
||||
for context in contexts:
|
||||
with profile(f"Processing {context_type} context"):
|
||||
if not context or not drawing_elements:
|
||||
continue
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=drawing_elements
|
||||
)
|
||||
processed = set()
|
||||
for elem in it:
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
drawing_elements -= processed
|
||||
|
||||
def generate_linework(self, context: bpy.types.Context) -> Union[str, None]:
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
|
||||
return
|
||||
svg_path = self.get_svg_path(cache_type="linework")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||
return svg_path
|
||||
|
||||
with profile("sync"):
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
# in case of printing multiple drawings we need to sync just once
|
||||
if self.sync and self.drawing_index == 0:
|
||||
with profile("sync"):
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_elements = exporter.sync_all_objects(skip_unlinking=True)
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
|
||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||
edited_guids = set()
|
||||
@@ -401,6 +529,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
for ifc_path, ifc in files.items():
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
# TODO: hash paths are never used
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
|
||||
@@ -408,139 +537,23 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Drawings only draw bodies and annotations (and facetation, due to a Revit bug).
|
||||
# A drawing prioritises a target view context first, followed by a model view context as a fallback.
|
||||
# Specifically for PLAN_VIEW and REFLECTED_PLAN_VIEW, any Plan context is also prioritised.
|
||||
|
||||
plan_body_target_contexts = []
|
||||
plan_body_model_contexts = []
|
||||
model_body_target_contexts = []
|
||||
model_body_model_contexts = []
|
||||
|
||||
plan_annotation_target_contexts = []
|
||||
plan_annotation_model_contexts = []
|
||||
model_annotation_target_contexts = []
|
||||
model_annotation_model_contexts = []
|
||||
|
||||
target_view = ifcopenshell.util.element.get_psets(self.camera_element)["EPset_Drawing"]["TargetView"]
|
||||
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.ContextType == "Plan":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
plan_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
plan_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
if rep_context.TargetView == target_view:
|
||||
model_body_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextIdentifier == "Annotation":
|
||||
if rep_context.TargetView == target_view:
|
||||
model_annotation_target_contexts.append(rep_context.id())
|
||||
elif rep_context.TargetView == "MODEL_VIEW":
|
||||
model_annotation_model_contexts.append(rep_context.id())
|
||||
elif rep_context.ContextType == "Model":
|
||||
# You should never purely assign to a "Model" context, but
|
||||
# if you do, this is what we assume your intention is.
|
||||
model_body_model_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
body_contexts = (
|
||||
[
|
||||
plan_body_target_contexts,
|
||||
plan_body_model_contexts,
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_body_target_contexts,
|
||||
model_body_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
annotation_contexts = (
|
||||
[
|
||||
plan_annotation_target_contexts,
|
||||
plan_annotation_model_contexts,
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
if target_view in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]
|
||||
else [
|
||||
model_annotation_target_contexts,
|
||||
model_annotation_model_contexts,
|
||||
]
|
||||
)
|
||||
|
||||
contexts = self.get_linework_contexts(ifc, target_view)
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
tree.enable_face_styles(True)
|
||||
|
||||
elements = drawing_elements.copy()
|
||||
for body_context in body_contexts:
|
||||
with profile("Processing body context"):
|
||||
if body_context and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(body_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(body_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
|
||||
elements = drawing_elements.copy()
|
||||
for annotation_context in annotation_contexts:
|
||||
with profile("Processing annotation context"):
|
||||
if annotation_context and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
if ifc.by_id(annotation_context[0]).ContextType == "Plan" and "PLAN_VIEW" in target_view:
|
||||
offset = ifcopenshell.ifcopenshell_wrapper.float_array_3()
|
||||
# A 2mm Z offset to combat Z-fighting in plan or RCPs
|
||||
offset[2] = 0.002 if target_view == "PLAN_VIEW" else -0.002
|
||||
geom_settings.offset = offset
|
||||
geom_settings.set_context_ids(annotation_context)
|
||||
it = ifcopenshell.geom.iterator(
|
||||
geom_settings, ifc, multiprocessing.cpu_count(), include=elements
|
||||
)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "body", drawing_elements, target_view)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "annotation", drawing_elements, target_view)
|
||||
|
||||
if self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
# The camera must always be included, regardless of any include/exclude filters.
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
for elem in it:
|
||||
self.serialiser.write(elem)
|
||||
|
||||
with profile("Finalizing"):
|
||||
@@ -588,8 +601,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if global_id not in elements_with_faces:
|
||||
continue
|
||||
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||
# Rounding is necessary to ensure coincident points are coincident
|
||||
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
start, end = [[float(o) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
if start == end:
|
||||
continue
|
||||
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
|
||||
@@ -884,13 +896,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# the style of the face when running tree.select_ray()
|
||||
# tree.enable_face_styles(True)
|
||||
|
||||
def yield_from_iterator(self, it):
|
||||
if it.initialize():
|
||||
while True:
|
||||
yield it.get()
|
||||
if not it.next():
|
||||
break
|
||||
|
||||
def get_svg_classes(self, element):
|
||||
classes = [element.is_a()]
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
@@ -970,7 +975,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath = "M" + subpath.strip()
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
coords = [[float(o) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
if coords[0] != coords[-1]:
|
||||
has_open_paths = True
|
||||
line_strings.append(shapely.LineString(coords))
|
||||
@@ -1034,7 +1039,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath_co = "M" + subpath.strip(" Z")
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath_co.split()]
|
||||
coords = [[float(o) for o in co[1:].split(",")] for co in subpath_co.split()]
|
||||
if subpath.strip().lower().endswith("z"):
|
||||
coords.append(coords[0])
|
||||
if len(coords) > 2 and coords[0] == coords[-1]:
|
||||
@@ -1132,14 +1137,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
continue
|
||||
return space
|
||||
|
||||
def get_material_name(self, element):
|
||||
def get_material_name(self, element: ifcopenshell.entity_instance) -> str:
|
||||
if hasattr(element, "Name") and element.Name:
|
||||
return element.Name
|
||||
elif hasattr(element, "LayerSetName") and element.LayerSetName:
|
||||
return element.LayerSetName
|
||||
return "mat-" + str(element.id())
|
||||
|
||||
def get_svg_path(self, cache_type=None):
|
||||
def get_svg_path(self, cache_type: Optional[str] = None) -> str:
|
||||
drawing_path = tool.Drawing.get_document_uri(self.camera_document)
|
||||
drawings_dir = os.path.dirname(drawing_path)
|
||||
|
||||
@@ -1362,7 +1367,7 @@ class SelectAllDrawings(bpy.types.Operator):
|
||||
bl_label = "Select All Drawings"
|
||||
view: bpy.props.StringProperty()
|
||||
bl_description = "Select all drawings in the drawing list.\n\n" + "SHIFT+CLICK to deselect all drawings"
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# deselect all drawings on shift+click
|
||||
@@ -1468,10 +1473,6 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
|
||||
class ActivateDrawing(bpy.types.Operator):
|
||||
"""
|
||||
Activates the selected drawing view
|
||||
"""
|
||||
|
||||
bl_idname = "bim.activate_drawing"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -1617,8 +1618,14 @@ class ReloadDrawingStyles(bpy.types.Operator):
|
||||
|
||||
json_path = Path(tool.Ifc.resolve_uri(rel_path))
|
||||
if not json_path.exists():
|
||||
self.report({"ERROR"}, "Shading styles file not found: {}".format(json_path))
|
||||
return {"CANCELLED"}
|
||||
ootb_resource = Path(context.scene.BIMProperties.data_dir) / "assets" / "shading_styles.json"
|
||||
print(
|
||||
f"WARNING. Couldn't find shading_styles for the drawing by the path: {json_path}. "
|
||||
f"Default BBIM resource will be copied from {ootb_resource}"
|
||||
)
|
||||
if ootb_resource.exists():
|
||||
os.makedirs(json_path.parent, exist_ok=True)
|
||||
shutil.copy(ootb_resource, json_path)
|
||||
|
||||
with open(json_path, "r") as fi:
|
||||
shading_styles_json = json.load(fi)
|
||||
@@ -2299,8 +2306,16 @@ class AssignSelectedObjectAsProduct(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
objs = context.selected_objects[:]
|
||||
objs.remove(context.active_object)
|
||||
other_selected_object = next(obj for obj in context.selected_objects if obj != context.active_object)
|
||||
obj1 = objs[0]
|
||||
element1 = tool.Ifc.get_entity(obj1)
|
||||
obj2 = objs[1]
|
||||
element2 = tool.Ifc.get_entity(obj2)
|
||||
if element1.is_a("IfcAnnotation"):
|
||||
other_selected_object = obj2
|
||||
bpy.context.view_layer.objects.active = obj1
|
||||
elif element2.is_a("IfcAnnotation"):
|
||||
other_selected_object = obj1
|
||||
bpy.context.view_layer.objects.active = obj2
|
||||
context.active_object.BIMAssignedProductProperties.relating_product = other_selected_object
|
||||
bpy.ops.bim.edit_assigned_product()
|
||||
return {"FINISHED"}
|
||||
@@ -2358,6 +2373,7 @@ class LoadSheets(bpy.types.Operator, Operator):
|
||||
if not filepath.is_file():
|
||||
sheet_name = f"{sheet_prop.identification} - {sheet_prop.name}"
|
||||
sheets_not_found.append(f'"{sheet_name}" - {document_uri}')
|
||||
core.regenerate_sheet(tool.Drawing, sheet)
|
||||
|
||||
if sheets_not_found:
|
||||
self.report({"ERROR"}, "Some sheets svg files are missing:\n" + "\n".join(sheets_not_found))
|
||||
@@ -2555,8 +2571,16 @@ class EnableEditingElementFilter(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.context.scene.camera
|
||||
if obj:
|
||||
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
|
||||
if not obj:
|
||||
return
|
||||
obj.data.BIMCameraProperties.filter_mode = self.filter_mode
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
|
||||
filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
|
||||
try:
|
||||
tool.Search.import_filter_query(query, filter_groups)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
class EditElementFilter(bpy.types.Operator, Operator):
|
||||
@@ -2698,3 +2722,50 @@ class AddReferenceImage(bpy.types.Operator, Operator):
|
||||
)
|
||||
tool.Style.reload_material_from_ifc(material)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
|
||||
class ConvertSVGToDXF(bpy.types.Operator):
|
||||
bl_idname = "bim.convert_svg_to_dxf"
|
||||
bl_label = "Convert SVG to DXF"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
view: bpy.props.StringProperty()
|
||||
bl_description = "Convert current drawing's .svg to .dxf.\n\nSHIFT+CLICK to convert all selected drawings"
|
||||
convert_all: bpy.props.BoolProperty(name="Convert All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# convert all drawings on shift+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.shift:
|
||||
self.open_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if self.convert_all:
|
||||
drawings = [
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
drawings = [tool.Ifc.get().by_id(context.scene.DocProperties.drawings.get(self.view).ifc_definition_id)]
|
||||
|
||||
drawing_uris: list[Path] = []
|
||||
drawings_not_found: list[str] = []
|
||||
|
||||
for drawing in drawings:
|
||||
drawing_uri = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_document(drawing))
|
||||
if drawing_uri is None or not os.path.exists(drawing_uri):
|
||||
drawings_not_found.append(drawing.Name)
|
||||
else:
|
||||
drawing_uris.append(Path(drawing_uri))
|
||||
|
||||
if drawings_not_found:
|
||||
msg = "Some drawings .svg files were not found, need to print them first: \n{}.".format(
|
||||
"\n".join(drawings_not_found)
|
||||
)
|
||||
self.report({"ERROR"}, msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
for drawing_uri in drawing_uris:
|
||||
tool.Drawing.convert_svg_to_dxf(drawing_uri, drawing_uri.with_suffix(".dxf"))
|
||||
|
||||
self.report({"INFO"}, f"{len(drawing_uris)} drawings were converted to .dxf.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -66,6 +66,11 @@ def get_location_hint(self, context):
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
if not context.scene.camera or context.scene.camera.data != self.id_data:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not element:
|
||||
return
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
@@ -87,6 +92,11 @@ def update_diagram_scale(self, context):
|
||||
|
||||
|
||||
def update_is_nts(self, context):
|
||||
if not context.scene.camera or context.scene.camera.data != self.id_data:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not element:
|
||||
return
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
@@ -194,7 +204,7 @@ def update_document_name(self, context):
|
||||
def update_has_underlay(self, context):
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
# making sure that camera is active
|
||||
if self.has_underlay and (context.active_object and context.active_object.data == self.id_data):
|
||||
if self.has_underlay and (context.scene.camera and context.scene.camera.data == self.id_data):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
@@ -208,10 +218,12 @@ def update_has_annotation(self, context):
|
||||
|
||||
|
||||
def update_layer(self, context, name, value):
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not context.scene.camera or context.scene.camera.data != self.id_data:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
@@ -229,9 +241,8 @@ def update_titleblocks(self, context):
|
||||
SheetsData.data["titleblocks"] = SheetsData.titleblocks()
|
||||
|
||||
|
||||
def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
def update_should_draw_decorations(self, context):
|
||||
if self.should_draw_decorations:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.BIMObjectProperties.collection
|
||||
for obj in collection.objects:
|
||||
@@ -343,7 +354,7 @@ class DocProperties(PropertyGroup):
|
||||
active_sheet_index: IntProperty(name="Active Sheet Index")
|
||||
ifc_files: CollectionProperty(name="IFCs", type=StrProperty)
|
||||
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
|
||||
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
|
||||
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=update_should_draw_decorations)
|
||||
sheets_dir: StringProperty(default=os.path.join("sheets") + os.path.sep, name="Default Sheets Directory")
|
||||
layouts_dir: StringProperty(default=os.path.join("layouts") + os.path.sep, name="Default Layouts Directory")
|
||||
titleblocks_dir: StringProperty(
|
||||
|
||||
@@ -29,9 +29,11 @@ import svgwrite
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.selector
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from blenderbim.bim.module.drawing.data import DrawingsData
|
||||
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -39,6 +41,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, ceil, atan, degrees, acos
|
||||
from mathutils import geometry, Vector
|
||||
from bpy_extras import view3d_utils
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class External(svgwrite.container.Group):
|
||||
@@ -90,7 +93,7 @@ class SvgWriter:
|
||||
|
||||
def setup_drawing_resource_paths(self, element):
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns"):
|
||||
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles"):
|
||||
resource_path = pset.get(resource)
|
||||
if not resource_path:
|
||||
self.resource_paths[resource] = None
|
||||
@@ -322,7 +325,7 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
def draw_misc_annotation(self, obj):
|
||||
def draw_misc_annotation(self, obj: bpy.types.Object) -> None:
|
||||
# We have to decide whether this should come from Blender or from IFC.
|
||||
# For the moment, for convenience of experimenting with ideas, it comes
|
||||
# from Blender. In the future, it should probably come from IFC.
|
||||
@@ -356,14 +359,14 @@ class SvgWriter:
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def get_attribute_classes(self, obj):
|
||||
def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
global_id = "GlobalId-{}".format(element.GlobalId)
|
||||
predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name(
|
||||
str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
)
|
||||
classes = [global_id, element.is_a(), predefined_type]
|
||||
custom_classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
custom_classes: str = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
if custom_classes:
|
||||
classes.extend(custom_classes.split())
|
||||
for key in self.metadata:
|
||||
@@ -787,7 +790,7 @@ class SvgWriter:
|
||||
attrib["filter"] = "url(#fill-background)"
|
||||
return element
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
def draw_text_annotation(self, text_obj: bpy.types.Object, position: Vector) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
element = tool.Ifc.get_entity(text_obj)
|
||||
@@ -799,17 +802,7 @@ class SvgWriter:
|
||||
text_position_svg = text_position * self.svg_scale
|
||||
text_position_svg_str = ", ".join(map(str, text_position_svg))
|
||||
|
||||
def get_basis_vector(matrix, i=0):
|
||||
"""returns basis vector for i in world space, unaffected by object scale"""
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
angle = self.get_empty_object_angle(text_obj)
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
fill_bg = "fill-bg" in classes
|
||||
@@ -817,8 +810,7 @@ class SvgWriter:
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
|
||||
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
|
||||
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
@@ -847,7 +839,7 @@ class SvgWriter:
|
||||
return None
|
||||
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
|
||||
self.draw_symbol(symbol, symbol_transform)
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
@@ -865,17 +857,14 @@ class SvgWriter:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
def draw_empty_annotation(self, obj, classes):
|
||||
def draw_empty_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
point = self.project_point_onto_camera(obj.matrix_world.translation)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
svg_id = tool.Drawing.get_annotation_symbol(element)
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
@@ -883,9 +872,37 @@ class SvgWriter:
|
||||
|
||||
point = Vector(((x_offset + point.x), (y_offset - point.y)))
|
||||
symbol_position_svg = point * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
svg_position_str = ", ".join(map(str, symbol_position_svg))
|
||||
|
||||
def draw_point_annotation(self, obj, classes):
|
||||
angle = self.get_empty_object_angle(obj)
|
||||
symbol_transform = self.get_symbol_transform(svg_position_str, angle, obj)
|
||||
self.draw_symbol(svg_id, symbol_transform)
|
||||
|
||||
def get_empty_object_angle(self, obj: bpy.types.Object) -> float:
|
||||
def get_basis_vector(matrix: bpy.types.Object, i: int = 0) -> Vector:
|
||||
"""returns basis vector for i in world space, unaffected by object scale"""
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
return math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
def get_symbol_transform(
|
||||
self, svg_position_str: str, angle: float = 0.0, obj: Optional[bpy.types.Object] = None
|
||||
) -> str:
|
||||
"""`obj` will be used to identify symbol scale,
|
||||
ignore it if object scale doesn't match the symbol scale"""
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
scale = 1.0 if not obj else obj.scale.x
|
||||
return f"translate({svg_position_str}) rotate({angle}) scale({scale})"
|
||||
|
||||
def draw_symbol(self, symbol_id: str, symbol_transform: str) -> None:
|
||||
self.svg.add(self.svg.use(f"#{symbol_id}", transform=symbol_transform))
|
||||
|
||||
def draw_point_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None:
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
@@ -893,10 +910,7 @@ class SvgWriter:
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
svg_id = tool.Drawing.get_annotation_symbol(element)
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
@@ -905,7 +919,10 @@ class SvgWriter:
|
||||
for symbol_position in projected_points:
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
symbol_position_svg = symbol_position * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
svg_position_str = ", ".join(map(str, symbol_position_svg))
|
||||
|
||||
symbol_transform = self.get_symbol_transform(svg_position_str)
|
||||
self.draw_symbol(svg_id, symbol_transform)
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -1066,7 +1083,9 @@ class SvgWriter:
|
||||
# calculating text parameters and adding text
|
||||
text_position = arc_mid_point + arc_mid_dir * 5
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
angle_text = f"{int(angle)}deg"
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
decimal_places = drawing_pset_data.get("AngleDecimalPlaces", None)
|
||||
angle_text = f"{round(angle, decimal_places)}°"
|
||||
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
|
||||
|
||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||
@@ -1402,7 +1421,7 @@ class SvgWriter:
|
||||
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
def project_point_onto_camera(self, point: Vector) -> Vector:
|
||||
# TODO is this needlessly complex?
|
||||
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
point.xyz,
|
||||
|
||||
@@ -268,6 +268,11 @@ class BIM_PT_drawings(Panel):
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
|
||||
convert_to_dxf = row.row(align=True)
|
||||
op = convert_to_dxf.operator("bim.convert_svg_to_dxf", text="", icon="IMAGE_DATA")
|
||||
op.view = active_drawing.name
|
||||
convert_to_dxf.enabled = active_drawing.ifc_definition_id > 0
|
||||
|
||||
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
|
||||
|
||||
open_drawing_button = row.row(align=True)
|
||||
|
||||
@@ -250,7 +250,7 @@ class AnnotationToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_annotation_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
|
||||
|
||||
|
||||
@@ -49,7 +49,17 @@ classes = (
|
||||
operator.SwitchRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
operator.RemoveRepresentationItem,
|
||||
operator.EnableEditingRepresentationItemStyle,
|
||||
operator.EditRepresentationItemStyle,
|
||||
operator.DisableEditingRepresentationItemStyle,
|
||||
operator.UnassignRepresentationItemStyle,
|
||||
operator.EnableEditingRepresentationItemShapeAspect,
|
||||
operator.EditRepresentationItemShapeAspect,
|
||||
operator.DisableEditingRepresentationItemShapeAspect,
|
||||
operator.RemoveRepresentationItemFromShapeAspect,
|
||||
prop.RepresentationItem,
|
||||
prop.ShapeAspect,
|
||||
prop.BIMObjectGeometryProperties,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_placement,
|
||||
@@ -93,7 +103,9 @@ def register():
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
|
||||
kmi = km.keymap_items.new(
|
||||
"bim.object_duplicate_move_linked_aggregate_macro", "D", "PRESS", ctrl=True, shift=True
|
||||
)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
@@ -38,6 +38,7 @@ class RepresentationsData:
|
||||
def load(cls):
|
||||
cls.data = {"representations": cls.representations()}
|
||||
cls.data["contexts"] = cls.contexts()
|
||||
cls.data["shape_aspects"] = cls.shape_aspects()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -92,6 +93,26 @@ class RepresentationsData:
|
||||
)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def shape_aspects(cls):
|
||||
obj = bpy.context.active_object
|
||||
if not obj.data:
|
||||
return []
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
active_representation_id = obj.data.BIMMeshProperties.ifc_definition_id
|
||||
base_representation = tool.Ifc.get().by_id(active_representation_id)
|
||||
|
||||
# shape aspects matching context of the active representation
|
||||
matching_shape_aspects = []
|
||||
for shape_aspect in ifcopenshell.util.element.get_shape_aspects(element):
|
||||
matching_representation = tool.Geometry.get_shape_aspect_representation(shape_aspect, base_representation)
|
||||
if matching_representation:
|
||||
matching_shape_aspects.append(shape_aspect)
|
||||
|
||||
# blender enum items
|
||||
new_shape_aspect = [("NEW", "Create A New Shape Aspect", "")]
|
||||
return new_shape_aspect + [(str(s.id()), s.Name or "Unnamed", "") for s in matching_shape_aspects]
|
||||
|
||||
|
||||
class RepresentationItemsData:
|
||||
data = {}
|
||||
|
||||
@@ -21,12 +21,14 @@ import bmesh
|
||||
import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, pow
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Helper:
|
||||
def __init__(self, file):
|
||||
def __init__(self, file: ifcopenshell.file):
|
||||
self.file = file
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -34,7 +36,7 @@ class Helper:
|
||||
# edge that shares a single vertex only with that face to find the extrusion
|
||||
# edge. A face with the normal facing down is prioritised. A limited
|
||||
# dissolve ensure that faces are quads and not tris.
|
||||
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh):
|
||||
def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
@@ -115,7 +117,9 @@ class Helper:
|
||||
|
||||
return {"profile": profile, "extrusion": extrusion}
|
||||
|
||||
def auto_detect_arbitrary_profile_with_voids(self, obj, mesh):
|
||||
def auto_detect_arbitrary_profile_with_voids(
|
||||
self, obj: bpy.types.Object, mesh: bpy.types.Mesh
|
||||
) -> Union[tuple, dict]:
|
||||
groups = {"IFCARCINDEX": [], "IFCCIRCLE": []}
|
||||
for i, group in enumerate(obj.vertex_groups):
|
||||
if "IFCARCINDEX" in group.name:
|
||||
@@ -138,28 +142,31 @@ class Helper:
|
||||
total_groups = 0
|
||||
is_circle = False
|
||||
for group_type, group_indices in groups.items():
|
||||
for group_index in group_indices:
|
||||
if group_index in vert[deform_layer]:
|
||||
if group_type == "IFCCIRCLE":
|
||||
is_circle = True
|
||||
group_verts[group_type].setdefault(group_index, 0)
|
||||
group_verts[group_type][group_index] += 1
|
||||
total_groups += 0
|
||||
if not group_indices:
|
||||
continue
|
||||
is_special, group_index = tool.Blender.bmesh_check_vertex_in_groups(vert, deform_layer, group_indices)
|
||||
if not is_special:
|
||||
continue
|
||||
if group_type == "IFCCIRCLE":
|
||||
is_circle = True
|
||||
group_verts[group_type].setdefault(group_index, 0)
|
||||
group_verts[group_type][group_index] += 1
|
||||
total_groups += 0
|
||||
if total_groups > 1: # A vert can only belong to one group
|
||||
return (False, "AMBIGUOUS_SPECIAL_VERTEX")
|
||||
elif is_circle:
|
||||
pass # Circles are allowed to be unclosed
|
||||
pass # Circles are allowed to be unclosed
|
||||
elif total_groups == 0 and len(vert.link_edges) != 2: # Unclosed loop or forked loop
|
||||
return (False, "UNCLOSED_LOOP")
|
||||
|
||||
for group_type, group_counts in group_verts.items():
|
||||
if group_type == "IFCARCINDEX":
|
||||
for group_count in group_counts.values():
|
||||
if group_count != 3: # Each arc needs 3 verts
|
||||
if group_count != 3: # Each arc needs 3 verts
|
||||
return (False, "3POINT_ARC")
|
||||
elif group_type == "IFCCIRCLE":
|
||||
for group_count in group_counts.values():
|
||||
if group_count != 2: # Each circle needs 2 verts
|
||||
if group_count != 2: # Each circle needs 2 verts
|
||||
return (False, "CIRCLE")
|
||||
|
||||
loop_edges = set(bm.edges)
|
||||
@@ -269,7 +276,7 @@ class Helper:
|
||||
inner_loops.remove(outer_loop)
|
||||
|
||||
# Copy vectors to prevent random data mangling after bmesh is freed.
|
||||
points = [Vector(list(v.co)) for v in bm.verts]
|
||||
points = [v.co.copy() for v in bm.verts]
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
|
||||
@@ -21,10 +21,12 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import json
|
||||
import re
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.core.aggregate
|
||||
@@ -36,6 +38,7 @@ import blenderbim.bim.handler
|
||||
from mathutils import Vector, Matrix
|
||||
from time import time
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
class Operator:
|
||||
@@ -321,8 +324,8 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
# We are explicitly casting to a tessellation, so remove all parametric materials.
|
||||
element_type = ifcopenshell.util.element.get_type(product)
|
||||
if element_type: # Some invalid IFCs use material sets without a type.
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type])
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product])
|
||||
else:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
@@ -767,11 +770,13 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
self.report({'INFO'}, "Did not duplicate. Duplicate drawings through the Drawings and Documents UI.")
|
||||
self.report(
|
||||
{"INFO"}, "Did not duplicate. Duplicate drawings through the Drawings and Documents UI."
|
||||
)
|
||||
obj.select_set(False)
|
||||
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
|
||||
elif element.is_a("IfcProject"):
|
||||
self.report({'INFO'}, "Did not duplicate. IFC can only have one project.")
|
||||
self.report({"INFO"}, "Did not duplicate. IFC can only have one project.")
|
||||
obj.select_set(False)
|
||||
continue # Only one IfcProject is allowed.
|
||||
|
||||
@@ -838,6 +843,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(decomposition_relationships, old_to_new)
|
||||
OverrideDuplicateMove.handle_linked_aggregates(old_to_new)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return old_to_new
|
||||
|
||||
@@ -890,6 +896,26 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
if entity in old_to_new.keys():
|
||||
core.remove_connection(tool.Geometry, connection=connection)
|
||||
|
||||
@staticmethod
|
||||
def handle_linked_aggregates(old_to_new):
|
||||
for old, new in old_to_new.items():
|
||||
pset = ifcopenshell.util.element.get_pset(new[0], "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
old_aggregate = ifcopenshell.util.element.get_aggregate(old)
|
||||
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
|
||||
if old_aggregate == new_aggregate:
|
||||
parts = ifcopenshell.util.element.get_parts(new_aggregate)
|
||||
if parts:
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
index += 1
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_linked_macro"
|
||||
@@ -948,17 +974,36 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
index = 0
|
||||
index = DuplicateMoveLinkedAggregate.get_max_index(parts)
|
||||
add_linked_aggregate_pset(element, index)
|
||||
index += 1
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
select_objects_and_add_data(part)
|
||||
|
||||
add_linked_aggregate_pset(part, index)
|
||||
index += 1
|
||||
else:
|
||||
add_linked_aggregate_pset(part, index)
|
||||
index += 1
|
||||
|
||||
obj = tool.Ifc.get_object(part)
|
||||
obj.select_set(True)
|
||||
|
||||
def add_linked_aggregate_pset(part, index):
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
||||
return index
|
||||
|
||||
def add_linked_aggregate_group(element):
|
||||
linked_aggregate_group = None
|
||||
product_groups_name = [
|
||||
@@ -970,24 +1015,32 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
return
|
||||
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def add_linked_aggregate_pset(part, index):
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
|
||||
else:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
"group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group
|
||||
)
|
||||
|
||||
return index
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
for new in old_to_new.values():
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
group_elements = [
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
][0]
|
||||
|
||||
number = len(group_elements) - 1
|
||||
number = f"{number:02d}"
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
pattern1 = r"_\d"
|
||||
if re.findall(pattern1, new_obj.name):
|
||||
split_name = new_obj.name.split("_")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
continue
|
||||
pattern2 = r"\.\d{3}"
|
||||
if re.findall(pattern2, new_obj.name):
|
||||
split_name = new_obj.name.split(".")
|
||||
new_obj.name = split_name[0] + "_" + number
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
@@ -1009,6 +1062,8 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
old_to_new = OverrideDuplicateMove.execute_ifc_duplicate_operator(self, context, linked=True)
|
||||
|
||||
custom_incremental_naming_for_element_assembly(old_to_new)
|
||||
|
||||
# Recreate aggregate relationship
|
||||
for old in old_to_new.keys():
|
||||
if old.is_a("IfcElementAssembly"):
|
||||
@@ -1018,6 +1073,16 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
return old_to_new
|
||||
|
||||
@staticmethod
|
||||
def get_max_index(parts):
|
||||
psets = [ifcopenshell.util.element.get_pset(p, "BBIM_Linked_Aggregate") for p in parts]
|
||||
index = [i["Index"] for i in psets if i]
|
||||
if len(index) > 0:
|
||||
index = max(index)
|
||||
return index
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_linked_aggregate"
|
||||
@@ -1042,6 +1107,7 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
self.pset_name = "BBIM_Linked_Aggregate"
|
||||
refresh_start_time = time()
|
||||
old_to_new = {}
|
||||
original_names = {}
|
||||
|
||||
def delete_objects(element):
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
@@ -1054,6 +1120,61 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element))
|
||||
|
||||
def get_original_names(element):
|
||||
group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if self.group_name in r.RelatingGroup.Name
|
||||
][0].id()
|
||||
original_names[group] = {}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset["Index"]
|
||||
original_names[group][index] = tool.Ifc.get_object(element).name
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
original_names | get_original_names(part)
|
||||
else:
|
||||
try:
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
except:
|
||||
continue
|
||||
index = pset["Index"]
|
||||
original_names[group][index] = tool.Ifc.get_object(part).name
|
||||
|
||||
return original_names
|
||||
|
||||
def set_original_name(obj, original_names):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if ifcopenshell.util.element.get_parts(
|
||||
element
|
||||
): # if element has parts it means it is the base of and aggregate or sub-aggregate
|
||||
aggregate = element
|
||||
|
||||
group = [
|
||||
r.RelatingGroup
|
||||
for r in getattr(aggregate, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if self.group_name in r.RelatingGroup.Name
|
||||
]
|
||||
if not group:
|
||||
return
|
||||
|
||||
group = group[0].id()
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
index = pset["Index"]
|
||||
|
||||
try:
|
||||
obj.name = original_names[group][index]
|
||||
except:
|
||||
return
|
||||
|
||||
def get_element_assembly(element):
|
||||
if element.is_a("IfcElementAssembly"):
|
||||
return element
|
||||
@@ -1139,6 +1260,8 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
object_duplicate = tool.Ifc.get_object(element)
|
||||
duplicate_matrix = object_duplicate.matrix_world.decompose()
|
||||
|
||||
original_names = get_original_names(element)
|
||||
|
||||
delete_objects(element)
|
||||
|
||||
for obj in context.selected_objects:
|
||||
@@ -1146,7 +1269,9 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Ifc.get_object(base_instance).select_set(True)
|
||||
|
||||
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context)
|
||||
old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(
|
||||
self, context
|
||||
)
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
new_base_matrix = Matrix.LocRotScale(*duplicate_matrix)
|
||||
@@ -1154,6 +1279,7 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
new_obj_matrix = new_base_matrix @ matrix_diff
|
||||
new_obj.matrix_world = new_obj_matrix
|
||||
|
||||
for old, new in old_to_new.items():
|
||||
if element_aggregate and new[0].is_a("IfcElementAssembly"):
|
||||
new_aggregate = ifcopenshell.util.element.get_aggregate(new[0])
|
||||
|
||||
@@ -1166,6 +1292,10 @@ class RefreshLinkedAggregate(bpy.types.Operator):
|
||||
related_obj=tool.Ifc.get_object(new[0]),
|
||||
)
|
||||
|
||||
for old, new in old_to_new.items():
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
set_original_name(new_obj, original_names)
|
||||
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
operator_time = time() - refresh_start_time
|
||||
@@ -1198,18 +1328,23 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
return self.join_blender_obj()
|
||||
|
||||
def join_ifc_obj(self):
|
||||
representation = tool.Ifc.get().by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
|
||||
ifc_file = tool.Ifc.get()
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
representation = ifc_file.by_id(self.target.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ("Tessellation", "Brep"):
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
|
||||
bpy.ops.object.join()
|
||||
bpy.ops.bim.update_representation(obj=self.target.name, ifc_representation_class="")
|
||||
elif representation.RepresentationType == "SweptSolid":
|
||||
target_placement = np.array(self.target.matrix_world)
|
||||
target_placement[:, 3][:3] /= si_conversion
|
||||
|
||||
items = list(representation.Items)
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == self.target:
|
||||
@@ -1222,31 +1357,28 @@ class OverrideJoin(bpy.types.Operator, Operator):
|
||||
continue
|
||||
|
||||
# Only objects of the same representation type can be joined
|
||||
obj_rep = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
obj_rep = ifc_file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if obj_rep.RepresentationType != "SweptSolid":
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
placement = np.array(obj.matrix_world)
|
||||
placement[:, 3][:3] /= si_conversion
|
||||
|
||||
for item in obj_rep.Items:
|
||||
copied_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
|
||||
copied_item = ifcopenshell.util.element.copy_deep(ifc_file, item)
|
||||
for style in item.StyledByItem:
|
||||
copied_style = ifcopenshell.util.element.copy(tool.Ifc.get(), style)
|
||||
copied_style = ifcopenshell.util.element.copy(ifc_file, style)
|
||||
copied_style.Item = copied_item
|
||||
if copied_item.Position:
|
||||
position = ifcopenshell.util.placement.get_axis2placement(copied_item.Position)
|
||||
else:
|
||||
position = np.eye(4)
|
||||
position = np.eye(4, dtype=float)
|
||||
position = placement @ position
|
||||
position = np.linalg.inv(target_placement) @ position
|
||||
copied_item.Position = tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([float(n) for n in position[:, 3][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 2][:3]]),
|
||||
tool.Ifc.get().createIfcDirection([float(n) for n in position[:, 0][:3]]),
|
||||
)
|
||||
copied_item.Position = builder.create_axis2_placement_3d_from_matrix(position)
|
||||
items.append(copied_item)
|
||||
ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element)
|
||||
ifcopenshell.api.run("root.remove_product", ifc_file, product=element)
|
||||
representation.Items = items
|
||||
bpy.ops.object.join()
|
||||
core.switch_representation(
|
||||
@@ -1311,7 +1443,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
for obj in selected_objs:
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.data:
|
||||
obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj)
|
||||
if not obj_supports_edit_mode:
|
||||
self.report({"INFO"}, message)
|
||||
obj.select_set(False)
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -1590,6 +1724,16 @@ class EnableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
new.surface_style = style.Name or "Unnamed"
|
||||
elif inverse.is_a("IfcPresentationLayerAssignment"):
|
||||
new.layer = inverse.Name or "Unnamed"
|
||||
elif inverse.is_a("IfcShapeRepresentation"):
|
||||
if inverse.OfShapeAspect:
|
||||
shape_aspect = inverse.OfShapeAspect[0]
|
||||
new.shape_aspect = shape_aspect.Name
|
||||
new.shape_aspect_id = shape_aspect.id()
|
||||
|
||||
# sort created items
|
||||
sorted_items = sorted(props.items[:], key=lambda i: (not i.shape_aspect, i.shape_aspect))
|
||||
for i, item in enumerate(sorted_items[:-1]): # last item is sorted automatically
|
||||
props.items.move(props.items[:].index(item), i)
|
||||
|
||||
|
||||
class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
@@ -1600,3 +1744,270 @@ class DisableEditingRepresentationItems(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
obj.BIMGeometryProperties.is_editing = False
|
||||
|
||||
|
||||
class RemoveRepresentationItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_representation_item"
|
||||
bl_label = "Remove Representation Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
representation_item_id: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.active_object is None or len(context.active_object.BIMGeometryProperties.items) <= 1:
|
||||
cls.poll_message_set(
|
||||
"Active object need to have more than 1 representation items to keep representation valid"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item = ifc_file.by_id(self.representation_item_id)
|
||||
tool.Geometry.remove_representation_item(representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
def poll_editing_representaiton_item_style(cls, context):
|
||||
if not (obj := getattr(context, "active_object", None)):
|
||||
return False
|
||||
props = obj.BIMGeometryProperties
|
||||
if not props.is_editing:
|
||||
return False
|
||||
if not 0 <= props.active_item_index < len(props.items):
|
||||
return False
|
||||
item = props.items[props.active_item_index]
|
||||
shape_aspect = item.shape_aspect
|
||||
if shape_aspect == "":
|
||||
return True
|
||||
|
||||
from blenderbim.bim.module.material.data import ObjectMaterialData
|
||||
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
constituents = ObjectMaterialData.data["active_material_constituents"]
|
||||
if any(c for c in constituents if c == shape_aspect):
|
||||
cls.poll_message_set(
|
||||
"Style comes from item's shape aspect related to the material constituent "
|
||||
"with the same name and cannot be edited on representation item directly - "
|
||||
"you can change it from the material constituent"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class EnableEditingRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_representation_item_style"
|
||||
bl_label = "Enable Editing Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return poll_editing_representaiton_item_style(cls, context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_style = True
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# set dropdown to currently active style
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
style = tool.Style.get_representation_item_style(representation_item)
|
||||
if style:
|
||||
props.representation_item_style = str(style.id())
|
||||
|
||||
|
||||
class EditRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_representation_item_style"
|
||||
bl_label = "Edit Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
surface_style = ifc_file.by_id(int(props.representation_item_style))
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, surface_style)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class DisableEditingRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_representation_item_style"
|
||||
bl_label = "Disable Editing Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
|
||||
|
||||
class UnassignRepresentationItemStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_representation_item_style"
|
||||
bl_label = "Unassign Representation Item Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return poll_editing_representaiton_item_style(cls, context)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_style = False
|
||||
|
||||
# get active representation item
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = tool.Ifc.get_entity_by_id(representation_item_id)
|
||||
if not representation_item:
|
||||
self.report({"ERROR"}, f"Couldn't find representation item by id {representation_item_id}.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, None)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_representation_item_shape_aspect"
|
||||
bl_label = "Enable Editing Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = True
|
||||
|
||||
# set dropdown to currently active shape aspect
|
||||
shape_aspect_id = props.items[props.active_item_index].shape_aspect_id
|
||||
if shape_aspect_id != 0:
|
||||
props.representation_item_shape_aspect = str(shape_aspect_id)
|
||||
|
||||
|
||||
class EditRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_representation_item_shape_aspect"
|
||||
bl_label = "Edit Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = False
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
|
||||
if props.representation_item_shape_aspect == "NEW":
|
||||
active_representation = tool.Geometry.get_active_representation(obj)
|
||||
# find IfcProductRepresentationSelect based on current representation
|
||||
if hasattr(element, "Representation"): # IfcProduct
|
||||
product_shape = element.Representation
|
||||
else: # IfcTypeProduct
|
||||
for representation_map in element.RepresentationMaps:
|
||||
if representation_map.MappedRepresentation == active_representation:
|
||||
product_shape = representation_map
|
||||
previous_shape_aspect_id = props.items[props.active_item_index].shape_aspect_id
|
||||
# will be None if item didn't had a shape aspect
|
||||
previous_shape_aspect = tool.Ifc.get_entity_by_id(previous_shape_aspect_id)
|
||||
shape_aspect = tool.Geometry.create_shape_aspect(
|
||||
product_shape, active_representation, [representation_item], previous_shape_aspect
|
||||
)
|
||||
else:
|
||||
shape_aspect = ifc_file.by_id(int(props.representation_item_shape_aspect))
|
||||
tool.Geometry.add_representation_item_to_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
# set attributes from UI
|
||||
shape_aspect_attrs = props.shape_aspect_attrs
|
||||
shape_aspect.Name = shape_aspect_attrs.name
|
||||
shape_aspect.Description = shape_aspect_attrs.description
|
||||
|
||||
shape_aspect_representation = tool.Geometry.get_shape_aspect_representation_for_item(
|
||||
shape_aspect, representation_item
|
||||
)
|
||||
styles = tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
tool.Ifc.run(
|
||||
"style.assign_representation_styles",
|
||||
shape_representation=shape_aspect_representation,
|
||||
styles=styles,
|
||||
)
|
||||
tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
|
||||
class DisableEditingRepresentationItemShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_representation_item_shape_aspect"
|
||||
bl_label = "Disable Editing Representation Item Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMGeometryProperties
|
||||
props.is_editing_item_shape_aspect = False
|
||||
|
||||
|
||||
class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_representation_item_from_shape_aspect"
|
||||
bl_label = "Remove Representation Item From Shape Aspect"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
representation_item_id = props.items[props.active_item_index].ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
shape_aspect = ifc_file.by_id(props.items[props.active_item_index].shape_aspect_id)
|
||||
|
||||
# unassign items before removing items as removing items
|
||||
# might remove shape aspect
|
||||
if representation_item.StyledByItem:
|
||||
styles = tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
self.remove_styles_from_item(representation_item, styles)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
tool.Geometry.remove_representation_items_from_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
# reload items ui
|
||||
bpy.ops.bim.disable_editing_representation_items()
|
||||
bpy.ops.bim.enable_editing_representation_items()
|
||||
|
||||
def remove_styles_from_item(self, representation_item, styles):
|
||||
ifc_file = tool.Ifc.get()
|
||||
styled_item = representation_item.StyledByItem[0]
|
||||
new_styles = [s for s in styled_item.Styles if s not in styles]
|
||||
styled_item.Styles = new_styles
|
||||
|
||||
for style_ in new_styles:
|
||||
if style_.is_a("IfcPresentationStyleAssignment"):
|
||||
new_assignment_styles = [s for s in styled_item.Styles if s not in styles]
|
||||
if not new_assignment_styles:
|
||||
ifc_file.remove(style_)
|
||||
else:
|
||||
style_.Styles = new_assignment_styles
|
||||
|
||||
if not styled_item.Styles:
|
||||
ifc_file.remove(styled_item)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -47,11 +48,63 @@ def update_mode(self, context):
|
||||
bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT")
|
||||
|
||||
|
||||
def get_styles(self, context):
|
||||
# postponed import to avoid circular import
|
||||
from blenderbim.bim.module.material.data import MaterialsData
|
||||
|
||||
if not MaterialsData.is_loaded:
|
||||
MaterialsData.load()
|
||||
return MaterialsData.data["styles"]
|
||||
|
||||
|
||||
def get_shape_aspects(self, context):
|
||||
if not RepresentationsData.is_loaded:
|
||||
RepresentationsData.load()
|
||||
return RepresentationsData.data["shape_aspects"]
|
||||
|
||||
|
||||
def get_material_constituents(self, context, edit_text):
|
||||
from blenderbim.bim.module.material.data import ObjectMaterialData
|
||||
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
return ObjectMaterialData.data["active_material_constituents"]
|
||||
|
||||
|
||||
def update_shape_aspect(self, context):
|
||||
shape_aspect_id = self.representation_item_shape_aspect
|
||||
attrs = self.shape_aspect_attrs
|
||||
|
||||
if shape_aspect_id == "NEW": # new shape aspect
|
||||
attrs.name = tool.Blender.get_blender_prop_default_value(attrs, "name")
|
||||
attrs.description = tool.Blender.get_blender_prop_default_value(attrs, "description")
|
||||
else:
|
||||
shape_aspect = tool.Ifc.get().by_id(int(shape_aspect_id))
|
||||
attrs.name = shape_aspect.Name or ""
|
||||
attrs.description = shape_aspect.Description or ""
|
||||
|
||||
|
||||
class RepresentationItem(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
surface_style: StringProperty(name="Surface Style")
|
||||
layer: StringProperty(name="Layer")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
shape_aspect: StringProperty(name="Shape Aspect")
|
||||
shape_aspect_id: IntProperty(name="Shape Aspect IFC ID")
|
||||
|
||||
|
||||
class ShapeAspect(PropertyGroup):
|
||||
name: StringProperty(
|
||||
name="Name",
|
||||
description=(
|
||||
"Note that IfcMaterialConstituent is applied based on shape aspects using the same name as material constituent.\n"
|
||||
"In dropdown suggestions you can see names of existing material constituents."
|
||||
),
|
||||
**({} if bpy.app.version < (3, 3, 0) else {"search": get_material_constituents}),
|
||||
)
|
||||
description: StringProperty(
|
||||
name="Description",
|
||||
)
|
||||
|
||||
|
||||
class BIMObjectGeometryProperties(PropertyGroup):
|
||||
@@ -59,6 +112,13 @@ class BIMObjectGeometryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
items: CollectionProperty(name="Representation Items", type=RepresentationItem)
|
||||
active_item_index: IntProperty(name="Active Representation Item Index")
|
||||
is_editing_item_style: BoolProperty(name="Is Editing Item's Style", default=False)
|
||||
representation_item_style: EnumProperty(items=get_styles, name="Representation Item Style")
|
||||
is_editing_item_shape_aspect: BoolProperty(name="Is Editing Item's Shape Aspect", default=False)
|
||||
representation_item_shape_aspect: EnumProperty(
|
||||
items=get_shape_aspects, name="Representation Item Shape Aspect", update=update_shape_aspect
|
||||
)
|
||||
shape_aspect_attrs: PointerProperty(type=ShapeAspect)
|
||||
|
||||
|
||||
class BIMGeometryProperties(PropertyGroup):
|
||||
|
||||
@@ -29,6 +29,7 @@ from blenderbim.bim.module.geometry.data import (
|
||||
PlacementData,
|
||||
DerivedCoordinatesData,
|
||||
)
|
||||
from blenderbim.bim.module.layer.data import LayersData
|
||||
|
||||
|
||||
def mode_menu(self, context):
|
||||
@@ -133,6 +134,7 @@ class BIM_PT_representations(Panel):
|
||||
|
||||
if not RepresentationsData.data["representations"]:
|
||||
layout.label(text="No Representations Found")
|
||||
return
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -150,6 +152,18 @@ class BIM_PT_representations(Panel):
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
if representation["is_active"]:
|
||||
active_representation = representation
|
||||
|
||||
layout.separator()
|
||||
if not LayersData.is_loaded:
|
||||
LayersData.load()
|
||||
if LayersData.data["active_layers"]:
|
||||
layout.label(text="Representation Presentation Layers:")
|
||||
for layer_name in LayersData.data["active_layers"].values():
|
||||
layout.label(text=layer_name, icon="STICKY_UVS_LOC")
|
||||
else:
|
||||
layout.label(text="Representation Has No Presentation Layers", icon="STICKY_UVS_LOC")
|
||||
|
||||
|
||||
class BIM_PT_representation_items(Panel):
|
||||
@@ -187,20 +201,47 @@ class BIM_PT_representation_items(Panel):
|
||||
self.layout.template_list("BIM_UL_representation_items", "", props, "items", props, "active_item_index")
|
||||
|
||||
item_is_active = props.active_item_index < len(props.items)
|
||||
if item_is_active:
|
||||
surface_style = props.items[props.active_item_index].surface_style
|
||||
presentation_layer = props.items[props.active_item_index].layer
|
||||
if not item_is_active:
|
||||
return
|
||||
|
||||
active_item = props.items[props.active_item_index]
|
||||
surface_style = active_item.surface_style
|
||||
shape_aspect = active_item.shape_aspect
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if props.is_editing_item_style:
|
||||
# NOTE: we currently support 1 item having just 1 style
|
||||
# when IfcStyledItem can actually have multiple styles
|
||||
prop_with_search(row, props, "representation_item_style", icon="SHADING_RENDERED", text="")
|
||||
row.operator("bim.edit_representation_item_style", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_representation_item_style", icon="CANCEL", text="")
|
||||
else:
|
||||
surface_style, presentation_layer = None, None
|
||||
if surface_style:
|
||||
row.label(text=surface_style, icon="SHADING_RENDERED")
|
||||
else:
|
||||
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
|
||||
row.operator("bim.enable_editing_representation_item_style", icon="GREASEPENCIL", text="")
|
||||
if surface_style:
|
||||
row.operator("bim.unassign_representation_item_style", icon="X", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
if surface_style:
|
||||
row.label(text=surface_style, icon="SHADING_RENDERED")
|
||||
else:
|
||||
row.label(text="No Surface Style", icon="MESH_UVSPHERE")
|
||||
row.label(text=active_item.layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text=presentation_layer or "No Presentation Layer", icon="STICKY_UVS_LOC")
|
||||
row = self.layout.row(align=True)
|
||||
if props.is_editing_item_shape_aspect:
|
||||
row.prop(props, "representation_item_shape_aspect", icon="SHAPEKEY_DATA", text="")
|
||||
row.operator("bim.edit_representation_item_shape_aspect", icon="CHECKMARK", text="")
|
||||
row.operator("bim.disable_editing_representation_item_shape_aspect", icon="CANCEL", text="")
|
||||
|
||||
shape_aspect_attrs = props.shape_aspect_attrs
|
||||
self.layout.label(text="Shape Aspect Attributes:")
|
||||
self.layout.prop(shape_aspect_attrs, "name")
|
||||
self.layout.prop(shape_aspect_attrs, "description")
|
||||
else:
|
||||
row.label(text=shape_aspect or "No Shape Aspect", icon="SHAPEKEY_DATA")
|
||||
row.operator("bim.enable_editing_representation_item_shape_aspect", icon="GREASEPENCIL", text="")
|
||||
if shape_aspect:
|
||||
row.operator("bim.remove_representation_item_from_shape_aspect", icon="X", text="")
|
||||
|
||||
|
||||
class BIM_PT_connections(Panel):
|
||||
@@ -466,6 +507,11 @@ class BIM_UL_representation_items(UIList):
|
||||
if item:
|
||||
icon = "MATERIAL" if item.surface_style else "MESH_UVSPHERE"
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name, icon=icon)
|
||||
item_name = item.name
|
||||
if item.shape_aspect:
|
||||
item_name = f"{item.shape_aspect} {item_name}"
|
||||
row.label(text=item_name, icon=icon)
|
||||
if item.layer:
|
||||
row.label(text="", icon="STICKY_UVS_LOC")
|
||||
op = row.operator("bim.remove_representation_item", icon="X", text="")
|
||||
op.representation_item_id = item.ifc_definition_id
|
||||
|
||||
@@ -212,7 +212,7 @@ class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"group.unassign_group",
|
||||
self.file,
|
||||
**{
|
||||
"product": self.file.by_id(product.BIMObjectProperties.ifc_definition_id),
|
||||
"products": [self.file.by_id(product.BIMObjectProperties.ifc_definition_id)],
|
||||
"group": self.file.by_id(self.group),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,14 +40,15 @@ class LayersData:
|
||||
|
||||
@classmethod
|
||||
def active_layers(cls):
|
||||
if not bpy.context.active_object:
|
||||
return []
|
||||
data = bpy.context.active_object.data
|
||||
if not data:
|
||||
return []
|
||||
if not isinstance(data, bpy.types.Mesh) or not data.BIMMeshProperties.ifc_definition_id:
|
||||
return []
|
||||
results = []
|
||||
results = dict()
|
||||
shape = tool.Ifc.get().by_id(data.BIMMeshProperties.ifc_definition_id)
|
||||
for inverse in tool.Ifc.get().get_inverse(shape):
|
||||
if inverse.is_a("IfcPresentationLayerAssignment"):
|
||||
results.append(inverse.id())
|
||||
for inverse in shape.LayerAssignments:
|
||||
results[inverse.id()] = inverse.Name or "Unnamed"
|
||||
return results
|
||||
|
||||
@@ -137,6 +137,7 @@ class RemovePresentationLayer(bpy.types.Operator):
|
||||
class AssignPresentationLayer(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_presentation_layer"
|
||||
bl_label = "Assign Presentation Layer"
|
||||
bl_description = "Assign presentation layer to the active representation of the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty()
|
||||
layer: bpy.props.IntProperty()
|
||||
@@ -150,7 +151,10 @@ class AssignPresentationLayer(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"layer.assign_layer",
|
||||
self.file,
|
||||
**{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
|
||||
**{
|
||||
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
|
||||
"layer": self.file.by_id(self.layer),
|
||||
}
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -158,6 +162,7 @@ class AssignPresentationLayer(bpy.types.Operator):
|
||||
class UnassignPresentationLayer(bpy.types.Operator):
|
||||
bl_idname = "bim.unassign_presentation_layer"
|
||||
bl_label = "Unassign Presentation Layer"
|
||||
bl_description = "Unassign presentation layer from the active representation of the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty()
|
||||
layer: bpy.props.IntProperty()
|
||||
@@ -171,7 +176,10 @@ class UnassignPresentationLayer(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"layer.unassign_layer",
|
||||
self.file,
|
||||
**{"item": self.file.by_id(item.BIMMeshProperties.ifc_definition_id), "layer": self.file.by_id(self.layer)}
|
||||
**{
|
||||
"items": [self.file.by_id(item.BIMMeshProperties.ifc_definition_id)],
|
||||
"layer": self.file.by_id(self.layer),
|
||||
}
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ class BIM_UL_layers(UIList):
|
||||
op = row.operator("bim.assign_presentation_layer", text="", icon="KEYFRAME", emboss=False)
|
||||
op.layer = item.ifc_definition_id
|
||||
|
||||
# TODO: replace placeholder UI for hiding presentation layers
|
||||
row.operator("bim.disable_editing_layer", text="", icon="HIDE_OFF", emboss=False)
|
||||
row.operator("bim.disable_editing_layer", text="", icon="FREEZE", emboss=False)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.doc
|
||||
import ifcopenshell.util.schema
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -109,29 +110,41 @@ class MaterialsData:
|
||||
def active_styles(cls):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
results = []
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index].ifc_definition_id
|
||||
if not material:
|
||||
return results
|
||||
material = tool.Ifc.get().by_id(material)
|
||||
for definition in material.HasRepresentation:
|
||||
for representation in definition.Representations:
|
||||
if not representation.is_a("IfcStyledRepresentation"):
|
||||
if not props.materials or props.active_material_index >= len(props.materials):
|
||||
return results
|
||||
|
||||
material = props.materials[props.active_material_index].ifc_definition_id
|
||||
if not material:
|
||||
return results
|
||||
|
||||
material = tool.Ifc.get_entity_by_id(material)
|
||||
|
||||
# NOTE: it's possible that data will be refreshed during material removal
|
||||
# (when it will try to fetch active material type and will reach for material_types)
|
||||
# and props.materials[i].ifc_definition_id will contain already removed id
|
||||
if not material or not material.is_a("IfcMaterial"):
|
||||
return results
|
||||
|
||||
for definition in material.HasRepresentation:
|
||||
for representation in definition.Representations:
|
||||
if not representation.is_a("IfcStyledRepresentation"):
|
||||
continue
|
||||
context = representation.ContextOfItems
|
||||
for item in representation.Items:
|
||||
if not item.is_a("IfcStyledItem"):
|
||||
continue
|
||||
context = representation.ContextOfItems
|
||||
for item in representation.Items:
|
||||
if not item.is_a("IfcStyledItem"):
|
||||
continue
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
results.append({
|
||||
for style in item.Styles:
|
||||
if style.is_a("IfcSurfaceStyle"):
|
||||
results.append(
|
||||
{
|
||||
"context_type": context.ContextType,
|
||||
"context_identifier": getattr(context, "ContextIdentifier", ""),
|
||||
"target_view": getattr(context, "TargetView", ""),
|
||||
"name": style.Name or "Unnamed",
|
||||
"id": style.id(),
|
||||
"context_id": context.id(),
|
||||
})
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@@ -152,6 +165,7 @@ class ObjectMaterialData:
|
||||
cls.data["materials"] = cls.materials()
|
||||
cls.data["type_material"] = cls.type_material()
|
||||
cls.data["material_type"] = cls.material_type()
|
||||
cls.data["active_material_constituents"] = cls.active_material_constituents()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -318,3 +332,10 @@ class ObjectMaterialData:
|
||||
if version == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
|
||||
return [(m, m, ifcopenshell.util.doc.get_entity_doc(version, m).get("description", "")) for m in material_types]
|
||||
|
||||
@classmethod
|
||||
def active_material_constituents(cls):
|
||||
material = cls.material
|
||||
if not cls.material or not material.is_a("IfcMaterialConstituentSet"):
|
||||
return []
|
||||
return [m.Name for m in material.MaterialConstituents if m.Name]
|
||||
|
||||
@@ -34,6 +34,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_materials"
|
||||
bl_label = "Load Materials"
|
||||
bl_description = "Display list of named materials"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -117,7 +118,7 @@ class AddMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_material"
|
||||
bl_label = "Add Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj: bpy.props.StringProperty(name="Material Name")
|
||||
name: bpy.props.StringProperty(default="Default")
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -151,7 +152,10 @@ class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
res = core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material))
|
||||
if not res:
|
||||
self.report({"ERROR"}, "Material is used in material sets and cannot be removed.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -270,6 +274,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_layer"
|
||||
bl_label = "Add Layer"
|
||||
bl_description = "The List is Ordered From Origin Point"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
layer_set: bpy.props.IntProperty()
|
||||
@@ -294,6 +299,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reorder_material_set_item"
|
||||
bl_label = "Reorder Material Set Item"
|
||||
bl_description = "The List is Ordered From Origin Point"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
old_index: bpy.props.IntProperty()
|
||||
@@ -728,6 +734,20 @@ class EnableEditingMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.active_material_id = self.material
|
||||
props.editing_material_type = "STYLE"
|
||||
|
||||
# set already applied style and context if possible
|
||||
if not 0 <= props.active_material_index < len(props.materials):
|
||||
return
|
||||
|
||||
material = tool.Ifc.get().by_id(props.materials[props.active_material_index].ifc_definition_id)
|
||||
if not material.HasRepresentation:
|
||||
return
|
||||
|
||||
rep = material.HasRepresentation[0].Representations[0] # IfcStyledRepresentation
|
||||
props.contexts = str(rep.ContextOfItems.id())
|
||||
style = rep.Items[0].Styles[0]
|
||||
if style.Name: # props.styles only has named styles
|
||||
props.styles = str(rep.Items[0].Styles[0].id())
|
||||
|
||||
|
||||
class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_material_style"
|
||||
@@ -736,14 +756,19 @@ class EditMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
material = tool.Ifc.get().by_id(props.active_material_id)
|
||||
style = tool.Ifc.get().by_id(int(props.styles))
|
||||
context = tool.Ifc.get().by_id(int(props.contexts))
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_material_style", tool.Ifc.get(), material=material, style=style, context=context
|
||||
)
|
||||
ifc_file = tool.Ifc.get()
|
||||
material = ifc_file.by_id(props.active_material_id)
|
||||
style = ifc_file.by_id(int(props.styles))
|
||||
context = ifc_file.by_id(int(props.contexts))
|
||||
ifcopenshell.api.run("style.assign_material_style", ifc_file, material=material, style=style, context=context)
|
||||
tool.Material.disable_editing_material()
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
# NOTE: Update all elements that has this material and
|
||||
# not just the ones that are using it in IfcMaterialConstituent,
|
||||
# to handle cases where Style is connected to geometry implicitly:
|
||||
# e.g. RepItem->IfcElement->IfcMaterial->Style
|
||||
# instead of RepItem->ShapeAspect->Constituent->Style.
|
||||
tool.Material.update_elements_using_material(material)
|
||||
|
||||
|
||||
class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -762,3 +787,4 @@ class UnassignMaterialStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"style.unassign_material_style", tool.Ifc.get(), material=material, style=style, context=context
|
||||
)
|
||||
core.load_materials(tool.Material, props.material_type)
|
||||
tool.Material.update_elements_using_material(material)
|
||||
|
||||
@@ -81,6 +81,8 @@ class BIM_PT_materials(Panel):
|
||||
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
# TODO: data.py is not updated on changing active_material_index
|
||||
# so the active material styles go out of sync with the active material
|
||||
for style in MaterialsData.data["active_styles"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="", icon="SHADING_RENDERED")
|
||||
@@ -173,7 +175,7 @@ class BIM_PT_object_material(Panel):
|
||||
|
||||
if ObjectMaterialData.data["type_material"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="FILE_PARENT")
|
||||
row.label(text="Inherited Material: " + ObjectMaterialData.data["type_material"], icon="CON_CHILDOF")
|
||||
|
||||
if ObjectMaterialData.data["material_class"]:
|
||||
return self.draw_material_ui()
|
||||
@@ -228,7 +230,7 @@ class BIM_PT_object_material(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Profiles Available")
|
||||
row.operator("bim.add_profile_def", icon="ADD", text="")
|
||||
else:
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if ObjectMaterialData.data["set_item_name"] == "profile":
|
||||
prop_with_search(row, self.mprops, "profiles", icon="ITALIC", text="")
|
||||
|
||||
@@ -113,6 +113,7 @@ classes = (
|
||||
covering.AddInstanceCeilingCoveringFromCursor,
|
||||
covering.RegenSelectedCoveringObject,
|
||||
space.ToggleSpaceVisibility,
|
||||
space.ToggleHideSpaces,
|
||||
mep.FitFlowSegments,
|
||||
mep.RegenerateDistributionElement,
|
||||
prop.BIMModelProperties,
|
||||
@@ -193,15 +194,18 @@ addon_keymaps = []
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"bim.explore_tool"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(workspace.SlabTool, after={"bim.wall_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DoorTool, after={"bim.slab_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.window_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableCarrierTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableTool, after={"bim.cable_carrier_tool"}, separator=False, group=False)
|
||||
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
|
||||
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
|
||||
@@ -210,6 +214,7 @@ def register():
|
||||
bpy.types.Object.BIMDoorProperties = bpy.props.PointerProperty(type=prop.BIMDoorProperties)
|
||||
bpy.types.Object.BIMRailingProperties = bpy.props.PointerProperty(type=prop.BIMRailingProperties)
|
||||
bpy.types.Object.BIMRoofProperties = bpy.props.PointerProperty(type=prop.BIMRoofProperties)
|
||||
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(ui.add_mesh_object_menu)
|
||||
bpy.types.VIEW3D_MT_add.append(ui.add_menu)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
@@ -231,6 +236,8 @@ def unregister():
|
||||
bpy.utils.unregister_tool(workspace.BeamTool)
|
||||
bpy.utils.unregister_tool(workspace.DuctTool)
|
||||
bpy.utils.unregister_tool(workspace.PipeTool)
|
||||
bpy.utils.unregister_tool(workspace.CableCarrierTool)
|
||||
bpy.utils.unregister_tool(workspace.CableTool)
|
||||
bpy.utils.unregister_tool(workspace.BimTool)
|
||||
del bpy.types.Scene.BIMModelProperties
|
||||
del bpy.types.Object.BIMArrayProperties
|
||||
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
|
||||
|
||||
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
|
||||
@@ -35,15 +36,6 @@ def transparent_color(color, alpha=0.1):
|
||||
return color
|
||||
|
||||
|
||||
def bm_check_vertex_in_groups(vertex, deform_layer, groups):
|
||||
"""returns tuple boolean (whether vertex is in any of the groups)
|
||||
and related group index"""
|
||||
for group_index in vertex[deform_layer].keys():
|
||||
if group_index in groups:
|
||||
return True, group_index
|
||||
return False, None
|
||||
|
||||
|
||||
class ProfileDecorator:
|
||||
installed = None
|
||||
|
||||
@@ -148,12 +140,12 @@ class ProfileDecorator:
|
||||
# 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 = bm_check_vertex_in_groups(vertex, deform_layer, arc_groups)
|
||||
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 = bm_check_vertex_in_groups(vertex, deform_layer, circle_groups)
|
||||
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
|
||||
|
||||
@@ -542,7 +542,7 @@ class MEPGenerator:
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
rel = ifcopenshell.api.run("material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet")
|
||||
rel = ifcopenshell.api.run("material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet")
|
||||
profile_set = rel.RelatingMaterial
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile", ifc_file, profile_set=profile_set, material=material
|
||||
|
||||
@@ -24,16 +24,20 @@ import shapely
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.shape_builder
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi
|
||||
from math import pi, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy.types import Operator
|
||||
from bpy.types import SpaceView3D
|
||||
@@ -87,20 +91,28 @@ class FilledOpeningGenerator:
|
||||
|
||||
# In this prototype, we assume openings are only added to axis-based elements
|
||||
layers = tool.Model.get_material_layer_parameters(element)
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
if layers["layer_set_direction"] == "AXIS2":
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
elif layers["layer_set_direction"] == "AXIS3":
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
local_position_on_voided_obj = raycast[1]
|
||||
# Equivalent to "side Z" for a wall axis, so that stuff like skylights appear on the top.
|
||||
local_position_on_voided_obj.z = layers["offset"] + layers["thickness"]
|
||||
new_matrix.translation.xyz = voided_obj.matrix_world @ local_position_on_voided_obj
|
||||
rotation_matrix = Matrix.Rotation(radians(-90), 4, "X")
|
||||
new_matrix @= rotation_matrix
|
||||
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
@@ -176,7 +188,13 @@ class FilledOpeningGenerator:
|
||||
)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
filling = settings["related_object"]
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
for related_object in settings["related_objects"]:
|
||||
self._regenerate_from_type(related_object)
|
||||
|
||||
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
|
||||
filling = related_object
|
||||
if not getattr(filling, "FillsVoids", None):
|
||||
return
|
||||
|
||||
@@ -521,6 +539,7 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
if element2 and not element1:
|
||||
obj1, obj2 = obj2, obj1
|
||||
element1, element2 = element2, element1
|
||||
if not obj1.data or not hasattr(obj1.data, "BIMMeshProperties"):
|
||||
return {"FINISHED"}
|
||||
representation = tool.Ifc.get().by_id(obj1.data.BIMMeshProperties.ifc_definition_id)
|
||||
@@ -668,17 +687,17 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
except:
|
||||
continue
|
||||
|
||||
boolean = None
|
||||
boolean_id = None
|
||||
for inverse in tool.Ifc.get().get_inverse(item):
|
||||
if inverse.is_a("IfcBooleanResult"):
|
||||
boolean = inverse
|
||||
boolean_id = inverse.id()
|
||||
break
|
||||
ifcopenshell.api.run("geometry.remove_boolean", tool.Ifc.get(), item=item)
|
||||
|
||||
if obj.data.BIMMeshProperties.obj:
|
||||
upstream_obj = obj.data.BIMMeshProperties.obj
|
||||
element = tool.Ifc.get_entity(upstream_obj)
|
||||
bbim_boolean_updates.setdefault(element, []).append(boolean)
|
||||
bbim_boolean_updates.setdefault(element, []).append(boolean_id)
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if body:
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -692,8 +711,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
)
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
for element, booleans in bbim_boolean_updates.items():
|
||||
tool.Model.unmark_manual_booleans(element, booleans)
|
||||
for element, boolean_ids in bbim_boolean_updates.items():
|
||||
tool.Model.unmark_manual_booleans(element, boolean_ids)
|
||||
|
||||
tool.Blender.set_active_object(upstream_obj)
|
||||
return {"FINISHED"}
|
||||
@@ -761,8 +780,6 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
building_objs = set()
|
||||
model = tool.Ifc.get()
|
||||
all_openings = model.by_type("IfcOpeningElement")
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -773,23 +790,32 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
openings = [r.RelatedOpeningElement for r in element.HasOpenings]
|
||||
for opening in openings:
|
||||
similar_openings = [o for o in all_openings if o.ObjectPlacement == opening.ObjectPlacement]
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
building_objs.add(obj)
|
||||
|
||||
similar_openings = blenderbim.core.geometry.get_similar_openings(tool.Ifc, opening)
|
||||
similar_openings_building_objs = blenderbim.core.geometry.get_similar_openings_building_objs(
|
||||
tool.Ifc, similar_openings
|
||||
)
|
||||
building_objs.update(similar_openings_building_objs)
|
||||
|
||||
if opening_obj:
|
||||
if tool.Ifc.is_edited(opening_obj):
|
||||
tool.Geometry.run_geometry_update_representation(obj=opening_obj)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(
|
||||
tool.Geometry, opening, similar_openings
|
||||
)
|
||||
elif tool.Ifc.is_moved(opening_obj):
|
||||
blenderbim.core.geometry.edit_object_placement(
|
||||
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
|
||||
)
|
||||
for similar_opening in similar_openings:
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
element = similar_opening.VoidsElements[0].RelatingBuildingElement
|
||||
obj = tool.Ifc.get_object(element)
|
||||
building_objs.add(obj)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(
|
||||
tool.Geometry, opening, similar_openings
|
||||
)
|
||||
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
building_objs.update(
|
||||
self.get_all_building_objects_of_similar_openings(opening)
|
||||
) # NB this has nothing to do with clone similar_opening
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -25,9 +25,13 @@ import ifcopenshell.util.system
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.aggregate
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.spatial
|
||||
from . import wall, slab, profile, mep
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
@@ -273,7 +277,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
obj.location[2] = collection_obj.location[2] + bpy.context.scene.BIMModelProperties.rl2
|
||||
|
||||
@staticmethod
|
||||
def generate_layered_element(ifc_class, relating_type):
|
||||
def generate_layered_element(ifc_class: str, relating_type: ifcopenshell.entity_instance) -> bool:
|
||||
layer_set_direction = None
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
@@ -297,6 +301,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
material = ifcopenshell.util.element.get_material(tool.Ifc.get_entity(obj))
|
||||
material.LayerSetDirection = layer_set_direction
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -510,7 +515,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
if usecase_path == "material.assign_material":
|
||||
if not settings.get("material", None):
|
||||
return
|
||||
elements = [settings["product"]]
|
||||
elements = settings["products"]
|
||||
else:
|
||||
elements = []
|
||||
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
|
||||
@@ -546,7 +551,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
|
||||
|
||||
def ensure_material_unassigned(usecase_path, ifc_file, settings):
|
||||
elements = [settings["product"]]
|
||||
elements = settings["products"]
|
||||
if elements[0].is_a("IfcElementType"):
|
||||
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
|
||||
for element in elements:
|
||||
|
||||
@@ -21,14 +21,18 @@ import copy
|
||||
import bmesh
|
||||
import mathutils.geometry
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
import blenderbim.core.root
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
@@ -92,7 +96,7 @@ class DumbProfileGenerator:
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
material.CardinalPoint = self.cardinal_point
|
||||
@@ -207,12 +211,19 @@ class DumbProfileRegenerator:
|
||||
return results
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
||||
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
|
||||
return
|
||||
|
||||
for related_object in settings["related_objects"]:
|
||||
self._regenerate_from_type(related_object)
|
||||
|
||||
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
|
||||
obj = tool.Ifc.get_object(related_object)
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
DumbProfileRecalculator().recalculate([obj])
|
||||
|
||||
|
||||
@@ -1013,17 +1024,30 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
start = obj.matrix_world @ obj.data.vertices[0].co.copy()
|
||||
end = obj.matrix_world @ obj.data.vertices[1].co.copy()
|
||||
matrix = obj.matrix_world
|
||||
previous_z_axis = matrix.col[2].to_3d().normalized()
|
||||
|
||||
start = matrix @ obj.data.vertices[0].co
|
||||
end = matrix @ obj.data.vertices[1].co
|
||||
depth = (end - start).length
|
||||
z_axis = (end - start).normalized()
|
||||
y_axis = Vector((0, 0, 1))
|
||||
# making sure z_axis != y_axis
|
||||
if z_axis == y_axis:
|
||||
y_axis = Vector((0, 1, 0))
|
||||
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
# if z-axis didn't changed we can just reuse the previous rotation
|
||||
if not tool.Cad.are_vectors_equal(previous_z_axis, z_axis):
|
||||
y_axis = Vector((0, 0, 1))
|
||||
# making sure z_axis != y_axis
|
||||
if z_axis == y_axis:
|
||||
y_axis = Vector((0, 1, 0))
|
||||
|
||||
x_axis = y_axis.cross(z_axis).normalized()
|
||||
y_axis = z_axis.cross(x_axis).normalized()
|
||||
|
||||
# update basises
|
||||
matrix.col[0].xyz = x_axis
|
||||
matrix.col[1].xyz = y_axis
|
||||
matrix.col[2].xyz = z_axis
|
||||
|
||||
matrix.translation = start
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -1036,16 +1060,6 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
matrix = Matrix(
|
||||
(
|
||||
[x_axis[0], y_axis[0], z_axis[0], start.x],
|
||||
[x_axis[1], y_axis[1], z_axis[1], start.y],
|
||||
[x_axis[2], y_axis[2], z_axis[2], start.z],
|
||||
[0, 0, 0, 1],
|
||||
)
|
||||
)
|
||||
|
||||
obj.matrix_world = matrix
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
joiner = DumbProfileJoiner()
|
||||
|
||||
@@ -99,7 +99,10 @@ class BIMModelProperties(PropertyGroup):
|
||||
items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")],
|
||||
name="Occurrence Name Style",
|
||||
)
|
||||
occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function")
|
||||
occurrence_name_function: bpy.props.StringProperty(
|
||||
name="Occurrence Name Function",
|
||||
description="Code that will be evaluated to generate occurrence name for CUSTOM occurrence name style",
|
||||
)
|
||||
getter_enum = {"ifc_class": get_ifc_class, "relating_type": get_relating_type_id}
|
||||
extrusion_depth: bpy.props.FloatProperty(min=0.001, default=42.0, subtype="DISTANCE")
|
||||
cardinal_point: bpy.props.EnumProperty(
|
||||
@@ -234,7 +237,6 @@ class BIMStairProperties(PropertyGroup):
|
||||
unit="LENGTH",
|
||||
size=2,
|
||||
)
|
||||
# TODO: need to clamp at zero for non WOOD/STEEL
|
||||
nosing_length: bpy.props.FloatProperty(
|
||||
name="Nosing Length",
|
||||
description=(
|
||||
@@ -712,8 +714,15 @@ class BIMRoofProperties(PropertyGroup):
|
||||
height: bpy.props.FloatProperty(
|
||||
name="Height", default=1.0, description="Maximum height of the roof to be generated.", subtype="DISTANCE"
|
||||
)
|
||||
angle: bpy.props.FloatProperty(name="Slope Angle", default=pi / 18, subtype="ANGLE", update=lambda self, context: self.update_percentage())
|
||||
percentage: bpy.props.FloatProperty(name="Slope %", default=17.63269754733197, subtype="PERCENTAGE", update=lambda self, context: self.update_angle())
|
||||
angle: bpy.props.FloatProperty(
|
||||
name="Slope Angle", default=pi / 18, subtype="ANGLE", update=lambda self, context: self.update_percentage()
|
||||
)
|
||||
percentage: bpy.props.FloatProperty(
|
||||
name="Slope %",
|
||||
default=math.tan(pi / 18) * 100,
|
||||
subtype="PERCENTAGE",
|
||||
update=lambda self, context: self.update_angle(),
|
||||
)
|
||||
roof_thickness: bpy.props.FloatProperty(name="Roof Thickness", default=0.1, subtype="DISTANCE")
|
||||
rafter_edge_angle: bpy.props.FloatProperty(name="Rafter Edge Angle", min=0, max=pi, default=pi / 2, subtype="ANGLE")
|
||||
|
||||
@@ -745,4 +754,4 @@ class BIMRoofProperties(PropertyGroup):
|
||||
self.angle = math.atan(self.percentage / 100)
|
||||
|
||||
def update_percentage(self):
|
||||
self.percentage = math.tan(self.angle)*100
|
||||
self.percentage = math.tan(self.angle) * 100
|
||||
|
||||
@@ -20,17 +20,22 @@ import bpy
|
||||
import json
|
||||
import bmesh
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.type
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
from math import radians
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
@@ -110,7 +115,7 @@ def calculate_quantities(usecase_path, ifc_file, settings):
|
||||
|
||||
|
||||
class DumbSlabGenerator:
|
||||
def __init__(self, relating_type):
|
||||
def __init__(self, relating_type: ifcopenshell.entity_instance):
|
||||
self.relating_type = relating_type
|
||||
|
||||
def generate(self):
|
||||
@@ -173,7 +178,7 @@ class DumbSlabGenerator:
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
representation = ifcopenshell.api.run(
|
||||
@@ -246,27 +251,38 @@ class DumbSlabPlaner:
|
||||
self.change_thickness(element, total_thickness)
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
||||
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
|
||||
return
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
|
||||
layer_set_direction = None
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
for related_object in settings["related_objects"]:
|
||||
self._regenerate_from_type(related_object, layer_set_direction, new_thickness)
|
||||
|
||||
def _regenerate_from_type(
|
||||
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
|
||||
) -> None:
|
||||
obj = tool.Ifc.get_object(related_object)
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
|
||||
material = ifcopenshell.util.element.get_material(related_object)
|
||||
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
|
||||
return
|
||||
if layer_set_direction:
|
||||
material.LayerSetDirection = layer_set_direction
|
||||
if material.LayerSetDirection == "AXIS3":
|
||||
self.change_thickness(settings["related_object"], new_thickness)
|
||||
self.change_thickness(related_object, new_thickness)
|
||||
|
||||
def change_thickness(self, element, thickness):
|
||||
def change_thickness(self, element: ifcopenshell.entity_instance, thickness: float) -> None:
|
||||
body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
@@ -698,9 +714,7 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
new_footprint = ifcopenshell.api.run(
|
||||
"geometry.add_footprint_representation", tool.Ifc.get(), context=footprint_context, curves=curves
|
||||
)
|
||||
old_footprint = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "FootPrint", "SKETCH_VIEW"
|
||||
)
|
||||
old_footprint = ifcopenshell.util.representation.get_representation(element, "Plan", "FootPrint", "SKETCH_VIEW")
|
||||
if old_footprint:
|
||||
for inverse in tool.Ifc.get().get_inverse(old_footprint):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
|
||||
|
||||
@@ -29,13 +29,19 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_space"
|
||||
bl_label = "Generate Space"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Create a space from the cursor position. Move the cursor position into the desired position, select the right space collection and run the operator"
|
||||
bl_description = (
|
||||
"Create a space from the cursor position. "
|
||||
"Move the cursor position into the desired position, "
|
||||
"select the right space collection and run the operator"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
return tool.Ifc.get_entity(collection_obj)
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
return tool.Ifc.get_entity(collection_obj) and not element.is_a("IfcWall")
|
||||
|
||||
def _execute(self, context):
|
||||
# This works as a 2.5 extruded polygon based on a cutting plane. Note
|
||||
@@ -45,16 +51,14 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
# props = context.scene.BIMModelProperties
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
spatial_element = tool.Ifc.get_entity(collection_obj)
|
||||
if not spatial_element:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
core.generate_space(tool.Ifc, tool.Spatial, tool.Model, tool.Type)
|
||||
@@ -68,7 +72,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
return context.selected_objects and element.is_a("IfcWall")
|
||||
@@ -79,7 +83,7 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# In order to run, the active object must be a wall and
|
||||
# there must be selected walls
|
||||
|
||||
active_obj = bpy.context.active_object
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
container = tool.Spatial.get_container(element)
|
||||
|
||||
@@ -94,19 +98,30 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
if not context.selected_objects:
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
return
|
||||
|
||||
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||
|
||||
|
||||
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_space_visibility"
|
||||
bl_label = "Toggle Space Visibility"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Change the space visibility"
|
||||
|
||||
def execute(cls, context):
|
||||
def execute(self, context):
|
||||
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleHideSpaces(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.toggle_hide_spaces"
|
||||
bl_label = "Toggle Hide Spaces"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Hide or Unhide all spaces"
|
||||
|
||||
def execute(self, context):
|
||||
core.toggle_hide_spaces(tool.Ifc, tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -54,8 +54,10 @@ class LaunchTypeManager(bpy.types.Operator):
|
||||
props.type_page = 1
|
||||
if get_ifc_class(None, context):
|
||||
ifc_class = props.ifc_class or AuthoringData.data["ifc_element_type"]
|
||||
props.type_class = ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
|
||||
else:
|
||||
ifc_class = AuthoringData.data["ifc_element_type"]
|
||||
props.type_class = ifc_class
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9)
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
@@ -24,7 +24,9 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.type
|
||||
import mathutils.geometry
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
@@ -35,6 +37,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, sin, cos, degrees, radians
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.model.opening import FilledOpeningGenerator
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class JoinWall(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -555,7 +558,10 @@ class DumbWallGenerator:
|
||||
for face in sibling_obj.data.polygons:
|
||||
normal = (sibling_obj.matrix_world.to_quaternion() @ face.normal).normalized()
|
||||
face_center = sibling_obj.matrix_world @ face.center
|
||||
if normal.z != 0 or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01:
|
||||
if (
|
||||
normal.z != 0
|
||||
or abs(mathutils.geometry.distance_point_to_plane(self.location, face_center, normal)) > 0.01
|
||||
):
|
||||
continue
|
||||
|
||||
rotation = math.atan2(normal[1], normal[0])
|
||||
@@ -603,7 +609,7 @@ class DumbWallGenerator:
|
||||
should_add_representation=False,
|
||||
context=self.body_context,
|
||||
)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=self.relating_type)
|
||||
ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=self.relating_type)
|
||||
if self.axis_context:
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_axis_representation",
|
||||
@@ -724,18 +730,29 @@ class DumbWallPlaner:
|
||||
DumbWallRecalculator().recalculate([w for w in set(walls) if w])
|
||||
|
||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
||||
obj = tool.Ifc.get_object(settings["related_object"])
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
new_material = ifcopenshell.util.element.get_material(settings["relating_type"])
|
||||
relating_type = settings["relating_type"]
|
||||
|
||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
||||
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
|
||||
return
|
||||
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
||||
layer_set_direction = None
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
||||
material = ifcopenshell.util.element.get_material(settings["related_object"])
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
for related_object in settings["related_objects"]:
|
||||
self._regenerate_from_type(related_object, layer_set_direction)
|
||||
|
||||
def _regenerate_from_type(
|
||||
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
|
||||
) -> None:
|
||||
obj = tool.Ifc.get_object(related_object)
|
||||
if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return
|
||||
|
||||
material = ifcopenshell.util.element.get_material(related_object)
|
||||
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
|
||||
return
|
||||
if layer_set_direction:
|
||||
@@ -854,11 +871,11 @@ class DumbWallJoiner:
|
||||
if (location_on_base - location).length < (location_on_side - location).length:
|
||||
axis_offset = location_on_side - location_on_base
|
||||
offset_from_axis = location_on_base - location
|
||||
opening_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
|
||||
opening_matrix.translation = location_on_base - axis_offset - offset_from_axis
|
||||
else:
|
||||
axis_offset = location_on_side - location_on_base
|
||||
offset_from_axis = location_on_side - location
|
||||
opening_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
|
||||
opening_matrix.translation = location_on_side - axis_offset - offset_from_axis
|
||||
opening_matrixes[opening] = opening_matrix
|
||||
|
||||
for filling in [r.RelatedBuildingElement for r in opening.HasFillings]:
|
||||
@@ -871,11 +888,11 @@ class DumbWallJoiner:
|
||||
if (location_on_base - location).length < (location_on_side - location).length:
|
||||
axis_offset = location_on_side - location_on_base
|
||||
offset_from_axis = location_on_base - location
|
||||
filling_matrix.translation = (location_on_base - axis_offset - offset_from_axis)
|
||||
filling_matrix.translation = location_on_base - axis_offset - offset_from_axis
|
||||
else:
|
||||
axis_offset = location_on_side - location_on_base
|
||||
offset_from_axis = location_on_side - location
|
||||
filling_matrix.translation = (location_on_side - axis_offset - offset_from_axis)
|
||||
filling_matrix.translation = location_on_side - axis_offset - offset_from_axis
|
||||
filling_matrixes[filling] = filling_matrix
|
||||
|
||||
self.recreate_wall(element1, wall1, axis1["reference"], axis1["reference"])
|
||||
@@ -1120,13 +1137,8 @@ class DumbWallJoiner:
|
||||
other = tool.Ifc.get_object(rel.RelatedElement)
|
||||
if connection not in ["ATPATH", "NOTDEFINED"]:
|
||||
self.join(
|
||||
obj,
|
||||
other,
|
||||
connection,
|
||||
rel.RelatedConnectionType,
|
||||
is_relating=True,
|
||||
description=rel.Description
|
||||
)
|
||||
obj, other, connection, rel.RelatedConnectionType, is_relating=True, description=rel.Description
|
||||
)
|
||||
for rel in element.ConnectedFrom:
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
connection = rel.RelatedConnectionType
|
||||
@@ -1226,6 +1238,7 @@ class DumbWallJoiner:
|
||||
# If opening has filling then stick to the filling's position
|
||||
# We're applying new openings position only after wall position is applied
|
||||
for opening in [r.RelatedOpeningElement for r in element.HasOpenings if r.RelatedOpeningElement.HasFillings]:
|
||||
similar_openings = blenderbim.core.geometry.get_similar_openings(tool.Ifc, opening)
|
||||
filling_obj = tool.Ifc.get_object(opening.HasFillings[0].RelatedBuildingElement)
|
||||
filling_moved = tool.Ifc.is_moved(filling_obj)
|
||||
if filling_moved:
|
||||
@@ -1234,6 +1247,7 @@ class DumbWallJoiner:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world
|
||||
)
|
||||
blenderbim.core.geometry.edit_similar_opening_placement(tool.Geometry, opening, similar_openings)
|
||||
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
|
||||
@@ -176,6 +176,21 @@ class DuctTool(BimTool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableCarrierTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_carrier_tool"
|
||||
bl_label = "Create Cable Carrier"
|
||||
bl_description = "Create and edit cable carriers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cablecarrier")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableCarrierSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class PipeTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
@@ -191,6 +206,21 @@ class PipeTool(BimTool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class CableTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.cable_tool"
|
||||
bl_label = "Create Cable"
|
||||
bl_description = "Create and edit cables"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cable")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcCableSegmentType"
|
||||
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
modifiers = {
|
||||
"A": "EVENT_ALT",
|
||||
@@ -486,20 +516,21 @@ class BimToolUI:
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
else:
|
||||
row.label(text="No Construction Type", icon="FILE_3D")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
if AuthoringData.data["ifc_element_type"]:
|
||||
row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR")
|
||||
row = cls.layout.row()
|
||||
row.prop(cls.props, "type_name")
|
||||
row = cls.layout.row()
|
||||
row = cls.layout.row(align=True)
|
||||
op = row.operator(
|
||||
"bim.add_default_type", icon="ADD", text=f"Add {AuthoringData.data['ifc_element_type']}"
|
||||
)
|
||||
op.ifc_element_type = AuthoringData.data["ifc_element_type"]
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="")
|
||||
else:
|
||||
row.label(text="No Element Types Found", icon="ERROR")
|
||||
row.operator("bim.launch_type_manager", icon="LIGHTPROBE_GRID", text="Launch Type Manager")
|
||||
row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager")
|
||||
|
||||
@classmethod
|
||||
def draw_basic_bim_tool_interface(cls):
|
||||
|
||||
@@ -100,6 +100,7 @@ class ExecuteIfcPatch(bpy.types.Operator):
|
||||
}
|
||||
)
|
||||
ifcpatch.write(output, ifc_patch_output)
|
||||
self.report({"INFO"}, f"{props.ifc_patch_recipes} patch executed successfully")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import importlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import ifcpatch
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
|
||||
@@ -17,26 +17,37 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
from . import ui, prop, operator, workspace, gizmo, decorator
|
||||
|
||||
classes = (
|
||||
operator.AppendEntireLibrary,
|
||||
operator.AppendInspectedLinkedElement,
|
||||
operator.AppendLibraryElement,
|
||||
operator.AppendLibraryElementByQuery,
|
||||
operator.AssignLibraryDeclaration,
|
||||
operator.BIM_OT_load_clipping_planes,
|
||||
operator.BIM_OT_save_clipping_planes,
|
||||
operator.ChangeLibraryElement,
|
||||
operator.CreateClippingPlane,
|
||||
operator.CreateProject,
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
operator.FlipClippingPlane,
|
||||
operator.ImportIFC,
|
||||
operator.LinkIfc,
|
||||
operator.LoadLink,
|
||||
operator.LoadLinkedProject,
|
||||
operator.LoadProject,
|
||||
operator.LoadProjectElements,
|
||||
operator.NewProject,
|
||||
operator.QueryLinkedElement,
|
||||
operator.RefreshClippingPlanes,
|
||||
operator.RefreshLibrary,
|
||||
operator.ReloadLink,
|
||||
operator.RevertProject,
|
||||
operator.RewindLibrary,
|
||||
operator.SaveLibraryFile,
|
||||
@@ -48,7 +59,7 @@ classes = (
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
operator.ReloadLink,
|
||||
workspace.ExploreHotkey,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
prop.Link,
|
||||
@@ -62,6 +73,7 @@ classes = (
|
||||
ui.BIM_UL_library,
|
||||
ui.BIM_UL_filter_categories,
|
||||
ui.BIM_UL_links,
|
||||
gizmo.ClippingPlane,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +81,10 @@ addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties)
|
||||
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file.prepend(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.prepend(ui.file_menu)
|
||||
wm = bpy.context.window_manager
|
||||
@@ -88,9 +103,12 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.ExploreTool)
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
|
||||
bpy.types.TOPBAR_MT_file.remove(ui.file_menu)
|
||||
bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu)
|
||||
del bpy.types.Scene.BIMProjectProperties
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
|
||||
@@ -24,6 +24,7 @@ from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
def refresh():
|
||||
ProjectData.is_loaded = False
|
||||
LinksData.is_loaded = False
|
||||
|
||||
|
||||
class ProjectData:
|
||||
@@ -34,6 +35,7 @@ class ProjectData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"export_schema": cls.get_export_schema(),
|
||||
"library_file": cls.library_file(),
|
||||
"template_file": cls.template_file(),
|
||||
"last_saved": cls.last_saved(),
|
||||
}
|
||||
@@ -44,10 +46,17 @@ class ProjectData:
|
||||
return [(s, s, "") for s in IfcStore.schema_identifiers]
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
def library_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries"))
|
||||
results = [("0", "Custom Library", "")]
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def template_file(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "projects"))
|
||||
results = [("0", "Blank Project", "")]
|
||||
results.extend([(f, f.replace(".ifc", ""), "") for f in files if ".ifc" in f])
|
||||
results.extend([(f, os.path.splitext(f)[0], "") for f in files if ".ifc" in f])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
@@ -61,3 +70,9 @@ class ProjectData:
|
||||
return f"{save_date} {':'.join(save_time.split(':')[0:2])}"
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
class LinksData:
|
||||
linked_data = {}
|
||||
enable_culling = False
|
||||
is_loaded = False
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 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 bpy
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
@persistent
|
||||
def toggle_decorations_on_load(*args):
|
||||
if bpy.context.scene.BIMProjectProperties.clipping_planes:
|
||||
ClippingPlaneDecorator.install(bpy.context)
|
||||
else:
|
||||
ClippingPlaneDecorator.uninstall()
|
||||
|
||||
# NOTE: ProjectDecorator cannot be loaded at reopening .blend file
|
||||
# since selected_vertices and other data is stored in queried object's
|
||||
# custom attributes and they get purged after Blender session is closed
|
||||
# as queried object is linked from separate .blend file.
|
||||
|
||||
|
||||
class ProjectDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "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 __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].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
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# 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")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
|
||||
try:
|
||||
obj = context.scene.BIMProjectProperties.queried_obj
|
||||
selected_vertices = obj["selected_vertices"]
|
||||
selected_edges = obj["selected_edges"]
|
||||
selected_tris = obj["selected_tris"]
|
||||
except:
|
||||
return
|
||||
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
|
||||
|
||||
class ClippingPlaneDecorator:
|
||||
installed = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "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 __call__(self, context):
|
||||
self.addon_prefs = context.preferences.addons["blenderbim"].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
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
gpu.state.point_size_set(6)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind() # required to be able to change uniforms of the shader
|
||||
# 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")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
unselected_vertices = []
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
for clipping_plane in context.scene.BIMProjectProperties.clipping_planes:
|
||||
obj = clipping_plane.obj
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
continue # A profile decorator or something else is used here.
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
obj.data.calc_loop_triangles()
|
||||
|
||||
if obj.select_get():
|
||||
offset = len(selected_vertices)
|
||||
selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
else:
|
||||
offset = len(unselected_vertices)
|
||||
unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts])
|
||||
unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges])
|
||||
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
|
||||
|
||||
verts = [
|
||||
tuple(obj.matrix_world @ Vector((0, 0, 0))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0, -0.5))),
|
||||
tuple(obj.matrix_world @ Vector((-0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0.05, 0, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, -0.05, -0.45))),
|
||||
tuple(obj.matrix_world @ Vector((0, 0.05, -0.45))),
|
||||
]
|
||||
edges = [(0, 1), (1, 2), (1, 3), (1, 4), (1, 5)]
|
||||
color = selected_elements_color if obj in context.selected_objects else special_elements_color
|
||||
self.draw_batch("LINES", verts, color, edges)
|
||||
|
||||
if obj.mode != "EDIT":
|
||||
bm.free()
|
||||
|
||||
if unselected_edges:
|
||||
self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges)
|
||||
self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris)
|
||||
if selected_edges:
|
||||
self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges)
|
||||
self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris)
|
||||
@@ -0,0 +1,68 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import GizmoGroup
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
class ClippingPlane(GizmoGroup):
|
||||
bl_idname = "OBJECT_GGT_bim_clipping_plane"
|
||||
bl_label = "Clipping Plane"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options = {"3D", "PERSISTENT"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.object
|
||||
return (
|
||||
context.selected_objects
|
||||
and obj
|
||||
and obj.name.startswith("ClippingPlane")
|
||||
and obj in [sp.obj for sp in context.scene.BIMProjectProperties.clipping_planes]
|
||||
)
|
||||
|
||||
def setup(self, context):
|
||||
self.obj = None
|
||||
self.offset = 0
|
||||
self.mw = Matrix()
|
||||
self.last_mw = Matrix()
|
||||
|
||||
self.gizmo = self.gizmos.new("GIZMO_GT_arrow_3d")
|
||||
|
||||
def move_get_x():
|
||||
return self.offset
|
||||
|
||||
def move_set_x(value):
|
||||
self.obj.matrix_world.col[3] = self.mw.col[3] + (self.mw.col[2] * self.offset)
|
||||
self.last_mw = self.obj.matrix_world.copy()
|
||||
self.offset = value
|
||||
|
||||
self.gizmo.target_set_handler("offset", get=move_get_x, set=move_set_x)
|
||||
|
||||
def refresh(self, context):
|
||||
if self.obj != context.object or self.last_mw != context.object.matrix_world:
|
||||
self.obj = context.object
|
||||
self.offset = 0
|
||||
self.mw = context.object.matrix_world.copy()
|
||||
obj = context.object
|
||||
mw = context.object.matrix_world.normalized()
|
||||
mw.col[3] -= mw.col[2] * self.offset
|
||||
self.gizmo.matrix_basis = mw
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user