mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
557 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1caef5b4f | |||
| 7da96b4951 | |||
| 66c275f617 | |||
| 52a8fcd86e | |||
| 3ba7ecdb95 | |||
| 36f7625cee | |||
| ee82714423 | |||
| 281147367e | |||
| 0beea4255a | |||
| b03c4da7bf | |||
| c0402687b5 | |||
| c6fa7161b6 | |||
| 64516a173a | |||
| f507e3e8bd | |||
| 2f15d73b30 | |||
| 53cc6f0bd6 | |||
| 7680652c28 | |||
| e8b1d00e6d | |||
| 6ec7e48423 | |||
| ccf09b962a | |||
| 485d41ae09 | |||
| b67c5a0828 | |||
| 7b92057227 | |||
| a980ce1bfa | |||
| 51b7f24a00 | |||
| 136e1c3b3f | |||
| c094517a10 | |||
| b61a11d5b7 | |||
| 012da8fcb9 | |||
| d64c1358d7 | |||
| 0252701f7a | |||
| 6fed9bf795 | |||
| b088a763d8 | |||
| 104fe56612 | |||
| 9e0760b8d2 | |||
| f26986645a | |||
| a86456392b | |||
| 61de0ad64c | |||
| 243eee9ebf | |||
| f23ac034c7 | |||
| 363ede9801 | |||
| 18d26df24e | |||
| 0df62d5746 | |||
| f5031035c7 | |||
| fc50bdd3a0 | |||
| 3117d5b2e7 | |||
| e6dc1d1acd | |||
| bd8afabce3 | |||
| 01da8498f2 | |||
| a34b8d06ec | |||
| 5764cfcbb9 | |||
| 1ad2465ad4 | |||
| f28e28abc0 | |||
| 97e321dd12 | |||
| fc4b36e759 | |||
| 832cdce934 | |||
| e45974df62 | |||
| bba118ed19 | |||
| 709590401c | |||
| ab74d1456b | |||
| 4457da9042 | |||
| d501118eb4 | |||
| 0a03d20133 | |||
| f3f56c0891 | |||
| a689b31279 | |||
| 4d0be19720 | |||
| fb7591caef | |||
| 8efaf2fbcb | |||
| d41068e132 | |||
| 4e65f143da | |||
| 40656297a6 | |||
| f88835b87b | |||
| 8f18db64cf | |||
| fdd473234a | |||
| 569f56f713 | |||
| 61607f9de2 | |||
| 40b34f6a33 | |||
| 89b0674b08 | |||
| 37e4f98857 | |||
| e89a9cd01c | |||
| 20b63f530a | |||
| 183230b7f5 | |||
| 7e2b40176c | |||
| 3aff29dec1 | |||
| 2ce32c6521 | |||
| 9d84172f15 | |||
| 8341931a62 | |||
| 81a4ff711c | |||
| 2da86c170f | |||
| 41a176cb6d | |||
| 10e96239a1 | |||
| 897dfb1e33 | |||
| c1d972f7bf | |||
| d9196882dc | |||
| 580f8f6218 | |||
| e177f5f051 | |||
| 1a5c501af0 | |||
| 12c10f98a3 | |||
| e3a7f76276 | |||
| 046ce2b8e7 | |||
| 6dab4501ad | |||
| 2dc9dc2000 | |||
| addce9f0ea | |||
| 4a368938c3 | |||
| a0f68baefc | |||
| 3decb48ed0 | |||
| 2d330f4c90 | |||
| 1c996013b6 | |||
| 65cb47e2d8 | |||
| e9d0b8ab66 | |||
| 0bb9bcd9fb | |||
| 997ebe48df | |||
| 0cdf72fde1 | |||
| 97c6b99d57 | |||
| ed5d28de54 | |||
| f5c102402f | |||
| 7aa04611a1 | |||
| 5c7f14cc3c | |||
| 9e1249d8ee | |||
| 6cb7234cb5 | |||
| aab6a21bf3 | |||
| c72f960ed4 | |||
| b63b4ce326 | |||
| 71113b56bf | |||
| ef57c209a0 | |||
| e99c154cb2 | |||
| 520860bdb9 | |||
| 103e8a545b | |||
| d35c5c433f | |||
| 7dc98612e4 | |||
| 3853258b27 | |||
| 6825d26700 | |||
| 900c35e9fe | |||
| 7aafa87b52 | |||
| e719055d9a | |||
| a956038bfc | |||
| 5c2ae98c09 | |||
| 99d9c0f5bf | |||
| 2fbb9c569b | |||
| 4a9d1ff038 | |||
| bcf5f07718 | |||
| a3572f3372 | |||
| 6cd9161986 | |||
| 982f4a4965 | |||
| c5aab16696 | |||
| 6e1b18ba33 | |||
| 2b3d547c99 | |||
| a34be6ae5d | |||
| dfc5a280a4 | |||
| 6a2bbfa85c | |||
| ddef4820d0 | |||
| e6b05b547b | |||
| 5e8ec48d98 | |||
| 9c21304629 | |||
| 1278f98df6 | |||
| d31b470e83 | |||
| 1ecb25684e | |||
| 5999976d37 | |||
| 51e5f843ba | |||
| 3d787338fb | |||
| de67648f3e | |||
| afa803ea62 | |||
| 3a0aaee03b | |||
| a5353e02b5 | |||
| 1acc743370 | |||
| 375f4f2a86 | |||
| d4b88fd422 | |||
| df6cc74a87 | |||
| 80846061f2 | |||
| 80f9590410 | |||
| 16cda28d16 | |||
| 24ea024a0d | |||
| e90d7ccdb1 | |||
| 1a3bf513a7 | |||
| a855c40426 | |||
| c8ede38edb | |||
| 704e27ecca | |||
| 438ddc17f5 | |||
| a8dfed50e4 | |||
| 8cb99a77cf | |||
| f9b6e6f098 | |||
| 1365b76149 | |||
| 47cc3158fd | |||
| bac683e105 | |||
| 840818d681 | |||
| 206933a2f2 | |||
| ff209b4c11 | |||
| 4bc987924d | |||
| f26cfc93ea | |||
| 4bbb6cd2df | |||
| 9411a1d94b | |||
| 6a287ced7c | |||
| fb02ac34d4 | |||
| 81fd84223a | |||
| a8f1a28ac2 | |||
| d2f4f0d8c2 | |||
| 1c17cccc45 | |||
| 061930bc8e | |||
| 28cc168b3b | |||
| 88f53f7931 | |||
| 755fa81817 | |||
| 36fab12847 | |||
| 32393136d2 | |||
| c7b3331496 | |||
| 7c0c934b4f | |||
| 62944e04e9 | |||
| 3a93b15b55 | |||
| 83cd00271b | |||
| 820686a39b | |||
| 4fe896a3b6 | |||
| 975b1c444c | |||
| c3f2046491 | |||
| 3a1557bedc | |||
| a71b5e1f11 | |||
| 4806b9a260 | |||
| 0d67c43e16 | |||
| e2b52e7ed9 | |||
| 3c53067567 | |||
| 5ad7d4bb2a | |||
| 3803942c5f | |||
| 678f50f17d | |||
| 04fe2bdf2e | |||
| ed990c3e83 | |||
| 741c78cd6c | |||
| 2b88c542aa | |||
| 1f6b55e639 | |||
| 9f3fb13cde | |||
| 42f1d9c160 | |||
| f0875835c1 | |||
| 0828a89b66 | |||
| a8ca66b425 | |||
| 92b03c6b59 | |||
| d316efe902 | |||
| 77e4e939ce | |||
| fe7504edf2 | |||
| a8f1ab1923 | |||
| 1b5f9497d4 | |||
| a7bbb35c5e | |||
| 1787f064f6 | |||
| d1c24ebba4 | |||
| cc6dacb286 | |||
| c67eb60fd5 | |||
| 59c482489f | |||
| 7a0963617e | |||
| e4ec1dc457 | |||
| b035129788 | |||
| 78be8114ac | |||
| 18ad025c5d | |||
| 5825e0c71b | |||
| df64ca4cfb | |||
| fc1f5496a0 | |||
| 5a9dbc5a36 | |||
| eaeeede287 | |||
| e78b3a3977 | |||
| a8ca8217ea | |||
| 77c2e30733 | |||
| 5a3a387b8c | |||
| f68f60832c | |||
| b05417a4d8 | |||
| c2a4e30fae | |||
| 1a0280928b | |||
| dda16c847d | |||
| 47c6c2ce9a | |||
| 3758c454c9 | |||
| 4b6424e0e1 | |||
| d71887d846 | |||
| d61945a575 | |||
| 45e6ae6708 | |||
| ebbf30a88c | |||
| e5769766af | |||
| 64b9d2e5e3 | |||
| 3213ffe760 | |||
| a851689bca | |||
| 98f2bd66b6 | |||
| 1c12a98b68 | |||
| a3859385c4 | |||
| c7394faf51 | |||
| 3907ae656c | |||
| 5fc31be8aa | |||
| 7976fb4d09 | |||
| 01635ecd8a | |||
| f6657113b6 | |||
| 480f1a91a6 | |||
| 4865b62ea9 | |||
| 9a440899be | |||
| 3ee6bdf207 | |||
| 1f43ec8709 | |||
| d030582fbb | |||
| 60a8d9b2fd | |||
| a5de5d8c0e | |||
| b506e7dbbc | |||
| db8f1a9b70 | |||
| 032f52d434 | |||
| 1003e8de91 | |||
| c6ef402b60 | |||
| 5805653431 | |||
| 186da73135 | |||
| 528481e774 | |||
| 886ab52556 | |||
| 8196f31d82 | |||
| de3b844829 | |||
| fa4e19eba7 | |||
| 1488ae40ee | |||
| aa004ef0c6 | |||
| 0c946fae36 | |||
| f297739836 | |||
| b33e58f40b | |||
| 30d9743ae7 | |||
| c974306636 | |||
| 602176815a | |||
| 7652144184 | |||
| 0fd94d291b | |||
| 3c9f09f246 | |||
| d60fbd2c09 | |||
| 9d869ebb6d | |||
| 971e508e3d | |||
| 7020caf682 | |||
| 7cd7ad1281 | |||
| 586707dd28 | |||
| 4bb46fa607 | |||
| 7d5beb6c80 | |||
| ecc0263062 | |||
| 39c5dc6ea4 | |||
| e6d6ab6da7 | |||
| d7c3b5a990 | |||
| 3409cd7c29 | |||
| 91176349f7 | |||
| 6634025346 | |||
| a771061495 | |||
| 2629172ff4 | |||
| 36b746da7a | |||
| d3aa2e87c9 | |||
| c6090ef9b3 | |||
| 6354a11645 | |||
| 11ec8e04c2 | |||
| 8a9998bc65 | |||
| 065bda604e | |||
| 471aeab439 | |||
| f2c757b1bb | |||
| f6e72ec06f | |||
| 9dd82036ba | |||
| ff2fa70776 | |||
| e4b1f56f6d | |||
| 0d7a789ed8 | |||
| d73e77c5ed | |||
| dddbf64e93 | |||
| 1d5891fde7 | |||
| c5de64de51 | |||
| a43b2a2ae8 | |||
| 7ab92dfa0b | |||
| ff81286874 | |||
| 51ad31f5a3 | |||
| 6336db0069 | |||
| 7d7122ee4c | |||
| 7d852f9e51 | |||
| 605097de76 | |||
| 07638dac27 | |||
| 6f39648077 | |||
| 90c19f8454 | |||
| 929aa3a49b | |||
| 83e97b7205 | |||
| 5d583741b9 | |||
| d3d2c3608b | |||
| a0edd66b92 | |||
| c8c0c9a621 | |||
| 00c010e04d | |||
| 1d2c9780da | |||
| adec3853e8 | |||
| d3251c5ca1 | |||
| 68f8d8798a | |||
| 4c793be5f7 | |||
| 812eb5eaef | |||
| 6c2be8f886 | |||
| 301d4fecf8 | |||
| 604dfa8cf4 | |||
| 8bb4ab91b8 | |||
| 0cdd9a0cde | |||
| 4768ea23e4 | |||
| 659cbe69c3 | |||
| e22f4dd001 | |||
| 394b2003b3 | |||
| d72ac7827b | |||
| 610ec7d8ad | |||
| 8f41ae0c1b | |||
| f8113d4857 | |||
| 72169d06ad | |||
| 2b78871c01 | |||
| 1ce254cc9a | |||
| 3b07c086a8 | |||
| ff7b101b71 | |||
| 92405d819b | |||
| c367989ebd | |||
| 7cae099fb4 | |||
| 7dcccf9ea5 | |||
| 535e974cc3 | |||
| 629aff1e50 | |||
| 628b6830db | |||
| 1b2ab4e3f0 | |||
| f97752c368 | |||
| d2d5db9bf6 | |||
| a8554d9e56 | |||
| 11a38af901 | |||
| 27a4976b9d | |||
| eb54f5a0cb | |||
| a4a6dce6de | |||
| 4184e25c68 | |||
| d98b0766c2 | |||
| b8990dd0a4 | |||
| 2198201e0b | |||
| 53e58848f3 | |||
| 9c367d1702 | |||
| db18a81035 | |||
| d1d6b00f92 | |||
| 32fc147a7c | |||
| d98054234c | |||
| d6460026f2 | |||
| 88ba6514e8 | |||
| 894a688bd8 | |||
| c69ead4893 | |||
| e68fdb2db7 | |||
| ae9372c3d3 | |||
| 0365c6d43b | |||
| 18b6969221 | |||
| a1935acb3d | |||
| 9548c5e657 | |||
| 762299124f | |||
| f682bf5688 | |||
| f7ed3a8727 | |||
| c124506e41 | |||
| 273cdc6ea8 | |||
| 0ef6ea6bd4 | |||
| 607d9a06ec | |||
| 1b472ca5f2 | |||
| 18568d6ba7 | |||
| bfb59dd387 | |||
| 38cf88d5d2 | |||
| 7ca6f9d7a1 | |||
| 5874508263 | |||
| 954de793dd | |||
| df74d5efbe | |||
| f7b9a7bfb5 | |||
| 63eaa88d3d | |||
| f518521ea1 | |||
| 5e1ee22bcd | |||
| ca09ff2ef0 | |||
| 8643aff800 | |||
| 2e88993654 | |||
| b77849d279 | |||
| c7e288466f | |||
| bb48d4aac1 | |||
| 2b99cc1e58 | |||
| ca286f8d19 | |||
| 73a3cc2cc5 | |||
| 169709ed04 | |||
| 2e746242ce | |||
| 951b8e2b15 | |||
| ede3edcf1e | |||
| 184c9f87b2 | |||
| 0793a4602d | |||
| bb285c47dc | |||
| 4cb6fc05d6 | |||
| a302e5db64 | |||
| ff21749da4 | |||
| 23b5da7670 | |||
| c31e479f47 | |||
| 260eb31a69 | |||
| 1fb6dac6fb | |||
| 34d947c51f | |||
| 23f7a6d5a6 | |||
| b15926c20a | |||
| 0a7ef80ca7 | |||
| be08406cc1 | |||
| cc9777b1e8 | |||
| 539b3a243b | |||
| 41e58a3d46 | |||
| 8c302ed85c | |||
| dc4dc46b62 | |||
| a07fa5a2c2 | |||
| ad9dbce003 | |||
| d0526d0ae5 | |||
| 75e9c3265d | |||
| b37705ab6e | |||
| d9d5f86a6e | |||
| 1f3ad95c00 | |||
| 9e360f7efa | |||
| 64fd6a1f52 | |||
| ba04df4ce3 | |||
| cd27a026fd | |||
| baedf71106 | |||
| 4f6c95b119 | |||
| b1761ac393 | |||
| fcdf3e583f | |||
| 2001119dff | |||
| 60a9d96ac8 | |||
| 6bd177d250 | |||
| 1ece84e31d | |||
| 40ea9457a8 | |||
| e95d91a91e | |||
| c8fb92175f | |||
| a6fb4cbfbf | |||
| 30bb6d05c5 | |||
| 9422f804fb | |||
| 415a7746be | |||
| ee903208dc | |||
| f89e834fea | |||
| e8b4335741 | |||
| 83010f0542 | |||
| 04e5f5036c | |||
| 5d86d38a14 | |||
| a82db1f622 | |||
| fb618d536b | |||
| 23f96779fa | |||
| 7b67854654 | |||
| 2e7f941465 | |||
| 1c1706fde3 | |||
| ca77a895e0 | |||
| 0f447f964e | |||
| 2c31fc6b25 | |||
| 0ba79dc105 | |||
| 52c686b656 | |||
| 64d98f1295 | |||
| ceb2c4075c | |||
| 166e7d52ec | |||
| 644c2652ea | |||
| 63b201fd95 | |||
| d70616056c | |||
| a65e376886 | |||
| a985c70f0c | |||
| 0d09e02e7e | |||
| 23e3551f27 | |||
| 885bca0865 | |||
| 0f874dad51 | |||
| 107873cf6e | |||
| 4a8f5af7ba | |||
| 83084aeb7b | |||
| 51640ccc28 | |||
| aefbc134ea | |||
| 29e1d70334 | |||
| 9118e529e6 | |||
| 8ee0a1611d | |||
| 2af4388f40 | |||
| 93a2d18ea6 | |||
| fc49859ea4 | |||
| c0ac9cb380 | |||
| d5cc6c3c8e | |||
| 9b22b5000f | |||
| 3b69a4cc78 | |||
| adf54efcc5 | |||
| f6962eb58e | |||
| 11601d6532 | |||
| be2131d62c | |||
| 3ffbf46ac2 | |||
| 3943107eec | |||
| 94e3f75341 | |||
| 8bc8b950e7 | |||
| 05a95ac666 | |||
| f016c803e4 |
@@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39, py310]
|
||||
pyver: [py39, py310]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
name: ci-ifctester-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 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifctester-pypi.yml'
|
||||
|
||||
|
||||
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
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
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.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')"
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/ifctester &&
|
||||
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/ifctester/dist
|
||||
@@ -86,7 +86,15 @@ src/blenderbim/blenderbim/libs
|
||||
|
||||
# blenderbim test temp files
|
||||
src/blenderbim/test/files/temp
|
||||
src/blenderbim/drawings
|
||||
src/blenderbim/layouts
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
IfcOpenShell
|
||||
============
|
||||
<p align="center">
|
||||
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/79010126/9e540403-c79f-446a-88c6-7c1ebc6ba864">
|
||||
</p>
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x3, and IFC4x3. Extensive geometric support
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and IFC4x3. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Dockerfile for Running AWS Lambda Function with Python and IfcOpenShell
|
||||
|
||||
# Base image: Python 3.9 from AWS's public container registry
|
||||
FROM public.ecr.aws/docker/library/python:3.9 AS build
|
||||
|
||||
# Set the location of your Lambda function code
|
||||
ARG FUNCTION_DIR="/var/task"
|
||||
|
||||
# Install necessary packages
|
||||
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)
|
||||
ARG IFC_OPENSHELL_BUILD="39-v0.7.0-476ab50"
|
||||
|
||||
# Download and extract IfcOpenShell
|
||||
RUN curl https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip -O && \
|
||||
unzip ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip && \
|
||||
mv ifcopenshell ${FUNCTION_DIR} && \
|
||||
rm ifcopenshell-python-${IFC_OPENSHELL_BUILD}-linux64.zip
|
||||
|
||||
# Copy the requirements file and install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# Copy the Lambda function code
|
||||
COPY ./example_handler ${FUNCTION_DIR}/example_handler
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR ${FUNCTION_DIR}
|
||||
|
||||
# Set the entrypoint for the Lambda function
|
||||
ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]
|
||||
|
||||
# Set the Python import path to the Lambda function handler
|
||||
# This path is relative to the root of the Lambda function code (FUNCTION_DIR)
|
||||
# Lambda invocation will look for this path
|
||||
CMD ["example_handler.extract_wall_psets_handler"]
|
||||
@@ -0,0 +1,27 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import boto3
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
|
||||
def extract_wall_psets_handler(event, context):
|
||||
print("Hello from lambda")
|
||||
print("Event: {}".format(event))
|
||||
print("Context: {}".format(context))
|
||||
|
||||
filename = event['body']['filename']
|
||||
|
||||
s3.download_file('my_ifc_files_bucket', filename, f'/tmp/{filename}')
|
||||
ifc_file = ifcopenshell.open(f'/tmp/{filename}')
|
||||
walls = ifc_file.by_type('IfcWall')
|
||||
|
||||
psets = []
|
||||
for wall in walls:
|
||||
wall_data = ifcopenshell.util.element.get_psets(wall)
|
||||
wall_data['id'] = wall.GlobalId
|
||||
psets.append(wall_data)
|
||||
|
||||
return {
|
||||
'statusCode': 200,
|
||||
'body': psets
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
boto3
|
||||
+68
-11
@@ -21,7 +21,7 @@ cmake_minimum_required(VERSION 3.1.3)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
|
||||
project (IfcOpenShell VERSION 0.7.0)
|
||||
project(IfcOpenShell VERSION 0.7.0)
|
||||
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
@@ -54,12 +54,14 @@ OPTION(USE_VLD "Use Visual Leak Detector for debugging memory leaks, MSVC-only."
|
||||
OPTION(USE_MMAP "Adds a command line options to parse IFC files from memory mapped files using Boost.Iostreams" OFF)
|
||||
OPTION(BUILD_SHARED_LIBS "Build IfcParse and IfcGeom as shared libs (SO/DLL)." OFF)
|
||||
OPTION(MSVC_PARALLEL_BUILD "Multi-threaded compilation in Microsoft Visual Studio (/MP)" OFF)
|
||||
OPTION(NO_WARN "Disable all warnings" OFF)
|
||||
OPTION(WASM_BUILD OFF)
|
||||
if (${HAS_MAX})
|
||||
OPTION(BUILD_IFCMAX "Build IfcMax, a 3ds Max plug-in, Windows-only." OFF)
|
||||
endif()
|
||||
if (${BUILD_CONVERT})
|
||||
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
OPTION(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
endif()
|
||||
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
@@ -72,6 +74,14 @@ if(MSVC AND MSVC_PARALLEL_BUILD)
|
||||
add_definitions("/MP")
|
||||
endif()
|
||||
|
||||
if(NO_WARN)
|
||||
if(MSVC)
|
||||
add_compile_options("/w")
|
||||
else()
|
||||
add_compile_options("-w")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# QtViewer requires Qt5
|
||||
OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer" OFF)
|
||||
include(GNUInstallDirs)
|
||||
@@ -137,11 +147,6 @@ UNIFY_ENVVARS_AND_CACHE(PYTHON_EXECUTABLE)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(HDF5_LIBRARIES)
|
||||
endif()
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if (NOT MINIMAL_BUILD)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(CGAL_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(GMP_INCLUDE_DIR)
|
||||
@@ -149,6 +154,8 @@ UNIFY_ENVVARS_AND_CACHE(GMP_LIBRARY_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(MPFR_LIBRARY_DIR)
|
||||
endif()
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_ROOT)
|
||||
UNIFY_ENVVARS_AND_CACHE(BOOST_LIBRARYDIR)
|
||||
|
||||
if(WASM_BUILD)
|
||||
# when using the nix/build-all.py build script we should not
|
||||
@@ -170,6 +177,54 @@ add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Add USD support to serializers
|
||||
if(NOT MINIMAL_BUILD AND USD_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_INCLUDE_DIR)
|
||||
UNIFY_ENVVARS_AND_CACHE(USD_LIBRARY_DIR)
|
||||
|
||||
if("${USD_INCLUDE_DIR}" STREQUAL "")
|
||||
find_path(USD_INCLUDE_DIR pxr.h
|
||||
[PATHS
|
||||
/usr/include/pxr
|
||||
/usr/local/include/pxr
|
||||
]
|
||||
REQUIRED
|
||||
)
|
||||
if(USD_INCLUDE_DIR)
|
||||
message(STATUS "Found USD include files in: ${USD_INCLUDE_DIR}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD include directory, specify USD_INCLUDE_DIR manually.")
|
||||
endif()
|
||||
else()
|
||||
set(USD_INCLUDE_DIR ${USD_INCLUDE_DIR} CACHE FILEPATH "USD header files")
|
||||
message(STATUS "Looking for USD include files in: ${USD_INCLUDE_DIR}")
|
||||
endif()
|
||||
|
||||
set(USD_LIBRARIES
|
||||
usd_usd
|
||||
usd_usdGeom
|
||||
usd_usdShade
|
||||
usd_usdLux
|
||||
usd_vt
|
||||
usd_sdf
|
||||
usd_tf
|
||||
usd_gf
|
||||
)
|
||||
|
||||
find_library(USD_LIBRARY
|
||||
NAMES ${USD_LIBRARIES}
|
||||
PATHS ${USD_LIBRARY_DIR})
|
||||
if(USD_LIBRARY)
|
||||
message(STATUS "USD libraries ${USD_LIBRARIES} found in: ${USD_LIBRARY_DIR}")
|
||||
link_directories(${USD_LIBRARY_DIR})
|
||||
else()
|
||||
message(FATAL_ERROR "Unable to find USD libraries in: ${USD_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_USD)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_USD)
|
||||
endif()
|
||||
|
||||
# Set INSTALL_RPATH for target
|
||||
MACRO(SET_INSTALL_RPATHS _target _paths)
|
||||
SET(${_target}_rpaths "")
|
||||
@@ -633,6 +688,7 @@ ENDIF()
|
||||
|
||||
INCLUDE_DIRECTORIES(${INCLUDE_DIRECTORIES} ${OCC_INCLUDE_DIR} ${OPENCOLLADA_INCLUDE_DIRS}
|
||||
${Boost_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${HDF5_INCLUDE_DIR}
|
||||
${USD_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
function(files_for_ifc_version IFC_VERSION RESULT_NAME)
|
||||
@@ -841,9 +897,9 @@ endforeach()
|
||||
add_library(Serializers ${SERIALIZERS_FILES})
|
||||
set_target_properties(Serializers PROPERTIES COMPILE_FLAGS "-DIFC_GEOM_EXPORTS ${CONVERT_PRECISION}" VERSION "0.6.0" SOVERSION "0.6")
|
||||
|
||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(Serializers ${SERIALIZER_SCHEMA_LIBRARIES} ${OPENCOLLADA_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
endif(BUILD_CONVERT or BUILD_IFCPYTHON)
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
if (BUILD_CONVERT)
|
||||
|
||||
@@ -854,7 +910,7 @@ set(IFCCONVERT_FILES ${IFCCONVERT_CPP_FILES} ${IFCCONVERT_H_FILES})
|
||||
ADD_EXECUTABLE(IfcConvert ${IFCCONVERT_FILES})
|
||||
set_target_properties(IfcConvert PROPERTIES COMPILE_FLAGS "${CONVERT_PRECISION}")
|
||||
|
||||
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES})
|
||||
TARGET_LINK_LIBRARIES(IfcConvert ${IFCOPENSHELL_LIBRARIES} ${OPENCASCADE_LIBRARIES} ${Boost_LIBRARIES} ${HDF5_LIBRARIES} ${USD_LIBRARIES})
|
||||
|
||||
if ((NOT WIN32) AND BUILD_SHARED_LIBS)
|
||||
# Only set RPATHs when building shared libraries (i.e. IfcParse and
|
||||
@@ -897,7 +953,7 @@ find_package (Git)
|
||||
if (GIT_FOUND)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
|
||||
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
@@ -906,7 +962,8 @@ if (GIT_FOUND)
|
||||
string(REPLACE "*" "" git_branch_candidate_temp "${git_branch_candidate}")
|
||||
string(STRIP "${git_branch_candidate_temp}" git_branch_candidate_2)
|
||||
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate_2})
|
||||
string(REPLACE "/" ";" git_branch_candidate_2_list "${git_branch_candidate_2}")
|
||||
list(GET git_branch_candidate_2_list -1 git_branch)
|
||||
endif()
|
||||
endforeach()
|
||||
execute_process(
|
||||
|
||||
+1
-1
@@ -41,4 +41,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install -j 2
|
||||
ninja install -j 1
|
||||
|
||||
+4
-2
@@ -24,7 +24,7 @@ requirements:
|
||||
host:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt
|
||||
- occt ==7.7.0
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
@@ -32,11 +32,12 @@ requirements:
|
||||
- gmp # [unix]
|
||||
- mpir # [win]
|
||||
- nlohmann_json
|
||||
- zlib
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- occt ==7.7.0
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
@@ -44,6 +45,7 @@ requirements:
|
||||
- gmp # [unix]
|
||||
- mpir # [win]
|
||||
- nlohmann_json
|
||||
- zlib
|
||||
|
||||
test:
|
||||
imports:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: bcf
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/bcf
|
||||
abstract: >-
|
||||
Library to read and write BCF-XML and query OpenCDE
|
||||
BCF-API modules
|
||||
keywords:
|
||||
- BCF
|
||||
- IFC
|
||||
license: LGPL-3.0-or-later
|
||||
@@ -11,7 +11,7 @@ name = "bcf-client"
|
||||
description = "BCF-XML file handler."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
keywords = ["IFC", "BCF", "BIM", "eingineering"]
|
||||
keywords = ["IFC", "BCF", "BIM"]
|
||||
dependencies = [
|
||||
"xsdata",
|
||||
"numpy",
|
||||
|
||||
@@ -275,6 +275,7 @@ class TopicHandler:
|
||||
"""
|
||||
new_viewpoint = VisualizationInfoHandler.create_new(element, self._xml_handler)
|
||||
self.add_visinfo_handler(new_viewpoint)
|
||||
return new_viewpoint
|
||||
|
||||
def add_viewpoint_from_point_and_guids(self, position: NDArray[np.float_], *guids: str) -> None:
|
||||
"""Add a viewpoint pointing at an XYZ point in space
|
||||
@@ -287,6 +288,7 @@ class TopicHandler:
|
||||
position, *guids, xml_handler=self._xml_handler
|
||||
)
|
||||
self.add_visinfo_handler(vi_handler)
|
||||
return vi_handler
|
||||
|
||||
def add_visinfo_handler(self, new_viewpoint: VisualizationInfoHandler) -> None:
|
||||
self.viewpoints[new_viewpoint.guid + ".bcfv"] = new_viewpoint
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# This CITATION.cff file was generated with cffinit.
|
||||
# Visit https://bit.ly/cffinit to generate yours today!
|
||||
|
||||
cff-version: 1.2.0
|
||||
title: BlenderBIM Add-on
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
type: software
|
||||
authors:
|
||||
- name: "IfcOpenShell contributors"
|
||||
repository-code: >-
|
||||
https://github.com/IfcOpenShell/IfcOpenShell/tree/v0.7.0/src/blenderbim
|
||||
url: 'https://blenderbim.org/'
|
||||
repository-artifact: 'https://blenderbim.org/download.html'
|
||||
abstract: >-
|
||||
Add-on to Blender providing a graphical native IFC
|
||||
authoring platform
|
||||
keywords:
|
||||
- IFC
|
||||
- Blender
|
||||
- NativeIFC
|
||||
license: GPL-3.0-or-later
|
||||
commit: 1fb6dac
|
||||
version: v0.0.230506
|
||||
date-released: '2023-05-23'
|
||||
+111
-101
@@ -36,12 +36,8 @@ endif
|
||||
endif
|
||||
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
PYVERSION:=py310
|
||||
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYLIBDIR:=python3.7
|
||||
PYNUMBER:=37
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
@@ -52,123 +48,100 @@ PYNUMBER:=310
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py37h5f1835d_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py37h95e2c48_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py37h0379df6_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1d/a4/931d0780f31f3ea8c4f9ef6464a2825137c5241e6707a5fb03bef760a7eb/shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/ed/d5/c2e84e1e36ab8ebea033921d5886a056c77e18bab5ab1051fcc22de2e8a2/Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/linux-64/hpp-fcl-1.7.5-py39hbcdfc36_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/linux-64/eigenpy-2.6.5-py39h5aed9d1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py39h5472131_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/linux-64/hpp-fcl-1.8.0-py310hdaf7e41_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/linux-64/eigenpy-2.6.11-py310hf3e5c9c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/linux-64/boost-1.74.0-py310h7c3ba0c_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/linux-64/octomap-1.9.7-h4bd325d_0.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)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/1e/3e/f0abc15d5dac50939bccc589aae336d5ead4c72e7ad1039a2e0f3630ea92/lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/49/c26fc3b5b0e82bdc9d8751d6b939da29327b0d98f7c3b95a575cbfed2743/Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py39h1e32b98_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py39h5405915_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py39ha641261_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-64/hpp-fcl-1.8.0-py310h651ac30_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/osx-64/eigenpy-2.6.11-py310hc03097c_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py310h509978a_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-64/octomap-1.9.7-h940c156_0.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)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
# Warning: py37 not supported on Apple M1
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-64/hpp-fcl-1.7.5-py37h2d7f23a_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/osx-64/eigenpy-2.6.5-py37h0695097_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/osx-64/boost-1.74.0-py37hd79e0ac_3.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-64/lxml-4.9.1-py37h994c40b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e6/7d/4923f27c340339e1c896c77cafc8ed672c8d381a025bbab6c6ddcba27e8f/shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/osx-arm64/hpp-fcl-1.7.5-py39ha69f3c1_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.7/download/osx-arm64/eigenpy-2.6.7-py39ha69f3c1_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h2550fe3_0.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/osx-arm64/hpp-fcl-1.8.0-py310h5699539_2.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.9/download/osx-arm64/eigenpy-2.6.9-py310habfc766_1.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h4566fd1_0.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/osx-arm64/octomap-1.9.7-hc021e02_0.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)
|
||||
ifeq ($(PYVERSION), py37)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py37h839d6b1_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py37h2c32e34_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py37h3b38789_3.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/9e/5e/171ee9d40a600f565fe691ec5bf7596247ec62cfb2edc00c91afe8ea837b/lxml-4.6.3-cp37-cp37m-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/e2/87/b8b8d8d57b429b01aa56b7d723075c09f33c988b8091bb6f790c83436909/shapely-2.0.1-cp37-cp37m-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/69/f5/9e802159d78b2eaf26bf1f8b94648605993f5ca7247ac8870f065063fc40/Pillow-9.2.0-cp37-cp37m-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.7.5/download/win-64/hpp-fcl-1.7.5-py39h2e7c763_0.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.5/download/win-64/eigenpy-2.6.5-py39h3ce40e6_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py39hefe7e4c_3.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py39hea4d8d1_4.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/72/d4/426ecb8849c47c3e370c87aa0ac05d85768df917ffea27fcd6686a5e6495/lxml-4.6.3-cp39-cp39-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a7/ae/eab645c4075093584b7a65ab71cb8ff4563a015bd935c9b55dba14b2c1eb/shapely-2.0.1-cp39-cp39-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/19/3f/b4d4bcf05dbcbe07f2e9613a8f4180c297395e73a91d8ad22c32c6624f8c/Pillow-9.2.0-cp39-cp39-win_amd64.whl
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/1.8.0/download/win-64/hpp-fcl-1.8.0-py310hc5a3c62_1.tar.bz2
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/2.6.11/download/win-64/eigenpy-2.6.11-py310hbd43d28_0.tar.bz2
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.74.0/download/win-64/boost-1.74.0-py310hc781a3c_5.tar.bz2
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py310heb59267_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py310hd146abe_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/win-64/boost-1.78.0-py310h220cb41_4.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe1500cebb179db96232e2f623bfe6a1f4294e39/lxml-4.8.0-cp310-cp310-win_amd64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.7/download/win-64/octomap-1.9.7-h5362a0b_0.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
|
||||
|
||||
@@ -190,7 +163,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-476ab50-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-8f41ae0-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -206,9 +179,7 @@ endif
|
||||
# IfcOpenBot sometimes lags behind, so we hotfix the Python utilities
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/util/* dist/blenderbim/libs/site/packages/ifcopenshell/util/
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/api/* dist/blenderbim/libs/site/packages/ifcopenshell/api/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/entity_instance.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/file.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/validate.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/*.py dist/blenderbim/libs/site/packages/ifcopenshell/
|
||||
cp dist/working/IfcOpenShell-0.7.0/src/ifcopenshell-python/ifcopenshell/express/rule_executor.py dist/blenderbim/libs/site/packages/ifcopenshell/express/
|
||||
# Provides bcf functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/bcf/src/bcf dist/blenderbim/libs/site/packages/
|
||||
@@ -408,14 +379,16 @@ endif
|
||||
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pytest --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
# Provides Brickschema functionality
|
||||
cd dist/working && . env/bin/activate && $(PIP) install brickschema[persistence] --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 && tar -xf hpp-fcl*
|
||||
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/
|
||||
@@ -424,6 +397,10 @@ 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/
|
||||
@@ -440,21 +417,45 @@ 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
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && tar -xf eigenpy*
|
||||
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
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# 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
|
||||
@@ -470,6 +471,9 @@ 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)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
endif
|
||||
@@ -485,11 +489,33 @@ 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)
|
||||
@@ -500,6 +526,9 @@ 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
|
||||
@@ -618,37 +647,11 @@ endif
|
||||
|
||||
# Provides Brickschema functionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
|
||||
cd dist/working && tar -xzvf brickschema*
|
||||
# This is an evil hack because we don't want to bundle flask
|
||||
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
|
||||
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../blenderbim/bim/schema/Brick.ttl
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
|
||||
cd dist/working && tar -xzvf pyshacl*
|
||||
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf rdflib*
|
||||
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf owlrl*
|
||||
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# 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
|
||||
@@ -664,6 +667,13 @@ endif
|
||||
cp -r dist/working/bpypolyskel-master/bpypolyskel dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required for Desktop icon and file association
|
||||
cp -r blenderbim/libs/desktop dist/blenderbim/libs/
|
||||
|
||||
# Remove dependencies also bundled with Blender
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
@@ -34,7 +34,8 @@ bl_info = {
|
||||
if sys.modules.get("bpy", None):
|
||||
# Process *.pth in /libs/site/packages to setup globally importable modules
|
||||
# This is 3 levels deep as required by the static RPATH of ../../ from dependencies taken from Anaconda
|
||||
site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
# site.addsitedir(os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "libs", "site", "packages"))
|
||||
|
||||
import blenderbim.bim
|
||||
|
||||
|
||||
@@ -95,6 +95,8 @@ classes = [
|
||||
operator.SelectIfcFile,
|
||||
operator.ReloadSelectedIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
operator.SelectURIAttribute,
|
||||
operator.EditBlenderCollection,
|
||||
operator.BIM_OT_open_webbrowser,
|
||||
@@ -109,6 +111,7 @@ classes = [
|
||||
prop.PsetQto,
|
||||
prop.GlobalId,
|
||||
prop.BIMObjectProperties,
|
||||
prop.BIMCollectionProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMMeshProperties,
|
||||
ui.BIM_PT_section_plane,
|
||||
@@ -156,6 +159,7 @@ def register():
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_post.append(handler.ensureIfcExported)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
|
||||
@@ -182,6 +186,7 @@ def unregister():
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_post.remove(handler.ensureIfcExported)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Collection.BIMCollectionProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
del bpy.types.Material.BIMMaterialProperties
|
||||
|
||||
@@ -0,0 +1,755 @@
|
||||
{
|
||||
"Technical": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Standard",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "BOTH",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "SINGLE",
|
||||
"scene.display.shading.curvature_ridge_factor": 1.0,
|
||||
"scene.display.shading.curvature_valley_factor": 1.0,
|
||||
"scene.display.shading.light": "FLAT",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": false,
|
||||
"scene.display.shading.show_object_outline": true,
|
||||
"scene.display.shading.show_shadows": false,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": false,
|
||||
"space.overlay.show_axis_y": false,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": false,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": false,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": true,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 0.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
},
|
||||
"Shaded": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_WORKBENCH",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Standard",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "BOTH",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "MATERIAL",
|
||||
"scene.display.shading.curvature_ridge_factor": 1.0,
|
||||
"scene.display.shading.curvature_valley_factor": 1.0,
|
||||
"scene.display.shading.light": "STUDIO",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": true,
|
||||
"scene.display.shading.show_object_outline": true,
|
||||
"scene.display.shading.show_shadows": true,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": false,
|
||||
"space.overlay.show_axis_y": false,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": false,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": false,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": false,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 0.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
},
|
||||
"Blender Default": {
|
||||
"render_type": "VIEWPORT",
|
||||
"raster_style": {
|
||||
"bpy.data.worlds[0].color": [
|
||||
0.05087608844041824,
|
||||
0.05087608844041824,
|
||||
0.05087608844041824
|
||||
],
|
||||
"scene.render.bake_bias": 0.0010000000474974513,
|
||||
"scene.render.bake_margin": 16,
|
||||
"scene.render.bake_margin_type": "ADJACENT_FACES",
|
||||
"scene.render.bake_samples": 256,
|
||||
"scene.render.bake_type": "NORMALS",
|
||||
"scene.render.bake_user_scale": 0.0,
|
||||
"scene.render.border_max_x": 1.0,
|
||||
"scene.render.border_max_y": 1.0,
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_EEVEE",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
"scene.render.fps_base": 1.0,
|
||||
"scene.render.frame_map_new": 100,
|
||||
"scene.render.frame_map_old": 100,
|
||||
"scene.render.hair_subdiv": 0,
|
||||
"scene.render.hair_type": "STRAND",
|
||||
"scene.render.line_thickness": 1.0,
|
||||
"scene.render.line_thickness_mode": "ABSOLUTE",
|
||||
"scene.render.metadata_input": "SCENE",
|
||||
"scene.render.motion_blur_shutter": 0.5,
|
||||
"scene.render.pixel_aspect_x": 1.0,
|
||||
"scene.render.pixel_aspect_y": 1.0,
|
||||
"scene.render.preview_pixel_size": "AUTO",
|
||||
"scene.render.resolution_percentage": 100,
|
||||
"scene.render.resolution_x": 1000,
|
||||
"scene.render.resolution_y": 1000,
|
||||
"scene.render.sequencer_gl_preview": "SOLID",
|
||||
"scene.render.simplify_child_particles": 1.0,
|
||||
"scene.render.simplify_child_particles_render": 1.0,
|
||||
"scene.render.simplify_gpencil": false,
|
||||
"scene.render.simplify_gpencil_antialiasing": true,
|
||||
"scene.render.simplify_gpencil_modifier": true,
|
||||
"scene.render.simplify_gpencil_onplay": false,
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
"scene.render.stamp_font_size": 12,
|
||||
"scene.render.stamp_note_text": "",
|
||||
"scene.render.threads": 20,
|
||||
"scene.render.threads_mode": "AUTO",
|
||||
"scene.render.use_bake_clear": true,
|
||||
"scene.render.use_bake_lores_mesh": false,
|
||||
"scene.render.use_bake_multires": false,
|
||||
"scene.render.use_bake_selected_to_active": false,
|
||||
"scene.render.use_bake_user_scale": false,
|
||||
"scene.render.use_border": false,
|
||||
"scene.render.use_compositing": true,
|
||||
"scene.render.use_crop_to_border": false,
|
||||
"scene.render.use_file_extension": true,
|
||||
"scene.render.use_freestyle": false,
|
||||
"scene.render.use_high_quality_normals": false,
|
||||
"scene.render.use_lock_interface": false,
|
||||
"scene.render.use_motion_blur": false,
|
||||
"scene.render.use_multiview": false,
|
||||
"scene.render.use_overwrite": true,
|
||||
"scene.render.use_persistent_data": false,
|
||||
"scene.render.use_placeholder": false,
|
||||
"scene.render.use_render_cache": false,
|
||||
"scene.render.use_sequencer": true,
|
||||
"scene.render.use_sequencer_override_scene_strip": false,
|
||||
"scene.render.use_simplify": false,
|
||||
"scene.render.use_single_layer": false,
|
||||
"scene.render.use_stamp": false,
|
||||
"scene.render.use_stamp_camera": true,
|
||||
"scene.render.use_stamp_date": true,
|
||||
"scene.render.use_stamp_filename": true,
|
||||
"scene.render.use_stamp_frame": true,
|
||||
"scene.render.use_stamp_frame_range": false,
|
||||
"scene.render.use_stamp_hostname": false,
|
||||
"scene.render.use_stamp_labels": true,
|
||||
"scene.render.use_stamp_lens": false,
|
||||
"scene.render.use_stamp_marker": false,
|
||||
"scene.render.use_stamp_memory": false,
|
||||
"scene.render.use_stamp_note": false,
|
||||
"scene.render.use_stamp_render_time": true,
|
||||
"scene.render.use_stamp_scene": true,
|
||||
"scene.render.use_stamp_sequencer_strip": false,
|
||||
"scene.render.use_stamp_time": true,
|
||||
"scene.render.views_format": "STEREO_3D",
|
||||
"scene.view_settings.exposure": 0.0,
|
||||
"scene.view_settings.gamma": 1.0,
|
||||
"scene.view_settings.look": "None",
|
||||
"scene.view_settings.use_curve_mapping": false,
|
||||
"scene.view_settings.view_transform": "Filmic",
|
||||
"scene.display.shading.aov_name": "",
|
||||
"scene.display.shading.background_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.background_type": "THEME",
|
||||
"scene.display.shading.cavity_ridge_factor": 1.0,
|
||||
"scene.display.shading.cavity_type": "WORLD",
|
||||
"scene.display.shading.cavity_valley_factor": 1.0,
|
||||
"scene.display.shading.color_type": "MATERIAL",
|
||||
"scene.display.shading.curvature_ridge_factor": 0.0,
|
||||
"scene.display.shading.curvature_valley_factor": 0.0,
|
||||
"scene.display.shading.light": "STUDIO",
|
||||
"scene.display.shading.object_outline_color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scene.display.shading.render_pass": "COMBINED",
|
||||
"scene.display.shading.shadow_intensity": 0.5,
|
||||
"scene.display.shading.show_backface_culling": false,
|
||||
"scene.display.shading.show_cavity": false,
|
||||
"scene.display.shading.show_object_outline": false,
|
||||
"scene.display.shading.show_shadows": false,
|
||||
"scene.display.shading.show_specular_highlight": true,
|
||||
"scene.display.shading.show_xray": false,
|
||||
"scene.display.shading.show_xray_wireframe": false,
|
||||
"scene.display.shading.single_color": [
|
||||
0.800000011920929,
|
||||
0.800000011920929,
|
||||
0.800000011920929
|
||||
],
|
||||
"scene.display.shading.studio_light": "Default",
|
||||
"scene.display.shading.studiolight_background_alpha": 0.0,
|
||||
"scene.display.shading.studiolight_background_blur": 0.0,
|
||||
"scene.display.shading.studiolight_intensity": 0.0,
|
||||
"scene.display.shading.studiolight_rotate_z": 0.0,
|
||||
"scene.display.shading.type": "SOLID",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
"scene.display.shading.use_scene_lights_render": false,
|
||||
"scene.display.shading.use_scene_world": false,
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
0.5773502588272095,
|
||||
0.5773502588272095,
|
||||
0.5773502588272095
|
||||
],
|
||||
"scene.display.matcap_ssao_attenuation": 1.0,
|
||||
"scene.display.matcap_ssao_distance": 0.20000000298023224,
|
||||
"scene.display.matcap_ssao_samples": 16,
|
||||
"scene.display.render_aa": "8",
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
"space.overlay.gpencil_fade_layer": 0.5,
|
||||
"space.overlay.gpencil_fade_objects": 0.5,
|
||||
"space.overlay.gpencil_grid_opacity": 0.5,
|
||||
"space.overlay.gpencil_vertex_paint_opacity": 1.0,
|
||||
"space.overlay.grid_lines": 16,
|
||||
"space.overlay.grid_scale": 1.0,
|
||||
"space.overlay.grid_subdivisions": 10,
|
||||
"space.overlay.normals_constant_screen_size": 7.0,
|
||||
"space.overlay.normals_length": 0.10000000149011612,
|
||||
"space.overlay.sculpt_curves_cage_opacity": 0.5,
|
||||
"space.overlay.sculpt_mode_face_sets_opacity": 1.0,
|
||||
"space.overlay.sculpt_mode_mask_opacity": 0.75,
|
||||
"space.overlay.show_annotation": true,
|
||||
"space.overlay.show_axis_x": true,
|
||||
"space.overlay.show_axis_y": true,
|
||||
"space.overlay.show_axis_z": false,
|
||||
"space.overlay.show_bones": true,
|
||||
"space.overlay.show_cursor": true,
|
||||
"space.overlay.show_curve_normals": false,
|
||||
"space.overlay.show_edge_bevel_weight": true,
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
"space.overlay.show_extra_face_area": false,
|
||||
"space.overlay.show_extra_indices": false,
|
||||
"space.overlay.show_extras": true,
|
||||
"space.overlay.show_face_center": false,
|
||||
"space.overlay.show_face_normals": false,
|
||||
"space.overlay.show_face_orientation": false,
|
||||
"space.overlay.show_faces": true,
|
||||
"space.overlay.show_fade_inactive": false,
|
||||
"space.overlay.show_floor": true,
|
||||
"space.overlay.show_freestyle_edge_marks": true,
|
||||
"space.overlay.show_freestyle_face_marks": true,
|
||||
"space.overlay.show_look_dev": false,
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": true,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
"space.overlay.show_overlays": true,
|
||||
"space.overlay.show_paint_wire": false,
|
||||
"space.overlay.show_relationship_lines": true,
|
||||
"space.overlay.show_sculpt_curves_cage": true,
|
||||
"space.overlay.show_sculpt_face_sets": true,
|
||||
"space.overlay.show_sculpt_mask": true,
|
||||
"space.overlay.show_split_normals": false,
|
||||
"space.overlay.show_stats": false,
|
||||
"space.overlay.show_statvis": false,
|
||||
"space.overlay.show_text": true,
|
||||
"space.overlay.show_vertex_normals": false,
|
||||
"space.overlay.show_viewer_attribute": true,
|
||||
"space.overlay.show_weight": false,
|
||||
"space.overlay.show_wireframes": false,
|
||||
"space.overlay.show_wpaint_contours": false,
|
||||
"space.overlay.show_xray_bone": false,
|
||||
"space.overlay.texture_paint_mode_opacity": 1.0,
|
||||
"space.overlay.use_debug_freeze_view_culling": false,
|
||||
"space.overlay.use_gpencil_canvas_xray": false,
|
||||
"space.overlay.use_gpencil_edit_lines": true,
|
||||
"space.overlay.use_gpencil_fade_gp_objects": false,
|
||||
"space.overlay.use_gpencil_fade_layers": false,
|
||||
"space.overlay.use_gpencil_fade_objects": false,
|
||||
"space.overlay.use_gpencil_grid": false,
|
||||
"space.overlay.use_gpencil_multiedit_line_only": false,
|
||||
"space.overlay.use_gpencil_onion_skin": false,
|
||||
"space.overlay.use_gpencil_show_directions": false,
|
||||
"space.overlay.use_gpencil_show_material_name": false,
|
||||
"space.overlay.use_normals_constant_screen_size": false,
|
||||
"space.overlay.vertex_opacity": 1.0,
|
||||
"space.overlay.vertex_paint_mode_opacity": 1.0,
|
||||
"space.overlay.viewer_attribute_opacity": 1.0,
|
||||
"space.overlay.weight_paint_mode_opacity": 1.0,
|
||||
"space.overlay.wireframe_opacity": 1.0,
|
||||
"space.overlay.wireframe_threshold": 1.0,
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,55 @@
|
||||
<link href="jsgantt.css" rel="stylesheet" type="text/css"/>
|
||||
<link href="../../module/sequence/gantt/main.css" rel="stylesheet" type="text/css"/>
|
||||
<script src="jsgantt.js" type="text/javascript"></script>
|
||||
<style>
|
||||
.gtaskcellwkend,
|
||||
.gtaskcellcurrent,
|
||||
.gminorheadingwkend {
|
||||
background-color: #e1e1e1;
|
||||
}
|
||||
|
||||
.gitemhighlight td {
|
||||
background-color: #ffdaaa;
|
||||
}
|
||||
|
||||
.gtaskblue {
|
||||
background: #4281A4;
|
||||
}
|
||||
|
||||
.gtaskred {
|
||||
background: #C1666B;
|
||||
}
|
||||
|
||||
.gtaskgreen {
|
||||
background: #48A9A6;
|
||||
}
|
||||
|
||||
.gtaskyellow {
|
||||
background: #D4B483;
|
||||
}
|
||||
|
||||
.gmainleft {
|
||||
overflow: visible;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
</style>
|
||||
<a href="#print" id="print">Print Mode</a>
|
||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||
<script type="text/javascript">
|
||||
var g = new JSGantt.GanttChart(document.getElementById('GanttChartDIV'), 'day');
|
||||
g.setOptions({
|
||||
vCaptionType: 'Caption', // Set to Show Caption : None,Caption,Resource,Duration,Complete,
|
||||
vQuarterColWidth: 36,
|
||||
vDateTaskDisplayFormat: 'day dd month yyyy', // Shown in tool tip box
|
||||
vDayMajorDateDisplayFormat: 'mon yyyy - Week ww',// Set format to dates in the "Major" header of the "Day" view
|
||||
vWeekMinorDateDisplayFormat: 'dd mon', // Set format to display dates in the "Minor" header of the "Week" view
|
||||
vLang: 'en',
|
||||
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
|
||||
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
|
||||
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
|
||||
vFormatArr: ['Day', 'Week', 'Month', 'Quarter'], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
|
||||
vShowRes: false, // Disable the resource column.
|
||||
vShowComp: false, // Disable the completion column.
|
||||
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
|
||||
vAdditionalHeaders: {ifcduration: {title: 'Duration'}},
|
||||
vUseToolTip: false, // Disable tooltips.
|
||||
vTotalHeight: 900,
|
||||
});
|
||||
document.getElementById('print').addEventListener('click', function() {
|
||||
g.setTotalHeight("");
|
||||
g.Draw();
|
||||
});
|
||||
var json_data = `
|
||||
{{{json_data}}}
|
||||
`;
|
||||
JSGantt.parseJSONString(json_data, g);
|
||||
g.Draw();
|
||||
<script type="text/javascript" src="../../module/sequence/gantt/index.js"></script>
|
||||
<script>
|
||||
var json_data = `{{{json_data}}}`;
|
||||
</script>
|
||||
|
||||
<div id="options" class="no-print">
|
||||
<h4>Choose a language:
|
||||
<select id="lang" class="sweet_button" onchange="set_language(event)">
|
||||
<option value='cn'>Chinese (cn)</option>
|
||||
<option value='cs'>Czech (cs)</option>
|
||||
<option value='nl'>Dutch (Standard)</option>
|
||||
<option value='en' selected>English (en)</option>
|
||||
<option value='fr'>French (fr)</option>
|
||||
<option value='de'>German (de)</option>
|
||||
<option value='hu'>Hungarian (hu)</option>
|
||||
<option value='id'>Indonesian (id)</option>
|
||||
<option value='it'>Italian (it)</option>
|
||||
<option value='ja'>Japanese (ja)</option>
|
||||
<option value='pt'>Portuguese (pt)</option>
|
||||
<option value='ru'>Russian (ru)</option>
|
||||
<option value='es'>Spanish (es)</option>
|
||||
<option value='sv'>Swedish (sv)</option>
|
||||
<option value='tr'>Turkish (tr)</option>
|
||||
</select>
|
||||
</h4>
|
||||
|
||||
<br>
|
||||
</div>
|
||||
<div class="top-right no-print" id="print_options">
|
||||
<select class="sweet_button" id="print_page_size">
|
||||
<option value="210,297">A4 Portrait</option>
|
||||
<option value="297,210">A4 Landscape</option>
|
||||
<option value="297,420">A3 Portrait</option>
|
||||
<option value="420,297">A3 Landscape</option>
|
||||
<option value="420,594">A2 Portrait</option>
|
||||
<option value="594,420">A2 Landscape</option>
|
||||
<option value="594,841">A1 Portrait</option>
|
||||
<option value="841,594">A1 Landscape</option>
|
||||
<option value="841,1189">A0 Portrait</option>
|
||||
<option value="1189,841">A0 Landscape</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="schedule-data" class="no-print"></div>
|
||||
<div id="schedule-header" style=""></div>
|
||||
<div style="position:relative" class="gantt" id="GanttChartDIV"></div>
|
||||
|
||||
<script>
|
||||
var data = `{{{data}}}`;
|
||||
setupPage(data);
|
||||
create_gantt_chart(json_data)
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,11 @@ 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));
|
||||
#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));
|
||||
#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.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
@@ -16,13 +17,15 @@ DATA;
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcURIReference',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -18,7 +18,7 @@ DATA;
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1DtsPn5a9FG8$zXHDDMavY',$,'ShowEndArrow','Display end arrow.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('2$6U0mLI9AiPRWeBabdY3u',$,'EndArrowSymbol','Custom symbol for the end of the section marker arrow. Need to make sure it''s present in "symbols.svg".',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('1naFqntIL7igCY7hCaE7kq',$,'HasConnectedSectionLine','Connect or disconnect section markers with line (by default = True).',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#14=IFCPROPERTYSETTEMPLATE('3V8oZ8YRD3_O7uR5vcUleS',$,'BBIM_Documentation','',.PSET_OCCURRENCEDRIVEN.,'IfcProject',(#15,#16,#17,#18,#19,#20,#21,#22,#23));
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('0ulAhgk3v9qfGlDILauR6J',$,'SheetsDir','Default sheets directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2yvlVKiQXASucfH40deCvu',$,'LayoutsDir','Default layouts directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('2kXZqXicL3jRwsOnLM_0ho',$,'TitleblocksDir','Default titleblocks directory',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -27,8 +27,12 @@ DATA;
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('23tejOrxj859R_eCRp1AFP',$,'MarkersPath','Default markers SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#24,#25));
|
||||
#24=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#28=IFCSIMPLEPROPERTYTEMPLATE('0bnzttUb9BPuN597uNTXOE',$,'TextSuffix','Text to add after annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -44,9 +44,8 @@ class IfcExporter:
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
|
||||
@@ -22,19 +22,18 @@ import addon_utils
|
||||
import ifcopenshell.api.owner.settings
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.owner as core_owner
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from mathutils import Vector
|
||||
from math import cos, degrees
|
||||
|
||||
|
||||
global_subscription_owner = object()
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
if not bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
return
|
||||
objects = bpy.context.selected_objects
|
||||
if bpy.context.active_object:
|
||||
objects += [bpy.context.active_object]
|
||||
@@ -75,19 +74,8 @@ def name_callback(obj, data):
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
if element.is_a("IfcSpatialStructureElement") or (hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy):
|
||||
collection = obj.users_collection[0]
|
||||
collection.name = obj.name
|
||||
if element.is_a("IfcGrid"):
|
||||
axis_obj = IfcStore.get_element(element.UAxes[0].id())
|
||||
axis_collection = axis_obj.users_collection[0]
|
||||
grid_collection = None
|
||||
for collection in bpy.data.collections:
|
||||
if axis_collection.name in collection.children.keys():
|
||||
grid_collection = collection
|
||||
break
|
||||
if grid_collection:
|
||||
grid_collection.name = obj.name
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = obj.name
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
refresh_ui_data()
|
||||
|
||||
@@ -99,6 +87,53 @@ def color_callback(obj, data):
|
||||
|
||||
def active_object_callback():
|
||||
refresh_ui_data()
|
||||
update_bim_tool_props()
|
||||
|
||||
|
||||
def update_bim_tool_props():
|
||||
"""update BIM Tools props (such as extrusion_depth, length and x_angle) when active object changes"""
|
||||
obj = bpy.context.active_object
|
||||
|
||||
# bunch of checks to see if we're in a valid state
|
||||
if not obj:
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname != "bim.bim_tool":
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
if not extrusion:
|
||||
return
|
||||
|
||||
def get_x_angle(extrusion):
|
||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
||||
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
||||
return x_angle
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
if AuthoringData.data["active_material_usage"] == "LAYER2":
|
||||
x_angle = get_x_angle(extrusion)
|
||||
axis = tool.Model.get_wall_axis(obj)["reference"]
|
||||
props.extrusion_depth = extrusion.Depth * si_conversion * cos(x_angle)
|
||||
props.length = (axis[1] - axis[0]).length
|
||||
props.x_angle = x_angle
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||
x_angle = get_x_angle(extrusion)
|
||||
props.x_angle = x_angle
|
||||
|
||||
elif AuthoringData.data["active_material_usage"] == "PROFILE":
|
||||
props.extrusion_depth = extrusion.Depth * si_conversion
|
||||
|
||||
|
||||
def active_material_index_callback(obj, data):
|
||||
@@ -133,6 +168,9 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
@@ -224,6 +262,12 @@ def get_application_version():
|
||||
)
|
||||
|
||||
|
||||
def viewport_shading_changed_callback(area):
|
||||
shading = area.spaces.active.shading.type
|
||||
if shading == "RENDERED":
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
global global_subscription_owner
|
||||
@@ -231,75 +275,21 @@ def setDefaultProperties(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=active_object_key, owner=global_subscription_owner, args=(), notify=active_object_callback
|
||||
)
|
||||
|
||||
# subscribe to changes in viewport shading mode
|
||||
# NOTE: couldn't find a way to make it work for new areas too
|
||||
# it starts working for them after blender restart though
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type != "VIEW_3D":
|
||||
continue
|
||||
shading = area.spaces.active.shading
|
||||
key = shading.path_resolve("type", False)
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=key, owner=global_subscription_owner, args=(area,), notify=viewport_shading_changed_callback
|
||||
)
|
||||
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
# TODO: Move to drawing module
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Blender Default"
|
||||
drawing_style.render_type = "DEFAULT"
|
||||
bpy.ops.bim.save_drawing_style(index="0")
|
||||
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
|
||||
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
|
||||
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
|
||||
RasterStyleProperty.SHADING_SHOW_CAVITY.value: False,
|
||||
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
|
||||
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
|
||||
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
|
||||
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
|
||||
RasterStyleProperty.SHADING_LIGHT.value: "FLAT",
|
||||
RasterStyleProperty.SHADING_COLOR_TYPE.value: "SINGLE",
|
||||
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: False,
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
|
||||
}
|
||||
)
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Shaded"
|
||||
drawing_style.render_type = "VIEWPORT"
|
||||
drawing_style.raster_style = json.dumps(
|
||||
{
|
||||
RasterStyleProperty.WORLD_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.RENDER_ENGINE.value: "BLENDER_WORKBENCH",
|
||||
RasterStyleProperty.RENDER_TRANSPARENT.value: False,
|
||||
RasterStyleProperty.SHADING_SHOW_OBJECT_OUTLINE.value: True,
|
||||
RasterStyleProperty.SHADING_SHOW_CAVITY.value: True,
|
||||
RasterStyleProperty.SHADING_CAVITY_TYPE.value: "BOTH",
|
||||
RasterStyleProperty.SHADING_CURVATURE_RIDGE_FACTOR.value: 1,
|
||||
RasterStyleProperty.SHADING_CURVATURE_VALLEY_FACTOR.value: 1,
|
||||
RasterStyleProperty.VIEW_TRANSFORM.value: "Standard",
|
||||
RasterStyleProperty.SHADING_LIGHT.value: "STUDIO",
|
||||
RasterStyleProperty.SHADING_COLOR_TYPE.value: "MATERIAL",
|
||||
RasterStyleProperty.SHADING_SINGLE_COLOR.value: (1, 1, 1),
|
||||
RasterStyleProperty.SHADING_SHOW_SHADOWS.value: True,
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Y.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_Z.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_OBJECT_ORIGINS.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_RELATIONSHIP_LINES.value: False,
|
||||
}
|
||||
)
|
||||
AuthoringData.type_thumbnails = {}
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
@@ -273,6 +273,9 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
class IfcHeaderExtractor:
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
@@ -284,6 +287,8 @@ class IfcHeaderExtractor:
|
||||
return self.extract_ifc_spf(ifc_file)
|
||||
elif extension.lower() == "ifczip":
|
||||
return self.extract_ifc_zip()
|
||||
elif extension.lower() == "ifcsqlite":
|
||||
return {} # TODO
|
||||
|
||||
def extract_ifc_spf(self, ifc_file):
|
||||
# https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
|
||||
@@ -120,6 +120,8 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
@@ -130,7 +132,9 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif extension.lower() == "ifc":
|
||||
elif bpy.context.scene.BIMProjectProperties.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -36,7 +36,7 @@ import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import get_diagram_scales
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class FileCopy(threading.Thread):
|
||||
@@ -178,11 +178,16 @@ class IfcImporter:
|
||||
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_curve = ifcopenshell.geom.settings()
|
||||
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
|
||||
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
|
||||
self.settings_native = ifcopenshell.geom.settings()
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
@@ -342,7 +347,8 @@ class IfcImporter:
|
||||
self.element_types = set(self.file.by_type("IfcTypeProduct"))
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
filtered_elements = self.elements | set(self.file.by_type("IfcGrid"))
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(filtered_elements)
|
||||
else:
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
|
||||
@@ -356,11 +362,13 @@ class IfcImporter:
|
||||
while True:
|
||||
results.add(spatial_element)
|
||||
spatial_element = ifcopenshell.util.element.get_aggregate(spatial_element)
|
||||
if not spatial_element or spatial_element.is_a("IfcContext"):
|
||||
if not spatial_element or spatial_element.is_a() in ("IfcProject", "IfcProjectLibrary"):
|
||||
break
|
||||
return results
|
||||
|
||||
def parse_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
for element in self.elements:
|
||||
if self.is_native(element):
|
||||
self.native_elements.add(element)
|
||||
@@ -376,7 +384,7 @@ class IfcImporter:
|
||||
representations = self.get_transformed_body_representations(element.Representation.Representations)
|
||||
|
||||
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
||||
if self.is_native_swept_disk_solid(representations):
|
||||
if self.is_native_swept_disk_solid(element, representations):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"representations": representations,
|
||||
"representation": self.get_body_representation(element.Representation.Representations),
|
||||
@@ -404,16 +412,20 @@ class IfcImporter:
|
||||
}
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(self, representations):
|
||||
def is_native_swept_disk_solid(self, element, representations):
|
||||
for representation in representations:
|
||||
items = representation["raw"].Items or [] # Be forgiving of invalid IFCs because Revit :(
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -568,6 +580,8 @@ class IfcImporter:
|
||||
return result
|
||||
|
||||
def create_grids(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
grids = self.file.by_type("IfcGrid")
|
||||
for grid in grids:
|
||||
shape = None
|
||||
@@ -613,34 +627,37 @@ class IfcImporter:
|
||||
|
||||
def create_element_type(self, element):
|
||||
self.ifc_import_settings.logger.info("Creating object %s", element)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
mesh = None
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
if not representation:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Annotation")
|
||||
if representation:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
self.type_products[element.GlobalId] = obj
|
||||
|
||||
def create_native_elements(self):
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
progress = 0
|
||||
checkpoint = time.time()
|
||||
total = len(self.native_elements)
|
||||
@@ -682,29 +699,87 @@ class IfcImporter:
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
if isinstance(self.file, ifcopenshell.sqlite):
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
# Based on my experience in viewing BIM models, representations are prioritised as follows:
|
||||
# 1. 3D Body, 2. 2D Plans, 3. Point clouds, 4. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, or 3, it will not show by default.
|
||||
# 1. 3D Body, 2. 2D Body, 3. 2D Plans / annotations, 4. Point clouds, 5. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, 3, or 4, it will not show by default.
|
||||
# The user can load them later if they want to view them.
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_curve_products(elements)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
for element in elements:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
self.create_product(element)
|
||||
|
||||
def create_products(self, products):
|
||||
def create_generic_sqlite_elements(self, elements):
|
||||
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}))
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
verts = geometry["verts"]
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
num_vertices = len(verts) // 3
|
||||
total_faces = len(geometry["faces"])
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry["faces"])
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", geometry["faces"])
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
mesh.from_pydata(vertices, edges, [])
|
||||
|
||||
mesh["ios_materials"] = geometry["materials"]
|
||||
mesh["ios_material_ids"] = geometry["material_ids"]
|
||||
self.meshes[mesh_name] = mesh
|
||||
|
||||
total = len(elements)
|
||||
for i, element in enumerate(elements):
|
||||
if i % 250 == 0:
|
||||
print("{} / {} elements processed ...".format(i, total))
|
||||
mesh = None
|
||||
geometry_id = self.geometry_cache["shapes"][element.id()]["geometry"]
|
||||
if geometry_id:
|
||||
mesh_name = tool.Loader.get_mesh_name(type("Geometry", (), {"id": geometry_id}))
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
self.create_product(element, mesh=mesh)
|
||||
|
||||
def create_products(self, products, settings=None):
|
||||
if settings is None:
|
||||
settings = self.settings
|
||||
|
||||
results = set()
|
||||
if not products:
|
||||
return results
|
||||
if self.ifc_import_settings.should_use_cpu_multiprocessing:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
self.settings, self.file, multiprocessing.cpu_count(), include=products
|
||||
)
|
||||
iterator = ifcopenshell.geom.iterator(settings, self.file, multiprocessing.cpu_count(), include=products)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
|
||||
iterator = ifcopenshell.geom.iterator(settings, self.file, include=products)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
@@ -771,10 +846,10 @@ class IfcImporter:
|
||||
self.structural_collection.children.link(self.structural_connection_collection)
|
||||
self.project["blender"].children.link(self.structural_collection)
|
||||
|
||||
self.create_curve_products(self.file.by_type("IfcStructuralCurveMember"))
|
||||
self.create_curve_products(self.file.by_type("IfcStructuralCurveConnection"))
|
||||
self.create_curve_products(self.file.by_type("IfcStructuralSurfaceMember"))
|
||||
self.create_curve_products(self.file.by_type("IfcStructuralSurfaceConnection"))
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
|
||||
self.create_structural_point_connections()
|
||||
|
||||
def create_structural_point_connections(self):
|
||||
@@ -862,40 +937,6 @@ class IfcImporter:
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
|
||||
def create_curve_products(self, products):
|
||||
results = set()
|
||||
if not products:
|
||||
return results
|
||||
if self.ifc_import_settings.should_use_cpu_multiprocessing:
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
self.settings_2d, self.file, multiprocessing.cpu_count(), include=products
|
||||
)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
|
||||
if self.ifc_import_settings.should_cache:
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
checkpoint = time.time()
|
||||
total = 0
|
||||
while True:
|
||||
total += 1
|
||||
if total % 250 == 0:
|
||||
print("{} elements processed in {:.2f}s ...".format(total, time.time() - checkpoint))
|
||||
checkpoint = time.time()
|
||||
shape = iterator.get()
|
||||
if shape:
|
||||
product = self.file.by_id(shape.id)
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
if not iterator.next():
|
||||
break
|
||||
print("Done creating geometry")
|
||||
return results
|
||||
|
||||
def create_product(self, element, shape=None, mesh=None):
|
||||
if element is None:
|
||||
return
|
||||
@@ -1298,7 +1339,6 @@ class IfcImporter:
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
context_override = {}
|
||||
bpy.ops.object.editmode_toggle(context_override)
|
||||
bpy.ops.mesh.remove_doubles(context_override)
|
||||
bpy.ops.mesh.tris_convert_to_quads(context_override)
|
||||
bpy.ops.mesh.normals_make_consistent(context_override)
|
||||
bpy.ops.object.editmode_toggle(context_override)
|
||||
@@ -1333,39 +1373,43 @@ class IfcImporter:
|
||||
bpy.context.scene.unit_settings.length_unit = "FEET"
|
||||
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT":
|
||||
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
|
||||
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
except: # Probably an invalid unit.
|
||||
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
|
||||
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT":
|
||||
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
|
||||
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
try:
|
||||
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
except: # Probably an invalid unit.
|
||||
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
|
||||
|
||||
def create_project(self):
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
self.project = {"ifc": project}
|
||||
obj = tool.Ifc.get_object(project)
|
||||
if obj:
|
||||
self.project["blender"] = obj.users_collection[0]
|
||||
self.project["blender"] = obj.BIMObjectProperties.collection
|
||||
self.has_existing_project = True
|
||||
return
|
||||
self.project["blender"] = bpy.data.collections.new(
|
||||
"{}/{}".format(self.project["ifc"].is_a(), self.project["ifc"].Name)
|
||||
)
|
||||
obj = self.create_product(self.project["ifc"])
|
||||
if obj:
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = self.project["blender"]
|
||||
|
||||
def create_collections(self):
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_decomposition_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
self.create_spatial_decomposition_collections()
|
||||
|
||||
def create_decomposition_collections(self):
|
||||
self.create_spatial_decomposition_collections()
|
||||
self.create_aggregate_collections()
|
||||
if self.ifc_import_settings.collection_mode == "DECOMPOSITION":
|
||||
self.create_aggregate_collections()
|
||||
elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION":
|
||||
pass
|
||||
|
||||
def create_spatial_decomposition_collections(self):
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
@@ -1408,7 +1452,7 @@ class IfcImporter:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
is_existing = True
|
||||
collection = obj.users_collection[0]
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
self.collections[element.GlobalId] = collection
|
||||
if not is_existing:
|
||||
collection = bpy.data.collections.new(tool.Loader.get_name(element))
|
||||
@@ -1494,183 +1538,18 @@ class IfcImporter:
|
||||
if not blender_material:
|
||||
name = style.Name or str(style.id())
|
||||
blender_material = bpy.data.materials.new(name)
|
||||
blender_material.use_fake_user = True
|
||||
|
||||
self.link_element(style, blender_material)
|
||||
|
||||
blender_material.BIMMaterialProperties.ifc_style_id = style.id()
|
||||
self.material_creator.styles[style.id()] = blender_material
|
||||
|
||||
rendering_style = None
|
||||
texture_style = None
|
||||
|
||||
for surface_style in style.Styles:
|
||||
if surface_style.is_a() == "IfcSurfaceStyleShading":
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleRendering"):
|
||||
rendering_style = surface_style
|
||||
self.create_surface_style_rendering(blender_material, surface_style)
|
||||
elif surface_style.is_a("IfcSurfaceStyleWithTextures"):
|
||||
texture_style = surface_style
|
||||
|
||||
if rendering_style and texture_style:
|
||||
self.create_surface_style_with_textures(blender_material, rendering_style, texture_style)
|
||||
|
||||
tool.Style.record_shading(blender_material)
|
||||
|
||||
def create_surface_style_shading(self, blender_material, surface_style):
|
||||
alpha = 1.0
|
||||
# Transparency was added in IFC4
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
alpha = 1 - surface_style.Transparency
|
||||
blender_material.diffuse_color = (
|
||||
surface_style.SurfaceColour.Red,
|
||||
surface_style.SurfaceColour.Green,
|
||||
surface_style.SurfaceColour.Blue,
|
||||
alpha,
|
||||
)
|
||||
|
||||
def create_surface_style_rendering(self, blender_material, surface_style):
|
||||
self.create_surface_style_shading(blender_material, surface_style)
|
||||
if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
blender_material.use_nodes = True
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if surface_style.DiffuseColour:
|
||||
if surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Base Color"].default_value = (
|
||||
surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue,
|
||||
surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue,
|
||||
1,
|
||||
)
|
||||
if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"):
|
||||
bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue
|
||||
if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"):
|
||||
bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue
|
||||
if hasattr(surface_style, "Transparency") and surface_style.Transparency:
|
||||
bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif surface_style.ReflectanceMethod == "FLAT":
|
||||
blender_material.use_nodes = True
|
||||
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
|
||||
mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader")
|
||||
mix.location = bsdf.location
|
||||
blender_material.node_tree.links.new(mix.outputs[0], output.inputs["Surface"])
|
||||
|
||||
blender_material.node_tree.nodes.remove(bsdf)
|
||||
|
||||
lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath")
|
||||
lightpath.location = mix.location - mathutils.Vector((200, -200))
|
||||
blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0])
|
||||
|
||||
bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent")
|
||||
bsdf.location = mix.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1])
|
||||
|
||||
rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
rgb.location = mix.location - mathutils.Vector((200, 200))
|
||||
blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2])
|
||||
|
||||
if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"):
|
||||
rgb.outputs[0].default_value = (
|
||||
surface_style.DiffuseColour.Red,
|
||||
surface_style.DiffuseColour.Green,
|
||||
surface_style.DiffuseColour.Blue,
|
||||
1,
|
||||
)
|
||||
|
||||
def create_surface_style_with_textures(self, blender_material, rendering_style, texture_style):
|
||||
for texture in texture_style.Textures:
|
||||
mode = getattr(texture, "Mode", None)
|
||||
node = None
|
||||
|
||||
if texture.is_a("IfcImageTexture"):
|
||||
image_url = texture.URLReference
|
||||
if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path():
|
||||
image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference)
|
||||
|
||||
if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]:
|
||||
bsdf = blender_material.node_tree.nodes["Principled BSDF"]
|
||||
if mode == "NORMAL":
|
||||
normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap")
|
||||
normalmap.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = normalmap.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"])
|
||||
elif mode == "EMISSIVE":
|
||||
output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||
|
||||
add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader")
|
||||
add.location = bsdf.location + mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1])
|
||||
blender_material.node_tree.links.new(add.outputs[0], output.inputs[0])
|
||||
|
||||
emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission")
|
||||
emission.location = add.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = emission.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0])
|
||||
elif mode == "METALLICROUGHNESS":
|
||||
separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB")
|
||||
separate.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"])
|
||||
blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"])
|
||||
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = separate.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
image.colorspace_settings.name = "Non-Color"
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0])
|
||||
elif mode == "OCCLUSION":
|
||||
# TODO work out how to implement glTF settings here
|
||||
# https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html
|
||||
pass
|
||||
elif mode == "DIFFUSE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((400, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"])
|
||||
blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"])
|
||||
blender_material.blend_method = "BLEND"
|
||||
elif rendering_style.ReflectanceMethod == "FLAT":
|
||||
bsdf = blender_material.node_tree.nodes["Mix Shader"]
|
||||
if mode == "EMISSIVE":
|
||||
node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
node.location = bsdf.location - mathutils.Vector((200, 0))
|
||||
image = bpy.data.images.load(image_url)
|
||||
node.image = image
|
||||
blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2])
|
||||
|
||||
if node and getattr(texture, "IsMappedBy", None):
|
||||
coordinates = texture.IsMappedBy[0]
|
||||
coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord")
|
||||
coord.location = node.location - mathutils.Vector((200, 0))
|
||||
if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD":
|
||||
blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"])
|
||||
elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE":
|
||||
blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"])
|
||||
else:
|
||||
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
blender_material.BIMStyleProperties.active_style_type = "External"
|
||||
else:
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self):
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
@@ -1690,6 +1569,8 @@ class IfcImporter:
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
collection = self.collections[element.GlobalId]
|
||||
collection.BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = collection
|
||||
collection.name = obj.name
|
||||
return collection.objects.link(obj)
|
||||
elif getattr(element, "Decomposes", None):
|
||||
@@ -1704,7 +1585,10 @@ class IfcImporter:
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
return self.collections[element.GlobalId].objects.link(obj)
|
||||
collection = self.collections[element.GlobalId]
|
||||
collection.BIMCollectionProperties.obj = obj
|
||||
obj.BIMObjectProperties.collection = collection
|
||||
return collection.objects.link(obj)
|
||||
elif element.is_a("IfcTypeObject"):
|
||||
return self.type_collection.objects.link(obj)
|
||||
elif element.is_a("IfcStructuralMember"):
|
||||
@@ -1731,14 +1615,8 @@ class IfcImporter:
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
|
||||
def is_curve_annotation(self, element):
|
||||
return element.ObjectType in [
|
||||
"DIMENSION",
|
||||
"EQUAL_DIMENSION",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT_LEADER",
|
||||
]
|
||||
object_type = element.ObjectType
|
||||
return object_type in ANNOTATION_TYPES_DATA and ANNOTATION_TYPES_DATA[object_type][3] == "curve"
|
||||
|
||||
def get_drawing_group(self, element):
|
||||
for rel in element.HasAssignments or []:
|
||||
@@ -1746,7 +1624,10 @@ class IfcImporter:
|
||||
return rel.RelatingGroup
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
if isinstance(element, ifcopenshell.sqlite_entity):
|
||||
result = self.geometry_cache["shapes"][element.id()]["matrix"]
|
||||
else:
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
result[0][3] *= self.unit_scale
|
||||
result[1][3] *= self.unit_scale
|
||||
result[2][3] *= self.unit_scale
|
||||
@@ -1903,6 +1784,7 @@ class IfcImporter:
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry.edges
|
||||
@@ -1979,6 +1861,7 @@ class IfcImportSettings:
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
self.merge_mode = None
|
||||
self.should_merge_materials_by_colour = False
|
||||
self.should_load_geometry = True
|
||||
self.should_use_native_meshes = False
|
||||
self.should_clean_mesh = True
|
||||
self.should_cache = True
|
||||
@@ -2008,6 +1891,7 @@ class IfcImportSettings:
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.merge_mode = props.merge_mode
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_load_geometry = props.should_load_geometry
|
||||
settings.should_use_native_meshes = props.should_use_native_meshes
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.should_cache = props.should_cache
|
||||
|
||||
@@ -40,16 +40,31 @@ class BIM_OT_assign_object(bpy.types.Operator, Operator):
|
||||
bl_label = "Assign Object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
|
||||
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
|
||||
)
|
||||
relating_obj = None
|
||||
if self.relating_object:
|
||||
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
|
||||
elif context.active_object:
|
||||
relating_obj = context.active_object
|
||||
if not relating_obj:
|
||||
return
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj == relating_obj:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
result = core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
if not result:
|
||||
self.report({"ERROR"}, f" Cannot aggregate {obj.name} to {relating_obj.name}")
|
||||
|
||||
|
||||
class BIM_OT_unassign_object(bpy.types.Operator, Operator):
|
||||
@@ -148,10 +163,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
def create_aggregate(self, context, ifc_class):
|
||||
aggregate_collection = bpy.data.collections.new(f"{ifc_class}/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
|
||||
return aggregate
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ class BIM_PT_aggregate(Panel):
|
||||
if props.relating_object:
|
||||
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
|
||||
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
|
||||
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -60,6 +60,8 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
new.data_type = "string"
|
||||
new.ifc_class = data["type"]
|
||||
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]"
|
||||
|
||||
blenderbim.bim.helper.import_attributes2(
|
||||
tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes, callback=callback
|
||||
|
||||
@@ -57,6 +57,7 @@ classes = (
|
||||
operator.SelectBcfBimSnippetReference,
|
||||
operator.SelectBcfDocumentReference,
|
||||
operator.SelectBcfHeaderFile,
|
||||
operator.UnloadBcfProject,
|
||||
operator.ViewBcfTopic,
|
||||
prop.BcfReferenceLink,
|
||||
prop.BcfLabel,
|
||||
|
||||
@@ -58,7 +58,6 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
if self.filepath:
|
||||
bcfstore.BcfStore.bcfxml = bcf.bcfxml.load(self.filepath)
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
@@ -72,6 +71,17 @@ class LoadBcfProject(bpy.types.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class UnloadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_bcf_project"
|
||||
bl_label = "Unload BCF Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bcfstore.BcfStore.set(None)
|
||||
context.scene.BCFProperties.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfTopics(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_topics"
|
||||
bl_label = "Load BCF Topics"
|
||||
|
||||
@@ -39,14 +39,16 @@ class BIM_PT_bcf(Panel):
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
|
||||
if not props.is_loaded:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.new_bcf_project", text="New Project")
|
||||
row.operator("bim.load_bcf_project", text="Load Project")
|
||||
return
|
||||
|
||||
row.operator("bim.save_bcf_project", text="Save Project")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_bcf_project", icon="EXPORT", text="Save Project")
|
||||
row.operator("bim.unload_bcf_project", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "name")
|
||||
|
||||
@@ -20,28 +20,37 @@ import bpy
|
||||
from . import ui, operator, prop
|
||||
|
||||
classes = (
|
||||
operator.AddBoundary,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.DisableEditingBoundaryGeometry,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.EditBoundaryGeometry,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.EnableEditingBoundaryGeometry,
|
||||
operator.HideBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.LoadProjectSpaceBoundaries,
|
||||
operator.LoadSpaceBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.SelectRelatedElementBoundaries,
|
||||
operator.SelectProjectBoundaries,
|
||||
operator.SelectRelatedElementBoundaries,
|
||||
operator.SelectRelatedElementTypeBoundaries,
|
||||
operator.SelectSpaceBoundaries,
|
||||
operator.ShowBoundaries,
|
||||
operator.UpdateBoundaryGeometry,
|
||||
ui.BIM_PT_Boundary,
|
||||
ui.BIM_PT_SceneBoundaries,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
prop.BIMBoundaryProperties,
|
||||
prop.BIMObjectBoundaryProperties,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
bpy.types.Scene.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBoundaryProperties
|
||||
del bpy.types.Object.bim_boundary_properties
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import gpu
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
|
||||
class BoundaryDecorator:
|
||||
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("3D_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("3D_UNIFORM_COLOR")
|
||||
|
||||
selected_vertices = []
|
||||
selected_edges = []
|
||||
selected_tris = []
|
||||
unselected_vertices = []
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
for boundary in context.scene.BIMBoundaryProperties.boundaries:
|
||||
obj = boundary.obj
|
||||
if not obj or not obj.data: # A boundary may not have data if it has no connection geometry
|
||||
continue
|
||||
|
||||
if obj.mode == "EDIT":
|
||||
continue # A profile decorator or something else is used here.
|
||||
else:
|
||||
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])
|
||||
|
||||
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)
|
||||
@@ -18,18 +18,28 @@
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import mathutils
|
||||
import logging
|
||||
import shapely
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from math import pi, inf
|
||||
from mathutils import Vector, Matrix
|
||||
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
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
space_collection = bpy.data.collections.get(blender_space.name, blender_space.users_collection[0])
|
||||
space_collection = blender_space.BIMObjectProperties.collection
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
@@ -38,6 +48,20 @@ def get_boundaries_collection(blender_space):
|
||||
return boundaries_collection
|
||||
|
||||
|
||||
def disable_editing_boundary_geometry(context):
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
old_mesh = obj.data
|
||||
loader = Loader()
|
||||
obj.data = loader.create_mesh(element)
|
||||
tool.Geometry.delete_data(old_mesh)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self):
|
||||
self.ifc_file = None
|
||||
@@ -376,3 +400,357 @@ class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_boundary_geometry"
|
||||
bl_label = "Enable Editing Boundary Geometry"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if element.ConnectionGeometry.is_a("IfcConnectionSurfaceGeometry"):
|
||||
surface = element.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
tool.Model.import_surface(surface, obj)
|
||||
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_boundary_geometry(context))
|
||||
if not bpy.app.background:
|
||||
bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_boundary_geometry"
|
||||
bl_label = "Edit Boundary Geometry"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
ProfileDecorator.uninstall()
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if element.ConnectionGeometry.is_a("IfcConnectionSurfaceGeometry"):
|
||||
surface = tool.Model.export_surface(obj)
|
||||
|
||||
if not surface:
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="INVALID PROFILE")
|
||||
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
ProfileDecorator.install(
|
||||
context, exit_edit_mode_callback=lambda: disable_editing_boundary_geometry(context)
|
||||
)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return
|
||||
|
||||
old_surface = element.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
for inverse in tool.Ifc.get().get_inverse(old_surface):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_surface, surface)
|
||||
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_surface)
|
||||
|
||||
old_mesh = obj.data
|
||||
loader = Loader()
|
||||
obj.data = loader.create_mesh(element)
|
||||
tool.Geometry.delete_data(old_mesh)
|
||||
|
||||
|
||||
class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_boundary_geometry"
|
||||
bl_label = "Disable Editing Boundary Geometry"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
return disable_editing_boundary_geometry(context)
|
||||
|
||||
|
||||
class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.show_boundaries"
|
||||
bl_label = "Show Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMBoundaryProperties
|
||||
loader = Loader()
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not getattr(element, "BoundedBy", None):
|
||||
continue
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
for rel in element.BoundedBy or []:
|
||||
boundary_obj = loader.load_boundary(rel, obj)
|
||||
tool.Boundary.decorate_boundary(boundary_obj)
|
||||
BoundaryDecorator.install(bpy.context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.hide_boundaries"
|
||||
bl_label = "Hide Boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
to_delete = set()
|
||||
spaces = set()
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if element.is_a("IfcSpace"):
|
||||
spaces.add(element)
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
spaces.add(element.RelatingSpace)
|
||||
|
||||
for element in spaces:
|
||||
for boundary in element.BoundedBy or []:
|
||||
boundary_obj = tool.Ifc.get_object(boundary)
|
||||
if boundary_obj:
|
||||
to_delete.add(boundary_obj)
|
||||
for boundary_obj in to_delete:
|
||||
tool.Ifc.unlink(obj=boundary_obj)
|
||||
bpy.data.objects.remove(boundary_obj)
|
||||
context.scene.BIMBoundaryProperties.boundaries.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boundary"
|
||||
bl_label = "Add Boundary"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
relating_space = None
|
||||
related_building_element = None
|
||||
relating_space_obj = None
|
||||
related_building_element_obj = None
|
||||
|
||||
objs = context.selected_objects
|
||||
if len(objs) == 2:
|
||||
# The user may select two objects, a space and its related building element
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if element.is_a("IfcSpace"):
|
||||
relating_space = element
|
||||
relating_space_obj = obj
|
||||
else:
|
||||
related_building_element = element
|
||||
related_building_element_obj = obj
|
||||
elif len(objs) == 1:
|
||||
# Optionally the user may select just the space, and the building element shall be auto-detected
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="NO ACTIVE STOREY")
|
||||
|
||||
element = tool.Ifc.get_entity(objs[0])
|
||||
if element.is_a("IfcSpace"):
|
||||
relating_space = element
|
||||
relating_space_obj = objs[0]
|
||||
|
||||
target = bpy.context.scene.cursor.location
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
if not collection_obj:
|
||||
bpy.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")
|
||||
return
|
||||
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(spatial_element):
|
||||
if not (subelement.is_a("IfcWall") or subelement.is_a("IfcSlab")):
|
||||
continue
|
||||
obj = tool.Ifc.get_object(subelement)
|
||||
if obj:
|
||||
raycast = obj.closest_point_on_mesh(obj.matrix_world.inverted() @ target, distance=0.1)
|
||||
if raycast[0]:
|
||||
related_building_element = subelement
|
||||
related_building_element_obj = obj
|
||||
break
|
||||
|
||||
if not relating_space or not related_building_element:
|
||||
return
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(relating_space_obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi * 2 / 360, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
target_distance = inf
|
||||
target_face = None
|
||||
for face in bm.faces:
|
||||
centroid = relating_space_obj.matrix_world @ face.calc_center_median()
|
||||
raycast = related_building_element_obj.closest_point_on_mesh(
|
||||
related_building_element_obj.matrix_world.inverted() @ centroid, distance=1
|
||||
)
|
||||
if raycast[0]:
|
||||
distance = (related_building_element_obj.matrix_world @ raycast[1] - centroid).length
|
||||
if distance < target_distance:
|
||||
target_face = face
|
||||
target_distance = distance
|
||||
|
||||
if not target_face:
|
||||
return
|
||||
|
||||
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
||||
|
||||
# Is this right? Or should I use loop?
|
||||
target_face_verts = [v.co.copy() for v in target_face.verts]
|
||||
target_face_matrix = self.get_face_matrix(*[v.copy() for v in target_face_verts[0:3]])
|
||||
target_face_matrix_i = target_face_matrix.inverted()
|
||||
|
||||
target_face_polygon = shapely.Polygon([tuple((target_face_matrix_i @ v).xy) for v in target_face_verts])
|
||||
|
||||
related_building_element_polygon = self.get_flattened_polygon(
|
||||
related_building_element, relating_space_obj, target_face_matrix_i
|
||||
)
|
||||
|
||||
gross_boundary_polygon = target_face_polygon.intersection(related_building_element_polygon)
|
||||
|
||||
if type(gross_boundary_polygon) == shapely.GeometryCollection:
|
||||
for geom in gross_boundary_polygon.geoms:
|
||||
if type(geom) == shapely.Polygon:
|
||||
gross_boundary_polygon = geom
|
||||
break
|
||||
|
||||
# The gross boundary polygon may not be a true gross boundary since it
|
||||
# may have openings already removed, such as in IFC4 Reference View. So
|
||||
# we cheat by using the exterior boundary to mean "gross". Later, we
|
||||
# can use this to check whether or not the opening is relevant to our
|
||||
# space.
|
||||
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
|
||||
|
||||
net_boundary_polygon = shapely.Polygon(gross_boundary_polygon)
|
||||
|
||||
inner_boundaries = []
|
||||
|
||||
for rel in getattr(related_building_element, "HasOpenings", []):
|
||||
opening = rel.RelatedOpeningElement
|
||||
filling = None
|
||||
if opening.HasFillings:
|
||||
filling = opening.HasFillings[0].RelatedBuildingElement
|
||||
|
||||
opening_polygon = self.get_flattened_polygon(opening, relating_space_obj, target_face_matrix_i)
|
||||
|
||||
net_boundary_polygon = net_boundary_polygon.difference(opening_polygon)
|
||||
# Only openings that are projected onto our exterior boundary are relevant.
|
||||
if opening_polygon.intersection(exterior_boundary_polygon).area == 0:
|
||||
continue
|
||||
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
|
||||
boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
||||
boundary.RelatingSpace = relating_space
|
||||
boundary.RelatedBuildingElement = filling or related_building_element
|
||||
boundary.ConnectionGeometry = connection_geometry
|
||||
boundary.PhysicalOrVirtualBoundary = "PHYSICAL" if filling else "VIRTUAL"
|
||||
boundary.InternalOrExternalBoundary = "INTERNAL"
|
||||
|
||||
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
|
||||
boundary.ParentBoundary = parent_boundary
|
||||
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(net_boundary_polygon, target_face_matrix)
|
||||
parent_boundary.RelatingSpace = relating_space
|
||||
parent_boundary.RelatedBuildingElement = related_building_element
|
||||
parent_boundary.ConnectionGeometry = connection_geometry
|
||||
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||
parent_boundary.InternalOrExternalBoundary = "INTERNAL"
|
||||
|
||||
bpy.ops.bim.show_boundaries()
|
||||
obj = tool.Ifc.get_object(parent_boundary)
|
||||
obj.select_set(True)
|
||||
|
||||
def get_face_matrix(self, p1, p2, p3):
|
||||
edge1 = p2 - p1
|
||||
edge2 = p3 - p1
|
||||
normal = edge1.cross(edge2)
|
||||
z_axis = normal.normalized()
|
||||
x_axis = p2 - p1
|
||||
x_axis.normalize()
|
||||
y_axis = z_axis.cross(x_axis)
|
||||
|
||||
mat = Matrix()
|
||||
mat.col[0][:3] = x_axis
|
||||
mat.col[1][:3] = y_axis
|
||||
mat.col[2][:3] = z_axis
|
||||
mat.col[3][:3] = p1
|
||||
|
||||
return mat
|
||||
|
||||
def get_flattened_polygon(self, element, relating_space_obj, target_face_matrix_i):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
space_matrix_i = relating_space_obj.matrix_world.inverted()
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if not element.is_a("IfcOpeningElement"):
|
||||
settings.set(settings.DISABLE_OPENING_SUBTRACTIONS, True)
|
||||
settings.set(settings.STRICT_TOLERANCE, True)
|
||||
# geometry = ifcopenshell.geom.create_shape(settings, body)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
m = shape.transformation.matrix.data
|
||||
mat = Matrix(([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1]))
|
||||
verts = [space_matrix_i @ mat @ Vector(v) for v in ifcopenshell.util.shape.get_vertices(shape.geometry)]
|
||||
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
|
||||
polygons = []
|
||||
for face in faces:
|
||||
polygon = shapely.Polygon([tuple((target_face_matrix_i @ verts[vi]).xy) for vi in face])
|
||||
polygons.append(polygon)
|
||||
return shapely.ops.unary_union(polygons)
|
||||
|
||||
def create_connection_geometry_from_polygon(self, polygon, target_face_matrix):
|
||||
surface = self.export_surface(polygon, target_face_matrix)
|
||||
return tool.Ifc.get().createIfcConnectionSurfaceGeometry(surface)
|
||||
|
||||
def export_surface(self, polygon, target_face_matrix):
|
||||
x_axis = target_face_matrix.col[0][:3]
|
||||
z_axis = target_face_matrix.col[2][:3]
|
||||
p1 = target_face_matrix.col[3][:3]
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
tool.Model.unit_scale = self.unit_scale
|
||||
|
||||
surface = tool.Ifc.get().createIfcCurveBoundedPlane()
|
||||
surface.BasisSurface = tool.Ifc.get().createIfcPlane(tool.Ifc.get().createIfcAxis2Placement3D(
|
||||
tool.Ifc.get().createIfcCartesianPoint([o / self.unit_scale for o in p1]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in z_axis]),
|
||||
tool.Ifc.get().createIfcDirection([float(o) for o in x_axis]),
|
||||
))
|
||||
|
||||
if tool.Ifc.get().schema != "IFC2X3":
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
|
||||
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
|
||||
outer_boundary = tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False)
|
||||
|
||||
inner_boundaries = []
|
||||
for interior in polygon.interiors:
|
||||
points = [tool.Model.convert_si_to_unit(list(co)) for co in interior.coords]
|
||||
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
|
||||
inner_boundaries.append(tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False))
|
||||
else:
|
||||
pass # TODO
|
||||
|
||||
surface.OuterBoundary = outer_boundary
|
||||
surface.InnerBoundaries = inner_boundaries
|
||||
|
||||
return surface
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from blenderbim.bim.prop import ObjProperty
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
@@ -52,9 +53,13 @@ def element_filter(self, object):
|
||||
return False
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
class BIMObjectBoundaryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
||||
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
boundaries: bpy.props.CollectionProperty(type=ObjProperty)
|
||||
|
||||
@@ -33,6 +33,9 @@ classes = (
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
operator.UndoBrick,
|
||||
operator.RedoBrick,
|
||||
operator.SerializeBrick,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
|
||||
@@ -110,7 +110,8 @@ class BrickschemaData:
|
||||
return []
|
||||
results = []
|
||||
for alias, uri in BrickStore.graph.namespaces():
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
# results.append((uri, f"{alias}: {uri}", ""))
|
||||
results.append((uri, f"{alias}", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -189,3 +189,24 @@ class RemoveBrick(bpy.types.Operator, Operator):
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
class UndoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.undo_brick"
|
||||
bl_label = "Undo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.undo_brick(tool.Brick)
|
||||
|
||||
class RedoBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.redo_brick"
|
||||
bl_label = "Redo Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.redo_brick(tool.Brick)
|
||||
|
||||
class SerializeBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.serialize_brick"
|
||||
bl_label = "Serialize Brick"
|
||||
|
||||
def _execute(self, context):
|
||||
core.serialize_brick(tool.Brick)
|
||||
@@ -58,6 +58,13 @@ class BIM_PT_brickschema(Panel):
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.undo_brick", icon="LOOP_BACK")
|
||||
row.operator("bim.redo_brick", icon="LOOP_FORWARDS")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.serialize_brick")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
|
||||
@@ -33,7 +33,6 @@ class CadTool(WorkSpaceTool):
|
||||
bl_description = "Gives you CAD authoring related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.cad")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.cad_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
("bim.cad_hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}),
|
||||
@@ -46,7 +45,7 @@ class CadTool(WorkSpaceTool):
|
||||
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, tool):
|
||||
def draw_settings(context, layout, workspace_tool):
|
||||
obj = context.active_object
|
||||
if not obj or not obj.data:
|
||||
return
|
||||
@@ -54,14 +53,19 @@ class CadTool(WorkSpaceTool):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
row.operator("bim.edit_extrusion_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if element.is_a("IfcProfileDef"):
|
||||
row.operator("bim.edit_arbitrary_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
row.operator("bim.edit_boundary_geometry", text="Save Profile")
|
||||
row.operator("bim.disable_editing_boundary_geometry", text="", icon="CANCEL")
|
||||
else:
|
||||
row.operator("bim.edit_extrusion_profile", text="Save Profile")
|
||||
row.operator("bim.align_view_to_profile", text="", icon="AXIS_FRONT")
|
||||
row.operator("bim.disable_editing_extrusion_profile", text="", icon="CANCEL")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
@@ -255,28 +259,30 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
if tool.Ifc.get_entity(bpy.context.active_object):
|
||||
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
if element.is_a("IfcProfileDef"):
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
bpy.ops.bim.edit_boundary_geometry()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
elif (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
|
||||
else:
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
if self.is_profile():
|
||||
|
||||
@@ -23,7 +23,7 @@ import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from mathutils import Matrix
|
||||
from mathutils import Matrix, Vector
|
||||
from math import radians
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -222,6 +222,7 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
@@ -230,12 +231,28 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
|
||||
if context.scene.BIMClashProperties.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint, mat):
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
context.scene.collection.objects.link(camera)
|
||||
camera.matrix_world = Matrix(mat)
|
||||
|
||||
bcf_camera = viewpoint.visualization_info.perspective_camera
|
||||
p = bcf_camera.camera_view_point
|
||||
z = bcf_camera.camera_direction
|
||||
z = Vector([z.x, z.y, z.z]) * -1
|
||||
y = bcf_camera.camera_up_vector
|
||||
y = Vector([y.x, y.y, y.z])
|
||||
x = y.cross(z)
|
||||
|
||||
mat = Matrix([
|
||||
[x[0], y[0], z[0], p.x],
|
||||
[x[1], y[1], z[1], p.y],
|
||||
[x[2], y[2], z[2], p.z],
|
||||
[0, 0, 0, 0],
|
||||
])
|
||||
|
||||
camera.matrix_world = mat
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
@@ -246,7 +263,8 @@ class ExecuteIfcClash(bpy.types.Operator):
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
return context.scene.render.filepath
|
||||
with open(context.scene.render.filepath, "rb") as f:
|
||||
return ("snapshot.png", f.read())
|
||||
|
||||
clasher.get_viewpoint_snapshot = get_viewpoint_snapshot
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ classes = (
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.CopyCostItem,
|
||||
operator.AddCurrency,
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
|
||||
@@ -46,9 +46,16 @@ class CostSchedulesData:
|
||||
"cost_quantities": cls.cost_quantities(),
|
||||
"cost_values": cls.cost_values(),
|
||||
"quantity_types": cls.quantity_types(),
|
||||
"currency": cls.currency(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def currency(cls):
|
||||
unit = tool.Unit.get_project_currency_unit()
|
||||
if unit:
|
||||
return {"id": unit.id(), "name": unit.Currency}
|
||||
|
||||
@classmethod
|
||||
def total_cost_schedules(cls):
|
||||
return len(tool.Ifc.get().by_type("IfcCostSchedule"))
|
||||
@@ -62,6 +69,7 @@ class CostSchedulesData:
|
||||
{
|
||||
"id": schedule.id(),
|
||||
"name": schedule.Name or "Unnamed",
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -70,6 +78,7 @@ class CostSchedulesData:
|
||||
{
|
||||
"id": schedule.id(),
|
||||
"name": schedule.Name or "Unnamed",
|
||||
"predefined_type": ifcopenshell.util.element.get_predefined_type(schedule),
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -107,6 +116,7 @@ class CostSchedulesData:
|
||||
data["UnitBasisUnitSymbol"] = None
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
@@ -119,10 +129,15 @@ class CostSchedulesData:
|
||||
cost_value_data = cls._cost_values[cost_value.id()]
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
if data["UnitBasisValueComponent"]:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] / data["UnitBasisValueComponent"] * data["TotalAppliedValue"]
|
||||
has_unit_basis = True
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalCostQuantity"] * data["TotalAppliedValue"]
|
||||
if data["TotalCostQuantity"] is not None:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"]
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item, data):
|
||||
|
||||
@@ -97,10 +97,9 @@ class AddSummaryCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a summary cost item"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Ifc.get().by_id(self.cost_schedule))
|
||||
core.add_summary_cost_item(tool.Ifc, tool.Cost, cost_schedule=tool.Cost.get_active_cost_schedule())
|
||||
|
||||
|
||||
class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -114,6 +113,16 @@ class AddCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.add_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
|
||||
|
||||
class CopyCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_cost_item"
|
||||
bl_label = "Copy Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Copy a cost item"
|
||||
|
||||
def _execute(self, context):
|
||||
core.copy_cost_item(tool.Ifc, tool.Cost)
|
||||
|
||||
|
||||
class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.expand_cost_item"
|
||||
bl_label = "Expand Cost Item"
|
||||
@@ -154,7 +163,7 @@ class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
|
||||
class EnableEditingCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -214,8 +223,9 @@ class UnassignCostItemType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.unassign_cost_item_type(
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
self.cost_item,
|
||||
products=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
|
||||
tool.Spatial,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
product_types=[tool.Ifc.get().by_id(self.related_object)] if self.related_object else [],
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -491,7 +501,7 @@ class AddCostColumn(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.add_cost_column(tool.Cost, self.name)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -619,15 +629,21 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
bl_label = "Export Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Export a cost schedule to a CSV, XSLX OR ODS file"
|
||||
cost_schedule: bpy.props.IntProperty()
|
||||
format: bpy.props.EnumProperty("Format", items=(("CSV", "CSV", ""), ("XLSX", "XLSX", ""), ("ODS", "ODS", "")))
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
core.export_cost_schedules(tool.Cost, format=self.format)
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
wm.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Choose a format")
|
||||
@@ -667,7 +683,9 @@ class LoadProductCostItems(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
core.load_product_cost_items(tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id))
|
||||
core.load_product_cost_items(
|
||||
tool.Cost, product=tool.Ifc.get().by_id(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -724,3 +742,12 @@ class ChangeParentCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if isinstance(r, str):
|
||||
self.report({"WARNING"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddCurrency(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_currency"
|
||||
bl_label = "Add Currency"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_currency(tool.Ifc, tool.Cost)
|
||||
|
||||
@@ -115,6 +115,27 @@ def get_schedule_predefined_types(self, context):
|
||||
return CostSchedulesData.data["predefined_types"]
|
||||
|
||||
|
||||
def get_currencies(self, context):
|
||||
return [
|
||||
("USD", "USD", "Dollar"),
|
||||
("EUR", "EUR", "Euro"),
|
||||
("GBP", "GBP", "Pound"),
|
||||
("AUD", "AUD", "Australian Dollar"),
|
||||
("CAD", "CAD", "Canadian Dollar"),
|
||||
("CHF", "CHF", "Swiss Franc"),
|
||||
("CNY", "CNY", "Chinese Yuan"),
|
||||
("HKD", "HKD", "Hong Kong Dollar"),
|
||||
("JPY", "JPY", "Japanese Yen"),
|
||||
("NZD", "NZD", "New Zealand Dollar"),
|
||||
("SEK", "SEK", "Swedish Krona"),
|
||||
("KRW", "KRW", "South Korean Won"),
|
||||
("SGD", "SGD", "Singapore Dollar"),
|
||||
("NOK", "NOK", "Norwegian Krone"),
|
||||
("MAD", "MAD", "Moroccan Dirham"),
|
||||
("CUSTOM", "Custom currency", "Custom"),
|
||||
]
|
||||
|
||||
|
||||
class CostItem(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=update_cost_item_name)
|
||||
identification: StringProperty(name="Identification", update=update_cost_item_identification)
|
||||
@@ -128,6 +149,8 @@ class CostItemQuantity(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
total_quantity: FloatProperty(name="Total Quantity")
|
||||
unit_symbol: StringProperty(name="Unit Symbol")
|
||||
total_cost_quantity: FloatProperty(name="Total Quantity")
|
||||
|
||||
|
||||
class CostItemType(PropertyGroup):
|
||||
@@ -137,7 +160,7 @@ class CostItemType(PropertyGroup):
|
||||
|
||||
def update_cost_item_parent(self, context):
|
||||
cost_item = tool.Cost.get_highlighted_cost_item()
|
||||
tool.Cost.toggle_cost_item_parent(cost_item=cost_item)
|
||||
tool.Cost.toggle_cost_item_parent_change(cost_item=cost_item)
|
||||
|
||||
|
||||
def update_active_cost_item_elements(self, context):
|
||||
@@ -188,6 +211,7 @@ class BIMCostProperties(PropertyGroup):
|
||||
cost_value_formula: StringProperty(name="Cost Value Formula")
|
||||
cost_column: StringProperty(name="Cost Column")
|
||||
should_show_column_ui: BoolProperty(name="Should Show Column UI", default=False)
|
||||
should_show_currency_ui: BoolProperty(name="Should Show Currency UI", default=False)
|
||||
columns: CollectionProperty(name="Columns", type=StrProperty)
|
||||
active_column_index: IntProperty(name="Active Column Index")
|
||||
cost_item_products: CollectionProperty(name="Cost Item Products", type=CostItemQuantity)
|
||||
@@ -204,7 +228,7 @@ class BIMCostProperties(PropertyGroup):
|
||||
cost_item_rates: CollectionProperty(name="Cost Item Rates", type=CostItem)
|
||||
active_cost_item_rate_index: IntProperty(name="Active Cost Rate Index")
|
||||
contracted_cost_item_rates: StringProperty(name="Contracted Cost Item Rates", default="[]")
|
||||
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItem)
|
||||
product_cost_items: CollectionProperty(name="Product Cost Items", type=CostItemQuantity)
|
||||
active_product_cost_item_index: IntProperty(name="Active Product Cost Item Index")
|
||||
enable_reorder: BoolProperty(name="Enable Reorder", default=False)
|
||||
show_nested_elements: BoolProperty(name="Show Nested Tasks", default=False, update=update_active_cost_item_elements)
|
||||
@@ -213,3 +237,8 @@ class BIMCostProperties(PropertyGroup):
|
||||
name="Show Nested Tasks", default=False, update=update_active_cost_item_resources
|
||||
)
|
||||
change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent)
|
||||
show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False)
|
||||
currency: EnumProperty(items=get_currencies, name="Currencies")
|
||||
custom_currency: StringProperty(
|
||||
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
|
||||
)
|
||||
|
||||
@@ -43,49 +43,68 @@ class BIM_PT_cost_schedules(Panel):
|
||||
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add new")
|
||||
if not self.props.active_cost_schedule_id:
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules Found found.", icon="COMMUNITY")
|
||||
row = self.layout.row()
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=cost_schedule["name"], icon="LINENUMBERS_ON")
|
||||
|
||||
if self.props.active_cost_schedule_id and self.props.active_cost_schedule_id == cost_schedule["id"]:
|
||||
op = row.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
||||
row.label(
|
||||
text="Currently editing: {}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]),
|
||||
icon="LINENUMBERS_ON",
|
||||
)
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=True)
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "LEFT"
|
||||
row1.label(text="Schedule tools")
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "RIGHT"
|
||||
row1.operator("bim.export_cost_schedules", text="Export", icon="EXPORT").cost_schedule = cost_schedule["id"]
|
||||
row2 = col.row(align=True)
|
||||
row2.alignment = "RIGHT"
|
||||
op = row2.operator("bim.select_cost_schedule_products", icon="RESTRICT_SELECT_OFF", text="Assigned")
|
||||
op.cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
|
||||
row2.operator("bim.select_unassigned_products", icon="RESTRICT_SELECT_OFF", text="Unassigned")
|
||||
|
||||
row.prop(self.props, "should_show_column_ui", text="", icon="SHORTDISPLAY")
|
||||
col = grid.column()
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "LEFT"
|
||||
row1.label(text="Settings")
|
||||
row1 = col.row(align=True)
|
||||
row1.alignment = "RIGHT"
|
||||
row1.prop(self.props, "should_show_currency_ui", text="Project Currency", icon="COPY_ID")
|
||||
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
row.operator("bim.add_summary_cost_item", text="", icon="ADD").cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
|
||||
elif self.props.active_cost_schedule_id:
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL")
|
||||
else:
|
||||
row.label(
|
||||
text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON"
|
||||
)
|
||||
row.operator("bim.enable_editing_cost_items", text="", icon="OUTLINER").cost_schedule = cost_schedule["id"]
|
||||
row.operator(
|
||||
"bim.enable_editing_cost_schedule_attributes", text="", icon="GREASEPENCIL"
|
||||
).cost_schedule = cost_schedule["id"]
|
||||
row.operator("bim.remove_cost_schedule", text="", icon="X").cost_schedule = cost_schedule["id"]
|
||||
|
||||
if self.props.active_cost_schedule_id == cost_schedule["id"]:
|
||||
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
|
||||
self.draw_editable_cost_schedule_ui()
|
||||
elif self.props.is_editing == "COST_ITEMS":
|
||||
if self.props.should_show_column_ui:
|
||||
self.draw_column_ui()
|
||||
if self.props.should_show_currency_ui:
|
||||
self.draw_currency_ui()
|
||||
self.draw_editable_cost_item_ui()
|
||||
|
||||
def draw_column_ui(self):
|
||||
@@ -94,6 +113,20 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row.operator("bim.add_cost_column", text="", icon="ADD").name = self.props.cost_column
|
||||
self.layout.template_list("BIM_UL_cost_columns", "", self.props, "columns", self.props, "active_column_index")
|
||||
|
||||
def draw_currency_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
if CostSchedulesData.data["currency"]:
|
||||
text = "Currency used: {}".format(CostSchedulesData.data["currency"]["name"])
|
||||
row.label(text=text)
|
||||
row.operator("bim.remove_unit", text="", icon="X").unit = CostSchedulesData.data["currency"]["id"]
|
||||
else:
|
||||
row.label(text="No currency set")
|
||||
row.prop(self.props, "currency", text="")
|
||||
if self.props.currency == "CUSTOM":
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "custom_currency", text="")
|
||||
row.operator("bim.add_currency", text="", icon="ADD")
|
||||
|
||||
def draw_editable_cost_schedule_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.cost_schedule_attributes, self.layout)
|
||||
|
||||
@@ -101,30 +134,39 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
ifc_definition_id = None
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Cost Item Tools")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
|
||||
row.operator("bim.expand_all_tasks", text="Expand All")
|
||||
row.operator("bim.contract_all_tasks", text="Contract All")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
|
||||
ifc_definition_id = self.props.cost_items[self.props.active_cost_item_index].ifc_definition_id
|
||||
if ifc_definition_id:
|
||||
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
|
||||
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
if not CostSchedulesData.data["is_editing_rates"]:
|
||||
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
|
||||
row.prop(self.props, "show_cost_item_operators", text="Edit", icon="DOWNARROW_HLT")
|
||||
row.operator("bim.add_cost_item", text="Add", icon="ADD").cost_item = ifc_definition_id
|
||||
row.operator("bim.copy_cost_item", text="Copy", icon="ADD")
|
||||
row.operator("bim.remove_cost_item", text="Delete", icon="X").cost_item = ifc_definition_id
|
||||
if self.props.show_cost_item_operators:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "change_cost_item_parent", text="", icon="LINKED")
|
||||
row.prop(self.props, "enable_reorder", text="", icon="SORTALPHA")
|
||||
if not CostSchedulesData.data["is_editing_rates"]:
|
||||
op = row.operator("bim.enable_editing_cost_item_quantities", text="", icon="PROPERTIES")
|
||||
op.cost_item = ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
op = row.operator("bim.enable_editing_cost_item_values", text="", icon="DISC")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
row.operator("bim.add_cost_item", text="", icon="ADD").cost_item = ifc_definition_id
|
||||
|
||||
if self.props.active_cost_item_id == ifc_definition_id:
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
|
||||
op.cost_item = ifc_definition_id
|
||||
|
||||
row.operator("bim.remove_cost_item", text="", icon="X").cost_item = ifc_definition_id
|
||||
|
||||
if self.props.active_cost_item_id == ifc_definition_id:
|
||||
if self.props.cost_item_editing_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_cost_item", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_cost_item", text="", icon="CANCEL")
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
|
||||
op.cost_item = ifc_definition_id
|
||||
self.layout.template_list(
|
||||
"BIM_UL_cost_items",
|
||||
"",
|
||||
@@ -579,7 +621,10 @@ class BIM_UL_cost_items_trait:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
layout.label(text="{0:.2f}".format(cost_item["TotalCost"]))
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_quantity_column(self, layout, cost_item):
|
||||
if CostSchedulesData.data["is_editing_rates"]:
|
||||
@@ -588,13 +633,18 @@ class BIM_UL_cost_items_trait:
|
||||
self.draw_total_quantity_column(layout, cost_item)
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
text = "{0:.2f}".format(cost_item["TotalAppliedValue"])
|
||||
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
||||
text += " / {}".format(round(cost_item["UnitBasisValueComponent"], 2))
|
||||
layout.label(text=text)
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
if cost_item["UnitBasisValueComponent"] not in [None, 1]:
|
||||
text = "{} / {}".format(text, round(cost_item["UnitBasisValueComponent"], 2))
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(text, currency["name"]) if currency else text
|
||||
layout.label(text=text)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "?" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
|
||||
def draw_order_operator(self, row, ifc_definition_id, cost_item):
|
||||
if cost_item["NestingIndex"] is not None:
|
||||
@@ -608,7 +658,18 @@ class BIM_UL_cost_items_trait:
|
||||
op.new_index = cost_item["NestingIndex"] - 1
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '?'}")
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
|
||||
# else:
|
||||
# if cost_item["TotalCostQuantity"] == 0:
|
||||
# layout.label(text="-")
|
||||
# else:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
|
||||
|
||||
|
||||
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
|
||||
@@ -658,14 +719,14 @@ class BIM_UL_cost_item_quantities(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
props = context.scene.BIMCostProperties
|
||||
cost_item = props.cost_items[props.active_cost_item_index]
|
||||
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.select_product", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.product = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.label(text=item.name)
|
||||
row.label(text="{0:.2f}".format(item.total_quantity))
|
||||
formatted_quantity = "{0:.2f}".format(item.total_quantity)
|
||||
row.label(text="{}{}".format(formatted_quantity, item.unit_symbol))
|
||||
op = row.operator("bim.unassign_cost_item_quantity", text="", icon="X")
|
||||
op.cost_item = cost_item.ifc_definition_id
|
||||
op.related_object = item.ifc_definition_id
|
||||
@@ -677,8 +738,11 @@ class BIM_UL_product_cost_items(UIList):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.highlight_product_cost_item", text="", icon="STYLUS_PRESSURE")
|
||||
op.cost_item = item.ifc_definition_id
|
||||
row.split(factor=0.8)
|
||||
row.split(factor=0.5)
|
||||
row.label(text=item.name)
|
||||
qty = "{0:.2f}".format(item.total_quantity)
|
||||
formatted_total_quantity = "{0:.2f}".format(item.total_cost_quantity)
|
||||
row.label(text="{}/{} {}".format(qty, formatted_total_quantity, item.unit_symbol))
|
||||
|
||||
|
||||
class BIM_PT_Costing_Tools(Panel):
|
||||
@@ -691,9 +755,7 @@ class BIM_PT_Costing_Tools(Panel):
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
row.operator(
|
||||
"bim.load_product_cost_items", icon="FILE_REFRESH"
|
||||
)
|
||||
row.operator("bim.load_product_cost_items", icon="FILE_REFRESH")
|
||||
row = self.layout.row()
|
||||
row.template_list(
|
||||
"BIM_UL_product_cost_items",
|
||||
|
||||
@@ -31,6 +31,7 @@ from blenderbim.bim.handler import purge_module_data
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_csv_attribute"
|
||||
bl_label = "Add CSV Attribute"
|
||||
bl_description = "Add a new IFC Attribute to the CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -52,6 +53,7 @@ class RemoveCsvAttribute(bpy.types.Operator):
|
||||
class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_all_csv_attributes"
|
||||
bl_label = "Remove all CSV Attributes"
|
||||
bl_description = "Remove all IFC Attributes from the CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
@@ -61,20 +63,25 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Attributes"
|
||||
bl_label = "Import CSV Template"
|
||||
bl_description = "Import a json template for CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = context.scene.CsvProperties.csv_attributes
|
||||
csv_attributes.clear()
|
||||
csv_props = context.scene.CsvProperties
|
||||
csv_json = json.load(open(self.filepath))
|
||||
i = 0
|
||||
for attribute in csv_json:
|
||||
csv_attributes.add()
|
||||
csv_attributes[i].name = attribute
|
||||
i += 1
|
||||
|
||||
expression = csv_json.get("expression", "")
|
||||
if expression:
|
||||
csv_props.ifc_selector = expression
|
||||
|
||||
attributes = csv_json.get("attributes", [])
|
||||
if attributes:
|
||||
csv_props.csv_attributes.clear()
|
||||
for attribute in attributes:
|
||||
csv_props.csv_attributes.add().name = attribute
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -85,19 +92,30 @@ class ImportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Attributes"
|
||||
bl_label = "Export CSV Template"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save a json template for CSV export"
|
||||
filename_ext = ".json"
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = []
|
||||
with open(self.filepath, "w") as outfile:
|
||||
for attribute in context.scene.CsvProperties.csv_attributes:
|
||||
csv_attributes.append(attribute.name)
|
||||
csv_props = context.scene.CsvProperties
|
||||
|
||||
json.dump(csv_attributes, outfile)
|
||||
csv_template = {}
|
||||
expression = csv_props.ifc_selector
|
||||
if expression:
|
||||
csv_template["expression"] = expression
|
||||
|
||||
csv_attributes = []
|
||||
for attribute in csv_props.csv_attributes:
|
||||
attribute_name = attribute.name
|
||||
csv_attributes.append(attribute_name)
|
||||
if csv_attributes:
|
||||
csv_template["attributes"] = csv_attributes
|
||||
|
||||
with open(self.filepath, "w") as outfile:
|
||||
json.dump(csv_template, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -110,12 +128,14 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to CSV"
|
||||
filename_ext = ".csv"
|
||||
bl_label = "Export IFC"
|
||||
#filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".csv")
|
||||
props = context.scene.CsvProperties
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, f".{props.format}")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
@@ -124,7 +144,7 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
import ifccsv
|
||||
|
||||
props = context.scene.CsvProperties
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".csv")
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, f".{props.format}")
|
||||
if props.should_load_from_memory:
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
@@ -132,14 +152,9 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
ifc_csv.attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.selector = selector
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results)
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -164,12 +179,8 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
ifc_csv.output = self.filepath
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
ifc_csv.delimiter = props.csv_custom_delimiter
|
||||
else:
|
||||
ifc_csv.delimiter = props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file)
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
|
||||
@@ -45,5 +45,14 @@ class CsvProperties(PropertyGroup):
|
||||
name="IFC CSV Delimiter",
|
||||
default=",",
|
||||
)
|
||||
format: EnumProperty(
|
||||
items=[
|
||||
("csv", "csv", ""),
|
||||
("xlsx", "xlsx", ""),
|
||||
("ods", "ods", ""),
|
||||
],
|
||||
name="Output format",
|
||||
default="csv",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -49,37 +49,32 @@ class BIM_PT_ifccsv(Panel):
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.add_csv_attribute")
|
||||
c = split.column()
|
||||
c.operator("bim.import_csv_attributes", icon="IMPORT", text="Load Template")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_csv_attribute", icon="ADD")
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT")
|
||||
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
if props.csv_attributes:
|
||||
row = layout.row()
|
||||
row.label()
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="Create Template")
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
if props.format == 'csv':
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
split = row.split(factor=0.5)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -30,6 +30,7 @@ classes = (
|
||||
operator.AddDrawingToSheet,
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddReferenceToSheet,
|
||||
operator.AddSheet,
|
||||
operator.AddTextLiteral,
|
||||
operator.BuildSchedule,
|
||||
@@ -61,16 +62,23 @@ classes = (
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RemoveSchedule,
|
||||
operator.ReloadDrawingStyles,
|
||||
operator.RemoveSheet,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.SelectAllDrawings,
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SaveDrawingStylesData,
|
||||
operator.SelectAssignedProduct,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.LoadReferences,
|
||||
operator.DisableEditingReferences,
|
||||
operator.AddReference,
|
||||
operator.RemoveReference,
|
||||
operator.OpenReference,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
prop.Document,
|
||||
prop.DrawingStyle,
|
||||
prop.Sheet,
|
||||
prop.DocProperties,
|
||||
@@ -85,6 +93,7 @@ classes = (
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
ui.BIM_PT_references,
|
||||
ui.BIM_PT_product_assignments,
|
||||
ui.BIM_PT_text,
|
||||
ui.BIM_UL_drawinglist,
|
||||
@@ -100,7 +109,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=False, group=True)
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Scene.BIMAnnotationProperties = bpy.props.PointerProperty(type=prop.BIMAnnotationProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
import bmesh
|
||||
|
||||
|
||||
class Annotator:
|
||||
@@ -44,7 +45,7 @@ class Annotator:
|
||||
font.name = "OpenGost Type B TT"
|
||||
obj.data.font = font
|
||||
obj.data.BIMTextProperties.font_size = "2.5"
|
||||
collection = bpy.context.scene.camera.users_collection[0]
|
||||
collection = bpy.context.scene.camera.BIMObjectProperties.collection
|
||||
collection.objects.link(obj)
|
||||
Annotator.resize_text(obj)
|
||||
return obj
|
||||
@@ -52,9 +53,10 @@ class Annotator:
|
||||
@staticmethod
|
||||
def resize_text(text_obj):
|
||||
camera = None
|
||||
for obj in text_obj.users_collection[0].objects:
|
||||
if isinstance(obj.data, bpy.types.Camera):
|
||||
camera = obj
|
||||
group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj))
|
||||
for element in tool.Drawing.get_drawing_elements(group):
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
camera = tool.Ifc.get_object(element)
|
||||
break
|
||||
if not camera:
|
||||
return
|
||||
@@ -62,12 +64,7 @@ class Annotator:
|
||||
font_size = 1.6 / 1000
|
||||
font_size *= float(text_obj.data.BIMTextProperties.font_size)
|
||||
|
||||
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
font_size /= float(numerator) / float(denominator)
|
||||
font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
|
||||
|
||||
text_obj.data.size = font_size
|
||||
|
||||
@@ -94,7 +91,7 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj):
|
||||
def add_plane_to_annotation(obj, remove_face=False):
|
||||
# default order = bot left, top left, bot right, top right
|
||||
# therefore we redefine the order
|
||||
face_verts = [0, 2, 3, 1]
|
||||
@@ -104,7 +101,9 @@ class Annotator:
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
new_verts = [bm.verts.new(v) for v in verts_local]
|
||||
|
||||
bm.faces.new([new_verts[i] for i in face_verts])
|
||||
face = bm.faces.new([new_verts[i] for i in face_verts])
|
||||
if remove_face:
|
||||
bmesh.ops.delete(bm, geom=[face], context="FACES_ONLY")
|
||||
tool.Blender.apply_bmesh(obj.data, bm, obj)
|
||||
return obj
|
||||
|
||||
@@ -114,7 +113,7 @@ class Annotator:
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world.translation = co1
|
||||
collection = camera.users_collection[0]
|
||||
collection = camera.BIMObjectProperties.collection
|
||||
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
@@ -27,11 +27,12 @@ from pathlib import Path
|
||||
def refresh():
|
||||
ProductAssignmentsData.is_loaded = False
|
||||
SheetsData.is_loaded = False
|
||||
SchedulesData.is_loaded = False
|
||||
DocumentsData.is_loaded = False
|
||||
DrawingsData.is_loaded = False
|
||||
AnnotationData.is_loaded = False
|
||||
DecoratorData.data = {}
|
||||
DecoratorData.cut_cache = {}
|
||||
DecoratorData.layerset_cache = {}
|
||||
|
||||
|
||||
class ProductAssignmentsData:
|
||||
@@ -102,6 +103,7 @@ class DrawingsData:
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_drawings": cls.total_drawings(),
|
||||
"location_hint": cls.location_hint(),
|
||||
"active_drawing_pset_data": cls.active_drawing_pset_data(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -123,14 +125,28 @@ class DrawingsData:
|
||||
return results
|
||||
return [(h.upper(), h, "") for h in ["North", "South", "East", "West"]]
|
||||
|
||||
@classmethod
|
||||
def active_drawing_pset_data(cls):
|
||||
ifc_file = tool.Ifc.get()
|
||||
drawing_id = bpy.context.scene.DocProperties.active_drawing_id
|
||||
if drawing_id == 0:
|
||||
return {}
|
||||
drawing = ifc_file.by_id(bpy.context.scene.DocProperties.active_drawing_id)
|
||||
return ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing")
|
||||
|
||||
class SchedulesData:
|
||||
|
||||
class DocumentsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"has_saved_ifc": cls.has_saved_ifc(), "total_schedules": cls.total_schedules()}
|
||||
documents = cls.count_documents()
|
||||
cls.data = {
|
||||
"has_saved_ifc": cls.has_saved_ifc(),
|
||||
"total_schedules": documents["SCHEDULE"],
|
||||
"total_references": documents["REFERENCE"],
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -138,8 +154,16 @@ class SchedulesData:
|
||||
return os.path.isfile(tool.Ifc.get_path())
|
||||
|
||||
@classmethod
|
||||
def total_schedules(cls):
|
||||
return len([d for d in tool.Ifc.get().by_type("IfcDocumentInformation") if d.Scope == "SCHEDULE"])
|
||||
def count_documents(cls):
|
||||
documents = {
|
||||
"SCHEDULE": 0,
|
||||
"REFERENCE": 0,
|
||||
}
|
||||
for d in tool.Ifc.get().by_type("IfcDocumentInformation"):
|
||||
scope = d.Scope
|
||||
documents[scope] = documents.get(scope, 0) + 1
|
||||
|
||||
return documents
|
||||
|
||||
|
||||
FONT_SIZES = {
|
||||
@@ -155,6 +179,7 @@ class DecoratorData:
|
||||
# stores 1 type of data per object
|
||||
data = {}
|
||||
cut_cache = {}
|
||||
layerset_cache = {}
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "BATTING"
|
||||
@classmethod
|
||||
@@ -226,18 +251,21 @@ class DecoratorData:
|
||||
|
||||
props = obj.BIMTextProperties
|
||||
# getting font size
|
||||
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {}
|
||||
# use `regular` as default
|
||||
if classes:
|
||||
classes_split = classes.split()
|
||||
# prioritize smaller font sizes just like in svg
|
||||
font_size_type = next(
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
else:
|
||||
font_size_type = "regular"
|
||||
|
||||
# get font size
|
||||
classes = pset_data.get("Classes", None) or "regular"
|
||||
classes_split = classes.split()
|
||||
# prioritize smaller font sizes just like in svg
|
||||
font_size_type = next(
|
||||
(font_size_type for font_size_type in FONT_SIZES if font_size_type in classes_split), "regular"
|
||||
)
|
||||
font_size = FONT_SIZES[font_size_type]
|
||||
|
||||
# get symbol
|
||||
symbol = pset_data.get("Symbol", None)
|
||||
|
||||
# other attributes
|
||||
props_literals = props.literals
|
||||
props_literals_n = len(props.literals)
|
||||
@@ -255,7 +283,7 @@ class DecoratorData:
|
||||
|
||||
literals_data.append(literal_data)
|
||||
|
||||
text_data = {"Literals": literals_data, "FontSize": font_size}
|
||||
text_data = {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol}
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
@@ -279,11 +307,15 @@ class DecoratorData:
|
||||
pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") or {}
|
||||
show_description_only = pset_data.get("ShowDescriptionOnly", False)
|
||||
suppress_zero_inches = pset_data.get("SuppressZeroInches", False)
|
||||
text_prefix = pset_data.get("TextPrefix", "")
|
||||
text_suffix = pset_data.get("TextSuffix", "")
|
||||
|
||||
dimension_data = {
|
||||
"dimension_style": dimension_style,
|
||||
"show_description_only": show_description_only,
|
||||
"suppress_zero_inches": suppress_zero_inches,
|
||||
"text_prefix": text_prefix,
|
||||
"text_suffix": text_suffix,
|
||||
}
|
||||
cls.data[obj.name] = dimension_data
|
||||
return dimension_data
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ from bpy import types
|
||||
from mathutils import Vector
|
||||
from mathutils import geometry
|
||||
from bpy_extras import view3d_utils
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader, BaseLinesShader
|
||||
from blenderbim.bim.module.drawing.shaders import DotsGizmoShader, ExtrusionGuidesShader
|
||||
from ifcopenshell.util.unit import si_conversions
|
||||
|
||||
|
||||
@@ -251,11 +251,12 @@ X3DISC = (
|
||||
class CustomGizmo:
|
||||
# FIXME: highliting/selection doesnt work
|
||||
def draw_very_custom_shape(self, ctx, custom_shape, select_id=None):
|
||||
# similar to draw_custom_shape
|
||||
shape, batch, shader = custom_shape
|
||||
# create shader and batch
|
||||
shader_wrapper, batch = custom_shape
|
||||
shader = shader_wrapper.get_shader()
|
||||
|
||||
# setup params
|
||||
shader.bind()
|
||||
|
||||
if select_id is not None:
|
||||
gpu.select.load_id(select_id)
|
||||
else:
|
||||
@@ -264,13 +265,17 @@ class CustomGizmo:
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float("color", color)
|
||||
shape.glenable()
|
||||
shader_wrapper.glenable()
|
||||
shader_wrapper.uniform_region(ctx)
|
||||
|
||||
shape.uniform_region(ctx)
|
||||
# shader.uniform_float('modelMatrix', self.matrix_world)
|
||||
# using `with` block to make sure matrix multiplication
|
||||
# won't affect other shaders
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.multiply_matrix(self.matrix_world)
|
||||
batch.draw()
|
||||
# using matrix_world seems to be unaffected by matrix_offset
|
||||
# therefore we use basis @ offset
|
||||
matrix = self.matrix_basis @ self.matrix_offset
|
||||
gpu.matrix.multiply_matrix(matrix)
|
||||
batch.draw(shader)
|
||||
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
@@ -360,6 +365,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
self.draw_custom_shape(self.custom_shape, select_id=select_id)
|
||||
|
||||
|
||||
# TODO: dead code?
|
||||
class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
"""Single dot viewport-aligned"""
|
||||
|
||||
@@ -389,10 +395,6 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape, select_id=select_id)
|
||||
|
||||
# doesn't get called
|
||||
# def test_select(self, ctx, location):
|
||||
# pass
|
||||
|
||||
|
||||
class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
"""Extrusion guides
|
||||
@@ -407,19 +409,25 @@ class ExtrusionGuidesGizmo(CustomGizmo, types.Gizmo):
|
||||
__slots__ = ("scale_value", "custom_shape")
|
||||
|
||||
def setup(self):
|
||||
shader = ExtrusionGuidesShader()
|
||||
self.custom_shape = shader, shader.batch(pos=((0, 0, 0), (0, 0, 1))), shader.prog
|
||||
self.use_draw_scale = False
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value("depth") / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
"""setup `custom_shape`"""
|
||||
shader_wrapper = ExtrusionGuidesShader()
|
||||
verts = [Vector((0, 0, 0)), Vector((0, 0, 1))]
|
||||
verts, edges = shader_wrapper.process_geometry(verts)
|
||||
self.custom_shape = shader_wrapper, shader_wrapper.batch(
|
||||
pos=verts,
|
||||
indices=edges,
|
||||
)
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
self.draw_very_custom_shape(ctx, self.custom_shape)
|
||||
|
||||
def refresh(self):
|
||||
depth = self.target_get_value("depth") / self.scale_value
|
||||
self.matrix_offset.col[2][2] = depth # z-scaled
|
||||
|
||||
|
||||
# TODO: dead code?
|
||||
class DimensionLabelGizmo(types.Gizmo):
|
||||
"""Text label for a dimension"""
|
||||
|
||||
@@ -486,6 +494,7 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
theme = ctx.preferences.themes[0].user_interface
|
||||
scale_value = self.get_scale_value(ctx.scene.unit_settings.system, ctx.scene.unit_settings.length_unit)
|
||||
|
||||
# setup handle
|
||||
gz = self.handle = self.gizmos.new("BIM_GT_uglydot_3d")
|
||||
gz.matrix_basis = basis
|
||||
gz.scale_basis = 0.1
|
||||
@@ -496,6 +505,7 @@ class ExtrusionWidget(types.GizmoGroup):
|
||||
gz.target_set_prop("offset", prop, "value")
|
||||
gz.scale_value = scale_value
|
||||
|
||||
# setup guides
|
||||
gz = self.guides = self.gizmos.new("BIM_GT_extrusion_guides")
|
||||
gz.matrix_basis = basis
|
||||
gz.color = gz.color_highlight = tuple(theme.gizmo_secondary)
|
||||
|
||||
@@ -154,13 +154,14 @@ def format_distance(
|
||||
# Separate ft and inches
|
||||
# Unless Inches are the specified Length Unit
|
||||
if unit_length != "INCHES":
|
||||
feet = math.floor(decInches / inPerFoot)
|
||||
feet = int(decInches / inPerFoot) # remove decimal
|
||||
decInches -= feet * inPerFoot
|
||||
else:
|
||||
feet = 0
|
||||
|
||||
# Separate Fractional Inches
|
||||
inches = math.floor(decInches)
|
||||
decInches = abs(decInches) # ignore the sign for inches
|
||||
inches = math.floor(decInches) # remove decimal
|
||||
if inches != 0:
|
||||
frac = round(base * (decInches - inches))
|
||||
else:
|
||||
@@ -268,7 +269,7 @@ def get_active_drawing(scene):
|
||||
props = scene.DocProperties
|
||||
try:
|
||||
camera = tool.Ifc.get_object(tool.Ifc.get().by_id(props.active_drawing_id))
|
||||
return camera.users_collection[0], camera
|
||||
return camera.BIMObjectProperties.collection, camera
|
||||
except:
|
||||
return None, None
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from blenderbim.bim.module.drawing.decoration import CutDecorator
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
import blenderbim.bim.export_ifc
|
||||
from lxml import etree
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Color
|
||||
from timeit import default_timer as timer
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty, Literal, RASTER_STYLE_PROPERTIES_EXCLUDE
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from pathlib import Path
|
||||
|
||||
@@ -150,7 +150,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
"Creates/refreshes a .svg drawing based on currently active camera.\n\n"
|
||||
+ "SHIFT+CLICK to print all selected drawings"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -158,12 +158,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
# printing 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.print_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.print_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -177,7 +174,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
for drawing_id in drawings_to_print:
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id, camera_view_point=False)
|
||||
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
@@ -189,7 +186,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(drawing_id).Name
|
||||
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||
self.get_scale()
|
||||
self.get_scale(context)
|
||||
if self.cprops.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
|
||||
@@ -220,7 +217,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id)
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_camera_dimensions(self):
|
||||
@@ -288,7 +285,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
bpy.ops.render.render(write_still=True)
|
||||
else:
|
||||
previous_visibility = {}
|
||||
for obj in self.camera.users_collection[0].objects:
|
||||
for obj in self.camera.BIMObjectProperties.collection.objects:
|
||||
if bpy.context.view_layer.objects.get(obj.name):
|
||||
previous_visibility[obj.name] = obj.hide_get()
|
||||
obj.hide_set(True)
|
||||
@@ -481,11 +478,27 @@ class CreateDrawing(bpy.types.Operator):
|
||||
tree.add_element(elem)
|
||||
elements -= processed
|
||||
|
||||
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):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
with profile("Finalizing"):
|
||||
self.serialiser.finalize()
|
||||
results = self.svg_buffer.get_value()
|
||||
|
||||
root = etree.fromstring(results)
|
||||
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
if not group:
|
||||
with open(svg_path, "wb") as svg:
|
||||
svg.write(etree.tostring(root))
|
||||
|
||||
return svg_path
|
||||
|
||||
self.move_projection_to_bottom(root)
|
||||
self.merge_linework_and_add_metadata(root)
|
||||
|
||||
@@ -620,7 +633,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
# Loop over the cell paths
|
||||
for pi, p in enumerate(g2.getElementsByTagName("path")):
|
||||
|
||||
d = p.getAttribute("d")
|
||||
coords = [co[1:].split(",") for co in d.split() if co[1:]]
|
||||
polygon = shapely.Polygon(coords)
|
||||
@@ -829,11 +841,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
|
||||
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
|
||||
continue
|
||||
|
||||
keys = []
|
||||
for query in join_criteria:
|
||||
key = ifcopenshell.util.selector.get_element_value(element, query)
|
||||
@@ -865,7 +881,11 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if is_closed_polygon:
|
||||
el.getparent().remove(el)
|
||||
|
||||
merged_polygons = shapely.ops.unary_union(polygons)
|
||||
try:
|
||||
merged_polygons = shapely.ops.unary_union(polygons)
|
||||
except:
|
||||
print("Warning. Portions of the merge failed. Please report a bug!", polygons)
|
||||
merged_polygons = polygons
|
||||
|
||||
if type(merged_polygons) == shapely.MultiPolygon:
|
||||
merged_polygons = merged_polygons.geoms
|
||||
@@ -923,18 +943,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def get_scale(self):
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
def get_scale(self, context):
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(self.camera)
|
||||
self.human_scale = diagram_scale["HumanScale"]
|
||||
self.scale = tool.Drawing.get_scale_ratio(diagram_scale["Scale"])
|
||||
|
||||
if self.camera.data.BIMCameraProperties.is_nts:
|
||||
if ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "IsNTS"):
|
||||
self.human_scale = "NTS"
|
||||
|
||||
numerator, denominator = fraction.split("/")
|
||||
self.scale = float(numerator) / float(denominator)
|
||||
|
||||
def is_landscape(self, render):
|
||||
return render.resolution_x > render.resolution_y
|
||||
|
||||
@@ -1004,12 +1020,14 @@ class AddSheet(bpy.types.Operator, Operator):
|
||||
|
||||
class OpenSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_sheet"
|
||||
bl_label = "Open Sheet"
|
||||
bl_label = "Open Sheet Layout"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.DocProperties
|
||||
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.update_sheet_drawing_sizes(sheet)
|
||||
core.open_sheet(tool.Drawing, sheet=sheet)
|
||||
|
||||
|
||||
@@ -1026,7 +1044,7 @@ class AddDrawingToSheet(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_drawing = props.drawings[props.active_drawing_index]
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
|
||||
drawing_reference = tool.Drawing.get_drawing_document(drawing)
|
||||
|
||||
@@ -1085,6 +1103,7 @@ class RemoveDrawingFromSheet(bpy.types.Operator, Operator):
|
||||
class CreateSheets(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.create_sheets"
|
||||
bl_label = "Create Sheets"
|
||||
bl_description = "Build a sheet from the sheet layout"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
@@ -1121,7 +1140,7 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
has_sheet_reference = True
|
||||
elif reference.Description == "RASTER":
|
||||
if reference.Location in raster_references:
|
||||
del raster_references[reference.Location]
|
||||
raster_references.remove(reference.Location)
|
||||
else:
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
|
||||
@@ -1169,16 +1188,13 @@ 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)
|
||||
select_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# deselect 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.select_all = False
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.select_all = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1197,16 +1213,13 @@ class OpenDrawing(bpy.types.Operator):
|
||||
+ 'or using "svg_command" from the BlenderBIM preferences (if provided).\n\n'
|
||||
+ "SHIFT+CLICK to open all selected drawings"
|
||||
)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False)
|
||||
open_all: bpy.props.BoolProperty(name="Open All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# opening 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
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.open_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -1245,9 +1258,13 @@ class ActivateModel(bpy.types.Operator):
|
||||
bl_description = "Activates the model view"
|
||||
|
||||
def execute(self, context):
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
dprops.active_drawing_id = 0
|
||||
|
||||
CutDecorator.uninstall()
|
||||
|
||||
bpy.ops.object.hide_view_clear()
|
||||
if not bpy.app.background:
|
||||
bpy.ops.object.hide_view_clear()
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -1280,17 +1297,40 @@ class ActivateDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.activate_drawing"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Activates the selected drawing view"
|
||||
bl_description = "Activates the selected drawing view.\n\n" + "ALT+CLICK to keep the viewport position"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
camera_view_point: bpy.props.BoolProperty(name="Camera View Point", default=True, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# keep the viewport position on alt+click
|
||||
# make sure to use SKIP_SAVE on property, otherwise it might get stuck
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.camera_view_point = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.drawing)
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
|
||||
if not self.camera_view_point:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
bpy.context.scene.DocProperties.active_drawing_id = self.drawing
|
||||
|
||||
if not self.camera_view_point:
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
# reset DrawingsData to reload_drawing_styles work correctly
|
||||
DrawingsData.is_loaded = False
|
||||
dprops.drawing_styles.clear()
|
||||
if ifcopenshell.util.element.get_pset(drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=tool.Ifc.get().by_id(self.drawing))
|
||||
CutDecorator.install(context)
|
||||
tool.Drawing.show_decorations()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1318,7 +1358,7 @@ class ResizeText(bpy.types.Operator):
|
||||
# TODO: check undo redo
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.scene.camera.users_collection[0].objects:
|
||||
for obj in context.scene.camera.BIMObjectProperties.collection.objects:
|
||||
if isinstance(obj.data, bpy.types.TextCurve):
|
||||
annotation.Annotator.resize_text(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -1331,16 +1371,13 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
bl_description = "Remove currently selected drawing.\n\n" + "SHIFT+CLICK to remove all selected drawings"
|
||||
|
||||
drawing: bpy.props.IntProperty()
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False)
|
||||
remove_all: bpy.props.BoolProperty(name="Remove All", default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
# removing all selected 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.remove_all = True
|
||||
else:
|
||||
# can't rely on default value since the line above
|
||||
# will set the value to `True` for all future operator calls
|
||||
self.remove_all = False
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -1351,24 +1388,88 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
else:
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
print("Removing drawings: {}".format([d for d in drawings]))
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
|
||||
for drawing in drawings:
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
class AddDrawingStyle(bpy.types.Operator):
|
||||
class ReloadDrawingStyles(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_drawing_styles"
|
||||
bl_label = "Reload Drawing Styles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload drawing styles for the active camera"
|
||||
|
||||
def execute(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
camera_props = context.active_object.data.BIMCameraProperties
|
||||
|
||||
# added this part as a temporary fallback
|
||||
# TODO: should remove it a bit later when projects get more accommodated
|
||||
# with saving shadingstyles to ifc and separate json file
|
||||
if "ShadingStyles" not in drawing_pset_data:
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = ifc_file.by_id(drawing_pset_data["id"])
|
||||
edit_properties = {
|
||||
"ShadingStyles": (
|
||||
shading_styles_path := tool.Drawing.get_default_drawing_resource_path("ShadingStyles")
|
||||
),
|
||||
"CurrentShadingStyle": tool.Drawing.get_default_shading_style(),
|
||||
}
|
||||
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties=edit_properties)
|
||||
tool.Drawing.setup_shading_styles_path(shading_styles_path)
|
||||
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
|
||||
if "ShadingStyles" not in drawing_pset_data:
|
||||
self.report({"ERROR"}, "Could not find shading styles path in EPset_Drawing.ShadingStyles.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
rel_path = drawing_pset_data["ShadingStyles"]
|
||||
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
|
||||
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"}
|
||||
|
||||
with open(json_path, "r") as fi:
|
||||
shading_styles_json = json.load(fi)
|
||||
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
drawing_styles.clear()
|
||||
styles = [style for style in shading_styles_json]
|
||||
for style_name in styles:
|
||||
style_data = shading_styles_json[style_name]
|
||||
drawing_style = drawing_styles.add()
|
||||
drawing_style["name"] = style_name # setting as attribute to avoid triggering setter
|
||||
drawing_style.render_type = style_data["render_type"]
|
||||
drawing_style.raster_style = json.dumps(style_data["raster_style"])
|
||||
|
||||
if current_style is not None:
|
||||
camera_props.active_drawing_style_index = styles.index(current_style)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_drawing_style"
|
||||
bl_label = "Add Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.DocProperties.drawing_styles.add()
|
||||
new.name = "New Drawing Style"
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
new = drawing_styles.add()
|
||||
# drawing style is saved to ifc on rename
|
||||
new.name = tool.Blender.ensure_unique_name("New Drawing Style", drawing_styles)
|
||||
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = len(drawing_styles) - 1
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDrawingStyle(bpy.types.Operator):
|
||||
class RemoveDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_drawing_style"
|
||||
bl_label = "Remove Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -1376,13 +1477,17 @@ class RemoveDrawingStyle(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.DocProperties.drawing_styles.remove(self.index)
|
||||
context.scene.camera.data.BIMCameraProperties.active_drawing_style_index = max(self.index - 1, 0)
|
||||
bpy.ops.bim.save_drawing_styles_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveDrawingStyle(bpy.types.Operator):
|
||||
class SaveDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.save_drawing_style"
|
||||
bl_label = "Save Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save current render setting to currently selected drawing style. Also resaves styles data to IFC"
|
||||
|
||||
index: bpy.props.StringProperty()
|
||||
# TODO: check undo redo
|
||||
|
||||
@@ -1390,19 +1495,46 @@ class SaveDrawingStyle(bpy.types.Operator):
|
||||
space = self.get_view_3d(context) # Do not remove. It is used later in eval
|
||||
scene = context.scene
|
||||
style = {}
|
||||
for prop in RasterStyleProperty:
|
||||
value = eval(prop.value)
|
||||
eval_namespace = {"context": context, "scene": scene, "space": space}
|
||||
|
||||
def add_prop_to_style(prop_path, context, scene, space):
|
||||
value = eval(prop_path)
|
||||
if not isinstance(value, str):
|
||||
try:
|
||||
value = tuple(value)
|
||||
except TypeError:
|
||||
pass
|
||||
style[prop.value] = value
|
||||
style[prop_path] = value
|
||||
|
||||
for prop in RasterStyleProperty:
|
||||
if prop.name.startswith("EVAL_PROP"):
|
||||
prop_path = prop.value
|
||||
add_prop_to_style(prop_path, **eval_namespace)
|
||||
else:
|
||||
props_source_path = prop.value
|
||||
props_source = eval(props_source_path)
|
||||
for prop_name in dir(props_source):
|
||||
if prop_name.startswith("__"):
|
||||
continue
|
||||
|
||||
prop_path = f"{props_source_path}.{prop_name}"
|
||||
prop_value = eval(prop_path)
|
||||
if (
|
||||
not isinstance(prop_value, (int, float, bool, str, Color, Vector))
|
||||
or props_source.is_property_readonly(prop_name)
|
||||
or prop_path in RASTER_STYLE_PROPERTIES_EXCLUDE
|
||||
):
|
||||
continue
|
||||
|
||||
add_prop_to_style(prop_path, **eval_namespace)
|
||||
|
||||
if self.index:
|
||||
index = int(self.index)
|
||||
else:
|
||||
index = context.active_object.data.BIMCameraProperties.active_drawing_style_index
|
||||
scene.DocProperties.drawing_styles[index].raster_style = json.dumps(style)
|
||||
|
||||
bpy.ops.bim.save_drawing_styles_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_view_3d(self, context):
|
||||
@@ -1415,19 +1547,84 @@ class SaveDrawingStyle(bpy.types.Operator):
|
||||
return space
|
||||
|
||||
|
||||
class ActivateDrawingStyle(bpy.types.Operator):
|
||||
class SaveDrawingStylesData(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.save_drawing_styles_data"
|
||||
bl_label = "Save Drawing Styles Data"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
skip_updating_current_style: bpy.props.BoolProperty(default=False)
|
||||
rename_style: bpy.props.BoolProperty(default=False)
|
||||
rename_style_from: bpy.props.StringProperty(default="")
|
||||
rename_style_to: bpy.props.StringProperty(default="")
|
||||
|
||||
def execute(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
drawing_styles = context.scene.DocProperties.drawing_styles
|
||||
|
||||
rel_path = drawing_pset_data["ShadingStyles"]
|
||||
current_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
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"}
|
||||
|
||||
styles_data = {}
|
||||
for style in drawing_styles:
|
||||
style_data = {"render_type": style.render_type, "raster_style": json.loads(style.raster_style)}
|
||||
styles_data[style.name] = style_data
|
||||
|
||||
with open(json_path, "w") as fo:
|
||||
json.dump(styles_data, fo, indent=4)
|
||||
|
||||
# TODO: currently it doesn't update current style for the other drawings
|
||||
# handling case when current style is not present in styles saved in ifc
|
||||
if not self.skip_updating_current_style and current_style not in styles_data and current_style is not None:
|
||||
# style was renamed
|
||||
if self.rename_style and current_style == self.rename_style_from:
|
||||
new_style_name = self.rename_style_to
|
||||
|
||||
# style was removed
|
||||
else:
|
||||
new_style_name = None
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
drawing = ifc_file.by_id(context.scene.DocProperties.active_drawing_id)
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": new_style_name}
|
||||
)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ActivateDrawingStyle(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.activate_drawing_style"
|
||||
bl_label = "Activate Drawing Style"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
if scene.camera.data.BIMCameraProperties.active_drawing_style_index < len(scene.DocProperties.drawing_styles):
|
||||
self.drawing_style = scene.DocProperties.drawing_styles[
|
||||
scene.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
]
|
||||
self.set_raster_style(context)
|
||||
self.set_query(context)
|
||||
ifc_file = tool.Ifc.get()
|
||||
active_drawing_style_index = scene.camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
|
||||
if active_drawing_style_index >= len(scene.DocProperties.drawing_styles):
|
||||
self.report({"ERROR"}, "Could not find active drawing style")
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.drawing_style = scene.DocProperties.drawing_styles[active_drawing_style_index]
|
||||
|
||||
self.set_raster_style(context)
|
||||
self.set_query(context)
|
||||
|
||||
drawing = ifc_file.by_id(scene.DocProperties.active_drawing_id)
|
||||
pset = tool.Pset.get_element_pset(drawing, "EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", ifc_file, pset=pset, properties={"CurrentShadingStyle": self.drawing_style.name}
|
||||
)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def set_raster_style(self, context):
|
||||
@@ -1512,10 +1709,14 @@ class AddSchedule(bpy.types.Operator, Operator):
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_schedule(
|
||||
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"SCHEDULE",
|
||||
uri=filepath,
|
||||
)
|
||||
|
||||
@@ -1531,7 +1732,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_schedule(tool.Ifc, tool.Drawing, schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
@@ -1566,7 +1767,7 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_schedule = props.schedules[props.active_schedule_index]
|
||||
active_sheet = props.sheets[props.active_sheet_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
schedule_location = tool.Drawing.get_path_with_ext(schedule.DocumentReferences[0].Location, "svg")
|
||||
@@ -1602,11 +1803,116 @@ class AddScheduleToSheet(bpy.types.Operator, Operator):
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_schedule(reference, schedule, sheet)
|
||||
sheet_builder.add_document(reference, schedule, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
|
||||
class AddReferenceToSheet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_reference_to_sheet"
|
||||
bl_label = "Add Reference To Sheet"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.DocProperties
|
||||
return props.references and props.sheets and context.scene.BIMProperties.data_dir
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
active_reference = props.references[props.active_reference_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
reference = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.DocumentReferences[0].Location, "svg")
|
||||
else:
|
||||
reference_location = tool.Drawing.get_path_with_ext(reference.HasDocumentReferences[0].Location, "svg")
|
||||
|
||||
sheet = tool.Ifc.get().by_id(active_sheet.ifc_definition_id)
|
||||
if not sheet.is_a("IfcDocumentInformation"):
|
||||
return
|
||||
|
||||
references = tool.Drawing.get_document_references(sheet)
|
||||
|
||||
has_reference = False
|
||||
for reference in references:
|
||||
if reference.Location == reference_location:
|
||||
has_reference = True
|
||||
break
|
||||
if has_reference:
|
||||
return
|
||||
|
||||
if not tool.Drawing.does_file_exist(tool.Ifc.resolve_uri(reference_location)):
|
||||
self.report({"ERROR"}, f"Cannot find reference svg by path {reference_location}.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
id_attr = "ItemReference" if tool.Ifc.get_schema() == "IFC2X3" else "Identification"
|
||||
attributes = {
|
||||
id_attr: str(len([r for r in references if r.Description in ("DRAWING", "REFERENCE")]) + 1),
|
||||
"Location": reference_location,
|
||||
"Description": "REFERENCE",
|
||||
}
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.data_dir = context.scene.BIMProperties.data_dir
|
||||
sheet_builder.add_document(reference, reference, sheet)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
|
||||
class AddReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_reference"
|
||||
bl_label = "Add Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
|
||||
def _execute(self, context):
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"REFERENCE",
|
||||
uri=filepath,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class RemoveReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_reference"
|
||||
bl_label = "Remove Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "REFERENCE", document=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
class OpenReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.open_reference"
|
||||
bl_label = "Open Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.open_reference(tool.Drawing, reference=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
# TODO: dead code - drawing style attributes are never used?
|
||||
class AddDrawingStyleAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.add_drawing_style_attribute"
|
||||
bl_label = "Add Drawing Style Attribute"
|
||||
@@ -1618,6 +1924,7 @@ class AddDrawingStyleAttribute(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# TODO: dead code - drawing style attributes are never used?
|
||||
class RemoveDrawingStyleAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_drawing_style_attribute"
|
||||
bl_label = "Remove Drawing Style Attribute"
|
||||
@@ -1930,6 +2237,24 @@ class DisableEditingSchedules(bpy.types.Operator, Operator):
|
||||
core.disable_editing_schedules(tool.Drawing)
|
||||
|
||||
|
||||
class LoadReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_references"
|
||||
bl_label = "Load References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_references(tool.Drawing)
|
||||
|
||||
|
||||
class DisableEditingReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_references"
|
||||
bl_label = "Disable Editing References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_references(tool.Drawing)
|
||||
|
||||
|
||||
class LoadDrawings(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_drawings"
|
||||
bl_label = "Load Drawings"
|
||||
|
||||
@@ -65,12 +65,27 @@ def get_location_hint(self, context):
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
if "|" not in scale:
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
.by_id(self.id_data.BIMMeshProperties.ifc_definition_id)
|
||||
.OfProductRepresentation[0]
|
||||
.ShapeOfProduct[0]
|
||||
)
|
||||
except:
|
||||
return
|
||||
human_scale, scale = scale.split("|")
|
||||
diagram_scale = tool.Drawing.get_diagram_scale(tool.Ifc.get_object(element))
|
||||
if not diagram_scale:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=diagram_scale)
|
||||
|
||||
|
||||
def update_is_nts(self, context):
|
||||
try:
|
||||
element = (
|
||||
tool.Ifc.get()
|
||||
@@ -85,9 +100,7 @@ def update_diagram_scale(self, context):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale, "HumanScale": human_scale}
|
||||
)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
@@ -156,13 +169,32 @@ def update_drawing_name(self, context):
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def update_schedule_name(self, context):
|
||||
schedule = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_schedule_name(tool.Ifc, tool.Drawing, schedule=schedule, name=self.name)
|
||||
def get_drawing_style_name(self):
|
||||
"""needed to make `set_drawing_style_name` work"""
|
||||
return self.get("name", "")
|
||||
|
||||
|
||||
def set_drawing_style_name(self, new_value):
|
||||
"""ensure the name is unique"""
|
||||
scene = bpy.context.scene
|
||||
drawing_styles = [s.name for s in scene.DocProperties.drawing_styles if s.name != self.name]
|
||||
new_value = tool.Blender.ensure_unique_name(new_value, drawing_styles)
|
||||
old_value = self.name
|
||||
self["name"] = new_value
|
||||
bpy.ops.bim.save_drawing_styles_data(rename_style=True, rename_style_from=old_value, rename_style_to=new_value)
|
||||
|
||||
|
||||
def update_document_name(self, context):
|
||||
document = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_document_name(tool.Ifc, tool.Drawing, document=document, name=self.name)
|
||||
|
||||
|
||||
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):
|
||||
bpy.ops.bim.reload_drawing_styles()
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
|
||||
|
||||
def update_has_linework(self, context):
|
||||
@@ -199,8 +231,11 @@ def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
collection = context.scene.camera.BIMObjectProperties.collection
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
continue
|
||||
tool.Drawing.update_text_value(obj)
|
||||
refresh_drawing_data()
|
||||
decoration.DecorationsHandler.install(context)
|
||||
@@ -220,9 +255,9 @@ class Drawing(PropertyGroup):
|
||||
is_selected: BoolProperty(name="Is Selected", default=True)
|
||||
|
||||
|
||||
class Schedule(PropertyGroup):
|
||||
class Document(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
name: StringProperty(name="Name", update=update_schedule_name)
|
||||
name: StringProperty(name="Name", update=update_document_name)
|
||||
identification: StringProperty(name="Identification")
|
||||
|
||||
|
||||
@@ -236,8 +271,8 @@ class Sheet(PropertyGroup):
|
||||
|
||||
|
||||
class DrawingStyle(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
raster_style: StringProperty(name="Raster Style")
|
||||
name: StringProperty(name="Name", get=get_drawing_style_name, set=set_drawing_style_name)
|
||||
raster_style: StringProperty(name="Raster Style", default="{}")
|
||||
render_type: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", ""),
|
||||
@@ -253,30 +288,20 @@ class DrawingStyle(PropertyGroup):
|
||||
|
||||
|
||||
class RasterStyleProperty(enum.Enum):
|
||||
WORLD_COLOR = "bpy.data.worlds[0].color"
|
||||
RENDER_ENGINE = "scene.render.engine"
|
||||
RENDER_TRANSPARENT = "scene.render.film_transparent"
|
||||
VIEW_TRANSFORM = "scene.view_settings.view_transform"
|
||||
SHADING_SHOW_OBJECT_OUTLINE = "scene.display.shading.show_object_outline"
|
||||
SHADING_SHOW_CAVITY = "scene.display.shading.show_cavity"
|
||||
SHADING_CAVITY_TYPE = "scene.display.shading.cavity_type"
|
||||
SHADING_CURVATURE_RIDGE_FACTOR = "scene.display.shading.curvature_ridge_factor"
|
||||
SHADING_CURVATURE_VALLEY_FACTOR = "scene.display.shading.curvature_valley_factor"
|
||||
SHADING_LIGHT = "scene.display.shading.light"
|
||||
SHADING_COLOR_TYPE = "scene.display.shading.color_type"
|
||||
SHADING_SINGLE_COLOR = "scene.display.shading.single_color"
|
||||
SHADING_SHOW_SHADOWS = "scene.display.shading.show_shadows"
|
||||
SHADING_SHADOW_INTENSITY = "scene.display.shading.shadow_intensity"
|
||||
DISPLAY_LIGHT_DIRECTION = "scene.display.light_direction"
|
||||
VIEW_USE_CURVE_MAPPING = "scene.view_settings.use_curve_mapping"
|
||||
OVERLAY_SHOW_WIREFRAMES = "space.overlay.show_wireframes"
|
||||
OVERLAY_WIREFRAME_THRESHOLD = "space.overlay.wireframe_threshold"
|
||||
OVERLAY_SHOW_FLOOR = "space.overlay.show_floor"
|
||||
OVERLAY_SHOW_AXIS_X = "space.overlay.show_axis_x"
|
||||
OVERLAY_SHOW_AXIS_Y = "space.overlay.show_axis_y"
|
||||
OVERLAY_SHOW_AXIS_Z = "space.overlay.show_axis_z"
|
||||
OVERLAY_SHOW_OBJECT_ORIGINS = "space.overlay.show_object_origins"
|
||||
OVERLAY_SHOW_RELATIONSHIP_LINES = "space.overlay.show_relationship_lines"
|
||||
# EVAL_PROP_ props will be evaluated explicitly
|
||||
EVAL_PROP_WORLD_COLOR = "bpy.data.worlds[0].color"
|
||||
|
||||
# those props attributes used as a source for shading style properties
|
||||
RENDER = "scene.render"
|
||||
VIEW_SETTINGS = "scene.view_settings"
|
||||
SHADING = "scene.display.shading"
|
||||
DISPLAY = "scene.display"
|
||||
OVERLAY = "space.overlay"
|
||||
|
||||
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = (
|
||||
"scene.render.filepath",
|
||||
)
|
||||
|
||||
|
||||
class DocProperties(PropertyGroup):
|
||||
@@ -286,6 +311,7 @@ class DocProperties(PropertyGroup):
|
||||
should_extract: BoolProperty(name="Should Extract", default=True)
|
||||
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
|
||||
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
|
||||
is_editing_references: BoolProperty(name="Is Editing References", default=False)
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
("PLAN_VIEW", "Plan", ""),
|
||||
@@ -303,8 +329,10 @@ class DocProperties(PropertyGroup):
|
||||
active_drawing_id: IntProperty(name="Active Drawing Id")
|
||||
active_drawing_index: IntProperty(name="Active Drawing Index")
|
||||
current_drawing_index: IntProperty(name="Current Drawing Index")
|
||||
schedules: CollectionProperty(name="Schedules", type=Schedule)
|
||||
schedules: CollectionProperty(name="Schedules", type=Document)
|
||||
active_schedule_index: IntProperty(name="Active Schedule Index")
|
||||
references: CollectionProperty(name="References", type=Document)
|
||||
active_reference_index: IntProperty(name="Active Reference Index")
|
||||
titleblock: EnumProperty(items=get_titleblocks, name="Titleblock", update=update_titleblocks)
|
||||
is_editing_sheets: BoolProperty(name="Is Editing Sheets", default=False)
|
||||
sheets: CollectionProperty(name="Sheets", type=Sheet)
|
||||
@@ -312,9 +340,6 @@ class DocProperties(PropertyGroup):
|
||||
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)
|
||||
decorations_colour: FloatVectorProperty(
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
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(
|
||||
@@ -327,6 +352,10 @@ class DocProperties(PropertyGroup):
|
||||
markers_path: StringProperty(default=os.path.join("drawings", "assets", "markers.svg"), name="Default Markers")
|
||||
symbols_path: StringProperty(default=os.path.join("drawings", "assets", "symbols.svg"), name="Default Symbols")
|
||||
patterns_path: StringProperty(default=os.path.join("drawings", "assets", "patterns.svg"), name="Default Patterns")
|
||||
shadingstyles_path: StringProperty(
|
||||
default=os.path.join("drawings", "assets", "shading_styles.json"), name="Default Shading Styles"
|
||||
)
|
||||
shadingstyle_default: StringProperty(default="Blender Default", name="Default Shading Style")
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
@@ -338,10 +367,11 @@ class BIMCameraProperties(PropertyGroup):
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
|
||||
custom_scale_numerator: bpy.props.StringProperty(default="1", update=update_diagram_scale)
|
||||
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
is_nts: BoolProperty(name="Is NTS")
|
||||
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
@@ -471,6 +501,7 @@ ANNOTATION_TYPES_DATA = {
|
||||
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
|
||||
"LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
|
||||
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
|
||||
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
|
||||
"FILL_AREA": ("Fill Area", "", "NODE_TEXTURE", "mesh"),
|
||||
"FALL": ("Fall", "", "SORT_ASC", "curve"),
|
||||
}
|
||||
@@ -494,6 +525,10 @@ def update_annotation_object_type(self, context):
|
||||
AnnotationData.is_loaded = False
|
||||
|
||||
|
||||
def update_sheet_data(self, context):
|
||||
SheetsData.is_loaded = False
|
||||
|
||||
|
||||
class BIMAnnotationProperties(PropertyGroup):
|
||||
object_type: bpy.props.EnumProperty(
|
||||
name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
|
||||
|
||||
@@ -16,12 +16,41 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||
import svgwrite
|
||||
|
||||
from odf.opendocument import load
|
||||
from odf.opendocument import load as load_ods
|
||||
from odf.table import Table, TableRow, TableColumn, TableCell
|
||||
from odf.text import P
|
||||
from odf.style import Style
|
||||
from textwrap import wrap
|
||||
from pathlib import Path
|
||||
import string
|
||||
|
||||
FONT_SIZE = 4.13
|
||||
FONT_WIDTH = lambda size: size * 0.45
|
||||
FONT_SIZE_PT = 12
|
||||
FONT_FAMILY = "OpenGost Type B TT"
|
||||
DEBUG = False
|
||||
|
||||
|
||||
def col2num(col):
|
||||
"""convert letter column index to number:
|
||||
`"A" -> 1`, `"AA" -> 27``
|
||||
"""
|
||||
num = 0
|
||||
for c in col:
|
||||
if c in string.ascii_letters:
|
||||
num = num * 26 + (ord(c.upper()) - ord("A")) + 1
|
||||
return num
|
||||
|
||||
|
||||
def a1_to_rc(cell):
|
||||
"""convert cell index from A1 format to RC: `"A1" -> (0,0)`"""
|
||||
column_letter = cell.strip(string.digits)
|
||||
col_number = col2num(column_letter) - 1
|
||||
row_number = int(cell[len(column_letter) :]) - 1
|
||||
return row_number, col_number
|
||||
|
||||
|
||||
class Scheduler:
|
||||
@@ -33,69 +62,334 @@ class Scheduler:
|
||||
)
|
||||
self.padding = 1
|
||||
self.margin = 1
|
||||
doc = load(infile)
|
||||
doc = load_ods(infile)
|
||||
|
||||
# useful for debugging ods
|
||||
if DEBUG:
|
||||
import xml.dom.minidom
|
||||
|
||||
path = Path(infile)
|
||||
dom = xml.dom.minidom.parseString(doc.xml())
|
||||
pretty_xml = dom.toprettyxml()
|
||||
|
||||
with open(path.with_suffix(".xml"), "w") as fo:
|
||||
fo.write(pretty_xml)
|
||||
|
||||
styles = {}
|
||||
for style in doc.getElementsByType(Style):
|
||||
name = style.getAttribute("name")
|
||||
if not style.firstChild:
|
||||
for cell_style in doc.getElementsByType(Style):
|
||||
name = cell_style.getAttribute("name")
|
||||
styles[name] = {}
|
||||
|
||||
# NOTE: there are also styles that inherit from parent styles that we do not process atm
|
||||
if not cell_style.firstChild:
|
||||
continue
|
||||
styles[name] = {key[1]: value for key, value in style.firstChild.attributes.items()}
|
||||
|
||||
if cell_style.firstChild.tagName in ["style:table-column-properties", "style:table-row-properties"]:
|
||||
style_children = [cell_style.firstChild]
|
||||
else:
|
||||
# for style:table-cell-properties we need to collect also text and paragraph properties
|
||||
style_children = cell_style.childNodes
|
||||
|
||||
for child in style_children:
|
||||
child_params = {key[1]: value for key, value in child.attributes.items()}
|
||||
styles[name].update(child_params)
|
||||
|
||||
table = doc.getElementsByType(Table)[0]
|
||||
|
||||
# related styles stored as a list of tuples:
|
||||
# [(child, parent), ...]
|
||||
related_styles = []
|
||||
|
||||
# collect columns width
|
||||
column_widths = []
|
||||
column_styles = []
|
||||
for col in table.getElementsByType(TableColumn):
|
||||
style_name = col.getAttribute("stylename")
|
||||
repeat = col.getAttribute("numbercolumnsrepeated")
|
||||
repeat = int(repeat) if repeat else 1
|
||||
for i in range(0, repeat):
|
||||
col_repeat = col.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
for i in range(col_repeat):
|
||||
if not style_name or "column-width" not in styles[style_name]:
|
||||
column_widths.append(50)
|
||||
column_width = 50
|
||||
else:
|
||||
column_widths.append(self.convert_to_mm(styles[style_name]["column-width"]))
|
||||
column_width = self.convert_to_mm(styles[style_name]["column-width"])
|
||||
column_styles.append(style_name)
|
||||
column_widths.append(column_width)
|
||||
cell_style = col.getAttribute("defaultcellstylename")
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
# collect rows height
|
||||
row_heights = []
|
||||
# TODO: never used yet because unsure about priority for row styles
|
||||
# over column styles or vice versa
|
||||
row_styles = []
|
||||
|
||||
for col in table.getElementsByType(TableRow):
|
||||
style_name = col.getAttribute("stylename")
|
||||
row_repeat = col.getAttribute("numberrowsrepeated")
|
||||
row_repeat = int(row_repeat) if row_repeat else 1
|
||||
for i in range(row_repeat):
|
||||
if not style_name or "row-height" not in styles[style_name]:
|
||||
row_height = 6
|
||||
else:
|
||||
row_height = self.convert_to_mm(styles[style_name]["row-height"])
|
||||
row_styles.append(style_name)
|
||||
row_heights.append(row_height)
|
||||
cell_style = col.getAttribute("defaultcellstylename")
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
while len(related_styles) > 0:
|
||||
# unzip related styles to children and parents
|
||||
children, parents = zip(*related_styles)
|
||||
independent_styles = set(parents) - set(children)
|
||||
for relation in related_styles[:]:
|
||||
child, parent = relation
|
||||
if parent in independent_styles:
|
||||
child_style = styles[child]
|
||||
styles[child] = styles[parent] | child_style
|
||||
related_styles.remove(relation)
|
||||
|
||||
# TODO: multiple print ranges? 😔
|
||||
print_range = table.getAttribute("printranges")
|
||||
if print_range:
|
||||
min_rc, max_rc = [a1_to_rc(cell.rsplit(".", 1)[1]) for cell in print_range.split(":")]
|
||||
else:
|
||||
# fallback if print range is not defined
|
||||
n_rows = len(row_heights)
|
||||
n_cols = len(column_widths)
|
||||
n_cells = len(row_heights) * len(column_widths)
|
||||
cells_limit = 10000
|
||||
if n_cells >= cells_limit:
|
||||
raise Exception(
|
||||
f"You were about to build a very big table with number of cells more than {cells_limit}.\n"
|
||||
f"In fact it is {n_rows} rows x {n_cols} cols = {n_cells} cells \n"
|
||||
"and the operation was stopped to prevent system freeze.\n"
|
||||
"Please define print range in .ods file to proceede\n"
|
||||
"(needed to make sure printed table will have reasonable size)."
|
||||
)
|
||||
|
||||
min_rc, max_rc = (0, 0), (1048576, 16384)
|
||||
min_row, min_col = min_rc
|
||||
max_row, max_col = max_rc
|
||||
|
||||
# draw table
|
||||
y = self.margin
|
||||
for tri, tr in enumerate(table.getElementsByType(TableRow)):
|
||||
x = self.margin
|
||||
height = 6
|
||||
tdi = 0
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
repeat = int(repeat) if repeat else 1
|
||||
for i in range(0, repeat):
|
||||
width = column_widths[tdi]
|
||||
self.svg.add(
|
||||
self.svg.rect(
|
||||
insert=(x, y),
|
||||
size=(width, height),
|
||||
style="fill: #ffffff; stroke-width:.125; stroke: #000000;",
|
||||
tri = 0
|
||||
stop_iterating_over_rows = False
|
||||
# TODO: row spans support?
|
||||
for tr in table.getElementsByType(TableRow):
|
||||
if stop_iterating_over_rows:
|
||||
break
|
||||
|
||||
row_repeat = tr.getAttribute("numberrowsrepeated")
|
||||
row_repeat = int(row_repeat) if row_repeat else 1
|
||||
|
||||
for i_row_repeat in range(row_repeat):
|
||||
if tri < min_row:
|
||||
tri += 1
|
||||
continue
|
||||
elif tri > max_row:
|
||||
stop_iterating_over_rows = True
|
||||
break
|
||||
|
||||
x = self.margin
|
||||
height = row_heights[tri]
|
||||
tdi = 0
|
||||
stop_iterating_over_columns = False
|
||||
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
if stop_iterating_over_columns:
|
||||
break
|
||||
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
|
||||
col_repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
|
||||
# figuring text alignment
|
||||
cell_style = self.get_style(td.getAttribute("stylename"), styles)
|
||||
|
||||
# drawing cells and text
|
||||
for i_col_repeat in range(col_repeat):
|
||||
start_tdi = tdi
|
||||
end_tdi = tdi + int(column_span) - 1
|
||||
# if the entire span is beyond print range => continue
|
||||
# if only part then keeping that part
|
||||
if start_tdi < min_col:
|
||||
if end_tdi < min_col:
|
||||
tdi += column_span
|
||||
continue
|
||||
else:
|
||||
start_tdi = min_col
|
||||
|
||||
# stop if start column is beyond print range
|
||||
if start_tdi > max_col:
|
||||
stop_iterating_over_columns = True
|
||||
break
|
||||
|
||||
# making sure last column won't go beyond the print range
|
||||
if end_tdi > max_col:
|
||||
end_tdi = max_col
|
||||
|
||||
width = sum(column_widths[start_tdi : end_tdi + 1])
|
||||
col_style = self.get_style(column_styles[tdi], styles)
|
||||
final_cell_style = cell_style or col_style
|
||||
background_color = final_cell_style.get("background-color", "#ffffff")
|
||||
|
||||
self.svg.add(
|
||||
self.svg.rect(
|
||||
insert=(x, y),
|
||||
size=(width, height),
|
||||
style=f"fill: {background_color}; stroke-width:.125; stroke: #000000;",
|
||||
)
|
||||
)
|
||||
)
|
||||
value = td.getElementsByType(P)
|
||||
if value:
|
||||
self.add_text(value[0], x + self.padding, y + self.padding)
|
||||
x += width
|
||||
tdi += 1
|
||||
y += height
|
||||
total_width = sum(column_widths) + (self.margin * 2)
|
||||
|
||||
p_tags = td.getElementsByType(P)
|
||||
text_color = final_cell_style.get("color", None)
|
||||
box_alignment = self.get_box_alignment(final_cell_style)
|
||||
wrap_text = final_cell_style.get("wrap-option", None) == "wrap"
|
||||
bold_text = final_cell_style.get("font-weight", None) == "bold"
|
||||
italic_text = final_cell_style.get("font-style", None) == "italic"
|
||||
# NOTE: very naive since we're scaling text proportionally
|
||||
font_size = (
|
||||
float(final_cell_style.get("font-size", f"{FONT_SIZE_PT}pt")[:-2])
|
||||
/ FONT_SIZE_PT
|
||||
* FONT_SIZE
|
||||
)
|
||||
|
||||
if p_tags:
|
||||
# figuring text position based on alignment
|
||||
text_position = [0.0, 0.0]
|
||||
if box_alignment.endswith("left"):
|
||||
text_position[0] = x + self.padding
|
||||
elif box_alignment.endswith("middle") or box_alignment == "center":
|
||||
text_position[0] = x + width / 2
|
||||
elif box_alignment.endswith("right"):
|
||||
text_position[0] = x + width - self.padding
|
||||
|
||||
if box_alignment.startswith("top"):
|
||||
text_position[1] = y + self.padding
|
||||
elif box_alignment.startswith("middle") or box_alignment == "center":
|
||||
text_position[1] = y + height / 2
|
||||
elif box_alignment.startswith("bottom"):
|
||||
text_position[1] = y + height - self.padding
|
||||
|
||||
self.add_text(
|
||||
p_tags,
|
||||
*text_position,
|
||||
font_size=font_size,
|
||||
box_alignment=box_alignment,
|
||||
wrap_text=wrap_text,
|
||||
cell_width=width,
|
||||
bold=bold_text,
|
||||
italic=italic_text,
|
||||
text_color=text_color,
|
||||
)
|
||||
x += width
|
||||
tdi += column_span
|
||||
|
||||
tri += 1
|
||||
y += height
|
||||
|
||||
total_width = x + self.margin
|
||||
total_height = y + self.margin
|
||||
self.svg["width"] = "{}mm".format(total_width)
|
||||
self.svg["height"] = "{}mm".format(y)
|
||||
self.svg["viewBox"] = "0 0 {} {}".format(total_width, y)
|
||||
self.svg["height"] = "{}mm".format(total_height)
|
||||
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
|
||||
self.svg.save(pretty=True)
|
||||
|
||||
def add_text(self, text, x, y):
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
str(text).upper(),
|
||||
insert=tuple((x, y)),
|
||||
**{
|
||||
"font-size": 4.13,
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": "start",
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "hanging",
|
||||
}
|
||||
)
|
||||
)
|
||||
def get_style(self, style_name, styles):
|
||||
style = styles[style_name] if style_name else {}
|
||||
return style
|
||||
|
||||
def get_box_alignment(self, style):
|
||||
if style and "vertical-align" in style and style["vertical-align"] != "automatic":
|
||||
vertical_align = style["vertical-align"]
|
||||
else:
|
||||
vertical_align = "bottom"
|
||||
|
||||
alignment_translation = {
|
||||
"center": "middle",
|
||||
"end": "right",
|
||||
"start": "left",
|
||||
}
|
||||
|
||||
if style and "text-align" in style and style["text-align"] != "automatic":
|
||||
horizontal_align = style["text-align"]
|
||||
horizontal_align = alignment_translation.get(horizontal_align, horizontal_align)
|
||||
else:
|
||||
horizontal_align = "left"
|
||||
|
||||
if vertical_align == "middle" and horizontal_align == "middle":
|
||||
box_alignment = "center"
|
||||
else:
|
||||
box_alignment = f"{vertical_align}-{horizontal_align}"
|
||||
|
||||
return box_alignment
|
||||
|
||||
def add_text(
|
||||
self,
|
||||
p_tags,
|
||||
x,
|
||||
y,
|
||||
font_size,
|
||||
box_alignment="bottom-left",
|
||||
wrap_text=False,
|
||||
cell_width=100,
|
||||
bold=False,
|
||||
italic=False,
|
||||
text_color=None,
|
||||
):
|
||||
"""
|
||||
Adds text to svg.
|
||||
|
||||
Args:
|
||||
p_tags: list of cell's P tags from odt file
|
||||
box_alignment: alignment of text in box
|
||||
wrap_text: if True, text will be wrapped to fit in cell
|
||||
cell_width: width of cell, used for wrapping text
|
||||
"""
|
||||
text_lines = [str(p).upper() for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
"font-family": FONT_FAMILY,
|
||||
}
|
||||
if bold:
|
||||
text_params["font-weight"] = "bold"
|
||||
if italic:
|
||||
text_params["font-style"] = "italic"
|
||||
if text_color:
|
||||
text_params["fill"] = text_color
|
||||
|
||||
if len(text_lines) == 1 and not wrap_text:
|
||||
text_params.update(box_alignment_params)
|
||||
text_tag = self.svg.text(text_lines[0], insert=(x, y), **(text_params))
|
||||
self.svg.add(text_tag)
|
||||
return
|
||||
|
||||
text_tag = self.svg.text("", **(text_params | {"font-size": "0"} | box_alignment_params))
|
||||
|
||||
# TODO: should be done in less naive way
|
||||
# without using magic number for FONT_WIDTH
|
||||
# currently it might not work for all fonts and font sizes
|
||||
if wrap_text:
|
||||
wrapped_lines = []
|
||||
for line in text_lines:
|
||||
wrapped_line = wrap(line, width=int(cell_width // FONT_WIDTH(font_size)), break_long_words=False)
|
||||
wrapped_lines.extend(wrapped_line)
|
||||
else:
|
||||
wrapped_lines = text_lines
|
||||
|
||||
for line_number, text_line in enumerate(wrapped_lines[::-1]):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
tspan = self.svg.tspan(text_line, insert=(x, y), **text_params)
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"-{line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
def convert_to_mm(self, value):
|
||||
# XSL is what defines the units of measurements in ODF
|
||||
|
||||
@@ -16,9 +16,23 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gpu.types import GPUShader
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import gpu
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
# NOTES:
|
||||
# Since Metal doesn't support geometry shaders we stick to builtin shaders
|
||||
# and generate all geometry data in python before passing it to the shader.
|
||||
# This way was considered to be the most reliable atm.
|
||||
# More: https://blender.stackexchange.com/questions/291674/migrating-geometry-shaders-to-metal
|
||||
#
|
||||
# BGL deprecation:
|
||||
# since `bgl` is deprecated, creating smoothing lines became tricky
|
||||
# Notes for creating shaders with smoothed lines:
|
||||
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
|
||||
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
|
||||
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
|
||||
|
||||
|
||||
BASE_DEF_GLSL = """
|
||||
@@ -29,12 +43,6 @@ BASE_DEF_GLSL = """
|
||||
#define lineSmooth true
|
||||
"""
|
||||
|
||||
# since `bgl` is deprecated, creating smoothing lines became tricky
|
||||
# Notes for creating shaders with smoothed lines:
|
||||
# in geom shader - use triangle_strip, DEFAULT_SETUP, do_edge_verts or do_vertex to emit vertices
|
||||
# in frag shader - use lineWidth uniform, smoothline flaot in, smoothing shader code from base shader
|
||||
# mind the vertex limit since emitting vertices for smoothed lines produces twice as much vertices
|
||||
|
||||
BASE_LIB_GLSL = """
|
||||
// TODO: redefine as macor instead
|
||||
uniform vec2 winsize;
|
||||
@@ -160,12 +168,11 @@ void do_vertex_util(vec4 pos, vec2 ofs)
|
||||
|
||||
// geometry utils
|
||||
void triangle_head(in vec4 side, in vec4 dir, in float length, in float width, in float radius, out vec4 head[5]) {
|
||||
vec4 nose = side * length;
|
||||
vec4 ear = dir * width;
|
||||
// TODO: radius is unnecessary?
|
||||
head[0] = side * -radius;
|
||||
head[1] = nose * -.5;
|
||||
head[2] = vec4(0) + ear;
|
||||
head[3] = nose * .5;
|
||||
head[1] = side * length * -.5;
|
||||
head[2] = dir * width;
|
||||
head[3] = side * length * .5;
|
||||
head[4] = side * radius;
|
||||
}
|
||||
|
||||
@@ -189,6 +196,22 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
|
||||
"""
|
||||
|
||||
|
||||
def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False):
|
||||
"""Add sequence of verts to output lists, returns next vertex index"""
|
||||
for i, v in enumerate(verts[:-1], start_i):
|
||||
output_verts.append(v)
|
||||
output_edges.append((i, i + 1))
|
||||
output_verts.append(verts[-1])
|
||||
if closed:
|
||||
output_edges.append((i + 1, start_i))
|
||||
return i + 2
|
||||
|
||||
|
||||
def add_offsets(v, offsets):
|
||||
"""returns list of verts with offsets added"""
|
||||
return [v + offset for offset in offsets]
|
||||
|
||||
|
||||
class BaseShader:
|
||||
"""Wrapper for GPUShader
|
||||
To use for viewport decorations with geometry generated on GPU side.
|
||||
@@ -265,48 +288,49 @@ class BaseShader:
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# NB: libcode arg doesn't work
|
||||
# TODO: rename to .shader
|
||||
self.prog = GPUShader(
|
||||
vertexcode=self.VERT_GLSL,
|
||||
fragcode=self.FRAG_GLSL,
|
||||
geocode=self.LIB_GLSL + self.GEOM_GLSL,
|
||||
defines=self.DEF_GLSL,
|
||||
)
|
||||
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
|
||||
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
|
||||
self.base_shader = gpu.shader.from_builtin("3D_UNIFORM_COLOR")
|
||||
|
||||
def get_shader(self):
|
||||
"""Returns shader for this type"""
|
||||
return self.line_shader if self.TYPE == "LINES" else self.base_shader
|
||||
|
||||
def batch(self, indices=None, **data):
|
||||
"""Returns automatic GPUBatch filled with provided parameters"""
|
||||
batch = batch_for_shader(self.prog, self.TYPE, data, indices=indices)
|
||||
batch.program_set(self.prog)
|
||||
shader = self.get_shader()
|
||||
batch = batch_for_shader(shader, self.TYPE, data, indices=indices)
|
||||
return batch
|
||||
|
||||
def bind(self):
|
||||
self.prog.bind()
|
||||
"""need to bind shader before changing it's uniforms"""
|
||||
shader = self.get_shader()
|
||||
shader.bind()
|
||||
return shader
|
||||
|
||||
def glenable(self):
|
||||
gpu.state.blend_set("ALPHA")
|
||||
gpu.state.depth_test_set("LESS_EQUAL")
|
||||
|
||||
def uniform_region(self, ctx):
|
||||
shader = self.bind()
|
||||
|
||||
region = ctx.region
|
||||
region3d = ctx.region_data
|
||||
|
||||
uniform_floats = {
|
||||
"viewMatrix": region3d.perspective_matrix,
|
||||
"winsize": (region.width, region.height),
|
||||
"ModelViewProjectionMatrix": region3d.perspective_matrix,
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
"viewportSize": (region.width, region.height),
|
||||
"lineWidth": 2.5,
|
||||
}
|
||||
|
||||
for name, value in uniform_floats.items():
|
||||
try:
|
||||
self.prog.uniform_float(name, value)
|
||||
# TODO: shouldn't just try'n'catch them
|
||||
# because they may indicate errors in code
|
||||
except ValueError: # unused uniform
|
||||
pass
|
||||
shader.uniform_float(name, value)
|
||||
|
||||
|
||||
# TODO: add smoothing if this shades is going to be used
|
||||
# TODO: add smoothing if this shader is going to be used
|
||||
# TODO: dead code?
|
||||
class BaseLinesShader(BaseShader):
|
||||
"""Draws line segments with gaps around vertices at endpoints"""
|
||||
|
||||
@@ -429,37 +453,24 @@ class ExtrusionGuidesShader(GizmoShader):
|
||||
|
||||
TYPE = "LINES"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseShader.DEF_GLSL
|
||||
+ """
|
||||
#define CROSS_SIZE .5
|
||||
"""
|
||||
)
|
||||
def process_geometry(self, verts):
|
||||
CROSS_SIZE = 0.5
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
p0, p1 = verts
|
||||
bx = Vector((1, 0, 0)) * CROSS_SIZE
|
||||
by = Vector((0, 1, 0)) * CROSS_SIZE
|
||||
|
||||
layout(lines) in;
|
||||
layout(triangle_strip, max_vertices=MAX_POINTS) out;
|
||||
output_verts = []
|
||||
output_edges = []
|
||||
out_kwargs = {
|
||||
"output_verts": output_verts,
|
||||
"output_edges": output_edges,
|
||||
}
|
||||
|
||||
void main() {
|
||||
// default setup for macro to work
|
||||
vec2 EDGE_DIR;
|
||||
start_i = 0
|
||||
start_i = add_verts_sequence(add_offsets(p0, [-bx, bx]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p0, [-by, by]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p1, [-bx, bx]), start_i, **out_kwargs)
|
||||
start_i = add_verts_sequence(add_offsets(p1, [-by, by]), start_i, **out_kwargs)
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
do_edge_verts(p0, p1);
|
||||
EndPrimitive();
|
||||
|
||||
vec4 bx = ModelViewProjectionMatrix[0] * CROSS_SIZE;
|
||||
vec4 by = ModelViewProjectionMatrix[1] * CROSS_SIZE;
|
||||
|
||||
do_edge_verts(p0 - bx, p0 + bx);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p0 - by, p0 + by);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p1 - bx, p1 + bx);
|
||||
EndPrimitive();
|
||||
do_edge_verts(p1 - by, p1 + by);
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
return output_verts, output_edges
|
||||
|
||||
@@ -27,6 +27,13 @@ import xml.etree.ElementTree as ET
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.geolocation
|
||||
from xml.dom import minidom
|
||||
from mathutils import Vector
|
||||
import re
|
||||
|
||||
VIEW_TITLE_OFFSET_Y = 5
|
||||
DEFAULT_POSITION = Vector((30, 30))
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
XLINK = "{http://www.w3.org/1999/xlink}"
|
||||
|
||||
|
||||
class SheetBuilder:
|
||||
@@ -99,25 +106,68 @@ class SheetBuilder:
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
|
||||
# add background
|
||||
if os.path.isfile(underlay_path):
|
||||
background = ET.SubElement(view, "image")
|
||||
background.attrib["data-type"] = "background"
|
||||
background.attrib["xlink:href"] = os.path.relpath(underlay_path, layout_dir)
|
||||
background.attrib["x"] = "30"
|
||||
background.attrib["y"] = "30"
|
||||
background.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
background.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
background.attrib["width"] = str(view_width)
|
||||
background.attrib["height"] = str(view_height)
|
||||
|
||||
# add foreground
|
||||
if os.path.isfile(drawing_path):
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "foreground"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(drawing_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
self.add_view_title(
|
||||
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def update_sheet_drawing_sizes(self, sheet):
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# iterate over all drawings in the sheet
|
||||
drawings_views = layout_root.findall(f'{SVG}g[@data-type="drawing"]')
|
||||
|
||||
for drawing_view in drawings_views:
|
||||
# find drawing in ifc file to get the drawing dimensions
|
||||
drawing = ifc_file.by_guid(drawing_view.attrib.get("data-drawing"))
|
||||
drawing_path = tool.Drawing.get_document_uri(tool.Drawing.get_drawing_reference(drawing))
|
||||
drawing_tree = ET.parse(drawing_path)
|
||||
drawing_root = drawing_tree.getroot()
|
||||
view_width = self.convert_to_mm(drawing_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(drawing_root.attrib.get("height"))
|
||||
|
||||
foreground = drawing_view.find(f'.//{SVG}image[@data-type="foreground"]')
|
||||
height = float(foreground.attrib["height"])
|
||||
width = float(foreground.attrib["width"])
|
||||
readjust = Vector((width - view_width, height - view_height)) / 2
|
||||
|
||||
for image in drawing_view.findall(f"{SVG}image"):
|
||||
x = float(image.attrib["x"])
|
||||
y = float(image.attrib["y"])
|
||||
if image.attrib["data-type"] == "view-title":
|
||||
image.attrib["x"] = str(x - readjust.x)
|
||||
image.attrib["y"] = str(y - readjust.y)
|
||||
else:
|
||||
image.attrib["x"] = str(x + readjust.x)
|
||||
image.attrib["y"] = str(y + readjust.y)
|
||||
image.attrib["width"] = str(view_width)
|
||||
image.attrib["height"] = str(view_height)
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def remove_drawing(self, reference, sheet):
|
||||
@@ -134,7 +184,7 @@ class SheetBuilder:
|
||||
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_schedule(self, reference, schedule, sheet):
|
||||
def add_document(self, reference, schedule, sheet):
|
||||
view_path = tool.Drawing.get_path_with_ext(tool.Drawing.get_document_uri(schedule), "svg")
|
||||
if not os.path.exists(view_path):
|
||||
tool.Drawing.create_svg_schedule(schedule)
|
||||
@@ -161,12 +211,14 @@ class SheetBuilder:
|
||||
foreground = ET.SubElement(view, "image")
|
||||
foreground.attrib["data-type"] = "table"
|
||||
foreground.attrib["xlink:href"] = os.path.relpath(view_path, layout_dir)
|
||||
foreground.attrib["x"] = "30"
|
||||
foreground.attrib["y"] = "30"
|
||||
foreground.attrib["x"] = str(DEFAULT_POSITION.x)
|
||||
foreground.attrib["y"] = str(DEFAULT_POSITION.y)
|
||||
foreground.attrib["width"] = str(view_width)
|
||||
foreground.attrib["height"] = str(view_height)
|
||||
|
||||
self.add_view_title(30, view_height + 35, view, layout_dir)
|
||||
self.add_view_title(
|
||||
DEFAULT_POSITION.x, view_height + DEFAULT_POSITION.y + VIEW_TITLE_OFFSET_Y, view, layout_dir
|
||||
)
|
||||
layout_tree.write(layout_path)
|
||||
|
||||
def add_view_title(self, x, y, parent, layout_dir):
|
||||
@@ -230,8 +282,61 @@ class SheetBuilder:
|
||||
titleblock.append(g)
|
||||
titleblock.remove(image)
|
||||
|
||||
def ensure_drawing_unique_styles(self, svg, drawing_id):
|
||||
"""ensures all drawing's classes and ids will be unique for the whole sheet
|
||||
by adding `drawing_id` based prefix
|
||||
"""
|
||||
prefix = f"d{drawing_id}" # just number doesn't work
|
||||
|
||||
# add .prefix class to all css selectors
|
||||
style = svg.find(f"{SVG}defs/{SVG}style")
|
||||
style_data = style.text
|
||||
text = ""
|
||||
brackets_level = 0
|
||||
for l in style_data:
|
||||
if l == "{":
|
||||
if brackets_level == 0:
|
||||
cur_line = text.splitlines()[-1]
|
||||
text = text[: -len(cur_line)]
|
||||
css_selectors = []
|
||||
# making sure cases like "text, tspan" will be
|
||||
# converted to "text.prefix, tspan.prefix"
|
||||
for css_selector in cur_line.split(","):
|
||||
css_selector = f"{css_selector.strip()}.{prefix}"
|
||||
css_selectors.append(css_selector)
|
||||
text += ", ".join(css_selectors) + " "
|
||||
brackets_level += 1
|
||||
elif l == "}":
|
||||
brackets_level -= 1
|
||||
text += l
|
||||
|
||||
# replace urls url(#marker) with url(#prefix-marker)
|
||||
# since url(#marker.prefix) doesn't seem to work
|
||||
text = re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
|
||||
style.text = text
|
||||
|
||||
for svg_element in svg.findall(f".//*"):
|
||||
if svg_element.tag in (f"{SVG}style", f"{SVG}svg"):
|
||||
continue
|
||||
attrib = svg_element.attrib
|
||||
# add "prefix-" to all ids
|
||||
if "id" in attrib:
|
||||
attrib["id"] = f"{prefix}-{attrib['id']}"
|
||||
# add class "prefix" to all classes
|
||||
if "class" in attrib:
|
||||
attrib["class"] += f" {prefix}"
|
||||
if svg_element.tag == f"{SVG}use":
|
||||
href_attrib = f"{XLINK}href"
|
||||
if href_attrib in attrib:
|
||||
href = attrib[href_attrib]
|
||||
if href.startswith("#"):
|
||||
attrib[href_attrib] = f"#{prefix}-{href[1:]}"
|
||||
|
||||
return svg
|
||||
|
||||
def build_drawings(self, root, sheet):
|
||||
for view in root.findall('{http://www.w3.org/2000/svg}g[@data-type="drawing"]'):
|
||||
drawing_id = int(view.attrib["data-id"])
|
||||
reference = tool.Ifc.get().by_id(int(view.attrib["data-id"]))
|
||||
drawing = tool.Ifc.get().by_id(view.attrib["data-drawing"])
|
||||
|
||||
@@ -250,7 +355,9 @@ class SheetBuilder:
|
||||
view_title = image
|
||||
|
||||
if foreground is not None:
|
||||
view.append(self.parse_embedded_svg(foreground, {}))
|
||||
svg = self.parse_embedded_svg(foreground, {})
|
||||
svg = self.ensure_drawing_unique_styles(svg, drawing_id)
|
||||
view.append(svg)
|
||||
|
||||
if background is not None:
|
||||
background_path = os.path.join(self.layout_dir, self.get_href(background))
|
||||
|
||||
@@ -100,6 +100,9 @@ class SvgWriter:
|
||||
if not os.path.exists(resource_path):
|
||||
resource_basename = os.path.basename(resource_path)
|
||||
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
|
||||
print(
|
||||
f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}"
|
||||
)
|
||||
if os.path.exists(ootb_resource):
|
||||
shutil.copy(ootb_resource, resource_path)
|
||||
self.resource_paths[resource] = resource_path
|
||||
@@ -121,23 +124,35 @@ class SvgWriter:
|
||||
self.height = self.raw_height * self.svg_scale
|
||||
|
||||
def add_stylesheet(self):
|
||||
if not self.resource_paths["Stylesheet"] or not os.path.exists(self.resource_paths["Stylesheet"]):
|
||||
path = self.resource_paths["Stylesheet"]
|
||||
if not path:
|
||||
return
|
||||
with open(self.resource_paths["Stylesheet"], "r") as stylesheet:
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find stylesheet for the drawing by the path: {path}")
|
||||
return
|
||||
with open(path, "r") as stylesheet:
|
||||
self.svg.defs.add(self.svg.style(stylesheet.read()))
|
||||
|
||||
def add_markers(self):
|
||||
if not self.resource_paths["Markers"] or not os.path.exists(self.resource_paths["Markers"]):
|
||||
path = self.resource_paths["Markers"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Markers"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find markers for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
|
||||
def add_symbols(self):
|
||||
if not self.resource_paths["Symbols"] or not os.path.exists(self.resource_paths["Symbols"]):
|
||||
path = self.resource_paths["Symbols"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Symbols"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find symbols for the drawing by the path: {path}")
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -148,9 +163,15 @@ class SvgWriter:
|
||||
return External(xml_symbol) if xml_symbol else None
|
||||
|
||||
def add_patterns(self):
|
||||
if not self.resource_paths["Patterns"] or not os.path.exists(self.resource_paths["Patterns"]):
|
||||
path = self.resource_paths["Patterns"]
|
||||
if not path:
|
||||
return
|
||||
tree = ET.parse(self.resource_paths["Patterns"])
|
||||
if not os.path.exists(path):
|
||||
print(f"WARNING. Couldn't find patterns for the drawing by the path: {path}")
|
||||
return
|
||||
if not path or not os.path.exists(path):
|
||||
return
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
for child in root:
|
||||
self.svg.defs.add(External(child))
|
||||
@@ -225,17 +246,24 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = (matrix_world @ points[0].co.xyz).z
|
||||
rl_value = (matrix_world @ points[0].co.xyz).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = rl_value / unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
text_style = self.get_box_alignment_parameters("bottom-left")
|
||||
self.svg.add(self.svg.text(f"RL +{rl}", insert=tuple(text_position), class_="SECTIONLEVEL", **text_style))
|
||||
text_style = SvgWriter.get_box_alignment_parameters("bottom-left")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL {}{}".format("" if rl_value < 0 else "+", rl),
|
||||
insert=tuple(text_position),
|
||||
class_="SECTIONLEVEL",
|
||||
**text_style,
|
||||
)
|
||||
)
|
||||
if tag:
|
||||
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
|
||||
|
||||
@@ -263,7 +291,7 @@ class SvgWriter:
|
||||
"UP",
|
||||
insert=tuple(text_position),
|
||||
class_="STAIR",
|
||||
**self.get_box_alignment_parameters("center"),
|
||||
**SvgWriter.get_box_alignment_parameters("center"),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -287,7 +315,7 @@ class SvgWriter:
|
||||
)
|
||||
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||
axis_tag = tool.Ifc.get_entity(obj).Name
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
axis_tag,
|
||||
@@ -396,8 +424,7 @@ class SvgWriter:
|
||||
self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes))
|
||||
)
|
||||
|
||||
# BATTING ANNOTATIONS
|
||||
if predefined_type == "BATTING":
|
||||
def draw_batting_annotation():
|
||||
v0_global = matrix_world @ obj.data.vertices[0].co.xyz
|
||||
v1_global = matrix_world @ obj.data.vertices[1].co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -463,7 +490,94 @@ class SvgWriter:
|
||||
)
|
||||
self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style))
|
||||
|
||||
elif predefined_type == "SECTION":
|
||||
def draw_revision_cloud_annotation():
|
||||
segment_width = 15.0
|
||||
base_height = 1
|
||||
width = 5
|
||||
|
||||
def get_svg_half_circle(height, width):
|
||||
cp0 = f"0,-{height}"
|
||||
cp1 = f"{width},-{height}"
|
||||
end_point = f"{width},0"
|
||||
circle = f"c{cp0} {cp1} {end_point}"
|
||||
return circle
|
||||
|
||||
def get_revision_pattern(base_offset):
|
||||
pattern = f"m{base_offset.x},{base_offset.y}"
|
||||
pattern += " " + get_svg_half_circle(2 * base_height, width)
|
||||
pattern += " " + get_svg_half_circle(2.5 * base_height, width)
|
||||
pattern += " " + get_svg_half_circle(1.5 * base_height, width)
|
||||
return pattern
|
||||
|
||||
def get_scale(size, direction):
|
||||
vector = direction * size
|
||||
shrinked_vector = size // segment_width * segment_width * direction
|
||||
scale = [1 if vector[i] == 0 else vector[i] / shrinked_vector[i] for i in range(2)]
|
||||
return "scale(%f, %f)" % (scale[0], scale[1])
|
||||
|
||||
def poly_to_edges(poly):
|
||||
edges = []
|
||||
n_verts = len(poly)
|
||||
lats_index = n_verts - 1
|
||||
for i in range(len(poly)):
|
||||
edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]]
|
||||
edges.append(edge)
|
||||
return edges
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
safe_offset_x = 2.0
|
||||
marker_width = segment_width + safe_offset_x * 2
|
||||
market_height = 15.0
|
||||
ref_y = 5.0
|
||||
revision_pattern = get_revision_pattern(Vector([safe_offset_x, ref_y]))
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data).copy()
|
||||
bmesh.ops.contextual_create(bm, geom=bm.edges[:])
|
||||
faces = bm.faces[:]
|
||||
assert len(faces) == 1, "Revision cloud edges must form just 1 polygon"
|
||||
|
||||
# ensure clockwise order of polygon verts
|
||||
# given default blender counter-clockwise order
|
||||
polygon = faces[0]
|
||||
if polygon.normal.z > 0:
|
||||
polygon.normal_flip()
|
||||
|
||||
marker_id = f"revision-cloud-{element.GlobalId}"
|
||||
svg_path = self.svg.path(style="fill: none; stroke:red; stroke-width:0.20", d=revision_pattern)
|
||||
marker = self.svg.marker(
|
||||
markerUnits="userSpaceOnUse",
|
||||
insert=(safe_offset_x, ref_y),
|
||||
size=(marker_width, market_height),
|
||||
orient="auto",
|
||||
id=marker_id,
|
||||
)
|
||||
marker.add(svg_path)
|
||||
self.svg.add(marker)
|
||||
|
||||
for v0, v1 in poly_to_edges(polygon.verts):
|
||||
v0_global = matrix_world @ v0.co.xyz
|
||||
v1_global = matrix_world @ v1.co.xyz
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
v1 = self.project_point_onto_camera(v1_global)
|
||||
start_svg = Vector(((x_offset + v0.x), (y_offset - v0.y))) * self.svg_scale
|
||||
end_svg = Vector(((x_offset + v1.x), (y_offset - v1.y))) * self.svg_scale
|
||||
|
||||
pattern_edge = end_svg - start_svg
|
||||
pattern_dir = pattern_edge.normalized()
|
||||
pattern_length = pattern_edge.length
|
||||
|
||||
segments = int(pattern_length // segment_width)
|
||||
pattern_dir_step = pattern_dir * segment_width
|
||||
points = [pattern_dir_step * i for i in range(segments)]
|
||||
|
||||
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
|
||||
polyline_transform = f"translate({start_svg.x}, {start_svg.y}) {get_scale(pattern_length, pattern_dir)}"
|
||||
polyline = self.svg.polyline(
|
||||
points=points, class_=" ".join(classes), style=polyline_style, transform=polyline_transform
|
||||
)
|
||||
self.svg.add(polyline)
|
||||
|
||||
def draw_section_annotation():
|
||||
display_data = DecoratorData.get_section_markers_display_data(obj)
|
||||
connect_markers = display_data["connect_markers"]
|
||||
|
||||
@@ -504,6 +618,12 @@ class SvgWriter:
|
||||
)
|
||||
)
|
||||
|
||||
if predefined_type == "BATTING":
|
||||
draw_batting_annotation()
|
||||
elif predefined_type == "REVISIONCLOUD":
|
||||
draw_revision_cloud_annotation()
|
||||
elif predefined_type == "SECTION":
|
||||
draw_section_annotation()
|
||||
else:
|
||||
for edge in obj.data.edges:
|
||||
draw_simple_edge_annotation(*edge.vertices[:])
|
||||
@@ -557,7 +677,7 @@ class SvgWriter:
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = symbol_position_svg
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id,
|
||||
@@ -590,7 +710,7 @@ class SvgWriter:
|
||||
|
||||
reference_id, sheet_id = self.get_reference_and_sheet_id_from_annotation(tool.Ifc.get_entity(obj))
|
||||
text_position = symbol_position_svg
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
reference_id, insert=(text_position[0], text_position[1] - 2.5), class_="ELEVATION", **text_style
|
||||
@@ -617,7 +737,8 @@ class SvgWriter:
|
||||
return (reference_id, sheet_id)
|
||||
return ("-", "-")
|
||||
|
||||
def get_box_alignment_parameters(self, box_alignment):
|
||||
@staticmethod
|
||||
def get_box_alignment_parameters(box_alignment):
|
||||
"""Convenience method to get svg parameters for text alignment
|
||||
in a readable way.
|
||||
|
||||
@@ -664,6 +785,7 @@ class SvgWriter:
|
||||
text_position = self.project_point_onto_camera(position)
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
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"""
|
||||
@@ -673,13 +795,18 @@ class SvgWriter:
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
transform = "rotate({}, {}, {})".format(angle, *text_position_svg)
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if symbol:
|
||||
if not symbol:
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
else:
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
|
||||
text_transform = symbol_transform
|
||||
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
symbol_xml = symbol_svg.get_xml()
|
||||
@@ -687,7 +814,7 @@ class SvgWriter:
|
||||
# if there is a symbol with template text fields
|
||||
# then we just populate it's fields with the data from text literals
|
||||
if template_text_fields:
|
||||
symbol_xml.attrib["transform"] = f"translate({', '.join(map(str, text_position_svg))})"
|
||||
symbol_xml.attrib["transform"] = symbol_transform
|
||||
symbol_xml.attrib.pop("id")
|
||||
# note: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
@@ -697,8 +824,9 @@ class SvgWriter:
|
||||
return None
|
||||
|
||||
if not symbol_svg or not template_text_fields:
|
||||
self.svg.add(self.svg.use(f"#{symbol}", insert=text_position_svg))
|
||||
self.svg.add(self.svg.use(f"#{symbol}", transform=symbol_transform))
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
# after pretty indentation some redundant spaces can occur in svg tags
|
||||
# this is why we apply "font-size: 0;" to the text tag to remove those spaces
|
||||
@@ -707,32 +835,39 @@ class SvgWriter:
|
||||
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
attribs = {
|
||||
"transform": transform,
|
||||
"transform": text_transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
|
||||
def add_text_tag(add_fill_bg):
|
||||
nonlocal line_number
|
||||
text_tag = self.svg.text(
|
||||
"",
|
||||
**(attribs | {"filter": "url(#fill-background)"}) if add_fill_bg else attribs,
|
||||
**self.get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
**SvgWriter.get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
text_lines = text.replace("\\n", "\n").split("\n")
|
||||
|
||||
for line_number, text_line in enumerate(text_lines):
|
||||
for text_line in text_lines:
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
tspan = self.svg.tspan(text_line, class_=classes_str, insert=text_position_svg)
|
||||
# note that tspan doesn't support using `transform` attribute
|
||||
# so we use (0,0) position because tspan is already offseted by text transform
|
||||
tspan = self.svg.tspan(text_line, class_=classes_str, insert=(0, 0))
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
line_number += 1
|
||||
|
||||
if add_fill_bg:
|
||||
# return line_number back to the original value
|
||||
line_number -= len(text_lines)
|
||||
|
||||
if "fill-bg" in classes:
|
||||
add_text_tag(True)
|
||||
add_text_tag(False)
|
||||
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -775,25 +910,25 @@ class SvgWriter:
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 2.5,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 1.0,
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = (matrix_world @ points[0].co).z
|
||||
rl_value = (matrix_world @ points[0].co).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl, precision=self.precision, decimal_places=self.decimal_places)
|
||||
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
rl /= unit_scale
|
||||
rl = rl_value / unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
|
||||
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
|
||||
text_style = self.get_box_alignment_parameters(box_alignment)
|
||||
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
"{}{}".format("" if rl_value < 0 else "+", rl),
|
||||
insert=tuple(text_position),
|
||||
class_="PLANLEVEL",
|
||||
**text_style,
|
||||
@@ -912,7 +1047,7 @@ class SvgWriter:
|
||||
text_offset = (text_position - center_position).xy.normalized() * 5
|
||||
text_position += text_offset
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
angle_text = abs(round(math.degrees(angle), 3))
|
||||
if is_reflex:
|
||||
angle_text = 360 - angle_text
|
||||
@@ -973,7 +1108,7 @@ class SvgWriter:
|
||||
)
|
||||
text_position += text_offset
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
radius = (points[-1].co - points[-2].co).length
|
||||
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
|
||||
tag = element.Description or f"R{radius}"
|
||||
@@ -1046,7 +1181,7 @@ class SvgWriter:
|
||||
)
|
||||
text_position += text_offset
|
||||
|
||||
text_style = self.get_box_alignment_parameters("center")
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
|
||||
def draw_diameter_annotations(self, obj):
|
||||
@@ -1071,6 +1206,8 @@ class SvgWriter:
|
||||
text_format=lambda x: "D" + x,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
)
|
||||
|
||||
def draw_dimension_annotations(self, obj):
|
||||
@@ -1092,6 +1229,8 @@ class SvgWriter:
|
||||
dimension_text=dimension_text,
|
||||
show_description_only=dimension_data["show_description_only"],
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
)
|
||||
|
||||
def draw_measureit_arch_dimension_annotations(self):
|
||||
@@ -1115,6 +1254,8 @@ class SvgWriter:
|
||||
text_format=lambda x: x,
|
||||
show_description_only=False,
|
||||
suppress_zero_inches=False,
|
||||
text_prefix="",
|
||||
text_suffix="",
|
||||
):
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -1135,27 +1276,86 @@ class SvgWriter:
|
||||
|
||||
# if annotation can't fit offset text to the right of marker
|
||||
text_position = mid if sheet_dimension > 5 else (end + (3 * vector.normalized()))
|
||||
rotation = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||
|
||||
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
|
||||
self.svg.add(line)
|
||||
|
||||
def create_text_tag(text, text_position, box_alignment):
|
||||
text_kwargs = {"transform": "rotate({} {} {})".format(rotation, text_position.x, text_position.y)}
|
||||
if not show_description_only:
|
||||
text = f"{text_prefix}{str(dimension)}{text_suffix}"
|
||||
text_tag = self.create_text_tag(
|
||||
text,
|
||||
text_position + perpendicular,
|
||||
angle,
|
||||
"bottom-middle",
|
||||
"DIMENSION",
|
||||
text_format=text_format,
|
||||
multiline=True,
|
||||
multiline_to_bottom=False,
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
if dimension_text:
|
||||
text_tag = self.create_text_tag(
|
||||
dimension_text,
|
||||
text_position - perpendicular,
|
||||
angle,
|
||||
"top-middle",
|
||||
"DIMENSION",
|
||||
text_format=text_format,
|
||||
multiline=True,
|
||||
multiline_to_bottom=True,
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
elif show_description_only and dimension_text:
|
||||
text_tag = self.create_text_tag(
|
||||
dimension_text,
|
||||
text_position + perpendicular,
|
||||
angle,
|
||||
"bottom-middle",
|
||||
"DIMENSION",
|
||||
text_format=text_format,
|
||||
multiline=True,
|
||||
multiline_to_bottom=False,
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
|
||||
def create_text_tag(
|
||||
self,
|
||||
text,
|
||||
text_position,
|
||||
angle,
|
||||
box_alignment,
|
||||
class_str,
|
||||
text_format=lambda x: x,
|
||||
multiline=False,
|
||||
multiline_to_bottom=False,
|
||||
):
|
||||
if not multiline:
|
||||
text_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
return self.svg.text(
|
||||
text_format(text),
|
||||
insert=text_position,
|
||||
class_="DIMENSION",
|
||||
**(text_kwargs | self.get_box_alignment_parameters(box_alignment)),
|
||||
class_=class_str,
|
||||
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
|
||||
)
|
||||
|
||||
if not show_description_only:
|
||||
self.svg.add(create_text_tag(str(dimension), text_position + perpendicular, "bottom-middle"))
|
||||
if dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position - perpendicular, "top-middle"))
|
||||
text_position_svg_str = ", ".join(map(str, text_position))
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
text_kwargs = {
|
||||
"transform": text_transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
|
||||
elif show_description_only and dimension_text:
|
||||
self.svg.add(create_text_tag(dimension_text, text_position + perpendicular, "bottom-middle"))
|
||||
text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment))
|
||||
text_lines = text.replace("\\n", "\n").split("\n")
|
||||
text_lines = text_lines if multiline_to_bottom else text_lines[::-1]
|
||||
|
||||
for line_number, text_line in enumerate(text_lines):
|
||||
tspan = self.svg.tspan(text_format(text_line), class_=class_str, insert=(0, 0))
|
||||
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
return text_tag
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
# TODO is this needlessly complex?
|
||||
|
||||
@@ -23,10 +23,11 @@ from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import (
|
||||
ProductAssignmentsData,
|
||||
SheetsData,
|
||||
SchedulesData,
|
||||
DocumentsData,
|
||||
DrawingsData,
|
||||
DecoratorData,
|
||||
)
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -70,22 +71,19 @@ class BIM_PT_camera(Panel):
|
||||
row = layout.row()
|
||||
row.prop(dprops, "should_extract")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "is_nts")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.resize_text")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_x")
|
||||
row = layout.row()
|
||||
row.prop(props, "raster_y")
|
||||
|
||||
row = layout.row()
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "diagram_scale")
|
||||
row.prop(props, "is_nts", text="", icon="MOD_EDGESPLIT")
|
||||
|
||||
if props.diagram_scale == "CUSTOM":
|
||||
row = layout.row()
|
||||
row.prop(props, "custom_diagram_scale")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "custom_scale_numerator", text="Custom Scale")
|
||||
row.prop(props, "custom_scale_denominator", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.create_drawing", text="Create Drawing", icon="OUTPUT")
|
||||
@@ -111,38 +109,53 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
layout.use_property_split = True
|
||||
dprops = context.scene.DocProperties
|
||||
props = context.active_object.data.BIMCameraProperties
|
||||
drawing_index_is_valid = props.active_drawing_style_index < len(dprops.drawing_styles)
|
||||
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_drawing_style")
|
||||
current_shading_style = drawing_pset_data.get("CurrentShadingStyle", None)
|
||||
if current_shading_style is None:
|
||||
row.label(text="Current style is not set.")
|
||||
else:
|
||||
row.label(text="Current Shading Style:")
|
||||
row.label(text=current_shading_style)
|
||||
row.operator("bim.add_drawing_style", icon="ADD", text="")
|
||||
if drawing_index_is_valid:
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
row.operator("bim.reload_drawing_styles", icon="FILE_REFRESH", text="")
|
||||
|
||||
if dprops.drawing_styles:
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
if not dprops.drawing_styles:
|
||||
return
|
||||
layout.template_list("BIM_UL_generic", "", dprops, "drawing_styles", props, "active_drawing_style_index")
|
||||
|
||||
if props.active_drawing_style_index < len(dprops.drawing_styles):
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
if not drawing_index_is_valid:
|
||||
return
|
||||
drawing_style = dprops.drawing_styles[props.active_drawing_style_index]
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
row.operator("bim.remove_drawing_style", icon="X", text="").index = props.active_drawing_style_index
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "name")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
row = layout.row()
|
||||
row.prop(drawing_style, "render_type")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "include_query")
|
||||
row = layout.row(align=True)
|
||||
row.prop(drawing_style, "exclude_query")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
row = layout.row()
|
||||
row.operator("bim.add_drawing_style_attribute")
|
||||
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
for index, attribute in enumerate(drawing_style.attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_drawing_style_attribute", icon="X", text="").index = index
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.save_drawing_style")
|
||||
row.operator("bim.activate_drawing_style")
|
||||
|
||||
|
||||
class BIM_PT_drawings(Panel):
|
||||
@@ -161,11 +174,7 @@ class BIM_PT_drawings(Panel):
|
||||
DrawingsData.load()
|
||||
|
||||
if not DrawingsData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
@@ -229,22 +238,18 @@ class BIM_PT_schedules(Panel):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not SchedulesData.is_loaded:
|
||||
SchedulesData.load()
|
||||
if not DocumentsData.is_loaded:
|
||||
DocumentsData.load()
|
||||
|
||||
if not SchedulesData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
if not DocumentsData.data["has_saved_ifc"]:
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_schedules:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{SchedulesData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
|
||||
row.label(text=f"{DocumentsData.data['total_schedules']} Schedules Found", icon="LONGDISPLAY")
|
||||
row.operator("bim.load_schedules", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
@@ -268,6 +273,58 @@ class BIM_PT_schedules(Panel):
|
||||
)
|
||||
|
||||
|
||||
def draw_project_not_saved_ui(self):
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
|
||||
|
||||
class BIM_PT_references(Panel):
|
||||
bl_label = "References"
|
||||
bl_idname = "BIM_PT_references"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
DocumentsData.load()
|
||||
|
||||
if not DocumentsData.data["has_saved_ifc"]:
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
if not self.props.is_editing_references:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
|
||||
row.operator("bim.load_references", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_reference", icon="ADD")
|
||||
row.operator("bim.disable_editing_references", text="", icon="CANCEL")
|
||||
|
||||
if self.props.references:
|
||||
if self.props.active_reference_index < len(self.props.references):
|
||||
active_reference = self.props.references[self.props.active_reference_index]
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.open_reference", icon="URL", text="").reference = active_reference.ifc_definition_id
|
||||
row.operator("bim.remove_reference", icon="X", text="").reference = active_reference.ifc_definition_id
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_generic", "", self.props, "references", self.props, "active_reference_index"
|
||||
)
|
||||
|
||||
|
||||
class BIM_PT_sheets(Panel):
|
||||
bl_label = "Sheets"
|
||||
bl_idname = "BIM_PT_sheets"
|
||||
@@ -284,11 +341,7 @@ class BIM_PT_sheets(Panel):
|
||||
SheetsData.load()
|
||||
|
||||
if not SheetsData.data["has_saved_ifc"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="Project Not Yet Saved", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
op = row.operator("export_ifc.bim", icon="EXPORT", text="Save Project")
|
||||
op.should_save_as = False
|
||||
draw_project_not_saved_ui(self)
|
||||
return
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
@@ -312,6 +365,7 @@ class BIM_PT_sheets(Panel):
|
||||
row.operator("bim.open_sheet", icon="URL", text="")
|
||||
row.operator("bim.add_drawing_to_sheet", icon="IMAGE_PLANE", text="")
|
||||
row.operator("bim.add_schedule_to_sheet", icon="PRESET_NEW", text="")
|
||||
row.operator("bim.add_reference_to_sheet", icon="IMAGE_REFERENCE", text="")
|
||||
row.operator("bim.create_sheets", icon="FILE_REFRESH", text="")
|
||||
if active_sheet.is_sheet:
|
||||
row.operator("bim.remove_sheet", icon="X", text="").sheet = active_sheet.ifc_definition_id
|
||||
@@ -450,55 +504,55 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
|
||||
op.object_type = "DIMENSION"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Angle", icon="DRIVER_ROTATIONAL_DIFFERENCE")
|
||||
op.object_type = "ANGLE"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Radius", icon="FORWARD")
|
||||
op.object_type = "RADIUS"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Diameter", icon="ARROW_LEFTRIGHT")
|
||||
op.object_type = "DIAMETER"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
|
||||
op.object_type = "TEXT"
|
||||
op.data_type = "empty"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
|
||||
op.object_type = "TEXT_LEADER"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
|
||||
op.object_type = "STAIR_ARROW"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
|
||||
op.object_type = "HIDDEN_LINE"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
|
||||
op.object_type = "PLAN_LEVEL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
|
||||
op.object_type = "SECTION_LEVEL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
|
||||
op.object_type = "BREAKLINE"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Line", icon="MESH_MONKEY")
|
||||
op.object_type = "LINEWORK"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Batting", icon="FORCE_FORCE")
|
||||
op.object_type = "BATTING"
|
||||
op.data_type = "mesh"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op.description = "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set"
|
||||
op = row.operator("bim.add_annotation", text="Fill Area", icon="NODE_TEXTURE")
|
||||
op.object_type = "FILL_AREA"
|
||||
@@ -506,7 +560,10 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
|
||||
op.object_type = "FALL"
|
||||
op.data_type = "curve"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
op = row.operator("bim.add_annotation", text="Revision", icon="VOLUME_DATA")
|
||||
op.object_type = "REVISION_CLOUD"
|
||||
op.data_type = tool.Drawing.get_annotation_data_type(op.object_type)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "should_draw_decorations", text="Viewport Annotations")
|
||||
@@ -561,6 +618,8 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
row.label(text="", icon="MENU_PANEL")
|
||||
elif item.reference_type == "REVISION":
|
||||
row.label(text="", icon="RECOVER_LAST")
|
||||
elif item.reference_type == "REFERENCE":
|
||||
row.label(text="", icon="IMAGE_REFERENCE")
|
||||
|
||||
if item.identification:
|
||||
name = f"{item.identification} - {item.name or 'Unnamed'}"
|
||||
|
||||
@@ -47,7 +47,6 @@ class AnnotationTool(WorkSpaceTool):
|
||||
bl_description = "Gives you Annotation related superpowers"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.annotation")
|
||||
bl_widget = None
|
||||
# https://docs.blender.org/api/current/bpy.types.KeyMapItems.html
|
||||
bl_keymap = tool.Blender.get_default_selection_keypmap() + (
|
||||
("bim.annotation_hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}),
|
||||
("bim.annotation_hotkey", {"type": "C", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_C")]}),
|
||||
@@ -108,7 +107,7 @@ def create_annotation_type(context):
|
||||
tool.Drawing.ensure_annotation_in_drawing_plane(obj)
|
||||
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
|
||||
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
@@ -133,7 +132,7 @@ def create_annotation_occurence(context):
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
obj = tool.Drawing.create_annotation_object(drawing, object_type)
|
||||
obj.name = relating_type.Name
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing))
|
||||
ifc_context = tool.Drawing.get_annotation_context(tool.Drawing.get_drawing_target_view(drawing), object_type)
|
||||
relating_type_repr = tool.Drawing.get_annotation_representation(relating_type)
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
@@ -179,8 +178,7 @@ class AnnotationToolUI:
|
||||
|
||||
if context.active_object and context.selected_objects:
|
||||
cls.draw_edit_object_interface(context)
|
||||
elif not context.selected_objects:
|
||||
cls.draw_create_object_interface()
|
||||
cls.draw_create_object_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_create_object_interface(cls):
|
||||
@@ -194,14 +192,6 @@ class AnnotationToolUI:
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
|
||||
|
||||
@classmethod
|
||||
def draw_header_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_basic_annotation_tool_interface(cls):
|
||||
cls.draw_type_selection_interface()
|
||||
|
||||
@classmethod
|
||||
def draw_type_selection_interface(cls):
|
||||
# shared by both sidebar and header
|
||||
|
||||
@@ -63,6 +63,8 @@ def register():
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
bpy.types.VIEW3D_MT_object.append(ui.object_menu)
|
||||
bpy.types.OUTLINER_MT_object.append(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(ui.object_menu)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
@@ -90,6 +92,8 @@ def register():
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_object.remove(ui.object_menu)
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
bpy.types.OUTLINER_MT_object.remove(ui.outliner_menu)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(ui.outliner_menu)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
del bpy.types.Object.BIMGeometryProperties
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -104,24 +104,50 @@ class ConnectionsData:
|
||||
def connections(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
for rel in getattr(element, "ConnectedTo", []):
|
||||
|
||||
connected_to = getattr(element, "ConnectedTo", [])
|
||||
connected_from = getattr(element, "ConnectedFrom", [])
|
||||
|
||||
for rel in connected_to:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
related_element = rel.RelatedPort
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": True,
|
||||
"Name": rel.RelatedElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatingConnectionType,
|
||||
"Name": related_element.Name or "Unnamed",
|
||||
"ConnectionType": related_element_connection_type,
|
||||
}
|
||||
)
|
||||
for rel in getattr(element, "ConnectedFrom", []):
|
||||
|
||||
for rel in connected_from:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
relating_element = rel.RelatingPort
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
"id": rel.id(),
|
||||
"is_relating": False,
|
||||
"Name": rel.RelatingElement.Name or "Unnamed",
|
||||
"ConnectionType": rel.RelatedConnectionType,
|
||||
"Name": relating_element.Name or "Unnamed",
|
||||
"ConnectionType": relating_element_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -152,10 +178,18 @@ class DerivedPlacementsData:
|
||||
|
||||
@classmethod
|
||||
def load_collection(cls):
|
||||
cls.collection = bpy.data.objects.get(bpy.context.active_object.users_collection[0].name)
|
||||
cls.collection = None
|
||||
cls.collection_z = 0
|
||||
if cls.collection:
|
||||
cls.collection_z = cls.collection.matrix_world.translation.z
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element:
|
||||
return
|
||||
parent = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not parent:
|
||||
parent = ifcopenshell.util.element.get_container(element)
|
||||
if parent:
|
||||
cls.collection = tool.Ifc.get_object(parent)
|
||||
if cls.collection:
|
||||
cls.collection_z = cls.collection.matrix_world.translation.z
|
||||
|
||||
@classmethod
|
||||
def has_collection(cls):
|
||||
|
||||
@@ -38,7 +38,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
face = None
|
||||
@@ -62,7 +62,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -90,7 +90,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -292,7 +292,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
potential_faces = []
|
||||
@@ -389,7 +389,7 @@ class Helper:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
faces = bm.faces
|
||||
|
||||
@@ -199,6 +199,13 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
# TODO: remove this code a bit later
|
||||
# added this as a fallback for easier transition some annotation types to 3d
|
||||
# if they were create before as 2d
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if tool.Drawing.is_annotation_object_type(element, ("FALL", "SECTION_LEVEL", "PLAN_LEVEL")):
|
||||
context_of_items = tool.Drawing.get_annotation_context("MODEL_VIEW")
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
|
||||
@@ -366,6 +373,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -373,7 +381,7 @@ class OverrideDelete(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
if tool.Ifc.get():
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
for obj in context.selected_objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -382,21 +390,55 @@ class OverrideDelete(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.confirm:
|
||||
if tool.Ifc.get():
|
||||
total_elements = len(tool.Ifc.get().wrapped_data.entity_names())
|
||||
total_polygons = sum([len(o.data.polygons) for o in context.selected_objects if o.type == "MESH"])
|
||||
# These numbers are a bit arbitrary, but basically batching is only
|
||||
# really necessary on large models and large geometry removals.
|
||||
self.is_batch = total_elements > 500000 and total_polygons > 2000
|
||||
if self.is_batch:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
elif self.confirm:
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
self.confirm = True
|
||||
return self.execute(context)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.label(text="Warning: Faster deletion will use more memory.", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
row.prop(self, "is_batch", text="Enable Faster Deletion")
|
||||
|
||||
def _execute(self, context):
|
||||
if self.is_batch:
|
||||
ifcopenshell.util.element.batch_remove_deep2(tool.Ifc.get())
|
||||
for obj in context.selected_objects:
|
||||
if tool.Ifc.get_entity(obj):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
if self.is_batch:
|
||||
old_file = tool.Ifc.get()
|
||||
old_file.end_transaction()
|
||||
new_file = ifcopenshell.util.element.unbatch_remove_deep2(tool.Ifc.get())
|
||||
new_file.begin_transaction()
|
||||
tool.Ifc.set(new_file)
|
||||
self.transaction_data = {"old_file": old_file, "new_file": new_file}
|
||||
IfcStore.add_transaction_operation(self)
|
||||
# Required otherwise gizmos are still visible
|
||||
context.view_layer.objects.active = None
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
tool.Ifc.set(data["old_file"])
|
||||
data["old_file"].undo()
|
||||
|
||||
def commit(self, data):
|
||||
data["old_file"].redo()
|
||||
tool.Ifc.set(data["new_file"])
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_idname = "bim.override_outliner_delete"
|
||||
@@ -519,6 +561,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
# clear object's collection so it will be able to have it's own
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
# Copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
if new:
|
||||
@@ -527,6 +571,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new, pset=array_pset)
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new]
|
||||
if new.is_a("IfcRelSpaceBoundary"):
|
||||
tool.Boundary.decorate_boundary(new_obj)
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
@@ -720,17 +766,26 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
|
||||
if context.active_object:
|
||||
context.active_object.select_set(True)
|
||||
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if element and element.is_a("IfcRelSpaceBoundary"):
|
||||
return bpy.ops.bim.enable_editing_boundary_geometry()
|
||||
|
||||
for obj in objs:
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
if not obj.data:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
# We are switching from OBJECT to EDIT mode.
|
||||
usage_type = tool.Model.get_usage_type(element)
|
||||
if usage_type:
|
||||
if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"):
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
# can be modified to be incompatible with the parametric
|
||||
# constraints.
|
||||
@@ -741,6 +796,36 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if not representation:
|
||||
continue
|
||||
|
||||
if (
|
||||
tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Window")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Stair")
|
||||
):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
if usage_type == "PROFILE":
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="A_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
if (
|
||||
tool.Geometry.is_profile_based(obj.data)
|
||||
or usage_type == "LAYER3"
|
||||
or tool.Geometry.is_swept_profile(representation)
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
or tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
):
|
||||
if len(context.selected_objects) == 1:
|
||||
bpy.ops.bim.hotkey(hotkey="S_E", description="")
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
@@ -834,6 +919,11 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if not tool.Ifc.get():
|
||||
return {"FINISHED"}
|
||||
|
||||
if context.active_object:
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if element and element.is_a("IfcRelSpaceBoundary"):
|
||||
return bpy.ops.bim.edit_boundary_geometry()
|
||||
|
||||
objs = context.selected_objects or [context.active_object]
|
||||
|
||||
self.edited_objs = []
|
||||
@@ -847,7 +937,22 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
if not element:
|
||||
continue
|
||||
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if tool.Profile.is_editing_profile():
|
||||
profile_id = context.scene.BIMProfileProperties.active_profile_id
|
||||
if profile_id:
|
||||
profile = tool.Ifc.get().by_id(profile_id)
|
||||
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Railing"):
|
||||
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
|
||||
elif tool.Pset.get_element_pset(element, "BBIM_Roof"):
|
||||
bpy.ops.bim.cad_hotkey(hotkey="S_Q")
|
||||
elif tool.Model.get_usage_type(element) == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
return self.execute(context)
|
||||
elif obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
if not tool.Geometry.has_geometric_data(obj):
|
||||
self.is_valid = False
|
||||
self.should_save = False
|
||||
|
||||
@@ -24,10 +24,14 @@ from blenderbim.bim.module.geometry.data import RepresentationsData, Connections
|
||||
|
||||
|
||||
def object_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
|
||||
def outliner_menu(self, context):
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.override_outliner_delete", icon='X')
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
bl_label = "IFC Representations"
|
||||
|
||||
@@ -33,6 +33,7 @@ classes = (
|
||||
operator.ToggleGroup,
|
||||
operator.UnassignGroup,
|
||||
operator.UpdateGroup,
|
||||
operator.SelectGroupElements,
|
||||
prop.ExpandedGroups,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
|
||||
@@ -275,3 +275,19 @@ class UpdateGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectGroupElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_group_elements"
|
||||
bl_label = "Select Group elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and context.active_object)
|
||||
|
||||
def execute(self, context):
|
||||
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(self.group))
|
||||
tool.Spatial.select_products(elements)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -107,6 +107,7 @@ class BIM_PT_object_groups(Panel):
|
||||
for group in ObjectGroupsData.data["groups"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=group["name"])
|
||||
row.operator("bim.select_group_elements", text="", icon="RESTRICT_SELECT_OFF").group = group["id"]
|
||||
op = row.operator("bim.unassign_group", text="", icon="X")
|
||||
op.group = group["id"]
|
||||
|
||||
|
||||
@@ -21,18 +21,28 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddFileToRepo,
|
||||
operator.AddRemote,
|
||||
operator.AddTag,
|
||||
operator.CloneRepo,
|
||||
operator.CommitChanges,
|
||||
operator.CreateRepo,
|
||||
operator.DeleteRemote,
|
||||
operator.DeleteTag,
|
||||
operator.DiscardUncommitted,
|
||||
operator.DisplayRevision,
|
||||
operator.DisplayUncommitted,
|
||||
operator.Fetch,
|
||||
operator.Merge,
|
||||
operator.ObjectLog,
|
||||
operator.Push,
|
||||
operator.RefreshGit,
|
||||
operator.SwitchRevision,
|
||||
prop.IfcGitTag,
|
||||
prop.IfcGitListItem,
|
||||
prop.IfcGitProperties,
|
||||
ui.IFCGIT_PT_panel,
|
||||
ui.COMMIT_UL_List,
|
||||
ui.IFCGIT_PT_revision_inspector,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,17 +15,29 @@ class IfcGitData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def make_sure_is_loaded(cls):
|
||||
if not cls.is_loaded:
|
||||
cls.load()
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"repo": cls.repo(),
|
||||
"remotes": cls.remotes(),
|
||||
"branch_names": cls.branch_names(),
|
||||
"remote_names": cls.remote_names(),
|
||||
"remote_urls": cls.remote_urls(),
|
||||
"path_ifc": cls.path_ifc(),
|
||||
"branches_by_hexsha": cls.branches_by_hexsha(),
|
||||
"tags_by_hexsha": cls.tags_by_hexsha(),
|
||||
"name_ifc": cls.name_ifc(),
|
||||
"dir_name": cls.dir_name(),
|
||||
"base_name": cls.base_name(),
|
||||
"working_dir": cls.working_dir(),
|
||||
"untracked_files": cls.untracked_files(),
|
||||
"is_detached": cls.is_detached(),
|
||||
"active_branch_name": cls.active_branch_name(),
|
||||
"is_dirty": cls.is_dirty(),
|
||||
"commit": cls.commit(),
|
||||
"current_revision": cls.current_revision(),
|
||||
@@ -42,10 +54,28 @@ class IfcGitData:
|
||||
return tool.IfcGit.repo_from_path(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def remotes(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().remotes
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def branch_names(cls):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def remote_names(cls):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def remote_urls(cls):
|
||||
result = {}
|
||||
if cls.repo():
|
||||
for remote in cls.repo().remotes:
|
||||
result[remote.name] = remote.url
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def path_ifc(cls):
|
||||
path_ifc = tool.Ifc.get_path()
|
||||
@@ -92,6 +122,27 @@ class IfcGitData:
|
||||
return os.path.basename(path_ifc)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def working_dir(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().working_dir
|
||||
|
||||
@classmethod
|
||||
def untracked_files(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().untracked_files
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def is_detached(cls):
|
||||
if cls.repo():
|
||||
return cls.repo().head.is_detached
|
||||
|
||||
@classmethod
|
||||
def active_branch_name(cls):
|
||||
if cls.repo() and not cls.is_detached():
|
||||
return cls.repo().active_branch.name
|
||||
|
||||
@classmethod
|
||||
def is_dirty(cls):
|
||||
if cls.repo() and cls.git_exe():
|
||||
|
||||
@@ -15,8 +15,9 @@ class CreateRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
if not path_ifc or not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if IfcGitData.data["repo"]:
|
||||
# repo already exists
|
||||
@@ -42,8 +43,9 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
path_ifc = IfcGitData.data["path_ifc"]
|
||||
if not os.path.isfile(path_ifc):
|
||||
if not path_ifc or not os.path.isfile(path_ifc):
|
||||
return False
|
||||
if not IfcGitData.data["repo"]:
|
||||
# repo doesn't exist
|
||||
@@ -57,6 +59,34 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CloneRepo(bpy.types.Operator):
|
||||
"""Clone a remote Git repository"""
|
||||
|
||||
bl_label = "Clone repository"
|
||||
bl_idname = "ifcgit.clone_repo"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if (
|
||||
props.remote_url
|
||||
and props.local_folder
|
||||
and os.path.isdir(props.local_folder)
|
||||
and not os.listdir(props.local_folder)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
|
||||
props.remote_url = ""
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DiscardUncommitted(bpy.types.Operator):
|
||||
"""Discard saved changes and update to HEAD"""
|
||||
|
||||
@@ -80,6 +110,7 @@ class CommitChanges(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if props.commit_message == "":
|
||||
@@ -101,7 +132,54 @@ class CommitChanges(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo, context)
|
||||
core.commit_changes(tool.IfcGit, tool.Ifc, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddTag(bpy.types.Operator):
|
||||
"""Tag selected revision"""
|
||||
|
||||
bl_label = "Tag selected revision"
|
||||
bl_idname = "ifcgit.add_tag"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.new_tag_name == "":
|
||||
return False
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo and (
|
||||
not tool.IfcGit.is_valid_ref_format(props.new_tag_name)
|
||||
or props.new_tag_name in [tag.name for tag in repo.tags]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.add_tag(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeleteTag(bpy.types.Operator):
|
||||
"""Delete a tag"""
|
||||
|
||||
bl_label = "Delete tag"
|
||||
bl_idname = "ifcgit.delete_tag"
|
||||
bl_options = {"REGISTER"}
|
||||
tag_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.delete_tag(tool.IfcGit, repo, self.tag_name)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
@@ -116,8 +194,9 @@ class RefreshGit(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
repo = IfcGitData.data["repo"]
|
||||
if repo != None and repo.heads:
|
||||
if repo:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -136,9 +215,15 @@ class DisplayRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.display_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.colourise_revision(tool.IfcGit, context)
|
||||
core.colourise_revision(tool.IfcGit)
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -165,6 +250,12 @@ class SwitchRevision(bpy.types.Operator):
|
||||
bl_idname = "ifcgit.switch_revision"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
core.switch_revision(tool.IfcGit, tool.Ifc)
|
||||
@@ -181,7 +272,9 @@ class Merge(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if IfcGitData.data["ifcmerge_exe"]:
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -192,3 +285,103 @@ class Merge(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class Push(bpy.types.Operator):
|
||||
"""Pushes the working branch to selected remote"""
|
||||
|
||||
bl_label = "Push working branch"
|
||||
bl_idname = "ifcgit.push"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.push(tool.IfcGit, repo, props.select_remote, self)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Fetch(bpy.types.Operator):
|
||||
"""Fetches from the selected remote"""
|
||||
|
||||
bl_label = "Fetch from remote"
|
||||
bl_idname = "ifcgit.fetch"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
remote = repo.remotes[props.select_remote]
|
||||
remote.fetch()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddRemote(bpy.types.Operator):
|
||||
"""Add a remote repository"""
|
||||
|
||||
bl_label = "Add Remote"
|
||||
bl_idname = "ifcgit.add_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
repo = IfcGitData.data["repo"]
|
||||
if (
|
||||
not repo
|
||||
or not tool.IfcGit.is_valid_ref_format(props.remote_name)
|
||||
or not props.remote_url
|
||||
or props.remote_name in [remote.name for remote in repo.remotes]
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.add_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DeleteRemote(bpy.types.Operator):
|
||||
"""Delete the selected remote"""
|
||||
|
||||
bl_label = "Delete Remote"
|
||||
bl_idname = "ifcgit.delete_remote"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.delete_remote(tool.IfcGit, repo)
|
||||
bpy.ops.ifcgit.refresh()
|
||||
refresh()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ObjectLog(bpy.types.Operator):
|
||||
"""Displays Git log of selected object"""
|
||||
|
||||
bl_label = "Selected Object History"
|
||||
bl_idname = "ifcgit.object_log"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
cls.poll_message_set("No Active Object")
|
||||
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
step_id = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
core.entity_log(tool.IfcGit, tool.Ifc, step_id, self)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -7,7 +7,7 @@ from bpy.props import (
|
||||
IntProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
def git_branches(self, context):
|
||||
@@ -21,9 +21,26 @@ def git_branches(self, context):
|
||||
IfcGitData.data["branch_names"].remove("main")
|
||||
IfcGitData.data["branch_names"] = ["main"] + IfcGitData.data["branch_names"]
|
||||
|
||||
if IfcGitData.data["remotes"]:
|
||||
for remote in IfcGitData.data["remotes"]:
|
||||
for remote_branch in remote.refs:
|
||||
IfcGitData.data["branch_names"].append(remote_branch.name)
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
||||
|
||||
|
||||
def git_remotes(self, context):
|
||||
"""remotes enum"""
|
||||
|
||||
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
|
||||
|
||||
if "origin" in IfcGitData.data["remote_names"]:
|
||||
IfcGitData.data["remote_names"].remove("origin")
|
||||
IfcGitData.data["remote_names"] = ["origin"] + IfcGitData.data["remote_names"]
|
||||
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
|
||||
|
||||
|
||||
def update_revlist(self, context):
|
||||
"""wrapper to trigger update of the revision list"""
|
||||
|
||||
@@ -32,6 +49,19 @@ def update_revlist(self, context):
|
||||
props.commit_index = 0
|
||||
|
||||
|
||||
class IfcGitTag(PropertyGroup):
|
||||
"""Properties of a Git tag"""
|
||||
|
||||
name: StringProperty(
|
||||
name="Tag name",
|
||||
default="",
|
||||
)
|
||||
message: StringProperty(
|
||||
name="Tag message",
|
||||
default="",
|
||||
)
|
||||
|
||||
|
||||
class IfcGitListItem(PropertyGroup):
|
||||
"""Group of properties representing an item in the list."""
|
||||
|
||||
@@ -45,6 +75,19 @@ class IfcGitListItem(PropertyGroup):
|
||||
description="does this commit reference our ifc file",
|
||||
default=False,
|
||||
)
|
||||
author_name: StringProperty(
|
||||
name="Author Name",
|
||||
default="",
|
||||
)
|
||||
author_email: StringProperty(
|
||||
name="Author Email",
|
||||
default="",
|
||||
)
|
||||
message: StringProperty(
|
||||
name="Commit Message",
|
||||
default="",
|
||||
)
|
||||
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
|
||||
|
||||
|
||||
class IfcGitProperties(PropertyGroup):
|
||||
@@ -61,7 +104,34 @@ class IfcGitProperties(PropertyGroup):
|
||||
description="A short name used to refer to this branch",
|
||||
default="",
|
||||
)
|
||||
new_tag_name: StringProperty(
|
||||
name="New tag name",
|
||||
description="A short name used to refer to this tag",
|
||||
default="",
|
||||
)
|
||||
new_tag_message: StringProperty(
|
||||
name="Tag message (optional)",
|
||||
description="An optional human readable description of this tag",
|
||||
default="",
|
||||
)
|
||||
remote_name: StringProperty(
|
||||
name="New remote name",
|
||||
description="A local name for a remote Git repository",
|
||||
default="",
|
||||
)
|
||||
remote_url: StringProperty(
|
||||
name="Git URL",
|
||||
description="A URL pointing to a Git repository",
|
||||
default="",
|
||||
)
|
||||
local_folder: StringProperty(
|
||||
name="Local folder",
|
||||
description="A local Git repository path",
|
||||
default="",
|
||||
subtype="DIR_PATH",
|
||||
)
|
||||
display_branch: EnumProperty(items=git_branches, update=update_revlist)
|
||||
select_remote: EnumProperty(items=git_remotes)
|
||||
ifcgit_filter: EnumProperty(
|
||||
items=[
|
||||
("all", "All", "All revisions"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bpy
|
||||
import time
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData, refresh
|
||||
from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
|
||||
|
||||
class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
@@ -34,8 +34,8 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
if path_ifc:
|
||||
if IfcGitData.data["repo"]:
|
||||
name_ifc = IfcGitData.data["name_ifc"]
|
||||
row.label(text=IfcGitData.data["repo"].working_dir, icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["repo"].untracked_files:
|
||||
row.label(text=IfcGitData.data["working_dir"], icon="SYSTEM")
|
||||
if name_ifc in IfcGitData.data["untracked_files"]:
|
||||
row.operator(
|
||||
"ifcgit.addfile",
|
||||
text="Add '" + name_ifc + "' to repository",
|
||||
@@ -54,6 +54,16 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
else:
|
||||
row.label(text="No Git repository found", icon="SYSTEM")
|
||||
row.label(text="No IFC project saved", icon="FILE")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Clone a remote Git repository")
|
||||
row = box.row()
|
||||
row.prop(props, "remote_url")
|
||||
row = box.row()
|
||||
row.prop(props, "local_folder")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.clone_repo", icon="IMPORT")
|
||||
return
|
||||
|
||||
is_dirty = IfcGitData.data["is_dirty"]
|
||||
@@ -69,7 +79,7 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "commit_message")
|
||||
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row = layout.row()
|
||||
row.label(text="HEAD is detached, commit will create a branch", icon="ERROR")
|
||||
row.prop(props, "new_branch_name")
|
||||
@@ -78,10 +88,10 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row.operator("ifcgit.commit_changes", icon="GREASEPENCIL")
|
||||
|
||||
row = layout.row()
|
||||
if IfcGitData.data["repo"].head.is_detached:
|
||||
if IfcGitData.data["is_detached"]:
|
||||
row.label(text="Working branch: Detached HEAD")
|
||||
else:
|
||||
row.label(text="Working branch: " + IfcGitData.data["repo"].active_branch.name)
|
||||
row.label(text="Working branch: " + IfcGitData.data["active_branch_name"])
|
||||
|
||||
grouped = layout.row()
|
||||
column = grouped.column()
|
||||
@@ -110,8 +120,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
row = column.row()
|
||||
row.operator("ifcgit.switch_revision", icon="CURRENT_FILE")
|
||||
|
||||
# TODO operator to tag selected
|
||||
|
||||
row = column.row()
|
||||
row.operator("ifcgit.merge", icon="EXPERIMENTAL", text="")
|
||||
|
||||
@@ -119,7 +127,6 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
return
|
||||
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
commit = IfcGitData.data["commit"]
|
||||
if not item.relevant:
|
||||
row = layout.row()
|
||||
row.label(text="Revision unrelated to current IFC project", icon="ERROR")
|
||||
@@ -127,11 +134,50 @@ class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
box = layout.box()
|
||||
column = box.column(align=True)
|
||||
row = column.row()
|
||||
row.label(text=commit.hexsha)
|
||||
row.label(text=item.hexsha)
|
||||
row = column.row()
|
||||
row.label(text=commit.author.name + " <" + commit.author.email + ">")
|
||||
row = column.row()
|
||||
row.label(text=commit.message)
|
||||
row.label(text=item.author_name + " <" + item.author_email + ">")
|
||||
for message_line in item.message.split("\n"):
|
||||
row = column.row()
|
||||
row.label(text=message_line)
|
||||
|
||||
for tag in item.tags:
|
||||
box = layout.box()
|
||||
item = box.row()
|
||||
column = item.column(align=True)
|
||||
row = column.row()
|
||||
row.label(text=tag.name)
|
||||
row.operator("ifcgit.delete_tag", icon="PANEL_CLOSE").tag_name = tag.name
|
||||
if tag.message:
|
||||
for message_line in tag.message.split("\n"):
|
||||
row = column.row()
|
||||
row.label(text=message_line)
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.prop(props, "new_tag_name")
|
||||
row = box.row()
|
||||
row.prop(props, "new_tag_message")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.add_tag", icon="GREASEPENCIL")
|
||||
|
||||
if IfcGitData.data["remotes"]:
|
||||
row = layout.row()
|
||||
row.prop(props, "select_remote", text="Select remote")
|
||||
urls = IfcGitData.data["remote_urls"]
|
||||
row.label(text=urls[props.select_remote])
|
||||
row.operator("ifcgit.delete_remote", text="", icon="PANEL_CLOSE")
|
||||
row = layout.row()
|
||||
row.operator("ifcgit.push", icon="EXPORT")
|
||||
row.operator("ifcgit.fetch", icon="IMPORT")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.prop(props, "remote_name")
|
||||
row = box.row()
|
||||
row.prop(props, "remote_url")
|
||||
row = box.row()
|
||||
row.operator("ifcgit.add_remote", icon="ADD")
|
||||
|
||||
|
||||
class COMMIT_UL_List(bpy.types.UIList):
|
||||
@@ -160,7 +206,36 @@ class COMMIT_UL_List(bpy.types.UIList):
|
||||
refs += "{" + tag.name + "} "
|
||||
|
||||
if commit == current_revision:
|
||||
layout.label(text="[HEAD] " + refs + commit.message, icon="DECORATE_KEYFRAME")
|
||||
layout.label(text="[HEAD] " + refs + commit.message.split("\n")[0], icon="DECORATE_KEYFRAME")
|
||||
else:
|
||||
layout.label(text=refs + commit.message, icon="DECORATE_ANIMATE")
|
||||
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
||||
|
||||
|
||||
class IFCGIT_PT_revision_inspector(bpy.types.Panel):
|
||||
"""Tool panel to interact with revision history"""
|
||||
|
||||
bl_idname = "IFCGIT_PT_revision_inspector"
|
||||
bl_label = "Git History"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
if not IfcGitData.is_loaded:
|
||||
IfcGitData.load()
|
||||
|
||||
layout = self.layout
|
||||
|
||||
if not IfcGitData.data["git_exe"]:
|
||||
row = layout.row()
|
||||
row.label(text="Git is not installed", icon="ERROR")
|
||||
return
|
||||
|
||||
row = layout.row()
|
||||
row.operator(
|
||||
"ifcgit.object_log",
|
||||
icon="TEXT",
|
||||
)
|
||||
|
||||
@@ -31,13 +31,16 @@ classes = (
|
||||
operator.ContractMaterialCategory,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterial,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
operator.DisableEditingMaterialSetItemProfile,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EditMaterial,
|
||||
operator.EditMaterialSetItem,
|
||||
operator.EditMaterialSetItemProfile,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.EnableEditingMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.EnableEditingMaterialSetItemProfile,
|
||||
operator.ExpandMaterialCategory,
|
||||
|
||||
@@ -96,6 +96,7 @@ class ObjectMaterialData:
|
||||
cls.data["total_thickness"] = cls.total_thickness()
|
||||
cls.data["materials"] = cls.materials()
|
||||
cls.data["type_material"] = cls.type_material()
|
||||
cls.data["material_type"] = cls.material_type()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -244,3 +245,17 @@ class ObjectMaterialData:
|
||||
else:
|
||||
name_attr = "Name"
|
||||
return getattr(material, name_attr, "Unnamed") or "Unnamed"
|
||||
|
||||
@classmethod
|
||||
def material_type(cls):
|
||||
material_types = [
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
version = tool.Ifc.get_schema()
|
||||
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]
|
||||
|
||||
@@ -56,7 +56,37 @@ class SelectByMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.select_by_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
core.select_by_material(tool.Material, tool.Spatial, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class EnableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_material"
|
||||
bl_label = "Enable Editing Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_material(tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class EditMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_material"
|
||||
bl_label = "Edit Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
|
||||
|
||||
|
||||
class DisableEditingMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_material"
|
||||
bl_label = "Disable Editing Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_material(tool.Material)
|
||||
|
||||
|
||||
class AssignParameterizedProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -144,59 +174,7 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
active_obj = context.active_object
|
||||
active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type
|
||||
material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material))
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_material",
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
type=active_object_material_type,
|
||||
material=material,
|
||||
)
|
||||
assigned_material = ifcopenshell.util.element.get_material(element)
|
||||
if assigned_material.is_a("IfcMaterialLayerSet"):
|
||||
if not assigned_material.MaterialLayers:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer = ifcopenshell.api.run(
|
||||
"material.add_layer",
|
||||
tool.Ifc.get(),
|
||||
layer_set=assigned_material,
|
||||
material=tool.Ifc.get().by_type("IfcMaterial")[0],
|
||||
)
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
elif assigned_material.is_a("IfcMaterialProfileSet"):
|
||||
if not assigned_material.MaterialProfiles:
|
||||
named_profiles = [p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName]
|
||||
if named_profiles:
|
||||
profile = named_profiles[0]
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
size = 0.5 / unit_scale
|
||||
profile = tool.Ifc.get().create_entity(
|
||||
"IfcRectangleProfileDef",
|
||||
ProfileName="New Profile",
|
||||
ProfileType="AREA",
|
||||
XDim=size,
|
||||
YDim=size,
|
||||
)
|
||||
material_profile = ifcopenshell.api.run(
|
||||
"material.add_profile",
|
||||
tool.Ifc.get(),
|
||||
profile_set=assigned_material,
|
||||
material=tool.Ifc.get().by_type("IfcMaterial")[0],
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_profile",
|
||||
tool.Ifc.get(),
|
||||
material_profile=material_profile,
|
||||
profile=profile,
|
||||
)
|
||||
core.assign_material(tool.Ifc, tool.Material, material_type= self.material_type , objects=objects )
|
||||
|
||||
|
||||
class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -207,12 +185,7 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else tool.Blender.get_selected_objects()
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if "Usage" not in material.is_a():
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element)
|
||||
core.unassign_material(tool.Ifc, tool.Material, objects=objects )
|
||||
|
||||
|
||||
class AddConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -243,12 +216,12 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
constituent: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.constituent)):
|
||||
if inverse.is_a("IfcMaterialConstituentSet") and len(inverse.MaterialConstituents) == 1:
|
||||
return
|
||||
ifcopenshell.api.run(
|
||||
"material.remove_constituent", tool.Ifc.get(), constituent=tool.Ifc.get().by_id(self.constituent)
|
||||
)
|
||||
constituent = tool.Ifc.get().by_id(self.constituent)
|
||||
for material_set in layer.ToMaterialConstituentSet:
|
||||
if len(material_set.MaterialConstituents) == 1:
|
||||
self.report({"ERROR"}, "At least one constituent must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
|
||||
|
||||
|
||||
class AddProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -278,10 +251,12 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
profile: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.profile)):
|
||||
if inverse.is_a("IfcMaterialProfileSet") and len(inverse.MaterialProfiles) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=tool.Ifc.get().by_id(self.profile))
|
||||
profile = tool.Ifc.get().by_id(self.profile)
|
||||
for material_set in profile.ToMaterialProfileSet:
|
||||
if len(material_set.MaterialProfiles) == 1:
|
||||
self.report({"ERROR"}, "At least one profile must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
|
||||
|
||||
|
||||
class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -294,7 +269,7 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
layer = ifcopenshell.api.run(
|
||||
"material.add_layer",
|
||||
self.file,
|
||||
**{
|
||||
@@ -303,6 +278,10 @@ class AddLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
},
|
||||
)
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
thickness = 0.1 # Arbitrary metric thickness for now
|
||||
layer.LayerThickness = thickness / unit_scale
|
||||
|
||||
|
||||
class ReorderMaterialSetItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reorder_material_set_item"
|
||||
@@ -336,10 +315,12 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
for inverse in tool.Ifc.get().get_inverse(tool.Ifc.get().by_id(self.layer)):
|
||||
if inverse.is_a("IfcMaterialLayerSet") and len(inverse.MaterialLayers) == 1:
|
||||
return
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=tool.Ifc.get().by_id(self.layer))
|
||||
layer = tool.Ifc.get().by_id(self.layer)
|
||||
for material_set in layer.ToMaterialLayerSet:
|
||||
if len(material_set.MaterialLayers) == 1:
|
||||
self.report({"ERROR"}, "At least one layer must exist")
|
||||
return {"ERROR"}
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
|
||||
|
||||
|
||||
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
@@ -85,24 +85,10 @@ def get_materials(self, context):
|
||||
return ObjectMaterialData.data["materials"]
|
||||
|
||||
|
||||
def get_object_material_types(self, context):
|
||||
global materialtypes_enum
|
||||
if len(materialtypes_enum) == 0 and IfcStore.get_file():
|
||||
material_types = [
|
||||
"IfcMaterial",
|
||||
"IfcMaterialConstituentSet",
|
||||
"IfcMaterialLayerSet",
|
||||
"IfcMaterialLayerSetUsage",
|
||||
"IfcMaterialProfileSet",
|
||||
"IfcMaterialProfileSetUsage",
|
||||
"IfcMaterialList",
|
||||
]
|
||||
version = tool.Ifc.get_schema()
|
||||
if version == "IFC2X3":
|
||||
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialLayerSetUsage", "IfcMaterialList"]
|
||||
materialtypes_enum.clear()
|
||||
materialtypes_enum = [(m, m, get_entity_doc(version, m).get("description", "")) for m in material_types]
|
||||
return materialtypes_enum
|
||||
def get_object_material_type(self, context):
|
||||
if not ObjectMaterialData.is_loaded:
|
||||
ObjectMaterialData.load()
|
||||
return ObjectMaterialData.data["material_type"]
|
||||
|
||||
|
||||
def get_material_types(self, context):
|
||||
@@ -131,10 +117,13 @@ class BIMMaterialProperties(PropertyGroup):
|
||||
materials: CollectionProperty(name="Materials", type=Material)
|
||||
active_material_index: IntProperty(name="Active Material Index")
|
||||
profiles: EnumProperty(items=get_profiles, name="Profiles")
|
||||
active_material_id: IntProperty(name="Active Material ID")
|
||||
material_attributes: CollectionProperty(name="Material Attributes", type=Attribute)
|
||||
editing_material_type = StringProperty(name="Editing Material Type")
|
||||
|
||||
|
||||
class BIMObjectMaterialProperties(PropertyGroup):
|
||||
material_type: EnumProperty(items=get_object_material_types, name="Material Type")
|
||||
material_type: EnumProperty(items=get_object_material_type, name="Material Type")
|
||||
material: EnumProperty(items=get_materials, name="Material")
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
material_set_usage_attributes: CollectionProperty(name="Material Set Usage Attributes", type=Attribute)
|
||||
|
||||
@@ -57,13 +57,21 @@ class BIM_PT_materials(Panel):
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.material_type == "IfcMaterial":
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
if self.props.active_material_id:
|
||||
row.operator("bim.edit_material", text="", icon="CHECKMARK").material = material.ifc_definition_id
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
|
||||
self.draw_editable_material_attributes_ui()
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
@@ -76,6 +84,9 @@ class BIM_PT_materials(Panel):
|
||||
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
|
||||
|
||||
|
||||
def draw_editable_material_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
|
||||
|
||||
class BIM_PT_material(Panel):
|
||||
bl_label = "IFC Material"
|
||||
bl_idname = "BIM_PT_material"
|
||||
@@ -144,8 +155,7 @@ class BIM_PT_object_material(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "material_type", text="")
|
||||
if self.props.material_type == "IfcMaterial" or self.props.material_type == "IfcMaterialList":
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
prop_with_search(row, self.props, "material", text="")
|
||||
row.operator("bim.assign_material", icon="ADD", text="")
|
||||
|
||||
def draw_material_ui(self):
|
||||
|
||||
@@ -30,7 +30,6 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMMiscProperties
|
||||
|
||||
row = layout.split(factor=0.2, align=True)
|
||||
row.prop(props, "override_colour", text="")
|
||||
row.operator("bim.set_override_colour")
|
||||
@@ -49,6 +48,8 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.draw_system_arrows")
|
||||
row = layout.row()
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
|
||||
@@ -86,6 +86,7 @@ classes = (
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
profile.Rotate90,
|
||||
profile.PatchNonParametricMepSegment,
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
slab.DisableEditingSketchExtrusionProfile,
|
||||
@@ -96,6 +97,8 @@ classes = (
|
||||
slab.ResetVertex,
|
||||
slab.SetArcIndex,
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -104,7 +107,6 @@ classes = (
|
||||
prop.BIMDoorProperties,
|
||||
prop.BIMRailingProperties,
|
||||
prop.BIMRoofProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_array,
|
||||
ui.BIM_PT_stair,
|
||||
ui.BIM_PT_sverchok,
|
||||
@@ -114,6 +116,7 @@ classes = (
|
||||
ui.BIM_PT_roof,
|
||||
ui.LaunchTypeManager,
|
||||
ui.BIM_MT_model,
|
||||
ui.BIM_PT_Grids,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_clever_stair,
|
||||
|
||||
@@ -86,9 +86,10 @@ class EnableEditingArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Array", "Data"))[self.item]
|
||||
props = obj.BIMArrayProperties
|
||||
props.count = data["count"]
|
||||
props.x = data["x"]
|
||||
props.y = data["y"]
|
||||
props.z = data["z"]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props.x = data["x"] * si_conversion
|
||||
props.y = data["y"] * si_conversion
|
||||
props.z = data["z"] * si_conversion
|
||||
props.use_local_space = data.get("use_local_space", False)
|
||||
props.sync_children = data.get("sync_children", False)
|
||||
props.method = data.get("method", "OFFSET")
|
||||
@@ -106,15 +107,16 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMArrayProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item] = {
|
||||
"children": data[self.item]["children"],
|
||||
"count": props.count,
|
||||
"x": props.x,
|
||||
"y": props.y,
|
||||
"z": props.z,
|
||||
"x": props.x / si_conversion,
|
||||
"y": props.y / si_conversion,
|
||||
"z": props.z / si_conversion,
|
||||
"use_local_space": props.use_local_space,
|
||||
"sync_children": props.sync_children,
|
||||
"method": props.method,
|
||||
@@ -187,6 +189,7 @@ class SelectArrayParent(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Input3DCursorXArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_x_array"
|
||||
bl_label = "Get 3d Cursor X Input for Array"
|
||||
@@ -202,6 +205,7 @@ class Input3DCursorXArray(bpy.types.Operator):
|
||||
props.x = cursor.location.x - obj.location.x
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Input3DCursorYArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_y_array"
|
||||
bl_label = "Get 3d Cursor Y Input for Array"
|
||||
@@ -217,6 +221,7 @@ class Input3DCursorYArray(bpy.types.Operator):
|
||||
props.y = cursor.location.y - obj.location.y
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class Input3DCursorZArray(bpy.types.Operator):
|
||||
bl_idname = "bim.input_cursor_z_array"
|
||||
bl_label = "Get 3d Cursor Z Input for Array"
|
||||
|
||||
@@ -59,9 +59,19 @@ class AuthoringData:
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail()
|
||||
cls.data["is_voidable_element"] = cls.is_voidable_element()
|
||||
cls.data["has_visible_openings"] = cls.has_visible_openings()
|
||||
cls.data["has_visible_boundaries"] = cls.has_visible_boundaries()
|
||||
cls.data["active_class"] = cls.active_class()
|
||||
cls.data["active_material_usage"] = cls.active_material_usage()
|
||||
cls.data["active_representation_type"] = cls.active_representation_type()
|
||||
cls.data["boundary_class"] = cls.boundary_class()
|
||||
|
||||
@classmethod
|
||||
def boundary_class(cls):
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcRelSpaceBoundary")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
version = tool.Ifc.get_schema()
|
||||
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)]
|
||||
|
||||
@classmethod
|
||||
def type_class(cls):
|
||||
@@ -69,7 +79,10 @@ class AuthoringData:
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names = [d.name() for d in declarations]
|
||||
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialStructureElementType")
|
||||
else:
|
||||
declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType")
|
||||
declarations = ifcopenshell.util.schema.get_subtypes(declaration)
|
||||
names.extend([d.name() for d in declarations])
|
||||
|
||||
@@ -161,6 +174,17 @@ class AuthoringData:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def has_visible_boundaries(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
if element.is_a("IfcRelSpaceBoundary"):
|
||||
return True
|
||||
for boundary in getattr(element, "BoundedBy", []):
|
||||
if tool.Ifc.get_object(boundary):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def active_class(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
@@ -177,7 +201,7 @@ class AuthoringData:
|
||||
def active_representation_type(cls):
|
||||
if bpy.context.active_object:
|
||||
representation = tool.Geometry.get_active_representation(bpy.context.active_object)
|
||||
if representation:
|
||||
if representation and representation.is_a("IfcShapeRepresentation"):
|
||||
return representation.RepresentationType
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -82,14 +82,14 @@ class ProfileDecorator:
|
||||
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
|
||||
|
||||
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
|
||||
faces_color = transparent_color(self.model_props.decorator_color_special)
|
||||
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
|
||||
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
|
||||
|
||||
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
|
||||
self.model_props = context.scene.BIMModelProperties
|
||||
selected_elements_color = self.model_props.decorator_color_selected
|
||||
unselected_elements_color = self.model_props.decorator_color_unselected
|
||||
special_elements_color = self.model_props.decorator_color_special
|
||||
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
|
||||
|
||||
obj = context.active_object
|
||||
|
||||
@@ -214,8 +214,8 @@ class ProfileDecorator:
|
||||
|
||||
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
|
||||
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
|
||||
self.draw_batch("POINTS", special_vertices, self.model_props.decorator_color_special)
|
||||
self.draw_batch("POINTS", selected_vertices, self.model_props.decorator_color_selected)
|
||||
self.draw_batch("POINTS", special_vertices, special_elements_color)
|
||||
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
|
||||
|
||||
# Draw arcs
|
||||
arc_centroids = []
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
import bmesh
|
||||
from bmesh.types import BMVert, BMFace
|
||||
|
||||
@@ -27,8 +26,6 @@ from ifcopenshell.util.shape_builder import V
|
||||
import blenderbim
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry as core
|
||||
from blenderbim.bim.helper import convert_property_group_from_si
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.model.window import create_bm_window, create_bm_box, update_simple_openings
|
||||
|
||||
from mathutils import Vector, Matrix
|
||||
@@ -44,32 +41,31 @@ def update_door_modifier_representation(context):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
ifc_file = tool.Ifc.get()
|
||||
sliding_door = "SLIDING" in props.door_type
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
representation_data = {
|
||||
"operation_type": props.door_type,
|
||||
"overall_height": props.overall_height,
|
||||
"overall_width": props.overall_width,
|
||||
"overall_height": props.overall_height / si_conversion,
|
||||
"overall_width": props.overall_width / si_conversion,
|
||||
"lining_properties": {
|
||||
"LiningDepth": props.lining_depth,
|
||||
"LiningThickness": props.lining_thickness,
|
||||
"LiningOffset": props.lining_offset,
|
||||
"LiningToPanelOffsetX": props.lining_to_panel_offset_x,
|
||||
"LiningToPanelOffsetY": props.lining_to_panel_offset_y,
|
||||
"TransomThickness": props.transom_thickness,
|
||||
"TransomOffset": props.transom_offset,
|
||||
"transomThickness": props.transom_thickness,
|
||||
"TransomOffset": props.transom_offset,
|
||||
"CasingThickness": props.casing_thickness,
|
||||
"CasingDepth": props.casing_depth,
|
||||
"ThresholdThickness": props.threshold_thickness,
|
||||
"ThresholdDepth": props.threshold_depth,
|
||||
"ThresholdOffset": props.threshold_offset,
|
||||
"LiningDepth": props.lining_depth / si_conversion,
|
||||
"LiningThickness": props.lining_thickness / si_conversion,
|
||||
"LiningOffset": props.lining_offset / si_conversion,
|
||||
"LiningToPanelOffsetX": props.lining_to_panel_offset_x / si_conversion,
|
||||
"LiningToPanelOffsetY": props.lining_to_panel_offset_y / si_conversion,
|
||||
"TransomThickness": props.transom_thickness / si_conversion,
|
||||
"TransomOffset": props.transom_offset / si_conversion,
|
||||
"CasingThickness": props.casing_thickness / si_conversion,
|
||||
"CasingDepth": props.casing_depth / si_conversion,
|
||||
"ThresholdThickness": props.threshold_thickness / si_conversion,
|
||||
"ThresholdDepth": props.threshold_depth / si_conversion,
|
||||
"ThresholdOffset": props.threshold_offset / si_conversion,
|
||||
},
|
||||
"panel_properties": {
|
||||
"PanelDepth": props.panel_depth,
|
||||
"PanelDepth": props.panel_depth / si_conversion,
|
||||
"PanelWidth": props.panel_width_ratio,
|
||||
"FrameDepth": props.frame_depth,
|
||||
"FrameThickness": props.frame_thickness,
|
||||
"FrameDepth": props.frame_depth / si_conversion,
|
||||
"FrameThickness": props.frame_thickness / si_conversion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -132,15 +128,16 @@ def update_door_modifier_representation(context):
|
||||
)
|
||||
|
||||
# type attributes
|
||||
element.OperationType = props.door_type
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width
|
||||
occurence.OverallHeight = props.overall_height
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width, props.overall_height)
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
# TODO: move it out to tools
|
||||
@@ -281,27 +278,26 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector =
|
||||
def update_door_modifier_bmesh(context):
|
||||
obj = context.object
|
||||
props = obj.BIMDoorProperties
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
overall_width = props.overall_width * si_conversion
|
||||
overall_height = props.overall_height * si_conversion
|
||||
overall_width = props.overall_width
|
||||
overall_height = props.overall_height
|
||||
door_type = props.door_type
|
||||
double_swing_door = "DOUBLE_SWING" in door_type
|
||||
double_door = "DOUBLE_DOOR" in door_type
|
||||
sliding_door = "SLIDING" in door_type
|
||||
|
||||
# lining params
|
||||
lining_depth = props.lining_depth * si_conversion
|
||||
lining_thickness_default = props.lining_thickness * si_conversion
|
||||
lining_offset = props.lining_offset * si_conversion
|
||||
lining_depth = props.lining_depth
|
||||
lining_thickness_default = props.lining_thickness
|
||||
lining_offset = props.lining_offset
|
||||
lining_to_panel_offset_x = (
|
||||
props.lining_to_panel_offset_x * si_conversion if not sliding_door else lining_thickness_default
|
||||
props.lining_to_panel_offset_x if not sliding_door else lining_thickness_default
|
||||
)
|
||||
panel_depth = props.panel_depth * si_conversion
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y * si_conversion if not sliding_door else -panel_depth
|
||||
panel_depth = props.panel_depth
|
||||
lining_to_panel_offset_y = props.lining_to_panel_offset_y if not sliding_door else -panel_depth
|
||||
|
||||
transom_thickness = props.transom_thickness * si_conversion / 2
|
||||
transfom_offset = props.transom_offset * si_conversion
|
||||
transom_thickness = props.transom_thickness / 2
|
||||
transfom_offset = props.transom_offset
|
||||
if transom_thickness == 0:
|
||||
transfom_offset = 0
|
||||
window_lining_height = overall_height - transfom_offset - transom_thickness
|
||||
@@ -316,24 +312,24 @@ def update_door_modifier_bmesh(context):
|
||||
side_lining_thickness = side_lining_thickness - panel_lining_overlap_x
|
||||
top_lining_thickness = top_lining_thickness - panel_top_lining_overlap_x
|
||||
|
||||
threshold_thickness = props.threshold_thickness * si_conversion
|
||||
threshold_depth = props.threshold_depth * si_conversion
|
||||
threshold_offset = props.threshold_offset * si_conversion
|
||||
threshold_thickness = props.threshold_thickness
|
||||
threshold_depth = props.threshold_depth
|
||||
threshold_offset = props.threshold_offset
|
||||
threshold_width = overall_width - side_lining_thickness * 2
|
||||
|
||||
casing_thickness = props.casing_thickness * si_conversion
|
||||
casing_depth = props.casing_depth * si_conversion
|
||||
casing_thickness = props.casing_thickness
|
||||
casing_depth = props.casing_depth
|
||||
|
||||
# panel params
|
||||
panel_width = door_opening_width * props.panel_width_ratio
|
||||
frame_depth = props.frame_depth * si_conversion
|
||||
frame_thickness = props.frame_thickness * si_conversion
|
||||
frame_depth = props.frame_depth
|
||||
frame_thickness = props.frame_thickness
|
||||
frame_height = window_lining_height - lining_to_panel_offset_x * 2
|
||||
glass_thickness = 0.01 * si_conversion
|
||||
glass_thickness = 0.01
|
||||
|
||||
# handle dimensions (hardcoded)
|
||||
handle_size = V(120, 40, 20) * 0.001 * si_conversion
|
||||
handle_offset = V(60, 0, 1000) * 0.001 * si_conversion # to the handle center
|
||||
handle_size = V(120, 40, 20) * 0.001
|
||||
handle_offset = V(60, 0, 1000) * 0.001 # to the handle center
|
||||
handle_center_offset = V(handle_size.y / 2, 0, handle_size.z) / 2
|
||||
|
||||
if transfom_offset:
|
||||
@@ -418,7 +414,7 @@ def update_door_modifier_bmesh(context):
|
||||
|
||||
if double_door:
|
||||
# keeping a little space between doors for readibility
|
||||
double_door_offset = 0.001 * si_conversion
|
||||
double_door_offset = 0.001
|
||||
panel_size.x = panel_size.x / 2 - double_door_offset
|
||||
door_verts.extend(create_bm_door_panel(panel_size, panel_position, "LEFT"))
|
||||
|
||||
@@ -488,10 +484,15 @@ class BIM_OT_add_door(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcDoor")
|
||||
obj = bpy.data.objects.new("IfcDoor", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
element = blenderbim.core.root.assign_class(
|
||||
tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcDoor", should_add_representation=False
|
||||
)
|
||||
element.PredefinedType = "DOOR"
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PredefinedType = "DOOR"
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.context.view_layer.objects.active = None
|
||||
@@ -512,18 +513,13 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType type to add a door.")
|
||||
if element.is_a() not in ("IfcDoor", "IfcDoorType", "IfcDoorStyle"):
|
||||
self.report({"ERROR"}, "Object has to be IfcDoor/IfcDoorType/IfcDoorStyle type to add a door.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.door_added_previously:
|
||||
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
door_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
|
||||
door_data["lining_properties"] = lining_props
|
||||
door_data["panel_properties"] = panel_props
|
||||
@@ -550,11 +546,13 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
props = obj.BIMDoorProperties
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data"))
|
||||
data.update(data.pop("lining_properties"))
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -581,9 +579,9 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMDoorProperties
|
||||
|
||||
door_data = props.get_general_kwargs()
|
||||
lining_props = props.get_lining_kwargs()
|
||||
panel_props = props.get_panel_kwargs()
|
||||
door_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
lining_props = props.get_lining_kwargs(convert_to_project_units=True)
|
||||
panel_props = props.get_panel_kwargs(convert_to_project_units=True)
|
||||
|
||||
door_data["lining_properties"] = lining_props
|
||||
door_data["panel_properties"] = panel_props
|
||||
@@ -612,15 +610,7 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data.update(data.pop("panel_properties"))
|
||||
|
||||
# required since we could load pset from .ifc and BIMDoorProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = ("is_editing", "door_type", "door_added_previously", "panel_width_ratio")
|
||||
skip_props += tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -638,7 +628,6 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Door")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.door_added_previously = True
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -30,32 +30,20 @@ def add_object(self, context):
|
||||
obj = bpy.data.objects.new("Grid", None)
|
||||
obj.name = "Grid"
|
||||
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
has_site_collection = False
|
||||
for child in context.view_layer.layer_collection.children:
|
||||
if "IfcProject/" not in child.name:
|
||||
continue
|
||||
for grandchild in child.children:
|
||||
if "IfcSite/" not in grandchild.name:
|
||||
continue
|
||||
has_site_collection = True
|
||||
grandchild.collection.children.link(collection)
|
||||
break
|
||||
if not has_site_collection:
|
||||
context.view_layer.active_layer_collection.collection.children.link(collection)
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
site = tool.Ifc.get().by_type("IfcSite")
|
||||
if site:
|
||||
site = site[0]
|
||||
site_obj = tool.Ifc.get_object(site)
|
||||
if site_obj:
|
||||
collection = site_obj.BIMObjectProperties.collection or collection
|
||||
|
||||
collection.objects.link(obj)
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
if self.file:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
|
||||
collection.name = obj.name
|
||||
grid = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if has_site_collection:
|
||||
site_obj = bpy.data.objects.get(grandchild.name)
|
||||
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=site_obj, element_obj=obj
|
||||
)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid")
|
||||
grid = tool.Ifc.get_entity(obj)
|
||||
|
||||
collection = obj.BIMObjectProperties.collection or collection
|
||||
|
||||
axes_collection = bpy.data.collections.new("UAxes")
|
||||
collection.children.link(axes_collection)
|
||||
@@ -73,15 +61,14 @@ def add_object(self, context):
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
|
||||
if self.file:
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
self.file,
|
||||
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
tool.Ifc.get(),
|
||||
**{"axis_tag": tag, "uvw_axes": "UAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
axes_collection = bpy.data.collections.new("VAxes")
|
||||
collection.children.link(axes_collection)
|
||||
@@ -99,18 +86,17 @@ def add_object(self, context):
|
||||
|
||||
axes_collection.objects.link(obj)
|
||||
|
||||
if self.file:
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
self.file,
|
||||
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
result = ifcopenshell.api.run(
|
||||
"grid.create_grid_axis",
|
||||
tool.Ifc.get(),
|
||||
**{"axis_tag": tag, "uvw_axes": "VAxes", "grid": grid},
|
||||
)
|
||||
IfcStore.link_element(result, obj)
|
||||
ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), **{"axis_curve": obj, "grid_axis": result})
|
||||
obj.BIMObjectProperties.ifc_definition_id = result.id()
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator):
|
||||
class BIM_OT_add_object(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "mesh.add_grid"
|
||||
bl_label = "IFC Grid"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -122,10 +108,7 @@ class BIM_OT_add_object(Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
return tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
add_object(self, context)
|
||||
|
||||
@@ -232,9 +232,16 @@ class FilledOpeningGenerator:
|
||||
curve_3d = ifcopenshell.util.representation.resolve_representation(profile).Items[0]
|
||||
|
||||
def get_curve_2d_from_3d(curve_3d):
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = ifc_file.createIfcCartesianPointList2D([Vector(p).xz for p in curve_3d.Points.CoordList])
|
||||
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
coords = [Vector(p).xz for p in shape_builder.get_polyline_coords(curve_3d)]
|
||||
ifc_curve = shape_builder.polyline(coords, closed=True)
|
||||
else:
|
||||
# using different algorithm to keep arc segments possible in the future
|
||||
ifc_segments = [shape_builder.deep_copy(s) for s in curve_3d.Segments]
|
||||
ifc_points = ifc_file.createIfcCartesianPointList2D(
|
||||
[Vector(p).xz for p in curve_3d.Points.CoordList]
|
||||
)
|
||||
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
|
||||
return ifc_curve
|
||||
|
||||
extrusion = shape_builder.extrude(
|
||||
@@ -657,7 +664,7 @@ class ShowOpenings(Operator, tool.Ifc.Operator):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
if not element or not getattr(element, "HasOpenings", None):
|
||||
continue
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
@@ -682,7 +689,6 @@ class HideOpenings(Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
to_delete = set()
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -694,7 +700,7 @@ class HideOpenings(Operator, tool.Ifc.Operator):
|
||||
if opening_obj:
|
||||
to_delete.add(opening_obj)
|
||||
for opening_obj in to_delete:
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
tool.Ifc.unlink(obj=opening_obj)
|
||||
bpy.data.objects.remove(opening_obj)
|
||||
tool.Model.clear_scene_openings()
|
||||
return {"FINISHED"}
|
||||
@@ -793,10 +799,10 @@ class DecorationsHandler:
|
||||
batch.draw(shader)
|
||||
|
||||
def __call__(self, context):
|
||||
self.model_props = context.scene.BIMModelProperties
|
||||
selected_elements_color = self.model_props.decorator_color_selected
|
||||
unselected_elements_color = self.model_props.decorator_color_unselected
|
||||
special_elements_color = self.model_props.decorator_color_special
|
||||
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]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.aggregate
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -67,7 +68,7 @@ class PieUpdateContainer(bpy.types.Operator):
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
spatial_obj = collection.BIMCollectionProperties.obj
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
@@ -86,6 +87,8 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
pie.operator("bim.assign_object", text ="Assign Aggregation")
|
||||
pie.operator("bim.unassign_object", text ="Unassign Aggregation")
|
||||
|
||||
|
||||
class VIEW3D_MT_PIE_bim_class(bpy.types.Menu):
|
||||
|
||||
@@ -37,12 +37,6 @@ from . import prop
|
||||
import json
|
||||
|
||||
|
||||
def select_and_activate_single_object(context, obj):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
|
||||
|
||||
class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
bl_idname = "bim.add_empty_type"
|
||||
bl_label = "Add Empty Type"
|
||||
@@ -52,7 +46,7 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
obj = bpy.data.objects.new("TYPEX", None)
|
||||
context.scene.collection.objects.link(obj)
|
||||
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
|
||||
select_and_activate_single_object(context, obj)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -97,7 +91,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
if self.generate_layered_element(ifc_class, relating_type):
|
||||
select_and_activate_single_object(context, context.selected_objects[-1])
|
||||
tool.Blender.select_and_activate_single_object(context, context.selected_objects[-1])
|
||||
return {"FINISHED"}
|
||||
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
|
||||
if mep.MepGenerator(relating_type).generate():
|
||||
@@ -133,9 +127,10 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(relating_type, instance_class), mesh)
|
||||
|
||||
obj.location = context.scene.cursor.location
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -144,6 +139,26 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
# Update required as core.type.assign_type may change obj.data
|
||||
context.view_layer.update()
|
||||
|
||||
if (
|
||||
building_obj
|
||||
and building_element
|
||||
and building_element.is_a() in ["IfcWall", "IfcWallStandardCase", "IfcCovering"]
|
||||
and instance_class in ["IfcWindow", "IfcDoor"]
|
||||
):
|
||||
# Fills should be a sibling to the building element
|
||||
parent = ifcopenshell.util.element.get_aggregate(building_element)
|
||||
if parent:
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=parent_obj, related_obj=obj
|
||||
)
|
||||
else:
|
||||
parent = ifcopenshell.util.element.get_container(building_element)
|
||||
parent_obj = tool.Ifc.get_object(parent)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurences properties for the types defined with modifiers
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
pset_name = f"BBIM_{instance_class[3:]}"
|
||||
@@ -179,7 +194,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
if ifc_class == "IfcDoorType" and len(context.selected_objects) >= 1:
|
||||
pass
|
||||
else:
|
||||
select_and_activate_single_object(context, obj)
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
@staticmethod
|
||||
@@ -277,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
if bpy.app.background:
|
||||
return
|
||||
|
||||
@@ -295,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
|
||||
offset = 0
|
||||
queue = queue[offset : offset + 9]
|
||||
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
while queue:
|
||||
# if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation
|
||||
element = queue.pop()
|
||||
obj = tool.Ifc.get_object(element)
|
||||
|
||||
if not obj:
|
||||
continue # Nothing to process
|
||||
elif AuthoringData.type_thumbnails.get(element.id(), None):
|
||||
continue # Already processed
|
||||
elif obj.preview and obj.preview.icon_id:
|
||||
AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id
|
||||
continue
|
||||
|
||||
if obj.data:
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
else:
|
||||
size = 128
|
||||
img = Image.new("RGBA", (size, size))
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
material = ifcopenshell.util.element.get_material(element)
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
profile = material.MaterialProfiles[0].Profile
|
||||
tool.Profile.draw_image_for_ifc_profile(draw, profile, size)
|
||||
|
||||
elif material and material.is_a("IfcMaterialLayerSet"):
|
||||
thicknesses = [l.LayerThickness for l in material.MaterialLayers]
|
||||
total_thickness = sum(thicknesses)
|
||||
si_total_thickness = total_thickness * unit_scale
|
||||
if si_total_thickness <= 0.051:
|
||||
width = 10
|
||||
elif si_total_thickness <= 0.11:
|
||||
width = 20
|
||||
elif si_total_thickness <= 0.21:
|
||||
width = 30
|
||||
elif si_total_thickness <= 0.31:
|
||||
width = 40
|
||||
else:
|
||||
width = 50
|
||||
|
||||
height = 100
|
||||
|
||||
is_horizontal = False
|
||||
if element.is_a("IfcSlabType"):
|
||||
is_horizontal = True
|
||||
|
||||
parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric")
|
||||
if parametric:
|
||||
layer_set_direction = parametric.get("LayerSetDirection", None)
|
||||
if layer_set_direction == "AXIS2":
|
||||
is_horizontal = False
|
||||
elif layer_set_direction == "AXIS3":
|
||||
is_horizontal = True
|
||||
|
||||
if is_horizontal:
|
||||
width, height = height, width
|
||||
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
current_thickness = 0
|
||||
del thicknesses[-1]
|
||||
for thickness in thicknesses:
|
||||
current_thickness += thickness
|
||||
if element.is_a("IfcSlabType"):
|
||||
y = (current_thickness / total_thickness) * height
|
||||
line = [x_offset, y_offset + y, x_offset + width, y_offset + y]
|
||||
else:
|
||||
x = (current_thickness / total_thickness) * width
|
||||
line = [x_offset + x, y_offset, x_offset + x, y_offset + height]
|
||||
draw.line(line, fill="white", width=2)
|
||||
elif False:
|
||||
# TODO: things like parametric duct segments
|
||||
pass
|
||||
elif not element.RepresentationMaps:
|
||||
# Empties are represented by a generic thumbnail
|
||||
width = height = 100
|
||||
x_offset = (size / 2) - (width / 2)
|
||||
y_offset = (size / 2) - (height / 2)
|
||||
draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2)
|
||||
draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2)
|
||||
draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5)
|
||||
else:
|
||||
draw.line([0, 0, size, size], fill="red", width=2)
|
||||
draw.line([0, size, size, 0], fill="red", width=2)
|
||||
|
||||
pixels = [item for sublist in img.getdata() for item in sublist]
|
||||
|
||||
obj.asset_generate_preview()
|
||||
while not obj.preview:
|
||||
pass
|
||||
|
||||
obj.preview.image_size = size, size
|
||||
obj.preview.image_pixels_float = pixels
|
||||
|
||||
queue.append(element)
|
||||
if tool.Model.update_thumbnail_for_element(element):
|
||||
queue.append(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -456,12 +374,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
obj.matrix_world = newmat
|
||||
|
||||
def copy_obj(self, obj):
|
||||
new = obj.copy()
|
||||
new.data = wall2.data.copy()
|
||||
wall1.users_collection[0].objects.link(wall2)
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
|
||||
|
||||
|
||||
def generate_box(usecase_path, ifc_file, settings):
|
||||
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
|
||||
|
||||
@@ -28,6 +28,7 @@ import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.core.material
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix, Quaternion
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
@@ -53,8 +54,8 @@ class DumbProfileGenerator:
|
||||
self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW")
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.depth = props.extrusion_depth * self.unit_scale
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.depth = props.extrusion_depth
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.cardinal_point = int(bpy.context.scene.BIMModelProperties.cardinal_point)
|
||||
@@ -144,12 +145,21 @@ class DumbProfileRegenerator:
|
||||
objs = []
|
||||
if not profile:
|
||||
return
|
||||
|
||||
element_types = set()
|
||||
for element in self.get_elements_using_profile(profile):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
if element.is_a("IfcElementType"):
|
||||
element_types.add(element)
|
||||
|
||||
DumbProfileRecalculator().recalculate(objs)
|
||||
|
||||
# update related thumbnails
|
||||
for element in self.get_element_types_using_profile(profile):
|
||||
tool.Model.update_thumbnail_for_element(element, refresh=True)
|
||||
|
||||
def regenerate_from_profile(self, usecase_path, ifc_file, settings):
|
||||
self.file = ifc_file
|
||||
objs = []
|
||||
@@ -164,9 +174,10 @@ class DumbProfileRegenerator:
|
||||
|
||||
def get_elements_using_profile(self, profile):
|
||||
results = []
|
||||
for profile_set in [
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]:
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcMaterialProfileSetUsage"):
|
||||
continue
|
||||
@@ -180,6 +191,18 @@ class DumbProfileRegenerator:
|
||||
results.extend(rel.RelatedObjects)
|
||||
return results
|
||||
|
||||
def get_element_types_using_profile(self, profile):
|
||||
results = []
|
||||
profile_sets = [
|
||||
mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile")
|
||||
]
|
||||
for profile_set in profile_sets:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
results.extend(inverse.RelatedObjects)
|
||||
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:
|
||||
@@ -857,6 +880,25 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PatchNonParametricMepSegment(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.patch_non_parametric_mep_segment"
|
||||
bl_label = "Set MEP segment Material Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object
|
||||
|
||||
def _execute(self, context):
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
blenderbim.core.material.patch_non_parametric_mep_segment(
|
||||
tool.Ifc, tool.Material, tool.Profile, obj=context.active_object
|
||||
)
|
||||
bpy.ops.bim.enable_editing_extrusion_axis()
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
styles = tool.Geometry.get_styles(context.active_object)
|
||||
|
||||
|
||||
class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_extrusion_axis"
|
||||
bl_label = "Enable Editing Extrusion Axis"
|
||||
@@ -927,7 +969,7 @@ class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.selected_objects
|
||||
|
||||
def _execute(self, context):
|
||||
return disable_editing_extrusion_axis()
|
||||
return disable_editing_extrusion_axis(context)
|
||||
|
||||
|
||||
class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -948,6 +990,10 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
|
||||
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()
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ def get_type_class(self, context):
|
||||
return AuthoringData.data["type_class"]
|
||||
|
||||
|
||||
def get_boundary_class(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["boundary_class"]
|
||||
|
||||
|
||||
def get_relating_type_id(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
@@ -51,7 +57,7 @@ def get_type_predefined_type(self, context):
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_types()
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail()
|
||||
|
||||
|
||||
@@ -63,6 +69,10 @@ def update_type_class(self, context):
|
||||
AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types()
|
||||
AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type()
|
||||
|
||||
type_class = self.type_class
|
||||
if (type_class, type_class, "") in get_ifc_class(self, context):
|
||||
self.ifc_class = type_class
|
||||
|
||||
|
||||
def update_relating_type_id(self, context):
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
@@ -76,7 +86,7 @@ def update_type_page(self, context):
|
||||
class BIMModelProperties(PropertyGroup):
|
||||
ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="Construction Class", update=update_ifc_class)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
items=get_relating_type_id, name="Construction Type", update=update_relating_type_id
|
||||
items=get_relating_type_id, name="Relating Type", update=update_relating_type_id
|
||||
)
|
||||
icon_id: bpy.props.IntProperty()
|
||||
updating: bpy.props.BoolProperty(default=False)
|
||||
@@ -120,7 +130,7 @@ class BIMModelProperties(PropertyGroup):
|
||||
z: bpy.props.FloatProperty(name="Z", default=0.5)
|
||||
rl1: bpy.props.FloatProperty(name="RL", default=1) # Used for things like walls, doors, flooring, skirting, etc
|
||||
rl2: bpy.props.FloatProperty(name="RL", default=1) # Used for things like windows, other hosted furniture
|
||||
x_angle: bpy.props.FloatProperty(name="X Angle", default=0)
|
||||
x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE")
|
||||
type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page)
|
||||
type_template: bpy.props.EnumProperty(
|
||||
items=(
|
||||
@@ -141,33 +151,7 @@ class BIMModelProperties(PropertyGroup):
|
||||
type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class)
|
||||
type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
decorator_color_selected: bpy.props.FloatVectorProperty(
|
||||
name="Selected Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(0.545, 0.863, 0, 1), # green
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of selected verts/edges (used in profile editing mode)",
|
||||
)
|
||||
decorator_color_unselected: bpy.props.FloatVectorProperty(
|
||||
name="Not Selected Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(1, 1, 1, 1), # green
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of not selected verts/edges (used in profile editing mode)",
|
||||
)
|
||||
decorator_color_special: bpy.props.FloatVectorProperty(
|
||||
name="Special Elements Color",
|
||||
subtype="COLOR",
|
||||
default=(0.157, 0.565, 1, 1), # blue
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=4,
|
||||
description="Color of special selected verts/edges (openings, preview verts/edges in roof editing, verts with arcs/circles in profile editing)",
|
||||
)
|
||||
boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class")
|
||||
|
||||
|
||||
class BIMArrayProperties(PropertyGroup):
|
||||
@@ -175,9 +159,9 @@ class BIMArrayProperties(PropertyGroup):
|
||||
default=-1, description="Currently edited array index. -1 if not in array editing mode."
|
||||
)
|
||||
count: bpy.props.IntProperty(name="Count", default=0, min=0)
|
||||
x: bpy.props.FloatProperty(name="X", default=0)
|
||||
y: bpy.props.FloatProperty(name="Y", default=0)
|
||||
z: bpy.props.FloatProperty(name="Z", default=0)
|
||||
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
|
||||
y: bpy.props.FloatProperty(name="Y", default=0, subtype="DISTANCE")
|
||||
z: bpy.props.FloatProperty(name="Z", default=0, subtype="DISTANCE")
|
||||
use_local_space: bpy.props.BoolProperty(
|
||||
name="Use Local Space",
|
||||
description="Use local space for array items offset instead of world space",
|
||||
@@ -196,25 +180,25 @@ class BIMArrayProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMStairProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "number_of_treads", "has_top_nib", "stair_type")
|
||||
stair_types = (
|
||||
("CONCRETE", "Concrete", ""),
|
||||
("WOOD/STEEL", "Wood / Steel", ""),
|
||||
("GENERIC", "Generic", ""),
|
||||
)
|
||||
|
||||
stair_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01)
|
||||
width: bpy.props.FloatProperty(name="Width", default=1.2, soft_min=0.01, subtype="DISTANCE")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, soft_min=0.01, subtype="DISTANCE")
|
||||
number_of_treads: bpy.props.IntProperty(name="Number of treads", default=6, soft_min=1)
|
||||
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01)
|
||||
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01)
|
||||
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0)
|
||||
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0)
|
||||
tread_depth: bpy.props.FloatProperty(name="Tread Depth", default=0.25, soft_min=0.01, subtype="DISTANCE")
|
||||
tread_run: bpy.props.FloatProperty(name="Tread Run", default=0.3, soft_min=0.01, subtype="DISTANCE")
|
||||
base_slab_depth: bpy.props.FloatProperty(name="Base slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
|
||||
top_slab_depth: bpy.props.FloatProperty(name="Top slab depth", default=0.25, soft_min=0, subtype="DISTANCE")
|
||||
has_top_nib: bpy.props.BoolProperty(name="Has top nib", default=True)
|
||||
stair_type: bpy.props.EnumProperty(name="Stair type", items=stair_types, default="CONCRETE")
|
||||
|
||||
def get_props_kwargs(self):
|
||||
def get_props_kwargs(self, convert_to_project_units=False):
|
||||
stair_kwargs = {
|
||||
"stair_type": self.stair_type,
|
||||
"width": self.width,
|
||||
@@ -224,27 +208,34 @@ class BIMStairProperties(PropertyGroup):
|
||||
}
|
||||
|
||||
if self.stair_type == "CONCRETE":
|
||||
stair_kwargs.update(
|
||||
{
|
||||
"base_slab_depth": self.base_slab_depth,
|
||||
"top_slab_depth": self.top_slab_depth,
|
||||
"has_top_nib": self.has_top_nib,
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
concrete_props = {
|
||||
"base_slab_depth": self.base_slab_depth,
|
||||
"top_slab_depth": self.top_slab_depth,
|
||||
"has_top_nib": self.has_top_nib,
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
stair_kwargs.update(concrete_props)
|
||||
|
||||
elif self.stair_type == "WOOD/STEEL":
|
||||
stair_kwargs.update(
|
||||
{
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
)
|
||||
return stair_kwargs
|
||||
wood_steel_props = {
|
||||
"tread_depth": self.tread_depth,
|
||||
}
|
||||
stair_kwargs.update(wood_steel_props)
|
||||
|
||||
elif self.stair_type == "GENERIC":
|
||||
pass
|
||||
|
||||
if not convert_to_project_units:
|
||||
return stair_kwargs
|
||||
|
||||
stair_kwargs = tool.Model.convert_data_to_project_units(stair_kwargs, self.non_si_units_props)
|
||||
return stair_kwargs
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMSverchokProperties(PropertyGroup):
|
||||
node_group: bpy.props.PointerProperty(name="Node Group", type=NodeTree)
|
||||
@@ -252,15 +243,13 @@ class BIMSverchokProperties(PropertyGroup):
|
||||
|
||||
def window_type_prop_update(self, context):
|
||||
number_of_panels, panels_data = self.window_types_panels[self.window_type]
|
||||
|
||||
si_coversion = 1.0 / ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
panels_data = [[v * si_coversion for v in data] for data in panels_data]
|
||||
self.first_mullion_offset, self.second_mullion_offset = panels_data[0]
|
||||
self.first_transom_offset, self.second_transom_offset = panels_data[1]
|
||||
|
||||
|
||||
# default prop values are in mm and converted later
|
||||
class BIMWindowProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "window_type")
|
||||
window_types = (
|
||||
("SINGLE_PANEL", "SINGLE_PANEL", ""),
|
||||
("DOUBLE_PANEL_HORIZONTAL", "DOUBLE_PANEL_HORIZONTAL", ""),
|
||||
@@ -288,55 +277,67 @@ class BIMWindowProperties(PropertyGroup):
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
window_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
window_type: bpy.props.EnumProperty(
|
||||
name="Window Type", items=window_types, default="SINGLE_PANEL", update=window_type_prop_update
|
||||
)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9)
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=0.9, subtype="DISTANCE")
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.6, subtype="DISTANCE")
|
||||
|
||||
# lining properties
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
|
||||
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
|
||||
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050)
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
|
||||
lining_offset: bpy.props.FloatProperty(name="Lining Offset", default=0.050, subtype="DISTANCE")
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(
|
||||
name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE"
|
||||
)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(
|
||||
name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE"
|
||||
)
|
||||
mullion_thickness: bpy.props.FloatProperty(name="Mullion Thickness", default=0.050, subtype="DISTANCE")
|
||||
first_mullion_offset: bpy.props.FloatProperty(
|
||||
name="First Mullion Offset",
|
||||
description="Distance from the first lining to the first mullion center",
|
||||
default=0.3,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
second_mullion_offset: bpy.props.FloatProperty(
|
||||
name="Second Mullion Offset",
|
||||
description="Distance from the first lining to the second mullion center",
|
||||
default=0.45,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050)
|
||||
transom_thickness: bpy.props.FloatProperty(name="Transom Thickness", default=0.050, subtype="DISTANCE")
|
||||
first_transom_offset: bpy.props.FloatProperty(
|
||||
name="First Transom Offset",
|
||||
description="Distance from the first lining to the first transom center",
|
||||
default=0.3,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
second_transom_offset: bpy.props.FloatProperty(
|
||||
name="Second Transom Offset",
|
||||
description="Distance from the first lining to the second transom center",
|
||||
default=0.6,
|
||||
subtype="DISTANCE",
|
||||
)
|
||||
|
||||
# panel properties
|
||||
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3)
|
||||
frame_thickness: bpy.props.FloatVectorProperty(name="Frame Thickness", size=3, default=[0.035] * 3)
|
||||
frame_depth: bpy.props.FloatVectorProperty(name="Frame Depth", size=3, default=[0.035] * 3, subtype="TRANSLATION")
|
||||
frame_thickness: bpy.props.FloatVectorProperty(
|
||||
name="Frame Thickness", size=3, default=[0.035] * 3, subtype="TRANSLATION"
|
||||
)
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"window_type": self.window_type,
|
||||
"overall_height": self.overall_height,
|
||||
"overall_width": self.overall_width,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ["window_type"])
|
||||
|
||||
def get_lining_kwargs(self):
|
||||
def get_lining_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"lining_depth": self.lining_depth,
|
||||
"lining_thickness": self.lining_thickness,
|
||||
@@ -373,16 +374,27 @@ class BIMWindowProperties(PropertyGroup):
|
||||
if self.window_type in ("TRIPLE_PANEL_HORIZONTAL",):
|
||||
kwargs["second_transom_offset"] = self.second_transom_offset
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def get_panel_kwargs(self):
|
||||
return {
|
||||
def get_panel_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"frame_depth": self.frame_depth,
|
||||
"frame_thickness": self.frame_thickness,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMDoorProperties(PropertyGroup):
|
||||
non_si_units_props = ("is_editing", "door_type", "panel_width_ratio")
|
||||
door_types = (
|
||||
("SINGLE_SWING_LEFT", "SINGLE_SWING_LEFT", ""),
|
||||
("SINGLE_SWING_RIGHT", "SINGLE_SWING_RIGHT", ""),
|
||||
@@ -394,51 +406,56 @@ class BIMDoorProperties(PropertyGroup):
|
||||
("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""),
|
||||
)
|
||||
|
||||
door_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
door_type: bpy.props.EnumProperty(name="Door Operation Type", items=door_types, default="SINGLE_SWING_LEFT")
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0)
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9)
|
||||
overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE")
|
||||
overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE")
|
||||
|
||||
# lining properties
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050)
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050)
|
||||
lining_depth: bpy.props.FloatProperty(name="Lining Depth", default=0.050, subtype="DISTANCE")
|
||||
lining_thickness: bpy.props.FloatProperty(name="Lining Thickness", default=0.050, subtype="DISTANCE")
|
||||
lining_offset: bpy.props.FloatProperty(
|
||||
name="Lining Offset",
|
||||
description="Offset from the outer side of the wall (by Y-axis). "
|
||||
"If present then adding casing is not possible.\n"
|
||||
"`0.025 mm` is good as default value",
|
||||
default=0.0,
|
||||
default=0.0,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025)
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025)
|
||||
lining_to_panel_offset_x: bpy.props.FloatProperty(name="Lining to Panel Offset X", default=0.025, subtype="DISTANCE")
|
||||
lining_to_panel_offset_y: bpy.props.FloatProperty(name="Lining to Panel Offset Y", default=0.025, subtype="DISTANCE")
|
||||
|
||||
transom_thickness: bpy.props.FloatProperty(
|
||||
name="Transom Thickness",
|
||||
description="Set values > 0 to add a transom.\n" "`0.050 mm` is good as default value",
|
||||
default=0.000,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
transom_offset: bpy.props.FloatProperty(
|
||||
name="Transom Offset",
|
||||
description="Distance from the bottom door opening to the beginning of the transom (unlike windows)",
|
||||
default=1.525,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
|
||||
casing_thickness: bpy.props.FloatProperty(
|
||||
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075
|
||||
name="Casing Thickness", description="Set values > 0 and LiningOffset = 0 to add a casing.", default=0.075,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005)
|
||||
casing_depth: bpy.props.FloatProperty(name="Casing Depth", default=0.005, subtype="DISTANCE")
|
||||
|
||||
threshold_thickness: bpy.props.FloatProperty(
|
||||
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025
|
||||
name="Threshold Thickness", description="Set values > 0 to add a threshold.", default=0.025,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1)
|
||||
threshold_depth: bpy.props.FloatProperty(name="Threshold Depth", default=0.1, subtype="DISTANCE")
|
||||
threshold_offset: bpy.props.FloatProperty(
|
||||
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000
|
||||
name="Threshold Offset", description="`0.025 mm` is good as default value", default=0.000,
|
||||
subtype="DISTANCE"
|
||||
)
|
||||
|
||||
# panel properties
|
||||
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035)
|
||||
panel_depth: bpy.props.FloatProperty(name="Panel Depth", default=0.035, subtype="DISTANCE")
|
||||
panel_width_ratio: bpy.props.FloatProperty(
|
||||
name="Panel Width Ratio",
|
||||
description="Width of this panel, given as ratio " "relative to the total clear opening width of the door",
|
||||
@@ -446,17 +463,20 @@ class BIMDoorProperties(PropertyGroup):
|
||||
soft_min=0,
|
||||
soft_max=1,
|
||||
)
|
||||
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035)
|
||||
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035)
|
||||
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035, subtype="DISTANCE")
|
||||
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035, subtype="DISTANCE")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
return {
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"door_type": self.door_type,
|
||||
"overall_height": self.overall_height,
|
||||
"overall_width": self.overall_width,
|
||||
}
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ["door_type"])
|
||||
|
||||
def get_lining_kwargs(self):
|
||||
def get_lining_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"lining_depth": self.lining_depth,
|
||||
"lining_thickness": self.lining_thickness,
|
||||
@@ -481,40 +501,56 @@ class BIMDoorProperties(PropertyGroup):
|
||||
kwargs["threshold_depth"] = self.threshold_depth
|
||||
kwargs["threshold_offset"] = self.threshold_offset
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs)
|
||||
|
||||
def get_panel_kwargs(self):
|
||||
def get_panel_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {"panel_depth": self.panel_depth, "panel_width_ratio": self.panel_width_ratio}
|
||||
|
||||
if self.transom_thickness:
|
||||
kwargs["frame_thickness"] = self.frame_thickness
|
||||
kwargs["frame_depth"] = self.frame_depth
|
||||
|
||||
return kwargs
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, ("panel_width_ratio", ))
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
|
||||
class BIMRailingProperties(PropertyGroup):
|
||||
non_si_units_props = (
|
||||
"is_editing",
|
||||
"railing_type",
|
||||
"use_manual_supports",
|
||||
"terminal_type",
|
||||
"path_data",
|
||||
)
|
||||
|
||||
railing_types = (
|
||||
("FRAMELESS_PANEL", "FRAMELESS_PANEL", ""),
|
||||
("WALL_MOUNTED_HANDRAIL", "WALL_MOUNTED_HANDRAIL", ""),
|
||||
)
|
||||
cap_types = (
|
||||
("none", "none", ""),
|
||||
("TO_END_POST_AND_FLOOR", "TO_END_POST_AND_FLOOR", ""),
|
||||
("TO_END_POST", "TO_END_POST", ""),
|
||||
("TO_FLOOR", "TO_FLOOR", ""),
|
||||
("TO_WALL", "TO_WALL", ""),
|
||||
("180", "180", ""),
|
||||
("to_wall", "to_wall", ""),
|
||||
("to_floor", "to_floor", ""),
|
||||
("to_end_post", "to_end_post", ""),
|
||||
("to_end_post_and_floor", "to_end_post_and_floor", ""),
|
||||
("NONE", "NONE", ""),
|
||||
)
|
||||
|
||||
railing_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
railing_type: bpy.props.EnumProperty(name="Railing Type", items=railing_types, default="FRAMELESS_PANEL")
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0)
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050)
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050)
|
||||
height: bpy.props.FloatProperty(name="Height", default=1.0, subtype="DISTANCE")
|
||||
thickness: bpy.props.FloatProperty(name="Thickness", default=0.050, subtype="DISTANCE")
|
||||
spacing: bpy.props.FloatProperty(name="Spacing", default=0.050, subtype="DISTANCE")
|
||||
|
||||
# wall mounted handrail specific properties
|
||||
use_manual_supports: bpy.props.BoolProperty(
|
||||
@@ -532,7 +568,7 @@ class BIMRailingProperties(PropertyGroup):
|
||||
)
|
||||
terminal_type: bpy.props.EnumProperty(name="Terminal Type", items=cap_types, default="180")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
base_kwargs = {
|
||||
"railing_type": self.railing_type,
|
||||
"height": self.height,
|
||||
@@ -551,17 +587,34 @@ class BIMRailingProperties(PropertyGroup):
|
||||
"support_spacing": self.support_spacing,
|
||||
"terminal_type": self.terminal_type,
|
||||
}
|
||||
return base_kwargs | additional_kwargs
|
||||
kwargs = base_kwargs | additional_kwargs
|
||||
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
|
||||
kwargs = tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
|
||||
return kwargs
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
class BIMRoofProperties(PropertyGroup):
|
||||
non_si_units_props = (
|
||||
"is_editing",
|
||||
"path_data",
|
||||
"roof_type",
|
||||
"generation_method",
|
||||
"angle",
|
||||
"rafter_edge_angle",
|
||||
)
|
||||
roof_types = (("HIP/GABLE ROOF", "HIP/GABLE ROOF", ""),)
|
||||
roof_generation_methods = (
|
||||
("HEIGHT", "HEIGHT", ""),
|
||||
("ANGLE", "ANGLE", ""),
|
||||
)
|
||||
|
||||
roof_added_previously: bpy.props.BoolProperty(default=False)
|
||||
is_editing: bpy.props.IntProperty(default=-1)
|
||||
is_editing_path: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@@ -573,14 +626,26 @@ class BIMRoofProperties(PropertyGroup):
|
||||
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")
|
||||
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")
|
||||
|
||||
def get_general_kwargs(self):
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"roof_type": self.roof_type,
|
||||
"generation_method": self.generation_method,
|
||||
"roof_thickness": self.roof_thickness,
|
||||
"rafter_edge_angle": self.rafter_edge_angle,
|
||||
}
|
||||
if self.generation_method == "HEIGHT":
|
||||
kwargs["height"] = self.height
|
||||
else:
|
||||
kwargs["angle"] = self.angle
|
||||
return kwargs
|
||||
|
||||
if not convert_to_project_units:
|
||||
return kwargs
|
||||
return tool.Model.convert_data_to_project_units(kwargs, self.non_si_units_props)
|
||||
|
||||
def set_props_kwargs_from_ifc_data(self, kwargs):
|
||||
kwargs = tool.Model.convert_data_to_si_units(kwargs, self.non_si_units_props)
|
||||
for prop_name in kwargs:
|
||||
setattr(self, prop_name, kwargs[prop_name])
|
||||
|
||||
@@ -38,14 +38,6 @@ import json
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm
|
||||
|
||||
|
||||
NON_SI_RAILING_PROPS = (
|
||||
"is_editing",
|
||||
"railing_type",
|
||||
"railing_added_previously",
|
||||
"use_manual_supports",
|
||||
"terminal_type",
|
||||
)
|
||||
|
||||
|
||||
def bm_split_edge_at_offset(edge, offset):
|
||||
v0, v1 = edge.verts
|
||||
@@ -92,16 +84,18 @@ def update_railing_modifier_ifc_data(context):
|
||||
if props.railing_type == "WALL_MOUNTED_HANDRAIL":
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
railing_path = [Vector(v) for v in RailingData.data["parameters"]["data_dict"]["path_data"]["verts"]]
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
representation_data = {
|
||||
"railing_type": props.railing_type,
|
||||
"context": body,
|
||||
"railing_path": railing_path,
|
||||
"use_manual_supports": props.use_manual_supports,
|
||||
"support_spacing": props.support_spacing,
|
||||
"railing_diameter": props.railing_diameter,
|
||||
"clear_width": props.clear_width,
|
||||
"support_spacing": props.support_spacing / si_conversion,
|
||||
"railing_diameter": props.railing_diameter / si_conversion,
|
||||
"clear_width": props.clear_width / si_conversion,
|
||||
"terminal_type": props.terminal_type,
|
||||
"height": props.height,
|
||||
"height": props.height / si_conversion,
|
||||
}
|
||||
model_representation = ifcopenshell.api.run(
|
||||
"geometry.add_railing_representation", ifc_file, **representation_data
|
||||
@@ -153,9 +147,9 @@ def update_railing_modifier_bmesh(context):
|
||||
|
||||
def generate_frameless_panel_railing():
|
||||
# generating FRAMELESS_PANEL railing
|
||||
height = props.height * si_conversion
|
||||
thickness = props.thickness * si_conversion
|
||||
spacing = props.spacing * si_conversion
|
||||
height = props.height
|
||||
thickness = props.thickness
|
||||
spacing = props.spacing
|
||||
|
||||
# spacing
|
||||
# split each edge in 3 segments by 0.5 * spacing by x-y plane
|
||||
@@ -279,6 +273,9 @@ class BIM_OT_add_railing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcRailing")
|
||||
obj = bpy.data.objects.new("IfcRailing", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -313,11 +310,8 @@ class AddRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "Object has to be IfcRailing/IfcRailingType type to add a railing.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.railing_added_previously:
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_RAILING_PROPS)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
@@ -350,13 +344,7 @@ class EnableEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRailingProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = NON_SI_RAILING_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
@@ -373,8 +361,7 @@ class CancelEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Railing", "Data"))
|
||||
props = obj.BIMRailingProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -405,7 +392,7 @@ class FinishEditingRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
RailingData.load()
|
||||
path_data = RailingData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
railing_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
@@ -437,7 +424,7 @@ class FlipRailingPathOrder(bpy.types.Operator, tool.Ifc.Operator):
|
||||
edge = [abs(vi - last_vert_i) for vi in edge[::-1]]
|
||||
edges.append(edge)
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
railing_data["path_data"] = path_data
|
||||
|
||||
update_bbim_railing_pset(element, railing_data)
|
||||
@@ -496,7 +483,7 @@ class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRailingProperties
|
||||
|
||||
railing_data = props.get_general_kwargs()
|
||||
railing_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
railing_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
@@ -524,7 +511,6 @@ class RemoveRailing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Railing")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.railing_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ from blenderbim.bim.module.model.data import RoofData, refresh
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
import json
|
||||
from math import tan, pi
|
||||
from math import tan, pi, radians
|
||||
from mathutils import Vector, Matrix
|
||||
import mathutils.geometry
|
||||
from bpypolyskel import bpypolyskel
|
||||
import shapely
|
||||
from pprint import pprint
|
||||
@@ -39,9 +40,6 @@ from pprint import pprint
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
NON_SI_ROOF_PROPS = ("is_editing", "roof_type", "roof_added_previously", "generation_method", "angle")
|
||||
|
||||
|
||||
def float_is_zero(f):
|
||||
return 0.0001 >= f >= -0.0001
|
||||
|
||||
@@ -87,12 +85,32 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
op_status, error_message = is_valid_roof_footprint(bm)
|
||||
if error_message:
|
||||
self.report(op_status, error_message)
|
||||
return {"CANCELLED"}
|
||||
|
||||
generate_hiped_roof_bmesh(bm, self.mode, self.height, self.angle)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutate_current_bmesh=True):
|
||||
def is_valid_roof_footprint(bm):
|
||||
# should be bmesh to support edit mode
|
||||
bm.verts.ensure_lookup_table()
|
||||
base_z = bm.verts[0].co.z
|
||||
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
|
||||
if not all_verts_same_level:
|
||||
return (
|
||||
{"ERROR"},
|
||||
"\nAll roof footprint vertices should have same Z-level.\nCurrently Z-level doesn't completely match",
|
||||
)
|
||||
return ({"FINISHED"}, "")
|
||||
|
||||
|
||||
def generate_hiped_roof_bmesh(
|
||||
bm, mode="ANGLE", height=1.0, roof_thickness=0.1, angle=pi / 18, rafter_edge_angle=pi / 2, mutate_current_bmesh=True
|
||||
):
|
||||
"""return bmesh with gable roof geometry
|
||||
|
||||
`mutate_current_bmesh` is a flag to indicate whether the input bmesh
|
||||
@@ -215,9 +233,15 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
verts_to_rip = []
|
||||
bottom_chords_to_remove = []
|
||||
|
||||
def is_footprint_vert(v):
|
||||
return float_is_zero(v.co.z - footprint_z)
|
||||
|
||||
def is_footprint_edge(edge):
|
||||
return all(is_footprint_vert(v) for v in edge.verts)
|
||||
|
||||
# find footprint edges
|
||||
for edge in bm.edges:
|
||||
if all(float_is_zero(v.co.z - footprint_z) for v in edge.verts):
|
||||
if is_footprint_edge(edge):
|
||||
footprint_edges.append(edge)
|
||||
footprint_verts.update(edge.verts)
|
||||
|
||||
@@ -229,27 +253,29 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
# iterate over edges from original geometry
|
||||
# if their angle was redefined by user - apply the changes to the related vertices
|
||||
# to match the requested angle
|
||||
|
||||
process_later = []
|
||||
for old_edge_verts, defined_angle, separate_verts in original_geometry_data["edges"]:
|
||||
if not defined_angle:
|
||||
continue
|
||||
|
||||
edge_verts_remaped = set(old_verts_remap[old_vert] for old_vert in old_edge_verts)
|
||||
|
||||
identical_edge = None
|
||||
for edge in footprint_edges:
|
||||
if set(edge.verts) == edge_verts_remaped:
|
||||
identical_edge = edge
|
||||
break
|
||||
assert identical_edge
|
||||
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move:
|
||||
if not separate_verts:
|
||||
vert_co = verts_to_change.get(v, v.co)
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_change[v] = new_vert_co
|
||||
else:
|
||||
if separate_verts:
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move:
|
||||
vert_co = v.co
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_rip.append([v, new_vert_co, identical_edge])
|
||||
else:
|
||||
process_later.append([identical_edge, edge_verts_remaped, defined_angle])
|
||||
|
||||
if defined_angle >= pi / 2:
|
||||
bottom_chords_to_remove.append(identical_edge)
|
||||
@@ -264,20 +290,91 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat
|
||||
if cur_edge == edge:
|
||||
continue
|
||||
bmesh.ops.contextual_create(bm, geom=[new_edge, cur_edge])
|
||||
return new_v
|
||||
|
||||
# correct angle for verts that require verts separation
|
||||
# store separated verts for angle correction later
|
||||
related_verts = {}
|
||||
for v, new_co, edge in verts_to_rip:
|
||||
new_v = separate_vert(bm, v, edge, new_co)
|
||||
related_verts.setdefault(v, []).append(new_v)
|
||||
|
||||
# verts angle correction.
|
||||
# we're taking into account new verts created after verts separation
|
||||
# required for asymmetrical gable roof
|
||||
for identical_edge, edge_verts_remaped, defined_angle in process_later:
|
||||
verts_to_move = find_other_polygon_verts(identical_edge)
|
||||
for v in verts_to_move[:]:
|
||||
verts_to_move.extend(related_verts.get(v, []))
|
||||
|
||||
for v in verts_to_move:
|
||||
vert_co = verts_to_change.get(v, v.co)
|
||||
new_vert_co = change_angle(vert_co, edge_verts_remaped, defined_angle)
|
||||
verts_to_change[v] = new_vert_co
|
||||
|
||||
# apply all changes once at the end
|
||||
for v in verts_to_change:
|
||||
v.co = verts_to_change[v]
|
||||
|
||||
for v, new_co, edge in verts_to_rip:
|
||||
separate_vert(bm, v, edge, new_co)
|
||||
|
||||
bmesh.ops.delete(bm, geom=bottom_chords_to_remove, context="EDGES")
|
||||
|
||||
extrusion_geom = bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"]
|
||||
extruded_verts = bm_sort_out_geom(extrusion_geom)["verts"]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, 0.1], verts=extruded_verts)
|
||||
# add roof thickness
|
||||
extrusion_geom = bm_sort_out_geom(bmesh.ops.extrude_face_region(bm, geom=bm.faces)["geom"])
|
||||
extruded_edges = extrusion_geom["edges"]
|
||||
extruded_verts = extrusion_geom["verts"]
|
||||
rafter_edge_angle = pi / 2 - rafter_edge_angle
|
||||
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
|
||||
footprint_verts = set()
|
||||
|
||||
if not float_is_zero(rafter_edge_angle):
|
||||
footprint_edges = []
|
||||
for edge in extruded_edges:
|
||||
if is_footprint_edge(edge):
|
||||
footprint_edges.append(edge)
|
||||
footprint_verts.update(edge.verts)
|
||||
|
||||
def get_top_vert(v):
|
||||
non_footprint_verts = []
|
||||
for edge in v.link_edges:
|
||||
v1 = edge.other_vert(v)
|
||||
if not is_footprint_vert(v1):
|
||||
non_footprint_verts.append(v1)
|
||||
|
||||
if len(non_footprint_verts) == 1:
|
||||
return non_footprint_verts[0]
|
||||
|
||||
return min(non_footprint_verts, key=lambda v1: (v1.co - v.co).length)
|
||||
|
||||
top_verts = dict()
|
||||
for v in footprint_verts:
|
||||
top_verts[v] = get_top_vert(v)
|
||||
|
||||
# TODO: rafter_edge_angle might differ
|
||||
# if footprint edges are not connected by 90 degrees
|
||||
verts_offsets = dict()
|
||||
for edge in footprint_edges:
|
||||
v0, v1 = edge.verts
|
||||
edge_dir = (v0.co - v1.co).normalized()
|
||||
offset_dir = Matrix.Rotation(rafter_edge_angle, 4, edge_dir) @ default_offset_dir
|
||||
for v in edge.verts:
|
||||
previous_offset = verts_offsets.get(v, None)
|
||||
if previous_offset:
|
||||
previous_offset.xy += offset_dir.xy
|
||||
else:
|
||||
verts_offsets[v] = offset_dir
|
||||
|
||||
for v in verts_offsets:
|
||||
vert_offset = verts_offsets[v]
|
||||
top = top_verts[v].co
|
||||
bot = v.co + default_offset_dir
|
||||
base = v.co
|
||||
offsetted = v.co + vert_offset
|
||||
# all this math is needed to maintain the same roof thickness
|
||||
# for different rafter edge angles
|
||||
v.co = mathutils.geometry.intersect_line_line(bot, top, base, offsetted)[0]
|
||||
|
||||
verts = [v for v in extruded_verts if v not in footprint_verts]
|
||||
bmesh.ops.translate(bm, vec=default_offset_dir, verts=verts)
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
|
||||
return bm
|
||||
|
||||
@@ -357,8 +454,15 @@ def update_roof_modifier_bmesh(context):
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
return
|
||||
|
||||
height = props.height * si_conversion
|
||||
generate_hiped_roof_bmesh(bm, props.generation_method, height, props.angle, mutate_current_bmesh=True)
|
||||
generate_hiped_roof_bmesh(
|
||||
bm,
|
||||
props.generation_method,
|
||||
props.height,
|
||||
props.roof_thickness,
|
||||
props.angle,
|
||||
props.rafter_edge_angle,
|
||||
mutate_current_bmesh=True,
|
||||
)
|
||||
tool.Blender.apply_bmesh(obj.data, bm)
|
||||
|
||||
|
||||
@@ -410,6 +514,9 @@ class BIM_OT_add_roof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mesh = bpy.data.meshes.new("IfcRoof")
|
||||
obj = bpy.data.objects.new("IfcRoof", mesh)
|
||||
obj.location = spawn_location
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
body_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
@@ -444,22 +551,30 @@ class AddRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "Object has to be IfcRoof/IfcRoofType type to add a roof.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# need to make sure all default props will have correct units
|
||||
if not props.roof_added_previously:
|
||||
convert_property_group_from_si(props, skip_props=NON_SI_ROOF_PROPS)
|
||||
# rejecting original roof shape to be safe
|
||||
# taking into account only it's bounding box dimensions
|
||||
if obj.dimensions.x == 0 or obj.dimensions.y == 0:
|
||||
min_x, min_y = -5, -5
|
||||
max_x, max_y = 5, 5
|
||||
min_z = 0
|
||||
else:
|
||||
bbox = tool.Blender.get_object_bounding_box(obj)
|
||||
min_x = bbox["min_x"]
|
||||
min_y = bbox["min_y"]
|
||||
max_x = bbox["max_x"]
|
||||
max_y = bbox["max_y"]
|
||||
min_z = bbox["min_z"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
path_data = get_path_data(obj)
|
||||
if not path_data:
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
|
||||
"verts": [
|
||||
Vector([-5.0, -5.0, 0.0]) / si_conversion,
|
||||
Vector([-5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, 5.0, 0.0]) / si_conversion,
|
||||
Vector([5.0, -5.0, 0.0]) / si_conversion,
|
||||
],
|
||||
}
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = {
|
||||
"edges": [[0, 1], [1, 2], [2, 3], [3, 0]],
|
||||
"verts": [
|
||||
Vector([min_x, min_y, min_z]) / si_conversion,
|
||||
Vector([min_x, max_y, min_z]) / si_conversion,
|
||||
Vector([max_x, max_y, min_z]) / si_conversion,
|
||||
Vector([max_x, min_y, min_z]) / si_conversion,
|
||||
],
|
||||
}
|
||||
roof_data["path_data"] = path_data
|
||||
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
@@ -479,17 +594,8 @@ class EnableEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = obj.BIMRoofProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
data["path_data"] = json.dumps(data["path_data"])
|
||||
|
||||
# required since we could load pset from .ifc and BIMRoofProperties won't be set
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
|
||||
# need to make sure all props that weren't used before
|
||||
# will have correct units
|
||||
skip_props = NON_SI_ROOF_PROPS + tuple(data.keys())
|
||||
convert_property_group_from_si(props, skip_props=skip_props)
|
||||
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
props.is_editing = 1
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -505,8 +611,7 @@ class CancelEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Roof", "Data"))
|
||||
props = obj.BIMRoofProperties
|
||||
# restore previous settings since editing was canceled
|
||||
for prop_name in data:
|
||||
setattr(props, prop_name, data[prop_name])
|
||||
props.set_props_kwargs_from_ifc_data(data)
|
||||
|
||||
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
@@ -537,7 +642,7 @@ class FinishEditingRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
RoofData.load()
|
||||
path_data = RoofData.data["parameters"]["data_dict"]["path_data"]
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
roof_data["path_data"] = path_data
|
||||
props.is_editing = -1
|
||||
|
||||
@@ -572,12 +677,21 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# copying to make sure not to mutate the edit mode bmesh
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
main_bm = bm.copy()
|
||||
op_status, error_message = is_valid_roof_footprint(main_bm)
|
||||
if error_message:
|
||||
print("Error: %s" % error_message)
|
||||
return main_bm
|
||||
|
||||
main_bm.edges.layers.int.new("BBIM_preview")
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
height = props.height * si_conversion
|
||||
second_bm = generate_hiped_roof_bmesh(
|
||||
bm, props.generation_method, height, props.angle, mutate_current_bmesh=False
|
||||
bm,
|
||||
props.generation_method,
|
||||
props.height,
|
||||
props.roof_thickness,
|
||||
props.angle,
|
||||
props.rafter_edge_angle,
|
||||
mutate_current_bmesh=False,
|
||||
)
|
||||
bmesh.ops.translate(second_bm, verts=second_bm.verts, vec=Vector((0, 0, 1)))
|
||||
|
||||
@@ -625,7 +739,13 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMRoofProperties
|
||||
|
||||
roof_data = props.get_general_kwargs()
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data)
|
||||
op_status, error_message = is_valid_roof_footprint(bm)
|
||||
if error_message:
|
||||
self.report(op_status, error_message)
|
||||
return {"CANCELLED"}
|
||||
|
||||
roof_data = props.get_general_kwargs(convert_to_project_units=True)
|
||||
path_data = get_path_data(obj)
|
||||
roof_data["path_data"] = path_data
|
||||
ProfileDecorator.uninstall()
|
||||
@@ -634,6 +754,7 @@ class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator):
|
||||
update_bbim_roof_pset(element, roof_data)
|
||||
refresh() # RoofData has to be updated before run update_roof_modifier_bmesh
|
||||
update_roof_modifier_bmesh(context)
|
||||
|
||||
update_roof_modifier_ifc_data(context)
|
||||
if bpy.context.object.mode == "EDIT":
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
@@ -654,7 +775,6 @@ class RemoveRoof(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
pset = tool.Pset.get_element_pset(element, "BBIM_Roof")
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), pset=pset)
|
||||
props.roof_added_previously = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ def sync_name(usecase_path, ifc_file, settings):
|
||||
style = tool.Style.get_style(obj)
|
||||
if style and style != element:
|
||||
style.Name = new_name
|
||||
collection = bpy.data.collections.get(obj.name)
|
||||
collection = obj.BIMObjectProperties.collection
|
||||
if collection:
|
||||
collection.name = new_name
|
||||
obj.name = new_name
|
||||
|
||||
@@ -133,13 +133,13 @@ class DumbSlabGenerator:
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
self.collection = bpy.context.view_layer.active_layer_collection.collection
|
||||
self.collection_obj = bpy.data.objects.get(self.collection.name)
|
||||
self.collection_obj = self.collection.BIMCollectionProperties.obj
|
||||
self.depth = sum(thicknesses) * unit_scale
|
||||
self.width = 3
|
||||
self.length = 3
|
||||
self.rotation = 0
|
||||
self.location = Vector((0, 0, 0))
|
||||
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
|
||||
self.x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
|
||||
return self.derive_from_cursor()
|
||||
|
||||
def derive_from_cursor(self):
|
||||
@@ -252,9 +252,17 @@ class DumbSlabPlaner:
|
||||
new_material = ifcopenshell.util.element.get_material(settings["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"])
|
||||
if material and material.is_a("IfcMaterialLayerSetUsage") and material.LayerSetDirection == "AXIS3":
|
||||
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)
|
||||
|
||||
def change_thickness(self, element, thickness):
|
||||
@@ -274,7 +282,7 @@ class DumbSlabPlaner:
|
||||
extrusion.Depth = thickness
|
||||
else:
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
|
||||
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
|
||||
new_rep = ifcopenshell.api.run(
|
||||
"geometry.add_slab_representation",
|
||||
tool.Ifc.get(),
|
||||
@@ -299,7 +307,7 @@ class DumbSlabPlaner:
|
||||
return
|
||||
else:
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else radians(props.x_angle)
|
||||
x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle
|
||||
representation = ifcopenshell.api.run(
|
||||
"geometry.add_slab_representation",
|
||||
tool.Ifc.get(),
|
||||
|
||||
@@ -26,12 +26,20 @@ import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
from shapely import Polygon, MultiPolygon
|
||||
|
||||
|
||||
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"
|
||||
|
||||
@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)
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
@@ -52,7 +60,7 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
relating_type = None
|
||||
|
||||
collection = context.view_layer.active_layer_collection.collection
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
collection_obj = collection.BIMCollectionProperties.obj
|
||||
if not collection_obj:
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
@@ -65,8 +73,13 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = None
|
||||
if bpy.context.selected_objects and active_obj:
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
x, y, z = active_obj.matrix_world.translation.xyz
|
||||
mat = active_obj.matrix_world
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in active_obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
x = global_bbox_center.x
|
||||
y = global_bbox_center.y
|
||||
z = (mat @ Vector(active_obj.bound_box[0])).z
|
||||
|
||||
h = active_obj.dimensions.z
|
||||
else:
|
||||
x, y = context.scene.cursor.location.xy
|
||||
@@ -166,3 +179,225 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
(obj.matrix_world @ Vector((max_x, max_y, 0.0))).to_2d(),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.generate_spaces_from_walls"
|
||||
bl_label = "Generate Spaces From Walls"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Generate spaces from selected walls. The active object must be a wall"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_obj = bpy.context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element:
|
||||
return context.selected_objects and element.is_a("IfcWall")
|
||||
|
||||
def _execute(self, context):
|
||||
# This only works based on a 2D plan only considering the standard
|
||||
# walls (i.e. prismatic) in the active object storey.
|
||||
# In order to run, the active objct must be a wall and
|
||||
# have to be selected walls
|
||||
props = context.scene.BIMModelProperties
|
||||
active_obj = bpy.context.active_object
|
||||
|
||||
if not active_obj:
|
||||
self.report({"ERROR"}, "No active object. Please select a wall")
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if element and not element.is_a("IfcWall"):
|
||||
return self.report({"ERROR"}, "The active object is not a wall. Please select a wall.")
|
||||
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if not container:
|
||||
self.report({"ERROR"}, "The wall is not contained.")
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
self.report({"ERROR"}, "No selected objects found. Please select walls.")
|
||||
return
|
||||
|
||||
x, y, z = active_obj.matrix_world.translation.xyz
|
||||
mat = active_obj.matrix_world
|
||||
h = active_obj.dimensions.z
|
||||
selected_objects = bpy.context.selected_objects
|
||||
|
||||
boundary_elements = self.get_boundary_elements(selected_objects)
|
||||
|
||||
polys = self.get_polygons(boundary_elements)
|
||||
|
||||
converted_tolerance = self.get_converted_tolerance(tolerance=0.03)
|
||||
|
||||
union = shapely.ops.unary_union(polys).buffer(converted_tolerance, cap_style=2, join_style=2)
|
||||
|
||||
union = self.get_purged_inner_holes_poly(union_geom = union, min_area = self.get_converted_tolerance(tolerance = 3))
|
||||
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = Polygon(linear_ring)
|
||||
poly = poly.buffer(converted_tolerance, single_sided=True, cap_style=2, join_style=2)
|
||||
|
||||
bm = self.get_bmesh_from_polygon(poly, mat, h)
|
||||
|
||||
name = "Space" + str(i)
|
||||
mesh = bpy.data.meshes.new(name=name)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
obj.matrix_world = mat
|
||||
|
||||
self.set_obj_origin_to_bboxcenter(obj)
|
||||
|
||||
if z != 0:
|
||||
obj.location = obj.location + Vector((0,0,z))
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||
container_obj = tool.Ifc.get_object(container)
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=container_obj, element_obj=obj
|
||||
)
|
||||
|
||||
def get_boundary_elements(self, selected_objects):
|
||||
boundary_elements = []
|
||||
for obj in selected_objects:
|
||||
subelement = tool.Ifc.get_entity(obj)
|
||||
if subelement.is_a("IfcWall") or subelement.is_a("IfcColumn"):
|
||||
boundary_elements.append(subelement)
|
||||
return boundary_elements
|
||||
|
||||
def get_polygons(self, boundary_elements):
|
||||
polys = []
|
||||
for boundary_element in boundary_elements:
|
||||
obj = tool.Ifc.get_object(boundary_element)
|
||||
if not obj:
|
||||
continue
|
||||
points = []
|
||||
base = self.get_obj_base_points(obj)
|
||||
for index in ["low_left", "low_right", "high_right", "high_left"]:
|
||||
point = base[index]
|
||||
points.append(point)
|
||||
|
||||
polys.append(Polygon(points))
|
||||
return polys
|
||||
|
||||
def get_obj_base_points(self, obj):
|
||||
x_values = [(obj.matrix_world @ Vector(v)).x for v in obj.bound_box]
|
||||
y_values = [(obj.matrix_world @ Vector(v)).y for v in obj.bound_box]
|
||||
return {
|
||||
"low_left": (x_values[0], y_values[0]),
|
||||
"high_left": (x_values[3], y_values[3]),
|
||||
"low_right": (x_values[4], y_values[4]),
|
||||
"high_right": (x_values[7], y_values[7]),
|
||||
}
|
||||
|
||||
def get_converted_tolerance(self, tolerance):
|
||||
model = tool.Ifc.get()
|
||||
project_unit = ifcopenshell.util.unit.get_project_unit(model, "LENGTHUNIT")
|
||||
prefix = getattr(project_unit, "Prefix", None)
|
||||
|
||||
converted_tolerance = ifcopenshell.util.unit.convert(
|
||||
value=tolerance,
|
||||
from_prefix=None,
|
||||
from_unit="METRE",
|
||||
to_prefix=prefix,
|
||||
to_unit=project_unit.Name,
|
||||
)
|
||||
return tolerance
|
||||
|
||||
def get_purged_inner_holes_poly(self, union_geom, min_area):
|
||||
interiors_list = []
|
||||
|
||||
if union_geom.geom_type == "MultiPolygon":
|
||||
for poly in union_geom.geoms:
|
||||
interiors_list = self.get_poly_valid_interior_list(poly = poly, min_area = min_area, interiors_list = interiors_list)
|
||||
|
||||
new_poly = Polygon(poly.exterior.coords, holes = interiors_list)
|
||||
|
||||
if union_geom.geom_type == "Polygon":
|
||||
interiors_list = self.get_poly_valid_interior_list(poly = union_geom, min_area = min_area, interiors_list = interiors_list)
|
||||
new_poly = Polygon(union_geom.exterior.coords, holes = interiors_list)
|
||||
|
||||
return new_poly
|
||||
|
||||
def get_poly_valid_interior_list(self, poly, min_area, interiors_list):
|
||||
for interior in poly.interiors:
|
||||
p = Polygon(interior)
|
||||
if p.area >= min_area:
|
||||
interiors_list.append(interior)
|
||||
return interiors_list
|
||||
|
||||
|
||||
def get_bmesh_from_polygon(self, poly, mat, h):
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
mat_invert = mat.inverted()
|
||||
|
||||
new_verts = [bm.verts.new(mat_invert @ Vector([v[0], v[1], 0])) for v in poly.exterior.coords[0:-1]]
|
||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
||||
bmesh.ops.triangle_fill(bm, edges=bm.edges)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
||||
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||
|
||||
return bm
|
||||
|
||||
def set_obj_origin_to_bboxcenter(self, obj):
|
||||
mat = obj.matrix_world
|
||||
inverted = mat.inverted()
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
|
||||
oldLoc = obj.location
|
||||
newLoc = global_bbox_center
|
||||
diff = newLoc - oldLoc
|
||||
for vert in obj.data.vertices:
|
||||
aux_vector = mat @ vert.co
|
||||
aux_vector = aux_vector - diff
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
|
||||
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):
|
||||
model = tool.Ifc.get()
|
||||
|
||||
spaces = model.by_type('IfcSpace')
|
||||
|
||||
if not spaces:
|
||||
print(spaces)
|
||||
return {"FINISHED"}
|
||||
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
|
||||
if bpy.data.objects[first_obj.name].display_type == 'TEXTURED':
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = True
|
||||
bpy.data.objects[obj.name].display_type = 'WIRE'
|
||||
return {"FINISHED"}
|
||||
|
||||
elif bpy.data.objects[first_obj.name].display_type == 'WIRE':
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = False
|
||||
bpy.data.objects[obj.name].display_type = 'TEXTURED'
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user