mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
483 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed4abacd1e | |||
| 8bf55ab46c | |||
| 7edabce657 | |||
| 56187e4da3 | |||
| 31a12faef1 | |||
| eb94c16465 | |||
| e8437a9c54 | |||
| 840039ef1e | |||
| 84a749605e | |||
| c1283d2ab0 | |||
| 07b5c4918b | |||
| 703f0a6b70 | |||
| 5e98dee46f | |||
| b2eddf55d2 | |||
| db7be640d4 | |||
| 04fa597844 | |||
| 694dbeb96c | |||
| 7afd358c3c | |||
| b0a5a570a9 | |||
| c45b84c850 | |||
| d575a43aee | |||
| 0b151ea559 | |||
| 926970b256 | |||
| d58af0bfa5 | |||
| 429da7c6f3 | |||
| 60a7433573 | |||
| 08a5d444d4 | |||
| 1982069b69 | |||
| 7c06c65df3 | |||
| 6263766202 | |||
| 66fbbeff54 | |||
| 3c5ad1328f | |||
| 3345d7e088 | |||
| 2bb25cb874 | |||
| 444a728e7a | |||
| 70bea0c865 | |||
| 6eea1e9035 | |||
| 4deb1836ad | |||
| 4c8007dcfe | |||
| 000116bb3d | |||
| a2b6f21c7a | |||
| a374ee39d3 | |||
| 0f5c82dffd | |||
| 268fed761e | |||
| fe01d252db | |||
| dc36d74337 | |||
| 7a0da7bd8e | |||
| 530d5a4e63 | |||
| 2d847d1704 | |||
| 631d8fcfb1 | |||
| a8888efb88 | |||
| a51ecea9b1 | |||
| 4d49517111 | |||
| d5d1b3567b | |||
| 7827ce3448 | |||
| a05059db01 | |||
| b78490ca56 | |||
| f8ffc97bf5 | |||
| 29d4a8d5fe | |||
| 2c1c13f27e | |||
| 4dc55e844c | |||
| b611151039 | |||
| 9fc32e5c2e | |||
| c5e5036fd3 | |||
| 0f68679295 | |||
| fea0d93162 | |||
| e43347161a | |||
| 706aa70899 | |||
| fe5a6749a7 | |||
| c3f352c018 | |||
| 79584dba13 | |||
| 1c74147d89 | |||
| cc53744c29 | |||
| 945836f578 | |||
| e88e2f5d9c | |||
| ea267d9443 | |||
| d64e4d70c5 | |||
| 156844b27d | |||
| b96944ff1b | |||
| 7bfa6115ee | |||
| 589b3928f3 | |||
| a884eb5c54 | |||
| 796d19a3a8 | |||
| b9211e2a93 | |||
| b662d6f08e | |||
| deecd60ff1 | |||
| 01615724fd | |||
| 97685a7a77 | |||
| dc1721ed60 | |||
| e801ba49b2 | |||
| 2446479d82 | |||
| 8908011ecf | |||
| fe6be61d7a | |||
| 45f81b4780 | |||
| 92df1b351c | |||
| 002c7c1ad9 | |||
| a583860b58 | |||
| 4efe198dbb | |||
| 7a6332c883 | |||
| a14ef1ebc2 | |||
| fab0f92c8a | |||
| 490937ecf9 | |||
| cbf8b7f3a2 | |||
| 6c5bc5a3e8 | |||
| f7aa67c6e9 | |||
| 4ce41b33bb | |||
| a0fd2a11cb | |||
| 4443f484c1 | |||
| 120a9f9f3e | |||
| e33a400ebb | |||
| 2cc9ea0539 | |||
| 3c8dc70198 | |||
| 08d8657588 | |||
| e0b4c1a810 | |||
| a517c9338d | |||
| ef01766f4d | |||
| e9f77f8d2e | |||
| a93d4da810 | |||
| e86d3731ff | |||
| 0e69f484f4 | |||
| 3a812a9ec1 | |||
| 12d70324b8 | |||
| caff4d0b20 | |||
| afb9aa178f | |||
| 356a10eac9 | |||
| 60f60798df | |||
| 360729738c | |||
| 794212c223 | |||
| 2015f0618a | |||
| d7158be431 | |||
| 1ccbe831c8 | |||
| 5a8e4fa8fd | |||
| 9048b67724 | |||
| 9059b5d7ec | |||
| 7af3f89688 | |||
| 8be0f2e1da | |||
| 630bb88a15 | |||
| 6575921ccd | |||
| 95eb78e919 | |||
| 78b4b09be9 | |||
| 32e02db3f7 | |||
| 9d4715c0a9 | |||
| 0ba5db751c | |||
| c1a15d944f | |||
| 7cd1c47ceb | |||
| a1aa047161 | |||
| e4896539b4 | |||
| 47ee667f06 | |||
| 7804c9ea80 | |||
| 3980666824 | |||
| eda0b33b41 | |||
| 8fdd0983c0 | |||
| 0ef141447b | |||
| 06ea0512f5 | |||
| 8af11db8c2 | |||
| 1c5a4ddf20 | |||
| b792b05753 | |||
| 524bace23b | |||
| 0d656040eb | |||
| 12fcdebd37 | |||
| c798309039 | |||
| e1913bf03e | |||
| bc541d335f | |||
| d4933c7f24 | |||
| 4e96f17dac | |||
| 4665b44ded | |||
| d834d94fd9 | |||
| f2db071cf3 | |||
| 0348e6d11a | |||
| 1eb7c006f1 | |||
| c80a2e1d64 | |||
| 7b2d99ba1e | |||
| 55c4ce0071 | |||
| 8e5dc01758 | |||
| 90632a363c | |||
| 4d6225bc32 | |||
| 411ec36e65 | |||
| 1a54f54c64 | |||
| 5d48dc15c1 | |||
| 5169599884 | |||
| c3323733a7 | |||
| 1468b7fddd | |||
| 41d6882b40 | |||
| a1ebc2e167 | |||
| d5d9c91f5a | |||
| 98a6cbb1aa | |||
| f48060e7c7 | |||
| dd827bb4c5 | |||
| b1cab9484c | |||
| 74637dd0ea | |||
| a61eae5f98 | |||
| 4c18a89ee6 | |||
| 4a7b8f1104 | |||
| c7a1acf974 | |||
| 630ac7250a | |||
| 08756a24a4 | |||
| 8edee78900 | |||
| 9864261c41 | |||
| bb3e978bba | |||
| 288cc698bd | |||
| a2e70cb454 | |||
| 1901533470 | |||
| c61854c7aa | |||
| 0dee353b8e | |||
| 2e3cc8e73a | |||
| 6b276b8aed | |||
| 1b71f4f569 | |||
| 452089596e | |||
| a360bac4c3 | |||
| 09bc05f1af | |||
| efc84606c5 | |||
| 268039fb13 | |||
| c59757c61a | |||
| 0e3386438e | |||
| 38782f4f53 | |||
| da23e39084 | |||
| 88278a9edb | |||
| a23a66edc2 | |||
| d2c22b0b0c | |||
| 76a4850238 | |||
| bf5c8f5e88 | |||
| 36b45b744d | |||
| bffbfb5fa3 | |||
| 963ffa39b7 | |||
| 25d76152f6 | |||
| d851614cbb | |||
| 9c7b073414 | |||
| 66ed95fbc1 | |||
| 44e98b17a0 | |||
| a095d93838 | |||
| 892b41a540 | |||
| 0cdce9b2d6 | |||
| 05e05d0d5a | |||
| 8cbe298072 | |||
| 7bc23c996b | |||
| b9ff4f3263 | |||
| 42bbc9a093 | |||
| 33930642a4 | |||
| 0573ffb11a | |||
| 21a614560a | |||
| 80d74866a9 | |||
| d75e85b3a6 | |||
| a101f46478 | |||
| 2463b77985 | |||
| e695bdbd0d | |||
| 17230e4170 | |||
| 80d88b3821 | |||
| 0500e666bd | |||
| 31bb220d38 | |||
| 944f2f701a | |||
| 844cc62960 | |||
| 147df7f46b | |||
| 53bd4bf5ea | |||
| d50e43197e | |||
| 90fce18042 | |||
| 772414451e | |||
| 0a840188bf | |||
| a2e7cb8158 | |||
| 2034fa203b | |||
| 572a8c812e | |||
| af4b5e1fe6 | |||
| 9c383e0c1a | |||
| a72231cb91 | |||
| 846dbc341d | |||
| ed95516b9d | |||
| b6ddc0d55e | |||
| be5950bcb7 | |||
| 6810bbff2f | |||
| 2fa7cbaabf | |||
| 69fa12423d | |||
| 8f609ff383 | |||
| 924bf2a3ad | |||
| 2be8f4a139 | |||
| 8411feedf4 | |||
| 56e6c8e0e1 | |||
| 818226d1e2 | |||
| 6336241431 | |||
| 7720166c31 | |||
| f5ba0673e3 | |||
| 3b05799956 | |||
| f3924249e9 | |||
| 7f9e8a207d | |||
| 38f7c044a3 | |||
| 7896d5b6e5 | |||
| 96d812eaba | |||
| 0390a65590 | |||
| ad4ab1567f | |||
| d633733d18 | |||
| cd1a813630 | |||
| c9e6da9b6c | |||
| cada2c36e3 | |||
| 33304eb1a5 | |||
| 4e72ef8b20 | |||
| b9d8e86748 | |||
| 3adfc63b65 | |||
| 91e7d49060 | |||
| 4ad1a2be73 | |||
| ef8f825012 | |||
| 56adfc70b5 | |||
| bc909a26fa | |||
| dd0a015ec3 | |||
| 3813c26db7 | |||
| 95c06c7f82 | |||
| db910d4024 | |||
| 669dbd2e8d | |||
| becbfccae9 | |||
| 2f02d401f2 | |||
| ec2ca4bcf4 | |||
| 89df937f89 | |||
| 7567971a32 | |||
| 74160731c9 | |||
| 5d7fefa042 | |||
| db4c61a472 | |||
| 298568d3ea | |||
| 4da7b355bf | |||
| 2a15743f8b | |||
| e54cc11ca6 | |||
| 24fdfc6d82 | |||
| 8689164769 | |||
| 86dd79daa6 | |||
| a231cc31e8 | |||
| e412d27398 | |||
| 60388f1a24 | |||
| d13d1493e3 | |||
| 556489d686 | |||
| 657e77117c | |||
| 7d97f5e375 | |||
| 7583b56738 | |||
| 073a00b2ca | |||
| 8baa0945e9 | |||
| 717c28b7c9 | |||
| f4a4e906ab | |||
| 052ff71c51 | |||
| 3dfd36877e | |||
| a9f2237d32 | |||
| 62c93f0462 | |||
| 60ea504267 | |||
| 1e3b45c0b8 | |||
| 757277cedf | |||
| 14fced7aba | |||
| 7a3b299148 | |||
| 45cf6a8ace | |||
| 9b1693e112 | |||
| cc59929cfd | |||
| 43fd012a4c | |||
| f96f663dc3 | |||
| 486bafc814 | |||
| 20a31be5bf | |||
| 98dad0cbbe | |||
| b0ce2e5a00 | |||
| 3aea0915d0 | |||
| 0ee5e3887b | |||
| 1afcec2005 | |||
| 656b7cb06e | |||
| 19d3f98b6f | |||
| 73a736f52a | |||
| 6adc48ade4 | |||
| 949a2bb347 | |||
| 8cb63720eb | |||
| 52ac1090e9 | |||
| 4f6180baf8 | |||
| 743339598e | |||
| 2e423f26ff | |||
| b3b45893cc | |||
| a65747dd2c | |||
| 4286ad6dcc | |||
| 0b823c772e | |||
| 9944024457 | |||
| 3cdad90755 | |||
| 71846d683b | |||
| 7be0fd6137 | |||
| 1eef746f60 | |||
| 1696ca5e08 | |||
| c011458ea2 | |||
| abf809f9dd | |||
| 9257816e62 | |||
| d47125b28f | |||
| b7453f584e | |||
| 6a7cf6843a | |||
| 9c4fa666c9 | |||
| a970120fac | |||
| 72907cd3f0 | |||
| ae7c9f35a6 | |||
| 0b111084b0 | |||
| 2ee0dcaf64 | |||
| 70eabead92 | |||
| 5a30d9fb9b | |||
| 3126db342d | |||
| c0db03438f | |||
| 1542217117 | |||
| 5b4010c67d | |||
| c18d96f0e3 | |||
| f9173700a6 | |||
| 7100a7fa8e | |||
| 0078d691d3 | |||
| fd626e37a0 | |||
| e62d7ad9ef | |||
| 80eebc03d3 | |||
| 62fea990d9 | |||
| 7ab01e6ac0 | |||
| 74a025b81c | |||
| ca8ed87f2a | |||
| 9fbcb3900f | |||
| 1533283822 | |||
| 6beca6382f | |||
| a1caef5b4f | |||
| 7da96b4951 | |||
| 66c275f617 | |||
| 52a8fcd86e | |||
| 3ba7ecdb95 | |||
| 36f7625cee | |||
| ee82714423 | |||
| 281147367e | |||
| 0beea4255a | |||
| b03c4da7bf | |||
| c0402687b5 | |||
| c6fa7161b6 | |||
| 64516a173a | |||
| f507e3e8bd | |||
| 700d8a3b9c | |||
| 2f15d73b30 | |||
| 53cc6f0bd6 | |||
| 7680652c28 | |||
| da3440d848 | |||
| e8b1d00e6d | |||
| 6ec7e48423 | |||
| ccf09b962a | |||
| 485d41ae09 | |||
| b67c5a0828 | |||
| 7b92057227 | |||
| a980ce1bfa | |||
| 51b7f24a00 | |||
| 136e1c3b3f | |||
| c094517a10 | |||
| 93aa24c6d2 | |||
| 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 | |||
| 37e4f98857 | |||
| 2ce32c6521 | |||
| 9d84172f15 | |||
| 8341931a62 | |||
| 81a4ff711c | |||
| d9196882dc | |||
| 580f8f6218 | |||
| e177f5f051 | |||
| 046ce2b8e7 | |||
| bcf5f07718 | |||
| a3572f3372 |
@@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pyver: [py37, py39, py310]
|
||||
pyver: [py39, py310]
|
||||
config:
|
||||
- {
|
||||
name: "Windows Build",
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-ifctester-pypi.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
|
||||
+4
-1
@@ -94,4 +94,7 @@ src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# apple
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
@@ -1,8 +1,9 @@
|
||||
|
||||
IfcOpenShell
|
||||
============
|
||||
|
||||
<p align="center">
|
||||
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/79010126/9e540403-c79f-446a-88c6-7c1ebc6ba864">
|
||||
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/88302/34901387-e2dd-4a0c-8e38-9ffc32a66cde">
|
||||
</p>
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
Dockerized AWS Lambda Function with Python and IfcOpenShell
|
||||
===========================================================
|
||||
|
||||
This guide provides a Dockerfile and sample code to help you run an AWS Lambda function written in Python and utilizing IfcOpenShell library.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
Make sure you have Docker installed on your machine. You can download Docker from the official website: [Docker Instalation](https://docs.docker.com/engine/install/)
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
1. **Clone this repository to your local machine:**
|
||||
|
||||
2. **Customize the Lambda function code:**
|
||||
|
||||
- Replace the sample Lambda function code in the `example_handler` directory with your own code.
|
||||
- Update the import path in the Dockerfile's `CMD` instruction to match your Lambda function's handler function.
|
||||
|
||||
3. **Install the required Python packages:**
|
||||
- Edit the `requirements.txt` file and add any additional dependencies required by your Lambda function.
|
||||
|
||||
4. **Build the Docker image:**
|
||||
|
||||
```shell
|
||||
$ docker build -t lambda-ifcopenshell .
|
||||
```
|
||||
|
||||
5. **Run the Docker container:**
|
||||
|
||||
```shell
|
||||
$ docker run lambda-ifcopenshell
|
||||
```
|
||||
6. **Test lambda locally**
|
||||
|
||||
Follow the steps in this [guide](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html)
|
||||
|
||||
|
||||
7. **Deploy to lambda**
|
||||
|
||||
This is beyond the scope of this example. Please refer to AWS documentation. Some tools that could be useful are - AWS CloudFormaton, AWS CDK, pulumi or terraform
|
||||
|
||||
|
||||
---------
|
||||
> Note: This is a superficial guide, in order to make this work for your usecase you will need a good understanding of other AWS services like SQS, S3, API Gateway etc.
|
||||
|
||||
For more information on building lambda containers refer to this [guide](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html).
|
||||
@@ -13,68 +13,77 @@ def request_repo_info(url: str):
|
||||
return resp
|
||||
|
||||
|
||||
def get_choco_package_info() -> str:
|
||||
resp = request_repo_info(URL_CHOCO_PACKAGE)
|
||||
html_txt = str(resp.read())
|
||||
return html_txt
|
||||
|
||||
|
||||
def get_latest_blender_version() -> list:
|
||||
html_txt = get_choco_package_info()
|
||||
return re.findall(RE_BLENDER_VERSION_MIN_MAJ_PAT, html_txt)
|
||||
|
||||
|
||||
URL_IFCOS_RELEASES = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
|
||||
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
|
||||
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"SET\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
|
||||
|
||||
|
||||
if sys.argv[1] == "--do_choco_release?":
|
||||
now = datetime.datetime.now()
|
||||
blenderbim_date = (now - datetime.timedelta(days=1)).strftime("%y%m%d")
|
||||
url = "https://github.com/IfcOpenShell/IfcOpenShell/releases"
|
||||
resp = request_repo_info(url)
|
||||
resp = request_repo_info(URL_IFCOS_RELEASES)
|
||||
text = str(resp.read())
|
||||
if blenderbim_date in text:
|
||||
print("do_choco_release", end="")
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_release_maj_min_pat?":
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
latest_blender_version = get_latest_blender_version()
|
||||
if latest_blender_version:
|
||||
print(latest_blender_version[0], end="")
|
||||
else:
|
||||
print("[ERROR] could not determine blender_version_min_maj_pat")
|
||||
quit(1)
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_release_maj_min?":
|
||||
re_blender_version_min_maj = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
html_txt = get_choco_package_info()
|
||||
blender_version_min_maj = re.findall(RE_BLENDER_VERSION_MIN_MAJ, html_txt)
|
||||
if blender_version_min_maj:
|
||||
print(blender_version_min_maj[0], end="")
|
||||
else:
|
||||
print("[ERROR] could not determine blender_version_min_maj")
|
||||
quit(1)
|
||||
|
||||
|
||||
elif sys.argv[1] == "--latest_blender_python_version_maj_min?":
|
||||
# get latest blender version first
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
latest_blender_version_tag = f"v{found[0]}"
|
||||
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
resp = request_repo_info(url)
|
||||
latest_blender_version = get_latest_blender_version()
|
||||
if latest_blender_version:
|
||||
latest_blender_version_tag = f"v{latest_blender_version[0]}"
|
||||
resp = request_repo_info(URL_BLENDER_CMAKE.format(latest_blender_version_tag))
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||
found = re.findall(RE_BLENDER_PYTHON_VERSION_MAJ_MIN, html_txt)
|
||||
if found:
|
||||
print(found[0], end="")
|
||||
else:
|
||||
print("[ERROR] could not determine blender_python_version_maj_min")
|
||||
quit(1)
|
||||
|
||||
|
||||
elif sys.argv[1] == "--pyver?":
|
||||
# get latest blender version first
|
||||
re_blender_version_min_maj_pat = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
|
||||
url = "https://community.chocolatey.org/packages/blender"
|
||||
resp = request_repo_info(url)
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_version_min_maj_pat, html_txt)
|
||||
if found:
|
||||
latest_blender_version_tag = f"v{found[0]}"
|
||||
re_blender_python_version_maj_min = r"SET\(PYTHON_VERSION (\d+.\d+) "
|
||||
url = f"https://raw.githubusercontent.com/blender/blender/{latest_blender_version_tag}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
|
||||
resp = request_repo_info(url)
|
||||
latest_blender_version = get_latest_blender_version()
|
||||
if latest_blender_version:
|
||||
latest_blender_version_tag = f"v{latest_blender_version[0]}"
|
||||
resp = request_repo_info(URL_BLENDER_CMAKE.format(latest_blender_version_tag))
|
||||
html_txt = str(resp.read())
|
||||
found = re.findall(re_blender_python_version_maj_min, html_txt)
|
||||
found = re.findall(RE_BLENDER_PYTHON_VERSION_MAJ_MIN, html_txt)
|
||||
if found:
|
||||
print(f"py{found[0].replace('.', '')}", end="")
|
||||
else:
|
||||
print("[ERROR] could not determine pyver")
|
||||
quit(1)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
|
||||
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
|
||||
+2
-1
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
|
||||
fi
|
||||
|
||||
cmake -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
@@ -41,4 +42,4 @@ cmake -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install -j 2
|
||||
ninja install -j 1
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ requirements:
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt ==7.7.0
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- mpfr
|
||||
- gmp # [unix]
|
||||
|
||||
@@ -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
|
||||
|
||||
+82
-69
@@ -36,12 +36,9 @@ endif
|
||||
endif
|
||||
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
PYVERSION:=py37
|
||||
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||
PYVERSION:=py310
|
||||
|
||||
ifeq ($(PYVERSION), py37)
|
||||
PYLIBDIR:=python3.7
|
||||
PYNUMBER:=37
|
||||
endif
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
@@ -52,123 +49,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 +164,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-8f41ae0-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fc50bdd-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -414,7 +388,8 @@ endif
|
||||
# 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/
|
||||
@@ -451,10 +426,28 @@ ifeq ($(PLATFORM), win)
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
ifeq ($(PLATFORM), linux)
|
||||
cp -r dist/working/lib/*.so* dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
endif
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(EIGENPY_URL)
|
||||
cd dist/working && tar -xf eigenpy*
|
||||
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
|
||||
@@ -505,6 +498,25 @@ ifeq ($(PLATFORM), win)
|
||||
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)
|
||||
@@ -664,6 +676,7 @@ endif
|
||||
rm -rf dist/blenderbim/libs/site/packages/numpy.libs
|
||||
|
||||
cd dist/blenderbim && $(SED) "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist/blenderbim/bim && $(SED) "s/8888888/$(LAST_COMMIT_HASH)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
|
||||
|
||||
@@ -22,13 +22,14 @@ import site
|
||||
|
||||
bl_info = {
|
||||
"name": "BlenderBIM",
|
||||
"description": "Author, import, and export data using the Industry Foundation Classes schema",
|
||||
"description": "Transforms Blender into a native Building Information Model authoring platform using IFC.",
|
||||
"author": "IfcOpenShell Contributors",
|
||||
"blender": (2, 80, 0),
|
||||
"blender": (3, 1, 0),
|
||||
"version": (0, 0, 999999),
|
||||
"location": "File > Export, File > Import, Scene / Object / Material / Mesh Properties",
|
||||
"location": "File Menu, Scene Properties Tab. See documentation for more.",
|
||||
"doc_url": "https://blenderbim.org/docs",
|
||||
"tracker_url": "https://github.com/IfcOpenShell/IfcOpenShell/issues",
|
||||
"category": "Import-Export",
|
||||
"category": "System",
|
||||
}
|
||||
|
||||
if sys.modules.get("bpy", None):
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import blenderbim
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator, helper
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
"search": None,
|
||||
@@ -86,9 +91,10 @@ classes = [
|
||||
operator.AddIfcFile,
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.ConfigureVisibility,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.SwitchTab,
|
||||
operator.SetTab,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SelectDataDir,
|
||||
@@ -105,7 +111,7 @@ classes = [
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
prop.Attribute,
|
||||
prop.ModuleVisibility,
|
||||
prop.BIMAreaProperties,
|
||||
prop.BIMProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
@@ -114,51 +120,75 @@ classes = [
|
||||
prop.BIMCollectionProperties,
|
||||
prop.BIMMaterialProperties,
|
||||
prop.BIMMeshProperties,
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
# Scene panel groups
|
||||
# Project overview
|
||||
ui.BIM_PT_project_info,
|
||||
ui.BIM_PT_project_setup,
|
||||
ui.BIM_PT_collaboration,
|
||||
ui.BIM_PT_selection,
|
||||
ui.BIM_PT_geometry,
|
||||
ui.BIM_PT_services,
|
||||
ui.BIM_PT_structural,
|
||||
ui.BIM_PT_4D5D,
|
||||
ui.BIM_PT_quality_control,
|
||||
ui.BIM_PT_integrations,
|
||||
# Object panel groups
|
||||
ui.BIM_PT_object_metadata,
|
||||
ui.BIM_PT_geometry_object,
|
||||
ui.BIM_PT_services_object,
|
||||
ui.BIM_PT_utilities_object,
|
||||
ui.BIM_PT_misc_object,
|
||||
ui.BIM_PT_tab_grouping_and_filtering,
|
||||
# Tabs panel
|
||||
ui.BIM_PT_tabs,
|
||||
# Object information
|
||||
ui.BIM_PT_tab_object_metadata,
|
||||
ui.BIM_PT_tab_misc,
|
||||
# Geometry and materials
|
||||
ui.BIM_PT_tab_representations,
|
||||
ui.BIM_PT_tab_geometric_relationships,
|
||||
ui.BIM_PT_tab_parametric_geometry,
|
||||
ui.BIM_PT_tab_materials,
|
||||
ui.BIM_PT_tab_styles,
|
||||
# Drawings and documents
|
||||
# Services and systems
|
||||
ui.BIM_PT_tab_services,
|
||||
ui.BIM_PT_tab_services_object,
|
||||
# Structural analysis
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
ui.BIM_PT_tab_4D5D,
|
||||
# Facility management
|
||||
ui.BIM_PT_tab_handover,
|
||||
ui.BIM_PT_tab_operations,
|
||||
# Quality and coordination
|
||||
ui.BIM_PT_tab_quality_control,
|
||||
ui.BIM_PT_tab_collaboration,
|
||||
ui.BIM_PT_tab_sandbox,
|
||||
# TODO: move this somewhere else and clean it up
|
||||
ui.BIM_PT_section_plane,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
classes.extend(mod.classes)
|
||||
|
||||
addon_keymaps = []
|
||||
icons = None
|
||||
is_registering = False
|
||||
last_commit_hash = "8888888"
|
||||
|
||||
|
||||
def on_register(scene):
|
||||
handler.setDefaultProperties(scene)
|
||||
global is_registering
|
||||
|
||||
if is_registering:
|
||||
return
|
||||
is_registering = True
|
||||
handler.load_post(scene)
|
||||
if not bpy.app.background:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
is_registering = False
|
||||
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.undo_pre.append(handler.undo_pre)
|
||||
bpy.app.handlers.undo_post.append(handler.undo_post)
|
||||
bpy.app.handlers.redo_pre.append(handler.redo_pre)
|
||||
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
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.Screen.BIMAreaProperties = bpy.props.CollectionProperty(type=prop.BIMAreaProperties)
|
||||
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)
|
||||
@@ -167,24 +197,53 @@ def register():
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.SCENE_PT_unit.append(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.append(ui.draw_statusbar)
|
||||
|
||||
for mod in modules.values():
|
||||
mod.register()
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY')
|
||||
kmi = km.keymap_items.new('bim.switch_tab', 'TAB', 'PRESS', ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
global icons
|
||||
|
||||
icons_dir = os.path.join(cwd, "data", "icons")
|
||||
icon_preview = bpy.utils.previews.new()
|
||||
for filename in os.listdir(icons_dir):
|
||||
if filename.endswith(".png"):
|
||||
icon_name = os.path.splitext(filename)[0]
|
||||
icon_path = os.path.join(icons_dir, filename)
|
||||
icon_preview.load(icon_name, icon_path, "IMAGE")
|
||||
|
||||
icons = icon_preview
|
||||
|
||||
global last_commit_hash
|
||||
try:
|
||||
import git
|
||||
path = Path(__file__).resolve().parent
|
||||
repo = git.Repo(str(path), search_parent_directories=True)
|
||||
last_commit_hash = repo.head.object.hexsha
|
||||
except:
|
||||
pass
|
||||
|
||||
def unregister():
|
||||
global icons
|
||||
|
||||
bpy.utils.previews.remove(icons)
|
||||
|
||||
for cls in reversed(classes):
|
||||
if getattr(cls, "is_registered", None) is None:
|
||||
bpy.utils.unregister_class(cls)
|
||||
elif cls.is_registered:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
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
|
||||
@@ -194,9 +253,30 @@ def unregister():
|
||||
del bpy.types.Curve.BIMMeshProperties
|
||||
del bpy.types.Camera.BIMMeshProperties
|
||||
del bpy.types.PointLight.BIMMeshProperties
|
||||
bpy.types.SCENE_PT_unit.remove(ui.ifc_units)
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.remove(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.remove(ui.draw_statusbar)
|
||||
|
||||
for mod in reversed(list(modules.values())):
|
||||
mod.unregister()
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
if kc:
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
|
||||
for panel in [
|
||||
"SCENE_PT_scene",
|
||||
"SCENE_PT_unit",
|
||||
"SCENE_PT_physics",
|
||||
"SCENE_PT_rigid_body_world",
|
||||
"SCENE_PT_audio",
|
||||
"SCENE_PT_keying_sets",
|
||||
"SCENE_PT_custom_props",
|
||||
]:
|
||||
try:
|
||||
bpy.utils.unregister_class(getattr(handler, f"Override_{panel}"))
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -27,6 +27,12 @@ text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type
|
||||
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
|
||||
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
|
||||
.PredefinedType-LINEWORK.dashed { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-LINEWORK.fine { stroke-width: 0.18; stroke: #777777; }
|
||||
.PredefinedType-LINEWORK.thin { stroke-width: 0.25; }
|
||||
.PredefinedType-LINEWORK.medium { stroke-width: 0.35; }
|
||||
.PredefinedType-LINEWORK.thick { stroke-width: 0.5; }
|
||||
.PredefinedType-LINEWORK.strong { stroke-width: 1; }
|
||||
.PredefinedType-BACKGROUND { stroke: black; stroke-width: 0.18; }
|
||||
.PredefinedType-GRID { marker-start: url(#grid-marker); marker-end: url(#grid-marker); }
|
||||
.PredefinedType-SECTION { stroke-dasharray: 12.5, 3, 3, 3; }
|
||||
@@ -40,7 +46,6 @@ text, tspan { /* 2.5mm */ fill: black; stroke: none; font-family: 'OpenGost Type
|
||||
.PredefinedType-SLOPEPERCENT { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-SLOPEFRACTION { marker-end: url(#radius-marker-end); }
|
||||
.PredefinedType-DIAMETER { marker-start: url(#diameter-marker-start); marker-end: url(#diameter-marker-end); }
|
||||
.PredefinedType-HIDDENLINE { stroke-dasharray: 3, 2; }
|
||||
.PredefinedType-STAIRARROW { marker-start: url(#stair-marker-start); marker-end: url(#stair-marker-end); }
|
||||
.PredefinedType-BOUNDARY { fill: none; stroke: red; stroke-width: 1; stroke-dasharray: 12, 4, 3, 4, 3, 4; }
|
||||
.PredefinedType-SEALANT { fill: url(#crosshatch1); stroke-width: 0.25; }
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<marker id="stair-marker-end" markerHeight="42" markerWidth="21" orient="auto" refX="21" refY="21">
|
||||
<path d="M 0 0 L 21 21 L 0 42" class="annotation stair" style="stroke-width:2; marker-start: none; marker-end: none;" />
|
||||
</marker>
|
||||
<marker id="plan-level-marker" markerHeight="30" markerWidth="30" refX="15" refY="15">
|
||||
<marker id="plan-level-marker" markerHeight="30" markerWidth="30" refX="15" refY="15" orient="auto">
|
||||
<g>
|
||||
<path d="M 15 0 L 15 30" class="annotation" style="stroke-width:1;" />
|
||||
<path d="M 0 15 L 30 15" class="annotation" style="stroke-width:1;" />
|
||||
@@ -84,7 +84,7 @@
|
||||
<path d="M 15 15 L 22.5 15 A 7.5 7.5 0 0 1 15 22.5 Z" fill="black"/>
|
||||
</g>
|
||||
</marker>
|
||||
<marker id="section-level-marker" markerHeight="11" markerWidth="90" refX="20" refY="10">
|
||||
<marker id="section-level-marker" markerHeight="11" markerWidth="90" refX="20" refY="10" orient="auto-start-reverse">
|
||||
<g>
|
||||
<path d="M 0 1 L 20 1 L 10 11 Z" fill="black" />
|
||||
<path d="M 0 1 L 90 1" class="annotation" style="stroke-width:1;" />
|
||||
|
||||
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -389,7 +389,7 @@
|
||||
"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.type": "RENDERED",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
@@ -640,7 +640,7 @@
|
||||
"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.type": "RENDERED",
|
||||
"scene.display.shading.use_compositor": "DISABLED",
|
||||
"scene.display.shading.use_dof": false,
|
||||
"scene.display.shading.use_scene_lights": false,
|
||||
@@ -752,4 +752,4 @@
|
||||
"space.overlay.xray_alpha_bone": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
<line x1="-5" x2="5" style="stroke: black; stroke-width: 0.25;" />
|
||||
<text y="2.5" class="regular" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="window-tag">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="m 0.0161133,-6.0583333 5.2339915,3.8366023 -2.031433,6.1633977 c 0,0 -6.4894866,-0.027414 -6.4894866,-0.027414 l -1.97929,-6.1803395 5.2662181,-3.7922467" />
|
||||
</g>
|
||||
<g id="space-tag">
|
||||
<text y="-5" class="large" text-anchor="middle" dominant-baseline="middle" data-type="text-template"></text>
|
||||
<rect x="-6" y="-2.5" width="12" height="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
@@ -41,4 +44,17 @@
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea">
|
||||
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-CONTROLPOINT">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
|
||||
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-TRAVERSEPOINT">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
|
||||
</g>
|
||||
<g id="SurveyArea-SPOTELEVATION">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EQto_BodyGeometryValidation.ifc','2020-01-01T00:00:00',(),(),'EQto_BodyGeometryValidation','EQto_BodyGeometryValidation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2KS9su6r517uLXTGKwwDdn',$,'EQto_BodyGeometryValidation','Quantities supplied for validating the correct interpretation of the body shape representation at import. In case of multiple representation items, the quantities are summed for each of the items (irrespective of any overlap). Choosing a suitable tolerance value for comparing the supplied numbers to the numbers calculated from the reconstructed geometry is at the discretion of the importing application.',.QTO_OCCURRENCEDRIVEN.,'IfcProduct',(#2,#3,#4,#5,#6,#7));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('3iCdOOLRjCwQ_HRTB41Xh8',$,'GrossSurfaceArea','Total gross area of the object, normally generated as perimeter * length + 2 * cross section area. It is the sum of OuterSurfaceArea + (2 x CrossSectionArea) and shall only be given, if the OuterSurfaceArea and CrossSectionArea cannot be established separately.\X2\000A000A\X0\Total gross surface area of the element before applying product-level geometric features such as openings and projections.',.Q_AREA.,$,$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('0juemEqF5889vg7HwR87Fv',$,'NetSurfaceArea','Net surface area of the object, normally generated as perimeter * length + 2 * cross section area taking into account possible processing features (cut-out''s, etc.) or openings and recesses.\X2\000A000A\X0\Total net surface area of the element after applying product-level geometric features such as openings and projections.',.Q_AREA.,$,$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('1LUuLSnMXFDgGLBCvte8aH',$,'GrossVolume','Total gross volume of the object. Openings, recesses, enclosed objects and projections are not taken into account.\X2\000A000A\X0\Total gross volume of the element before applying product-level geometric features such as openings and projections.',.Q_VOLUME.,$,$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0ny7TXUFz8wvnoQ2eU8czF',$,'NetVolume','Total net volume of the object, taking into account possible processing features (cut-out''s, etc.) or openings and recesses.\X2\000A000A\X0\Total net volume of the element before applying product-level geometric features such as openings and projections.',.Q_VOLUME.,$,$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('31g8_gdUv8KRkOWzW48KoP',$,'SurfaceGenusBeforeFeatures','The Surface Genus of the evaluated representation items before applying product-level geometric features such as openings and projections.Surface Genus is a topological measure that represents the number of "holes" or "handles" on a surface. For example, a sphere has genus 0, and a torus has genus 1.Computed using the Euler characteristic:$$\\chi=V-E+F$$With the numbers of vertices (V), edges (E) and faces (F)$$\\chi=2\X2\2212\X0\2g\X2\2212\X0\b$$With surface genus (g) and the number of boundaries (b) the latter zero in case of an enclosed volume.',.Q_COUNT.,$,$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('3kBBtGUebC09mUggGTKpjj',$,'SurfaceGenusAfterFeatures','The Surface Genus of the evaluated representation items after applying product-level geometric features such as openings and projections.Surface Genus is a topological measure that represents the number of "holes" or "handles" on a surface. For example, a sphere has genus 0, and a torus has genus 1.Computed using the Euler characteristic:$$\\chi=V-E+F$$With the numbers of vertices (V), edges (E) and faces (F)$$\\chi=2\X2\2212\X0\2g\X2\2212\X0\b$$With surface genus (g) and the number of boundaries (b) the latter zero in case of an enclosed volume.',.Q_COUNT.,$,$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -1,214 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
presets = {
|
||||
"Basic": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
|
||||
"Admin": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"cobie",
|
||||
"resource",
|
||||
"cost",
|
||||
"sequence",
|
||||
"group",
|
||||
"system",
|
||||
"structural",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"constraint",
|
||||
"document",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"lca",
|
||||
"csv",
|
||||
"bimtester",
|
||||
"diff",
|
||||
"patch",
|
||||
"covetool",
|
||||
"augin",
|
||||
"debug",
|
||||
],
|
||||
"BIM Coordination": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"georeference",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"clash",
|
||||
"csv",
|
||||
"bimtester",
|
||||
"diff",
|
||||
"patch",
|
||||
"debug",
|
||||
],
|
||||
"Architecture": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"Structural Engineering": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"structural",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"MEP": [
|
||||
"project",
|
||||
"search",
|
||||
"bcf",
|
||||
"root",
|
||||
"unit",
|
||||
"model",
|
||||
"georeference",
|
||||
"context",
|
||||
"drawing",
|
||||
"misc",
|
||||
"attribute",
|
||||
"type",
|
||||
"spatial",
|
||||
"void",
|
||||
"aggregate",
|
||||
"geometry",
|
||||
"group",
|
||||
"system",
|
||||
"boundary",
|
||||
"profile",
|
||||
"material",
|
||||
"style",
|
||||
"layer",
|
||||
"owner",
|
||||
"pset",
|
||||
"qto",
|
||||
"classification",
|
||||
"pset_template",
|
||||
"clash",
|
||||
"csv",
|
||||
],
|
||||
"Quantity Surveying": ["project", "search", "attribute", "spatial", "void", "pset", "qto", "classification", "csv"],
|
||||
"Model Enrichment": [
|
||||
"project",
|
||||
"search",
|
||||
"root",
|
||||
"attribute",
|
||||
"spatial",
|
||||
"pset",
|
||||
"qto",
|
||||
"pset_template",
|
||||
"csv",
|
||||
"patch",
|
||||
],
|
||||
"3D Visualisation": ["project", "search", "bcf", "attribute", "spatial", "pset", "qto"],
|
||||
"4D Scheduling": ["project", "search", "attribute", "spatial", "resource", "cost", "sequence", "pset", "qto"],
|
||||
"5D Cost Management": [
|
||||
"project",
|
||||
"search",
|
||||
"attribute",
|
||||
"spatial",
|
||||
"resource",
|
||||
"cost",
|
||||
"sequence",
|
||||
"pset",
|
||||
"qto",
|
||||
"lca",
|
||||
],
|
||||
"Facility Management": ["project", "search", "attribute", "spatial", "cobie", "pset", "qto", "lca"],
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
Name,foo
|
||||
TransomThickness,500
|
||||
|
Binary file not shown.
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import addon_utils
|
||||
@@ -30,6 +31,7 @@ from mathutils import Vector
|
||||
from math import cos, degrees
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
global_subscription_owner = object()
|
||||
|
||||
|
||||
@@ -52,10 +54,12 @@ def mode_callback(obj, data):
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
# TODO Do we still need this, now that we are monitoring the undo redo objects?
|
||||
try:
|
||||
obj.name
|
||||
except:
|
||||
# The object is invalid but somehow still has a callback. Clear all
|
||||
# msgbus subscriptions to prevent useless further triggers.
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
return # In case the object RNA is gone during an undo / redo operation
|
||||
# Blender names are up to 63 UTF-8 bytes
|
||||
if len(bytes(obj.name, "utf-8")) >= 63:
|
||||
@@ -72,6 +76,9 @@ def name_callback(obj, data):
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
return
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.is_a("IfcGridAxis"):
|
||||
element.AxisTag = obj.name.split("/")[1]
|
||||
refresh_ui_data()
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
if obj.BIMObjectProperties.collection:
|
||||
@@ -98,8 +105,8 @@ def update_bim_tool_props():
|
||||
if not obj:
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode).idname
|
||||
if current_tool != "bim.bim_tool":
|
||||
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:
|
||||
@@ -198,40 +205,22 @@ def loadIfcStore(scene):
|
||||
IfcStore.relink_all_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_pre(scene):
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo()
|
||||
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_pre(scene):
|
||||
IfcStore.track_undo_redo_stack_object_map()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo()
|
||||
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
IfcStore.track_undo_redo_stack_selected_objects()
|
||||
IfcStore.reload_undo_redo_stack_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def ensureIfcExported(scene):
|
||||
if IfcStore.get_file() and not bpy.context.scene.BIMProperties.ifc_file:
|
||||
bpy.ops.export_ifc.bim("INVOKE_DEFAULT")
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
def get_application(ifc):
|
||||
@@ -268,8 +257,78 @@ def viewport_shading_changed_callback(area):
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_scene"):
|
||||
|
||||
class Override_SCENE_PT_scene(bpy.types.SCENE_PT_scene):
|
||||
bl_idname = "SCENE_PT_scene_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_unit"):
|
||||
|
||||
class Override_SCENE_PT_unit(bpy.types.SCENE_PT_unit):
|
||||
bl_idname = "SCENE_PT_unit_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_physics"):
|
||||
|
||||
class Override_SCENE_PT_physics(bpy.types.SCENE_PT_physics):
|
||||
bl_idname = "SCENE_PT_physics_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_rigid_body_world"):
|
||||
|
||||
class Override_SCENE_PT_rigid_body_world(bpy.types.SCENE_PT_rigid_body_world):
|
||||
bl_idname = "SCENE_PT_rigid_body_world_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_audio"):
|
||||
|
||||
class Override_SCENE_PT_audio(bpy.types.SCENE_PT_audio):
|
||||
bl_idname = "SCENE_PT_audio_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_keying_sets"):
|
||||
|
||||
class Override_SCENE_PT_keying_sets(bpy.types.SCENE_PT_keying_sets):
|
||||
bl_idname = "SCENE_PT_keying_sets_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
if getattr(bpy.types, "SCENE_PT_custom_props"):
|
||||
|
||||
class Override_SCENE_PT_custom_props(bpy.types.SCENE_PT_custom_props):
|
||||
bl_idname = "SCENE_PT_custom_props_override"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "BLENDER")
|
||||
|
||||
|
||||
@persistent
|
||||
def setDefaultProperties(scene):
|
||||
def load_post(scene):
|
||||
global global_subscription_owner
|
||||
active_object_key = bpy.types.LayerObjects, "active"
|
||||
bpy.msgbus.subscribe_rna(
|
||||
@@ -293,3 +352,36 @@ def setDefaultProperties(scene):
|
||||
ifcopenshell.api.owner.settings.get_user = lambda ifc: core_owner.get_user(tool.Owner)
|
||||
ifcopenshell.api.owner.settings.get_application = get_application
|
||||
AuthoringData.type_thumbnails = {}
|
||||
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.should_setup_workspace:
|
||||
if "BIM" in bpy.data.workspaces:
|
||||
bpy.context.window.workspace = bpy.data.workspaces["BIM"]
|
||||
else:
|
||||
bpy.ops.workspace.append_activate(idname="BIM", filepath=os.path.join(cwd, "data", "workspace.blend"))
|
||||
|
||||
# To improve usability for new users, we hijack the scene properties
|
||||
# tab. We override default scene properties panels with our own poll
|
||||
# to hide them unless the user has chosen to view Blender properties.
|
||||
for panel in [
|
||||
"SCENE_PT_scene",
|
||||
"SCENE_PT_unit",
|
||||
"SCENE_PT_physics",
|
||||
"SCENE_PT_rigid_body_world",
|
||||
"SCENE_PT_audio",
|
||||
"SCENE_PT_keying_sets",
|
||||
"SCENE_PT_custom_props",
|
||||
]:
|
||||
if getattr(bpy.types, panel, None):
|
||||
try:
|
||||
bpy.utils.register_class(globals()[f"Override_{panel}"])
|
||||
bpy.utils.unregister_class(getattr(bpy.types, panel))
|
||||
except:
|
||||
pass
|
||||
|
||||
# https://blender.stackexchange.com/questions/140644/how-can-make-the-state-of-a-boolean-property-relative-to-the-3d-view-area
|
||||
for screen in bpy.data.screens:
|
||||
if len(screen.BIMAreaProperties) == 20:
|
||||
continue
|
||||
screen.BIMAreaProperties.clear()
|
||||
for i in range(20): # 20 is an arbitrary value of split areas
|
||||
screen.BIMAreaProperties.add()
|
||||
|
||||
@@ -26,6 +26,7 @@ import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
from pathlib import Path
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
|
||||
class IfcStore:
|
||||
@@ -43,9 +44,6 @@ class IfcStore:
|
||||
classification_file = None
|
||||
library_path = ""
|
||||
library_file = None
|
||||
element_listeners = set()
|
||||
undo_redo_stack_objects = set()
|
||||
undo_redo_stack_object_names = {}
|
||||
current_transaction = ""
|
||||
last_transaction = ""
|
||||
history = []
|
||||
@@ -160,96 +158,6 @@ class IfcStore:
|
||||
return
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_element_listener(callback):
|
||||
IfcStore.element_listeners.add(callback)
|
||||
|
||||
@staticmethod
|
||||
def track_undo_redo_stack_object_map():
|
||||
"""Keeps track of currently mapped object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
occur whenever an object is affected during an operation.
|
||||
|
||||
For example, if an operator deletes a modifier on o1, then o1 will be invalidated.
|
||||
"""
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
IfcStore.undo_redo_stack_object_names[key] = value.name
|
||||
except:
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
def track_undo_redo_stack_selected_objects():
|
||||
"""Keeps track of selected object names, typically during undo and redo
|
||||
|
||||
When any Blender object is stored outside a Blender PointerProperty, such as
|
||||
in a regular Python list, there is the likely probability that the object
|
||||
will be invalidated when undo or redo occurs. Object invalidation seems to
|
||||
occur for selected objects either pre/post undo/redo event, including
|
||||
selected objects for consecutive undo/redos, and all children. This is
|
||||
important because selected objects are often deleted from the scene.
|
||||
|
||||
So if I first select o1, then o2, then o3, then press undo, o3 will be
|
||||
invalidated. If instead I press undo twice, o3 and o2 will be invalidated.
|
||||
"""
|
||||
if bpy.context.active_object:
|
||||
objects = set([o.name for o in bpy.context.selected_objects + [bpy.context.active_object]])
|
||||
objects.update([o.name for o in bpy.context.active_object.children])
|
||||
else:
|
||||
objects = set([o.name for o in bpy.context.selected_objects])
|
||||
for obj in bpy.context.selected_objects:
|
||||
objects.update([o.name for o in obj.children])
|
||||
IfcStore.undo_redo_stack_objects |= objects
|
||||
|
||||
@staticmethod
|
||||
def reload_undo_redo_stack_objects():
|
||||
"""Reloads any invalidated objects after undo or redo
|
||||
|
||||
After an undo or redo operation, objects may have been invalidated in
|
||||
our id_map and guid_map. Invalidated objects are typically those that
|
||||
have been manipulated or deleted. This checks the cache of mapped and
|
||||
selected objects prior to the operation and ensures that if the object
|
||||
is invalidated, they are reloaded based on the object name that was
|
||||
tracked prior to the undo / redo.
|
||||
"""
|
||||
file = IfcStore.get_file()
|
||||
if not file:
|
||||
return
|
||||
|
||||
# First, reload objects that were selected or active
|
||||
for name in IfcStore.undo_redo_stack_objects:
|
||||
obj = bpy.data.objects.get(name)
|
||||
if not obj:
|
||||
continue
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
# Scan for any straggling invalidated objects which were indirectly affected and reload them too.
|
||||
for key, value in IfcStore.id_map.items():
|
||||
try:
|
||||
value.name
|
||||
except:
|
||||
# TODO not so sure about this obj_name check
|
||||
obj_name = IfcStore.undo_redo_stack_object_names.get(key, None)
|
||||
if not obj_name:
|
||||
continue
|
||||
obj = bpy.data.objects.get(obj_name)
|
||||
if not obj or not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
data = {"id": element.id(), "obj": obj.name}
|
||||
if hasattr(element, "GlobalId"):
|
||||
data["guid"] = element.GlobalId
|
||||
IfcStore.commit_link_element(data)
|
||||
|
||||
@staticmethod
|
||||
def relink_all_objects():
|
||||
if not IfcStore.get_file():
|
||||
@@ -282,6 +190,13 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def link_element(element, obj):
|
||||
# Please use tool.Ifc.link() instead of this method. We want to
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
|
||||
obj.BIMMeshProperties.ifc_definition_id = element.id()
|
||||
return
|
||||
|
||||
existing_obj = IfcStore.id_map.get(element.id(), None)
|
||||
if existing_obj == obj:
|
||||
return
|
||||
@@ -308,9 +223,6 @@ class IfcStore:
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
|
||||
for listener in IfcStore.element_listeners:
|
||||
listener(element, obj)
|
||||
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
|
||||
IfcStore.history[-1]["operations"].append(
|
||||
@@ -331,10 +243,12 @@ class IfcStore:
|
||||
IfcStore.id_map[data["id"]] = obj
|
||||
if "guid" in data:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "name", blenderbim.bim.handler.name_callback)
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@@ -400,24 +314,33 @@ class IfcStore:
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(operator, context):
|
||||
def execute_ifc_operator(operator, context, is_invoke=False):
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.begin_transaction(operator)
|
||||
IfcStore.get_file().begin_transaction()
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().begin_transaction()
|
||||
if BrickStore.graph:
|
||||
BrickStore.begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||
else:
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
result = getattr(operator, "_execute")(context)
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.get_file().end_transaction()
|
||||
IfcStore.add_transaction_operation(
|
||||
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
|
||||
)
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().end_transaction()
|
||||
IfcStore.add_transaction_operation(
|
||||
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
|
||||
)
|
||||
if BrickStore.graph:
|
||||
BrickStore.end_transaction()
|
||||
IfcStore.end_transaction(operator)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -425,8 +348,6 @@ class IfcStore:
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator):
|
||||
IfcStore.undo_redo_stack_objects = set()
|
||||
IfcStore.undo_redo_stack_object_names = {}
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@@ -452,19 +373,35 @@ class IfcStore:
|
||||
IfcStore.future = []
|
||||
|
||||
@staticmethod
|
||||
def undo():
|
||||
def undo(until_key=None):
|
||||
BrickStore.undo()
|
||||
if not IfcStore.history:
|
||||
return
|
||||
event = IfcStore.history.pop()
|
||||
for transaction in event["operations"][::-1]:
|
||||
transaction["rollback"](transaction["data"])
|
||||
IfcStore.future.append(event)
|
||||
|
||||
while IfcStore.history:
|
||||
if IfcStore.history[-1]["key"] == until_key:
|
||||
return
|
||||
|
||||
event = IfcStore.history.pop()
|
||||
for transaction in event["operations"][::-1]:
|
||||
transaction["rollback"](transaction["data"])
|
||||
IfcStore.future.append(event)
|
||||
|
||||
@staticmethod
|
||||
def redo():
|
||||
def redo(until_key=None):
|
||||
BrickStore.redo()
|
||||
|
||||
if not IfcStore.future:
|
||||
return
|
||||
event = IfcStore.future.pop()
|
||||
for transaction in event["operations"]:
|
||||
transaction["commit"](transaction["data"])
|
||||
IfcStore.history.append(event)
|
||||
|
||||
has_encountered_key = False
|
||||
while IfcStore.future:
|
||||
if has_encountered_key and IfcStore.future[-1]["key"] != until_key:
|
||||
return
|
||||
elif IfcStore.future[-1]["key"] == until_key:
|
||||
has_encountered_key = True
|
||||
|
||||
event = IfcStore.future.pop()
|
||||
for transaction in event["operations"]:
|
||||
transaction["commit"](transaction["data"])
|
||||
IfcStore.history.append(event)
|
||||
|
||||
@@ -52,6 +52,7 @@ class FileCopy(threading.Thread):
|
||||
class MaterialCreator:
|
||||
def __init__(self, ifc_import_settings, ifc_importer):
|
||||
self.mesh = None
|
||||
self.obj = None
|
||||
self.materials = {}
|
||||
self.styles = {}
|
||||
self.parsed_meshes = set()
|
||||
@@ -60,6 +61,8 @@ class MaterialCreator:
|
||||
|
||||
def create(self, element, obj, mesh):
|
||||
self.mesh = mesh
|
||||
# as ifcopenshell triangulates the mesh, we need to merge it to quads again
|
||||
self.obj = obj
|
||||
if (hasattr(element, "Representation") and not element.Representation) or (
|
||||
hasattr(element, "RepresentationMaps") and not element.RepresentationMaps
|
||||
):
|
||||
@@ -132,21 +135,67 @@ class MaterialCreator:
|
||||
return
|
||||
for style_id in style_ids:
|
||||
material = self.styles[style_id]
|
||||
ifc_coordinate_id = material.BIMMaterialProperties.ifc_coordinate_id
|
||||
if ifc_coordinate_id != 0:
|
||||
self.load_texture_map(tool.Ifc.get().by_id(ifc_coordinate_id))
|
||||
if self.mesh.materials.find(material.name) == -1:
|
||||
self.mesh.materials.append(material)
|
||||
return True
|
||||
|
||||
def load_texture_map(self, coordinates):
|
||||
# Get a BMesh representation
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(self.mesh)
|
||||
uv_layer = bm.loops.layers.uv.verify()
|
||||
|
||||
# remap the faceset CoordList index to the vertices in blender mesh
|
||||
coordinates_remap = []
|
||||
for co in coordinates.MappedTo.Coordinates.CoordList:
|
||||
co = mathutils.Vector(co)
|
||||
index = next(v.index for v in bm.verts if (v.co - co).length_squared < 1e-5)
|
||||
coordinates_remap.append(index)
|
||||
|
||||
faces_remap = None
|
||||
texture_map = None
|
||||
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
|
||||
faces_remap = [[coordinates_remap[i-1] for i in tex_coord_index.TexCoordsOf.CoordIndex]
|
||||
for tex_coord_index in coordinates.TexCoordIndices]
|
||||
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
|
||||
elif coordinates.is_a("IfcIndexedTriangleTextureMap"):
|
||||
faces_remap = [[coordinates_remap[i-1] for i in triangle_face]
|
||||
for triangle_face in coordinates.MappedTo.CoordIndex]
|
||||
texture_map = coordinates.TexCoordIndex
|
||||
|
||||
# apply uv to each face
|
||||
for bface in bm.faces:
|
||||
face = [loop.vert.index for loop in bface.loops]
|
||||
# find the corresponding TexCoordIndex by matching ifc faceset with blender face
|
||||
# remap TexCoordIndex as the loop start may different from blender face
|
||||
texCoordIndex = next(
|
||||
[tex_coord_index[face_remap.index(i)] for i in face]
|
||||
for tex_coord_index, face_remap in zip(texture_map, faces_remap)
|
||||
if all(i in face_remap for i in face)
|
||||
)
|
||||
# apply uv to each loop
|
||||
for loop, i in zip(bface.loops, texCoordIndex):
|
||||
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i-1]
|
||||
|
||||
# Finish up, write the bmesh back to the mesh
|
||||
bm.to_mesh(self.mesh)
|
||||
bm.free()
|
||||
|
||||
def assign_material_slots_to_faces(self):
|
||||
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
|
||||
return
|
||||
if len(self.mesh.materials) == 1:
|
||||
return
|
||||
material_to_slot = {}
|
||||
for i, material in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.mesh.materials.find(self.styles[material].name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
|
||||
for i, material in enumerate(self.mesh["ios_materials"]):
|
||||
slot_index = self.mesh.materials.find(self.styles[material].name)
|
||||
material_to_slot[i] = slot_index
|
||||
|
||||
material_index = [
|
||||
(material_to_slot[mat_id] if mat_id != -1 else 0) for mat_id in self.mesh["ios_material_ids"]
|
||||
]
|
||||
@@ -327,13 +376,19 @@ class IfcImporter:
|
||||
if isinstance(self.elements, set):
|
||||
self.elements = list(self.elements)
|
||||
# TODO: enable filtering for annotations
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
|
||||
else:
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
|
||||
else:
|
||||
self.elements = self.file.by_type("IfcElement")
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation") if not a.HasAssignments])
|
||||
|
||||
drawing_groups = [g for g in self.file.by_type("IfcGroup") if g.ObjectType == "DRAWING"]
|
||||
drawing_annotations = set()
|
||||
for drawing_group in drawing_groups:
|
||||
for rel in drawing_group.IsGroupedBy:
|
||||
drawing_annotations.update(rel.RelatedObjects)
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
|
||||
self.annotations -= drawing_annotations
|
||||
|
||||
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement")]
|
||||
if self.ifc_import_settings.is_coordinating:
|
||||
@@ -362,7 +417,7 @@ 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() in ("IfcContext", "IfcProject"):
|
||||
if not spatial_element or spatial_element.is_a() in ("IfcProject", "IfcProjectLibrary"):
|
||||
break
|
||||
return results
|
||||
|
||||
@@ -416,7 +471,7 @@ class IfcImporter:
|
||||
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"):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
@@ -424,7 +479,7 @@ class IfcImporter:
|
||||
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"):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
@@ -686,7 +741,7 @@ class IfcImporter:
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
elif native_data["type"] == "IfcFaceBasedSurfaceModel":
|
||||
mesh = self.create_native_faceted_brep(element, mesh_name)
|
||||
mesh.BIMMeshProperties.ifc_definition_id = representation.id()
|
||||
tool.Ifc.link(representation, mesh)
|
||||
mesh.name = mesh_name
|
||||
self.meshes[mesh_name] = mesh
|
||||
self.create_product(element, mesh=mesh)
|
||||
@@ -886,7 +941,7 @@ class IfcImporter:
|
||||
return None
|
||||
|
||||
for representation in representations:
|
||||
if representation.RepresentationType == "PointCloud":
|
||||
if representation.RepresentationType in ("PointCloud", "Point"):
|
||||
return representation
|
||||
|
||||
elif self.file.schema == "IFC2X3" and representation.RepresentationType == "GeometricSet":
|
||||
@@ -915,7 +970,7 @@ class IfcImporter:
|
||||
return result
|
||||
|
||||
def create_pointcloud(self, product, representation):
|
||||
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcCartesianPointList"):
|
||||
@@ -931,6 +986,7 @@ class IfcImporter:
|
||||
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
mesh.from_pydata(vertex_list, [], [])
|
||||
tool.Ifc.link(representation, mesh)
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
@@ -1336,12 +1392,21 @@ class IfcImporter:
|
||||
last_obj = obj
|
||||
if not last_obj:
|
||||
return
|
||||
|
||||
# temporarily unhide types collection to make sure all objects will be cleaned
|
||||
project_collection = bpy.context.view_layer.layer_collection.children[self.project["blender"].name]
|
||||
types_collection = project_collection.children[self.type_collection.name]
|
||||
types_collection.hide_viewport = False
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
|
||||
context_override = {}
|
||||
bpy.ops.object.editmode_toggle(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)
|
||||
|
||||
types_collection.hide_viewport = True
|
||||
bpy.context.view_layer.objects.active = last_obj
|
||||
IfcStore.edited_objs.clear()
|
||||
|
||||
def load_file(self):
|
||||
@@ -1421,27 +1486,8 @@ class IfcImporter:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], orphaned_spaces)
|
||||
orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections]
|
||||
|
||||
self.create_views_collection()
|
||||
self.create_type_collection()
|
||||
|
||||
def create_type_collection(self):
|
||||
for collection in self.project["blender"].children:
|
||||
if collection.name == "Types":
|
||||
self.type_collection = collection
|
||||
break
|
||||
if not self.type_collection:
|
||||
self.type_collection = bpy.data.collections.new("Types")
|
||||
self.project["blender"].children.link(self.type_collection)
|
||||
|
||||
def create_views_collection(self):
|
||||
view_collection = None
|
||||
for collection in self.project["blender"].children:
|
||||
if collection.name == "Views":
|
||||
view_collection = collection
|
||||
break
|
||||
if not view_collection:
|
||||
view_collection = bpy.data.collections.new("Views")
|
||||
self.project["blender"].children.link(view_collection)
|
||||
tool.Loader.create_project_collection("Views")
|
||||
self.type_collection = tool.Loader.create_project_collection("Types")
|
||||
|
||||
def create_spatial_decomposition_collection(self, parent, related_objects):
|
||||
for element in related_objects:
|
||||
@@ -1777,6 +1823,10 @@ class IfcImporter:
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry.faces)
|
||||
|
||||
# See bug 3546
|
||||
# ios_edges holds true edges that aren't triangulated.
|
||||
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry)))
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
@@ -1863,7 +1913,7 @@ class IfcImportSettings:
|
||||
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_clean_mesh = False
|
||||
self.should_cache = True
|
||||
self.is_coordinating = True
|
||||
self.deflection_tolerance = 0.001
|
||||
|
||||
@@ -22,13 +22,13 @@ from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_aggregate(Panel):
|
||||
bl_label = "IFC Aggregates"
|
||||
bl_label = "Aggregates"
|
||||
bl_idname = "BIM_PT_aggregate"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -54,12 +54,12 @@ def draw_ui(context, layout, obj_type, attributes):
|
||||
|
||||
|
||||
class BIM_PT_object_attributes(Panel):
|
||||
bl_label = "IFC Attributes"
|
||||
bl_label = "Attributes"
|
||||
bl_idname = "BIM_PT_object_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -76,7 +76,7 @@ class BIM_PT_object_attributes(Panel):
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
bl_label = "IFC Material Attributes"
|
||||
bl_label = "Material Attributes"
|
||||
bl_idname = "BIM_PT_material_attributes"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
|
||||
@@ -26,7 +26,7 @@ class BIM_PT_augin(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -29,7 +29,7 @@ class BIM_PT_bcf(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -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")
|
||||
|
||||
@@ -27,7 +27,7 @@ class BIM_PT_qa(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
|
||||
@@ -103,10 +103,8 @@ class Loader:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
matrix = mathutils.Matrix(
|
||||
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
|
||||
)
|
||||
matrix[0][3] *= unit_scale
|
||||
matrix[1][3] *= unit_scale
|
||||
matrix[2][3] *= unit_scale
|
||||
)
|
||||
matrix.translation *= unit_scale
|
||||
mesh.transform(matrix)
|
||||
return mesh
|
||||
|
||||
@@ -691,7 +689,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
mat.col[0][:3] = x_axis
|
||||
mat.col[1][:3] = y_axis
|
||||
mat.col[2][:3] = z_axis
|
||||
mat.col[3][:3] = p1
|
||||
mat.translation = p1
|
||||
|
||||
return mat
|
||||
|
||||
@@ -725,7 +723,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
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]
|
||||
p1 = target_face_matrix.translation
|
||||
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
tool.Model.unit_scale = self.unit_scale
|
||||
|
||||
@@ -25,7 +25,7 @@ from blenderbim.bim.module.boundary.data import SpaceBoundariesData
|
||||
|
||||
|
||||
class BIM_PT_SceneBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_label = "Space Boundaries"
|
||||
bl_id_name = "BIM_PT_scene_boundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -45,12 +45,12 @@ class BIM_PT_SceneBoundaries(Panel):
|
||||
|
||||
|
||||
class BIM_PT_Boundary(Panel):
|
||||
bl_label = "IFC Space Boundary"
|
||||
bl_label = "Space Boundary"
|
||||
bl_idname = "BIM_PT_Boundary"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_tab_geometric_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -117,14 +117,14 @@ class BIM_PT_Boundary(Panel):
|
||||
|
||||
|
||||
class BIM_PT_SpaceBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_label = "Space Boundaries"
|
||||
bl_idname = "BIM_PT_SpaceBoundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_tab_geometric_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -21,7 +21,7 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddBrick,
|
||||
operator.AddBrickFeed,
|
||||
operator.AddBrickRelation,
|
||||
operator.AssignBrickReference,
|
||||
operator.CloseBrickProject,
|
||||
operator.ConvertBrickProject,
|
||||
@@ -33,6 +33,9 @@ classes = (
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
operator.RemoveBrickRelation,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
|
||||
@@ -41,9 +41,7 @@ class BrickschemaData:
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"is_loaded": cls.get_is_loaded(),
|
||||
"attributes": cls.attributes(),
|
||||
"namespaces": cls.namespaces(),
|
||||
"brick_equipment_classes": cls.brick_equipment_classes(),
|
||||
"active_relations": cls.active_relations(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
@@ -51,7 +49,7 @@ class BrickschemaData:
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
def active_relations(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
@@ -59,76 +57,55 @@ class BrickschemaData:
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
except:
|
||||
return []
|
||||
results = []
|
||||
uri = brick.uri
|
||||
namespace = str(uri.split("#")[0])
|
||||
if namespace == "https://brickschema.org/schema/Brick":
|
||||
return []
|
||||
results = []
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
||||
SELECT DISTINCT ?name ?value ?sp ?sv WHERE {
|
||||
<{uri}> ?name ?value .
|
||||
SELECT DISTINCT ?predicate ?object ?sp ?sv WHERE {
|
||||
<{uri}> ?predicate ?object .
|
||||
OPTIONAL {
|
||||
{ ?name rdfs:range brick:TimeseriesReference . }
|
||||
{ ?predicate rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?name a brick:EntityProperty . }
|
||||
?value ?sp ?sv }
|
||||
{ ?predicate a brick:EntityProperty . }
|
||||
?object ?sp ?sv }
|
||||
}
|
||||
""".replace(
|
||||
"{uri}", uri
|
||||
)
|
||||
)
|
||||
|
||||
for row in query:
|
||||
name = row.get("name").toPython().split("#")[-1]
|
||||
value = row.get("value")
|
||||
predicate = row.get("predicate")
|
||||
predicate_name = predicate.toPython().split("#")[-1]
|
||||
object = row.get("object")
|
||||
object_name = object.toPython().split("#")[-1]
|
||||
results.append(
|
||||
{
|
||||
"name": name,
|
||||
"value": value.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(value, URIRef),
|
||||
"value_uri": value.toPython(),
|
||||
"is_globalid": name == "globalID",
|
||||
"predicate": predicate,
|
||||
"predicate_name": predicate_name,
|
||||
"object": object,
|
||||
"object_name": object_name,
|
||||
"is_uri": isinstance(object, URIRef),
|
||||
"object_uri": object.toPython(),
|
||||
"is_globalid": predicate == "globalID",
|
||||
}
|
||||
)
|
||||
if isinstance(row.get("value"), BNode):
|
||||
for s, p, o in BrickStore.graph.triples((value, None, None)):
|
||||
results.append(
|
||||
{
|
||||
"name": name + ":" + p.toPython().split("#")[-1],
|
||||
"value": o.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(o, URIRef),
|
||||
"value_uri": o.toPython(),
|
||||
"is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def namespaces(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
results = []
|
||||
for alias, uri in BrickStore.graph.namespaces():
|
||||
results.append((uri, f"{alias}: {uri}", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def brick_equipment_classes(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
results = []
|
||||
query = BrickStore.graph.query(
|
||||
"""
|
||||
PREFIX brick: <https://brickschema.org/schema/Brick#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
SELECT ?class WHERE {
|
||||
?class rdfs:subClassOf* brick:Equipment .
|
||||
}
|
||||
"""
|
||||
)
|
||||
for uri in sorted([x[0].toPython() for x in query]):
|
||||
results.append((uri, uri.split("#")[-1], ""))
|
||||
# if isinstance(row.get("object"), BNode):
|
||||
# for s, p, o in BrickStore.graph.triples((object, None, None)):
|
||||
# results.append(
|
||||
# {
|
||||
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
|
||||
# "object": o.toPython().split("#")[-1],
|
||||
# "is_uri": isinstance(o, URIRef),
|
||||
# "object_uri": o.toPython(),
|
||||
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
# }
|
||||
# )
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -16,17 +16,19 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
self._execute(context)
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -35,11 +37,16 @@ class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load in a Brick project from a file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath)
|
||||
if os.path.exists(self.filepath) and "ttl" in os.path.splitext(self.filepath)[1].lower():
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root)
|
||||
else:
|
||||
self.report({'ERROR'}, f'Failed to load {self.filepath}')
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
@@ -50,35 +57,42 @@ class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect the subclasses of this class"
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class, split_screen=self.split_screen)
|
||||
|
||||
|
||||
class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_item"
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect this entity in the viewer"
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item)
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Go back to the previous list view"
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.rewind_brick_class(tool.Brick)
|
||||
core.rewind_brick_class(tool.Brick, split_screen=self.split_screen)
|
||||
|
||||
|
||||
class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.close_brick_project"
|
||||
bl_label = "Close Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
core.close_brick_project(tool.Brick)
|
||||
@@ -113,6 +127,7 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick"
|
||||
bl_label = "Add Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -124,24 +139,32 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
tool.Brick,
|
||||
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_equipment_class,
|
||||
brick_class=props.brick_entity_class,
|
||||
library=library,
|
||||
label=props.new_brick_label,
|
||||
)
|
||||
|
||||
|
||||
class AddBrickFeed(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_feed"
|
||||
bl_label = "Add Brick Feed"
|
||||
class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_relation"
|
||||
bl_label = "Add Brick Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick relationship"
|
||||
|
||||
def _execute(self, context):
|
||||
source = tool.Ifc.get_entity([o for o in context.selected_objects if o != context.active_object][0])
|
||||
destination = tool.Ifc.get_entity(context.active_object)
|
||||
core.add_brick_feed(
|
||||
tool.Ifc,
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
if props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
object = props.new_brick_relation_object
|
||||
elif props.split_screen_toggled:
|
||||
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
|
||||
else:
|
||||
object = props.new_brick_relation_namespace + props.new_brick_relation_object
|
||||
core.add_brick_relation(
|
||||
tool.Brick,
|
||||
source=source,
|
||||
destination=destination,
|
||||
brick_uri=brick.uri,
|
||||
predicate=props.new_brick_relation_type,
|
||||
object=object
|
||||
)
|
||||
|
||||
|
||||
@@ -158,28 +181,55 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
|
||||
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
|
||||
|
||||
|
||||
class NewBrickFile(bpy.types.Operator, Operator):
|
||||
class NewBrickFile(bpy.types.Operator):
|
||||
bl_idname = "bim.new_brick_file"
|
||||
bl_label = "New Brick File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create a Brick project from scratch"
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
IfcStore.add_transaction_operation(self, rollback=self.rollback, commit=lambda data: True)
|
||||
self._execute(context)
|
||||
self.transaction_data = {
|
||||
"schema": BrickStore.schema,
|
||||
"path": BrickStore.path,
|
||||
"graph": BrickStore.graph,
|
||||
}
|
||||
IfcStore.add_transaction_operation(self, rollback=lambda data: True, commit=self.commit)
|
||||
IfcStore.end_transaction(self)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.new_brick_file(tool.Brick)
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.new_brick_file(tool.Brick, brick_root=root)
|
||||
|
||||
def rollback(self, data):
|
||||
BrickStore.purge()
|
||||
|
||||
def commit(self, data):
|
||||
BrickStore.schema = data["schema"]
|
||||
BrickStore.path = data["path"]
|
||||
BrickStore.graph = data["graph"]
|
||||
|
||||
|
||||
class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Refresh the list view"
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
core.refresh_brick_viewer(tool.Brick, split_screen=True)
|
||||
|
||||
|
||||
class RemoveBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick"
|
||||
bl_label = "Remove Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Delete this entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -189,3 +239,57 @@ 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 SerializeBrick(bpy.types.Operator):
|
||||
bl_idname = "bim.serialize_brick"
|
||||
bl_label = "Serialize Brick"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.should_save_as or not BrickStore.path:
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
if self.should_save_as or not BrickStore.path:
|
||||
BrickStore.path = self.filepath
|
||||
core.serialize_brick(tool.Brick)
|
||||
return {"FINISHED"}
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.should_save_as:
|
||||
return "Save Brick project to a selected file"
|
||||
return "Save the Brick project"
|
||||
|
||||
|
||||
class AddBrickNamespace(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_namespace"
|
||||
bl_label = "Add Brick Namespace"
|
||||
bl_description = "Bind a new namespace to the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
alias = props.new_brick_namespace_alias
|
||||
uri = props.new_brick_namespace_uri
|
||||
core.add_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
|
||||
class RemoveBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick_relation"
|
||||
bl_label = "Remove Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Delete this relationship"
|
||||
predicate: bpy.props.StringProperty(name="Relation")
|
||||
object: bpy.props.StringProperty(name="Object")
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
core.remove_brick_relation(tool.Brick, brick_uri=brick.uri, predicate=self.predicate, object=self.object)
|
||||
@@ -30,7 +30,9 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.tool.brick as tool
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
BrickschemaData.is_loaded = False
|
||||
@@ -43,15 +45,35 @@ def get_libraries(self, context):
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["namespaces"]
|
||||
return BrickStore.namespaces
|
||||
|
||||
|
||||
def get_brick_equipment_classes(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["brick_equipment_classes"]
|
||||
def get_brick_entity_classes(self, context):
|
||||
entity = self.brick_entity_create_type
|
||||
return BrickStore.entity_classes[entity]
|
||||
|
||||
|
||||
def get_brick_roots(self, context):
|
||||
return [(root, root, "") for root in BrickStore.root_classes]
|
||||
|
||||
|
||||
def get_brick_relations(self, context):
|
||||
def is_label(relation):
|
||||
return relation["predicate_name"] == "label"
|
||||
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
|
||||
new_relations = BrickStore.relationships.copy()
|
||||
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return new_relations
|
||||
return BrickStore.relationships
|
||||
|
||||
|
||||
def update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
|
||||
|
||||
def split_screen_update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
@@ -67,5 +89,28 @@ class BIMBrickProperties(PropertyGroup):
|
||||
bricks: CollectionProperty(name="Bricks", type=Brick)
|
||||
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
|
||||
# namespace manager
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
|
||||
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
|
||||
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
|
||||
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
|
||||
# create brick entity
|
||||
new_brick_label: StringProperty(name="New Brick Label")
|
||||
brick_entity_create_type: EnumProperty(name="Brick Entity Types", items=get_brick_roots)
|
||||
brick_entity_class: EnumProperty(name="Brick Equipment Class", items=get_brick_entity_classes)
|
||||
# create relations
|
||||
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
|
||||
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
|
||||
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
|
||||
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
|
||||
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
|
||||
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
# create relations split screen
|
||||
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
|
||||
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
|
||||
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
|
||||
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
|
||||
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
|
||||
@@ -20,15 +20,16 @@ import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
|
||||
class BIM_PT_brickschema(Panel):
|
||||
bl_label = "Brickschema Project"
|
||||
bl_idname = "BIM_PT_brickschema"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_operations"
|
||||
|
||||
def draw(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
@@ -42,38 +43,155 @@ class BIM_PT_brickschema(Panel):
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
row.label(text=self.props.active_brick_class)
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
if BrickStore.path:
|
||||
row.label(text=BrickStore.path, icon="FILEBROWSER")
|
||||
else:
|
||||
row.label(text="No file", icon="FILEBROWSER")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if BrickStore.last_saved:
|
||||
row.label(text=BrickStore.last_saved, icon="TIME")
|
||||
else:
|
||||
row.label(text="Not saved", icon="TIME")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
||||
op.should_save_as = False
|
||||
op = row.operator("bim.serialize_brick", icon="FILE_TICK", text="Save As")
|
||||
op.should_save_as = True
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
prop_with_search(row, self.props, "brick_equipment_class", text="")
|
||||
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
|
||||
|
||||
if self.props.brick_settings_toggled:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="Active Namespace:")
|
||||
|
||||
row = box.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text="Bind New Namespace:")
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Entity:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_entity_create_type", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_label", text="")
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
grid = self.layout.grid_flow(even_columns=True)
|
||||
grid_left = grid.column(align=True)
|
||||
row = grid_left.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = False
|
||||
row.label(text=self.props.active_brick_class)
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid_left.row(align=True)
|
||||
row.prop(data=self.props, property="brick_list_root", text="")
|
||||
|
||||
row = grid_left.row()
|
||||
BIM_UL_bricks.split_screen = False
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
if self.props.split_screen_toggled:
|
||||
grid_right = grid.column(align=True)
|
||||
row = grid_right.row(align=True)
|
||||
if len(self.props.split_screen_brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = True
|
||||
row.label(text=self.props.split_screen_active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid_right.row(align=True)
|
||||
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
|
||||
|
||||
row = grid_right.row()
|
||||
BIM_UL_bricks.split_screen = True
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
|
||||
|
||||
if BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
if attribute["is_uri"]:
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
|
||||
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Relation:")
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
if split_screen_selection.total_items:
|
||||
row.label(text="No selection", icon="INFO")
|
||||
else:
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Failed to find this entity!", icon="ERROR")
|
||||
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=relation["predicate_name"])
|
||||
row.label(text=relation["object_name"])
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
|
||||
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
|
||||
op.predicate = relation["predicate"]
|
||||
op.object = relation["object"]
|
||||
if relation["is_uri"] and relation["predicate_name"] != "type":
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = attribute["value_uri"]
|
||||
if attribute["is_globalid"]:
|
||||
op.item = relation["object_uri"]
|
||||
if relation["is_globalid"]:
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = attribute["value"]
|
||||
op.global_id = relation["object_name"]
|
||||
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
bl_label = "IFC Brickschema References"
|
||||
bl_label = "Brickschema References"
|
||||
bl_idname = "BIM_PT_ifc_brickschema_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -122,12 +240,15 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
row.label(text=item.label if item.label else item.name)
|
||||
if item.total_items:
|
||||
row.label(text=str(item.total_items))
|
||||
op.split_screen = self.split_screen
|
||||
label = label + " (" + str(item.total_items) + ")"
|
||||
row.label(text=label)
|
||||
|
||||
@@ -137,24 +137,22 @@ class CadTool(WorkSpaceTool):
|
||||
else:
|
||||
if (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["parameters"]
|
||||
and RailingData.data["pset_data"]
|
||||
and context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Railing Path").hotkey = "S_Q"
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_railing_path")
|
||||
row.operator("bim.cancel_editing_railing_path", icon="CANCEL", text="")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and RoofData.data["pset_data"]
|
||||
and context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
row = layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_Q")
|
||||
row.operator("bim.cad_hotkey", text="Apply Roof Path").hotkey = "S_Q"
|
||||
row.label(text="", icon=f"EVENT_TAB")
|
||||
row.operator("bim.finish_editing_roof_path")
|
||||
row.operator("bim.cancel_editing_roof_path", icon="CANCEL", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
@@ -229,7 +227,7 @@ class CadHotkey(bpy.types.Operator):
|
||||
row.prop(props, "y")
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and RoofData.data["pset_data"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
self.layout.row().prop(props, "gable_roof_edge_angle")
|
||||
@@ -270,26 +268,12 @@ class CadHotkey(bpy.types.Operator):
|
||||
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 (
|
||||
(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():
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["parameters"]
|
||||
and RoofData.data["pset_data"]
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_ifcclash(Panel):
|
||||
bl_label = "IFC Clash Sets"
|
||||
bl_label = "Clash Sets"
|
||||
bl_idname = "BIM_PT_ifcclash"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -30,7 +30,7 @@ from blenderbim.bim.module.classification.data import (
|
||||
|
||||
|
||||
class BIM_PT_classifications(Panel):
|
||||
bl_label = "IFC Classifications"
|
||||
bl_label = "Classifications"
|
||||
bl_idname = "BIM_PT_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -156,13 +156,13 @@ class ReferenceUI:
|
||||
|
||||
|
||||
class BIM_PT_classification_references(Panel, ReferenceUI):
|
||||
bl_label = "IFC Classification References"
|
||||
bl_label = "Classification References"
|
||||
bl_idname = "BIM_PT_classification_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -180,7 +180,7 @@ class BIM_PT_classification_references(Panel, ReferenceUI):
|
||||
|
||||
|
||||
class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
bl_label = "IFC Material Classifications"
|
||||
bl_label = "Material Classifications"
|
||||
bl_idname = "BIM_PT_material_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -206,7 +206,7 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
|
||||
|
||||
|
||||
class BIM_PT_cost_classifications(Panel, ReferenceUI):
|
||||
bl_label = "IFC Cost Classifications"
|
||||
bl_label = "Cost Classifications"
|
||||
bl_idname = "BIM_PT_cost_classifications"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_cobie(Panel):
|
||||
bl_label = "IFC COBie"
|
||||
bl_label = "COBie"
|
||||
bl_idname = "BIM_PT_cobie"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
bl_parent_id = "BIM_PT_tab_handover"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -23,7 +23,7 @@ from blenderbim.bim.module.constraint.data import ConstraintsData, ObjectConstra
|
||||
|
||||
|
||||
class BIM_PT_constraints(Panel):
|
||||
bl_label = "IFC Constraints"
|
||||
bl_label = "Constraints"
|
||||
bl_idname = "BIM_PT_constraints"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -68,14 +68,14 @@ class BIM_PT_constraints(Panel):
|
||||
|
||||
|
||||
class BIM_PT_object_constraints(Panel):
|
||||
bl_label = "IFC Constraints"
|
||||
bl_label = "Constraints"
|
||||
bl_idname = "BIM_PT_object_constraints"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
bl_parent_id = "BIM_PT_tab_misc"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -23,7 +23,7 @@ from blenderbim.bim.module.context.data import ContextData
|
||||
|
||||
|
||||
class BIM_PT_context(bpy.types.Panel):
|
||||
bl_label = "IFC Geometric Representation Contexts"
|
||||
bl_label = "Geometric Representation Contexts"
|
||||
bl_idname = "BIM_PT_context"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
|
||||
@@ -20,64 +20,65 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.AddCostSchedule,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.EditCostSchedule,
|
||||
operator.AddCostValue,
|
||||
operator.AddCurrency,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.AssignCostItemType,
|
||||
operator.AssignCostValue,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.ContractCostItem,
|
||||
operator.ContractCostItemRate,
|
||||
operator.ContractCostItems,
|
||||
operator.CopyCostItem,
|
||||
operator.CopyCostItemValues,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.EditCostItem,
|
||||
operator.EditCostItemQuantity,
|
||||
operator.EditCostItemValue,
|
||||
operator.EditCostItemValueFormula,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EditCostSchedule,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.ExportCostSchedules,
|
||||
operator.ExpandCostItems,
|
||||
operator.EnableEditingCostItemQuantities,
|
||||
operator.EnableEditingCostItemQuantity,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItemValue,
|
||||
operator.EnableEditingCostItemValueFormula,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.AddCostColumn,
|
||||
operator.RemoveCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.ExpandCostItem,
|
||||
operator.ContractCostItem,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ExpandCostItems,
|
||||
operator.ExportCostSchedules,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.LoadProductCostItems,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.RemoveCostColumn,
|
||||
operator.RemoveCostItem,
|
||||
operator.AssignCostItemType,
|
||||
operator.UnassignCostItemType,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.RemoveCostItemQuantity,
|
||||
operator.AddCostValue,
|
||||
operator.RemoveCostItemValue,
|
||||
operator.CopyCostItemValues,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectCostItemProducts,
|
||||
operator.SelectCostScheduleProducts,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.AssignCostValue,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ContractCostItemRate,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.LoadProductCostItems,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectUnassignedProducts,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.CopyCostItem,
|
||||
operator.AddCurrency,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.UnassignCostItemType,
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
|
||||
@@ -117,6 +117,7 @@ class CostSchedulesData:
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
is_sum = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
@@ -130,6 +131,11 @@ class CostSchedulesData:
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
has_unit_basis = True
|
||||
else:
|
||||
data["UnitBasisValueComponent"] = 1
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
@@ -137,7 +143,8 @@ class CostSchedulesData:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"]
|
||||
data["TotalAppliedValue"] = None
|
||||
if is_sum:
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item, data):
|
||||
@@ -154,7 +161,7 @@ class CostSchedulesData:
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
else:
|
||||
data["UnitSymbol"] = None
|
||||
data["UnitSymbol"] = "U"
|
||||
|
||||
# same_unit_nested_cost_item = set()
|
||||
# data["DerivedTotalCostQuantity"] = None
|
||||
|
||||
@@ -156,6 +156,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
|
||||
|
||||
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.contract_cost_items"
|
||||
bl_label = "Contract Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Collapse cost item tree"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.contract_cost_items(tool.Cost)
|
||||
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost Item"
|
||||
|
||||
@@ -24,13 +24,12 @@ from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
bl_label = "IFC Cost Schedules"
|
||||
bl_label = "Cost Schedules"
|
||||
bl_idname = "BIM_PT_cost_schedules"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_4D5D"
|
||||
bl_parent_id = "BIM_PT_tab_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -48,8 +47,9 @@ class BIM_PT_cost_schedules(Panel):
|
||||
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.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
|
||||
@@ -139,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
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.operator("bim.expand_cost_items", text="Expand All")
|
||||
row.operator("bim.contract_cost_items", 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):
|
||||
@@ -267,7 +267,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
|
||||
|
||||
class BIM_PT_cost_item_types(Panel):
|
||||
bl_label = "IFC Cost Item Types"
|
||||
bl_label = "Cost Item Types"
|
||||
bl_idname = "BIM_PT_cost_item_types"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -353,7 +353,7 @@ class BIM_PT_cost_item_types(Panel):
|
||||
|
||||
|
||||
class BIM_PT_cost_item_quantities(Panel):
|
||||
bl_label = "IFC Cost Item Quantities"
|
||||
bl_label = "Cost Item Quantities"
|
||||
bl_idname = "BIM_PT_cost_item_quantities"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -525,7 +525,7 @@ class BIM_PT_cost_item_quantities(Panel):
|
||||
|
||||
|
||||
class BIM_PT_cost_item_rates(Panel):
|
||||
bl_label = "IFC Cost Item Rates"
|
||||
bl_label = "Cost Item Rates"
|
||||
bl_idname = "BIM_PT_cost_item_rates"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -620,18 +620,22 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
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"]:
|
||||
self.draw_uom_column(layout, cost_item)
|
||||
else:
|
||||
self.draw_total_quantity_column(layout, cost_item)
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
@@ -643,8 +647,11 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
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_order_operator(self, row, ifc_definition_id, cost_item):
|
||||
if cost_item["NestingIndex"] is not None:
|
||||
@@ -657,19 +664,7 @@ class BIM_UL_cost_items_trait:
|
||||
op.cost_item = ifc_definition_id
|
||||
op.new_index = cost_item["NestingIndex"] - 1
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
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):
|
||||
|
||||
@@ -26,7 +26,7 @@ class BIM_PT_covetool(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -128,12 +128,13 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to 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"}
|
||||
@@ -142,17 +143,16 @@ 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:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
results = ifcopenshell.util.selector.filter_elements(ifc_file, props.ifc_selector)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
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=args.format, delimiter=sep)
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -194,9 +194,12 @@ class EyedropIfcCsv(bpy.types.Operator):
|
||||
global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in context.selected_objects:
|
||||
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
|
||||
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
|
||||
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
global_id = getattr(element, "GlobalId", None)
|
||||
if global_id:
|
||||
global_ids.append(global_id)
|
||||
context.scene.CsvProperties.ifc_selector = ",".join(global_ids)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -21,13 +21,13 @@ from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_ifccsv(Panel):
|
||||
bl_label = "IFC CSV Import/Export"
|
||||
bl_label = "CSV Import/Export"
|
||||
bl_idname = "BIM_PT_ifccsv"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -60,17 +60,21 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
|
||||
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.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")
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CopyDebugInformation,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.InspectFromObject,
|
||||
|
||||
@@ -20,6 +20,9 @@ import os
|
||||
import bpy
|
||||
import time
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
@@ -31,6 +34,58 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
bl_idname = "bim.copy_debug_information"
|
||||
bl_label = "Copy Debug Information"
|
||||
bl_description = "Copies debugging information to your clipboard for use in bugreports"
|
||||
|
||||
def execute(self, context):
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
addon.bl_info.get("version", (-1, -1, -1))
|
||||
for addon in addon_utils.modules()
|
||||
if addon.bl_info["name"] == "BlenderBIM"
|
||||
][0]
|
||||
]
|
||||
)
|
||||
info = {
|
||||
"os": platform.system(),
|
||||
"os_version": platform.version(),
|
||||
"python_version": platform.python_version(),
|
||||
"architecture": platform.architecture(),
|
||||
"machine": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"blender_version": bpy.app.version_string,
|
||||
"blenderbim_version": version,
|
||||
"ifc": False,
|
||||
}
|
||||
|
||||
if tool.Ifc.get():
|
||||
info.update(
|
||||
{
|
||||
"ifc": os.path.basename(tool.Ifc.get_path()) if os.path.isfile(tool.Ifc.get_path()) else "Unsaved",
|
||||
"schema": tool.Ifc.get().schema,
|
||||
"preprocessor_version": tool.Ifc.get().wrapped_data.header.file_name.preprocessor_version,
|
||||
"originating_system": tool.Ifc.get().wrapped_data.header.file_name.originating_system,
|
||||
}
|
||||
)
|
||||
|
||||
# Format it in a readable way
|
||||
text = "\n".join(f"{k}: {v}" for k, v in info.items())
|
||||
print(text)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
command = "echo | set /p nul=" + text.strip()
|
||||
elif platform.system() == "Darwin": # for MacOS
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | pbcopy'
|
||||
else: # Linux
|
||||
command = 'printf "' + text.strip().replace("\n", "\\n").replace('"', "") + '" | xclip -selection clipboard'
|
||||
subprocess.run(command, shell=True, check=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PrintIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.print_ifc_file"
|
||||
bl_label = "Print IFC File"
|
||||
@@ -66,14 +121,14 @@ class PurgeIfcLinks(bpy.types.Operator):
|
||||
|
||||
|
||||
class ConvertToBlender(bpy.types.Operator):
|
||||
bl_idname = "bim.converttoblender"
|
||||
bl_idname = "bim.convert_to_blender"
|
||||
bl_label = "Convert To Blender File"
|
||||
bl_description = "Removes all IFC data, and converts the file to a simple Blender file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for o in bpy.data.objects:
|
||||
if o.type in {'MESH', 'EMPTY'}:
|
||||
if o.type in {"MESH", "EMPTY"}:
|
||||
o.BIMObjectProperties.ifc_definition_id = 0
|
||||
if o.data:
|
||||
o.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
@@ -135,6 +190,8 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
|
||||
total = len(elements)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
failures = []
|
||||
excludes = () # For the developer to debug with
|
||||
for i, element in enumerate(elements):
|
||||
@@ -142,8 +199,16 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
continue
|
||||
print(f"{i}/{total}:", element)
|
||||
start = time.time()
|
||||
shape = None
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, element)
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
if shape:
|
||||
print(
|
||||
"Success",
|
||||
time.time() - start,
|
||||
@@ -151,9 +216,6 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
len(shape.geometry.edges),
|
||||
len(shape.geometry.faces),
|
||||
)
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
print(f"Failures: {len(failures)}")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
@@ -166,6 +228,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_description = "Recreate a mesh object from a STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_include_curves: bpy.props.BoolProperty()
|
||||
step_id: bpy.props.IntProperty(default=0)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -178,7 +241,7 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
self.ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
self.file = IfcStore.get_file()
|
||||
element = self.file.by_id(int(context.scene.BIMDebugProperties.step_id))
|
||||
element = self.file.by_id(self.step_id or int(context.scene.BIMDebugProperties.step_id))
|
||||
settings = ifcopenshell.geom.settings()
|
||||
if self.should_include_curves:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
@@ -316,10 +379,30 @@ class InspectFromObject(bpy.types.Operator):
|
||||
class PrintObjectPlacement(bpy.types.Operator):
|
||||
bl_idname = "bim.print_object_placement"
|
||||
bl_label = "Print Object Placement"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Print object placement to the system console.\n\n" + "ALT+CLICK create an empty object at that position"
|
||||
)
|
||||
step_id: bpy.props.IntProperty()
|
||||
create_empty_object: bpy.props.BoolProperty(name="Create Empty Object", default=False, options={"SKIP_SAVE"})
|
||||
arrow_size: bpy.props.FloatProperty(name="Arrow Size", default=0.2, subtype="DISTANCE")
|
||||
|
||||
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.create_empty_object = True
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
|
||||
placement = ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id))
|
||||
if self.create_empty_object:
|
||||
bpy.ops.object.empty_add(type="ARROWS")
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
context.active_object.matrix_world = placement.transpose()
|
||||
context.active_object.matrix_world.translation *= si_conversion
|
||||
context.active_object.empty_display_size = self.arrow_size
|
||||
print(placement)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_debug(Panel):
|
||||
bl_label = "IFC Debug"
|
||||
bl_label = "Debug"
|
||||
bl_idname = "BIM_PT_debug"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -48,6 +48,9 @@ class BIM_PT_debug(Panel):
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_debug_information")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
@@ -55,7 +58,10 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.purge_ifc_links")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.converttoblender")
|
||||
row.operator("bim.update_representation", text="Manually Save Representation")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.convert_to_blender")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.create_all_shapes")
|
||||
@@ -77,6 +83,16 @@ class BIM_PT_debug(Panel):
|
||||
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
|
||||
row.prop(props, "percentile_of_polygons", text="")
|
||||
|
||||
if context.active_object and context.active_object.data:
|
||||
mprops = context.active_object.data.BIMMeshProperties
|
||||
row = layout.row()
|
||||
row.operator("bim.get_representation_ifc_parameters")
|
||||
for index, ifc_parameter in enumerate(mprops.ifc_parameters):
|
||||
row = layout.row(align=True)
|
||||
row.prop(ifc_parameter, "name", text="")
|
||||
row.prop(ifc_parameter, "value", text="")
|
||||
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
|
||||
|
||||
layout.label(text="Inspector:")
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -24,13 +24,13 @@ import json
|
||||
|
||||
|
||||
class BIM_PT_diff(Panel):
|
||||
bl_label = "IFC Diff"
|
||||
bl_label = "Diff"
|
||||
bl_idname = "BIM_PT_diff"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
if not DiffData.is_loaded:
|
||||
|
||||
@@ -23,7 +23,7 @@ from blenderbim.bim.module.document.data import DocumentData, ObjectDocumentData
|
||||
|
||||
|
||||
class BIM_PT_documents(Panel):
|
||||
bl_label = "IFC Documents"
|
||||
bl_label = "Documents"
|
||||
bl_idname = "BIM_PT_documents"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -76,14 +76,14 @@ class BIM_PT_documents(Panel):
|
||||
|
||||
|
||||
class BIM_PT_object_documents(Panel):
|
||||
bl_label = "IFC Documents"
|
||||
bl_label = "Documents"
|
||||
bl_idname = "BIM_PT_object_documents"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
bl_parent_id = "BIM_PT_tab_misc"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -24,22 +24,27 @@ classes = (
|
||||
operator.ActivateDrawingStyle,
|
||||
operator.ActivateModel,
|
||||
operator.AddAnnotation,
|
||||
operator.AddAnnotationType,
|
||||
operator.AddDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.AddDrawingStyleAttribute,
|
||||
operator.AddDrawingToSheet,
|
||||
operator.AddReference,
|
||||
operator.AddReferenceToSheet,
|
||||
operator.AddSchedule,
|
||||
operator.AddScheduleToSheet,
|
||||
operator.AddReferenceToSheet,
|
||||
operator.AddSheet,
|
||||
operator.AddTextLiteral,
|
||||
operator.BuildSchedule,
|
||||
operator.CleanWireframes,
|
||||
operator.ContractSheet,
|
||||
operator.ContractTargetView,
|
||||
operator.CreateDrawing,
|
||||
operator.CreateSheets,
|
||||
operator.DisableAddAnnotationType,
|
||||
operator.DisableEditingAssignedProduct,
|
||||
operator.DisableEditingDrawings,
|
||||
operator.DisableEditingReferences,
|
||||
operator.DisableEditingSchedules,
|
||||
operator.DisableEditingSheets,
|
||||
operator.DisableEditingText,
|
||||
@@ -48,34 +53,34 @@ classes = (
|
||||
operator.EditSheet,
|
||||
operator.EditText,
|
||||
operator.EditTextPopup,
|
||||
operator.EnableAddAnnotationType,
|
||||
operator.EnableEditingAssignedProduct,
|
||||
operator.EnableEditingText,
|
||||
operator.ExpandSheet,
|
||||
operator.ExpandTargetView,
|
||||
operator.LoadDrawings,
|
||||
operator.LoadReferences,
|
||||
operator.LoadSchedules,
|
||||
operator.LoadSheets,
|
||||
operator.OpenDrawing,
|
||||
operator.OpenReference,
|
||||
operator.OpenSchedule,
|
||||
operator.OpenSheet,
|
||||
operator.ReloadDrawingStyles,
|
||||
operator.RemoveDrawing,
|
||||
operator.RemoveDrawingFromSheet,
|
||||
operator.RemoveDrawingStyle,
|
||||
operator.RemoveDrawingStyleAttribute,
|
||||
operator.RemoveReference,
|
||||
operator.RemoveSchedule,
|
||||
operator.ReloadDrawingStyles,
|
||||
operator.RemoveSheet,
|
||||
operator.RemoveTextLiteral,
|
||||
operator.SelectAllDrawings,
|
||||
operator.ResizeText,
|
||||
operator.SaveDrawingStyle,
|
||||
operator.SaveDrawingStylesData,
|
||||
operator.SelectAllDrawings,
|
||||
operator.SelectAssignedProduct,
|
||||
operator.SelectDocIfcFile,
|
||||
operator.LoadReferences,
|
||||
operator.DisableEditingReferences,
|
||||
operator.AddReference,
|
||||
operator.RemoveReference,
|
||||
operator.OpenReference,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Document,
|
||||
@@ -89,7 +94,6 @@ classes = (
|
||||
prop.BIMAnnotationProperties,
|
||||
ui.BIM_PT_camera,
|
||||
ui.BIM_PT_drawing_underlay,
|
||||
ui.BIM_PT_annotation_utilities,
|
||||
ui.BIM_PT_sheets,
|
||||
ui.BIM_PT_drawings,
|
||||
ui.BIM_PT_schedules,
|
||||
@@ -103,13 +107,14 @@ classes = (
|
||||
gizmos.DimensionLabelGizmo,
|
||||
gizmos.ExtrusionGuidesGizmo,
|
||||
gizmos.ExtrusionWidget,
|
||||
workspace.LaunchAnnotationTypeManager,
|
||||
workspace.Hotkey,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
|
||||
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)
|
||||
|
||||
@@ -152,6 +152,7 @@ class Annotator:
|
||||
def get_placeholder_coords(camera=None):
|
||||
if not camera:
|
||||
camera = bpy.context.scene.camera
|
||||
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
y = camera.data.ortho_scale / 4
|
||||
|
||||
@@ -162,9 +163,14 @@ class Annotator:
|
||||
|
||||
y_offset = camera.matrix_world.to_quaternion() @ Vector((0, y, 0))
|
||||
x_offset = camera.matrix_world.to_quaternion() @ Vector((y / 2, 0, 0))
|
||||
|
||||
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
center.z = 0
|
||||
center = camera.matrix_world @ center
|
||||
|
||||
return (
|
||||
camera.location + z_offset,
|
||||
camera.location + z_offset + y_offset,
|
||||
camera.location + z_offset + x_offset,
|
||||
camera.location + z_offset + x_offset + y_offset,
|
||||
center + z_offset,
|
||||
center + z_offset + y_offset,
|
||||
center + z_offset + x_offset,
|
||||
center + z_offset + x_offset + y_offset,
|
||||
)
|
||||
|
||||
@@ -287,28 +287,36 @@ class DecoratorData:
|
||||
cls.data[obj.name] = text_data
|
||||
return text_data
|
||||
|
||||
# used by Ifc Annotations with ObjectType = "DIMENSION" / "DIAMETER"
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj):
|
||||
"""used by Ifc Annotations with ObjectType:
|
||||
|
||||
DIMENSION / DIAMETER / SECTION_LEVEL / PLAN_LEVEL / RADIUS
|
||||
"""
|
||||
result = cls.data.get(obj.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
supported_object_types = ("DIMENSION", "DIAMETER")
|
||||
supported_object_types = ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS")
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in supported_object_types:
|
||||
return None
|
||||
|
||||
dimension_style = "arrow"
|
||||
fill_bg = False
|
||||
classes = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
if classes and "oblique" in classes.lower().split():
|
||||
dimension_style = "oblique"
|
||||
if classes:
|
||||
classes_split = classes.lower().split()
|
||||
if "oblique" in classes_split:
|
||||
dimension_style = "oblique"
|
||||
elif "fill-bg" in classes_split:
|
||||
fill_bg = True
|
||||
|
||||
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", "")
|
||||
text_prefix = pset_data.get("TextPrefix", None) or ""
|
||||
text_suffix = pset_data.get("TextSuffix", None) or ""
|
||||
|
||||
dimension_data = {
|
||||
"dimension_style": dimension_style,
|
||||
@@ -316,6 +324,7 @@ class DecoratorData:
|
||||
"suppress_zero_inches": suppress_zero_inches,
|
||||
"text_prefix": text_prefix,
|
||||
"text_suffix": text_suffix,
|
||||
"fill_bg": fill_bg,
|
||||
}
|
||||
cls.data[obj.name] = dimension_data
|
||||
return dimension_data
|
||||
@@ -329,18 +338,31 @@ class AnnotationData:
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.props = bpy.context.scene.BIMAnnotationProperties
|
||||
cls.data["relating_types"] = cls.get_relating_types()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id()
|
||||
cls.data["relating_types"] = cls.relating_types()
|
||||
|
||||
@classmethod
|
||||
def get_relating_types(cls):
|
||||
def relating_type_id(cls):
|
||||
object_type = cls.props.object_type
|
||||
relating_types = []
|
||||
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append(relating_type)
|
||||
|
||||
enum_items = [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types]
|
||||
results = [("0", "Untyped", "")]
|
||||
results.extend([(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in relating_types])
|
||||
return results
|
||||
|
||||
# item to create anootations without relating types
|
||||
enum_items.insert(0, ("0", "-", ""))
|
||||
return enum_items
|
||||
@classmethod
|
||||
def relating_types(cls):
|
||||
object_type = cls.props.object_type
|
||||
relating_types = []
|
||||
for relating_type in tool.Ifc.get().by_type("IfcTypeProduct"):
|
||||
if tool.Drawing.is_annotation_object_type(relating_type, object_type):
|
||||
relating_types.append({
|
||||
"id": relating_type.id(),
|
||||
"name": relating_type.Name or "Unnamed",
|
||||
"description": relating_type.Description or "No Description",
|
||||
})
|
||||
|
||||
return sorted(relating_types, key=lambda x: x["name"])
|
||||
|
||||
@@ -248,8 +248,13 @@ class BaseDecorator:
|
||||
if check_mode and obj.data.is_editmode:
|
||||
return self.get_editmesh_geom(obj)
|
||||
|
||||
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
|
||||
indices = [e.vertices for e in obj.data.edges]
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
|
||||
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
|
||||
bm.free()
|
||||
|
||||
return vertices, indices
|
||||
|
||||
def get_editmesh_geom(self, obj):
|
||||
@@ -363,7 +368,7 @@ class BaseDecorator:
|
||||
"""
|
||||
|
||||
if multiline:
|
||||
line_no = 0
|
||||
line_no = line_no
|
||||
lines = text.split("\\n")
|
||||
lines = lines if multiline_to_bottom else lines[::-1]
|
||||
for line in lines:
|
||||
@@ -454,6 +459,24 @@ class BaseDecorator:
|
||||
blf.draw(font_id, text)
|
||||
blf.disable(font_id, blf.ROTATION)
|
||||
|
||||
def draw_dimension_text(self, context, get_text, description, dimension_data, **draw_label_kwargs):
|
||||
prefix = dimension_data["text_prefix"]
|
||||
suffix = dimension_data["text_suffix"]
|
||||
show_description_only = dimension_data["show_description_only"]
|
||||
|
||||
line_number_start = 0
|
||||
if not show_description_only:
|
||||
text = get_text()
|
||||
full_prefix = ((description + "\\n") if description else "") + prefix
|
||||
text = full_prefix + text + suffix
|
||||
line_number_start -= full_prefix.count("\\n")
|
||||
else:
|
||||
if not description:
|
||||
return
|
||||
text = description
|
||||
|
||||
self.draw_label(context, text=text, line_no=line_number_start, multiline=True, **draw_label_kwargs)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def format_value(self, context, value):
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
@@ -502,7 +525,11 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
camera_matrix = camera.matrix_world.copy()
|
||||
camera_matrix[0][0] = 1
|
||||
camera_matrix[1][1] = 1
|
||||
camera_matrix[2][2] = 1
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
@@ -637,39 +664,35 @@ class DimensionDecorator(BaseDecorator):
|
||||
perpendicular = Vector((-text_dir.y, text_dir.x)).normalized()
|
||||
text_offset = perpendicular * text_offset_value
|
||||
|
||||
common_label_attrs = {
|
||||
"context": context,
|
||||
"multiline": True,
|
||||
"text_dir": text_dir,
|
||||
}
|
||||
base_pos = p0 + text_dir * 0.5
|
||||
|
||||
if not show_description_only:
|
||||
length = (v1 - v0).length
|
||||
text = self.format_value(context, length)
|
||||
if isinstance(self, DiameterDecorator):
|
||||
text = "D" + text
|
||||
text = text_prefix + text + text_suffix
|
||||
else:
|
||||
if not description:
|
||||
continue
|
||||
text = description
|
||||
|
||||
self.draw_label(
|
||||
context,
|
||||
text,
|
||||
p0 + text_dir * 0.5 + text_offset,
|
||||
text_dir,
|
||||
box_alignment="bottom-middle",
|
||||
multiline=True,
|
||||
multiline_to_bottom=False,
|
||||
)
|
||||
if description:
|
||||
self.draw_label(
|
||||
context,
|
||||
description,
|
||||
p0 + text_dir * 0.5 - text_offset,
|
||||
text_dir,
|
||||
box_alignment="top-middle",
|
||||
multiline=True,
|
||||
)
|
||||
self.draw_label(
|
||||
text=text,
|
||||
pos=base_pos + text_offset,
|
||||
box_alignment="bottom-middle",
|
||||
multiline_to_bottom=False,
|
||||
**common_label_attrs,
|
||||
)
|
||||
|
||||
elif show_description_only and description:
|
||||
if not show_description_only and description:
|
||||
self.draw_label(
|
||||
context,
|
||||
description,
|
||||
p0 + text_dir * 0.5 + text_offset,
|
||||
text_dir,
|
||||
box_alignment="bottom-middle",
|
||||
multiline=True,
|
||||
multiline_to_bottom=False,
|
||||
text=description, pos=base_pos - text_offset, box_alignment="top-middle", **common_label_attrs
|
||||
)
|
||||
|
||||
|
||||
@@ -734,7 +757,7 @@ class AngleDecorator(BaseDecorator):
|
||||
|
||||
arcs_color = None
|
||||
edges_color = UNSPECIAL_ELEMENT_COLOR
|
||||
if context.object == obj and obj.data.is_editmode:
|
||||
if context.active_object == obj and obj.data.is_editmode:
|
||||
arcs_color = context.preferences.addons["blenderbim"].preferences.decorator_color_special
|
||||
edges_color = None
|
||||
|
||||
@@ -772,19 +795,23 @@ class AngleDecorator(BaseDecorator):
|
||||
v0 = Vector(vertices[i0])
|
||||
v1 = Vector(vertices[i1])
|
||||
v2 = Vector(vertices[i1 + 1])
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p2 = location_3d_to_region_2d(region, region3d, v2)
|
||||
|
||||
edge0 = p0 - p1
|
||||
edge1 = p2 - p1
|
||||
# calculate angle value
|
||||
edge0_ws = v0 - v1
|
||||
edge1_ws = v2 - v1
|
||||
try:
|
||||
cos_a = edge0.dot(edge1) / (edge0.length * edge1.length)
|
||||
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
circle_angle_rad = acos(cos_a)
|
||||
circle_angle = circle_angle_rad / pi * 180
|
||||
|
||||
# calculate angle position
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p2 = location_3d_to_region_2d(region, region3d, v2)
|
||||
edge0 = p0 - p1
|
||||
edge1 = p2 - p1
|
||||
base_edge = edge0 if ccw(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-circle_angle_rad / 2, 2) @ base_edge).normalized() * ANGLE_LABEL_OFFSET
|
||||
label_position = p1 + text_offset
|
||||
@@ -824,12 +851,15 @@ class RadiusDecorator(BaseDecorator):
|
||||
|
||||
objecttype = "RADIUS"
|
||||
|
||||
def get_spline_end(self, obj):
|
||||
def get_spline_points(self, obj):
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if not spline_points:
|
||||
return Vector((0, 0, 0))
|
||||
return obj.matrix_world @ spline_points[0].co
|
||||
spline_points = [Vector([0, 0, 0])] * 2
|
||||
elif len(spline_points) == 1:
|
||||
spline_points = [spline_points[0]] * 2
|
||||
|
||||
return [(obj.matrix_world @ p.co) for p in spline_points[:2]]
|
||||
|
||||
def decorate(self, context, obj):
|
||||
self.draw_arrow(context, obj)
|
||||
@@ -838,15 +868,24 @@ class RadiusDecorator(BaseDecorator):
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
|
||||
spline_points = self.get_spline_points(obj)
|
||||
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if spline_points:
|
||||
length = (spline_points[-1].co - spline_points[-2].co).length
|
||||
text = self.format_value(context, length)
|
||||
self.draw_label(context, text, pos, dir, gap=0, center=False, vcenter=False)
|
||||
p0, p1 = [location_3d_to_region_2d(region, region3d, p) for p in self.get_spline_points(obj)]
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
description = element.Description
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
viewportDrawingScale = self.get_viewport_drawing_scale(context)
|
||||
text_offset = 20 * viewportDrawingScale
|
||||
|
||||
pos = p0 - (p1 - p0).normalized() * text_offset
|
||||
|
||||
def get_text():
|
||||
length = (spline_points[-1] - spline_points[-2]).length
|
||||
return "R" + self.format_value(context, length)
|
||||
|
||||
self.draw_dimension_text(
|
||||
context, get_text, description, dimension_data, pos=pos, text_dir=Vector((1, 0)), box_alignment="center"
|
||||
)
|
||||
|
||||
|
||||
class FallDecorator(BaseDecorator):
|
||||
@@ -1056,23 +1095,40 @@ class PlanLevelDecorator(BaseDecorator):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
description = element.Description
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
|
||||
for verts in splines:
|
||||
p0, p1 = [location_3d_to_region_2d(region, region3d, v) for v in verts[:2]]
|
||||
dir = p1 - p0
|
||||
if dir.length < 1:
|
||||
text_dir = p1 - p0
|
||||
if text_dir.length < 1:
|
||||
continue
|
||||
|
||||
if dir.x > 0:
|
||||
if text_dir.x > 0:
|
||||
box_alignment = "bottom-left"
|
||||
else:
|
||||
box_alignment = "bottom-right"
|
||||
dir *= -1
|
||||
text_dir *= -1
|
||||
|
||||
z = verts[0].z / unit_scale
|
||||
z = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), z)
|
||||
z *= unit_scale
|
||||
text = "RL " + self.format_value(context, z)
|
||||
self.draw_label(context, text, p0, dir, gap=8, center=False, box_alignment=box_alignment)
|
||||
def get_text():
|
||||
z = verts[0].z
|
||||
rl = self.format_value(context, z)
|
||||
text = "{}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
self.draw_dimension_text(
|
||||
context,
|
||||
get_text,
|
||||
description,
|
||||
dimension_data,
|
||||
box_alignment=box_alignment,
|
||||
pos=p0,
|
||||
text_dir=text_dir,
|
||||
gap=8,
|
||||
center=False,
|
||||
)
|
||||
|
||||
|
||||
class SectionLevelDecorator(BaseDecorator):
|
||||
@@ -1119,31 +1175,29 @@ class SectionLevelDecorator(BaseDecorator):
|
||||
self.draw_labels(context, obj, self.get_splines(obj), text_position.to_2d(), text_dir.to_2d())
|
||||
|
||||
def draw_labels(self, context, obj, splines, text_position, text_dir):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
storey = tool.Drawing.get_annotation_element(element)
|
||||
tag = storey.Name if storey else element.Description
|
||||
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
|
||||
for verts in splines:
|
||||
z = verts[0].z / unit_scale
|
||||
z = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), z)
|
||||
z *= unit_scale
|
||||
|
||||
text_lines = ["RL " + self.format_value(context, z)]
|
||||
if tag:
|
||||
text_lines.append(tag)
|
||||
def get_text():
|
||||
z = verts[0].z
|
||||
rl = self.format_value(context, z)
|
||||
text = "RL {}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
for line_i, line in zip((0, -1), text_lines):
|
||||
self.draw_label(
|
||||
context,
|
||||
line,
|
||||
text_position,
|
||||
text_dir,
|
||||
center=False,
|
||||
vcenter=False,
|
||||
line_no=line_i,
|
||||
)
|
||||
self.draw_dimension_text(
|
||||
context,
|
||||
get_text,
|
||||
tag,
|
||||
dimension_data,
|
||||
box_alignment="bottom-left",
|
||||
pos=text_position,
|
||||
text_dir=text_dir,
|
||||
)
|
||||
break # support only 1 label
|
||||
|
||||
|
||||
@@ -1917,7 +1971,12 @@ class DecorationsHandler:
|
||||
results.append((obj, dec))
|
||||
|
||||
elif isinstance(obj.data, bpy.types.Mesh):
|
||||
results.append((obj, self.decorators["MISC"]))
|
||||
if object_type == "LINEWORK" and "dashed" in str(
|
||||
ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
).split(" "):
|
||||
results.append((obj, self.decorators["HIDDEN_LINE"]))
|
||||
else:
|
||||
results.append((obj, self.decorators["MISC"]))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -354,7 +354,7 @@ class UglyDotGizmo(OffsetHandle, types.Gizmo):
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value("offset") / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shift
|
||||
self.matrix_offset.translation.z = offset # z-shift
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
@@ -385,7 +385,7 @@ class DotGizmo(CustomGizmo, OffsetHandle, types.Gizmo):
|
||||
|
||||
def refresh(self):
|
||||
offset = self.target_get_value("offset") / self.scale_value
|
||||
self.matrix_offset.col[3][2] = offset # z-shifted
|
||||
self.matrix_offset.translation.z = offset # z-shifted
|
||||
|
||||
def draw(self, ctx):
|
||||
self.refresh()
|
||||
@@ -443,7 +443,7 @@ class DimensionLabelGizmo(types.Gizmo):
|
||||
|
||||
def refresh(self, ctx):
|
||||
value = self.target_get_value("value")
|
||||
self.matrix_offset.col[3][2] = value * 0.5
|
||||
self.matrix_offset.translation.z = value * 0.5
|
||||
unit_system = ctx.scene.unit_settings.system
|
||||
self.text_label = bpy.utils.units.to_string(unit_system, "LENGTH", value, 3, split_unit=False)
|
||||
|
||||
|
||||
@@ -192,6 +192,8 @@ def format_distance(
|
||||
tx_dist += str(feet) + "'"
|
||||
if feet and add_inches:
|
||||
tx_dist += " - "
|
||||
if not feet and value < 0:
|
||||
tx_dist += "-"
|
||||
if add_inches:
|
||||
tx_dist += str(inches)
|
||||
if add_inches and frac:
|
||||
@@ -208,19 +210,19 @@ def format_distance(
|
||||
if precision:
|
||||
value = precision * round(float(value) / precision)
|
||||
|
||||
if decimal_places:
|
||||
if decimal_places is not None:
|
||||
fmt = "%1." + str(decimal_places) + "f"
|
||||
|
||||
# Meters
|
||||
if unit_length == "METERS":
|
||||
if not decimal_places:
|
||||
if decimal_places is None:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
# Centimeters
|
||||
elif unit_length == "CENTIMETERS":
|
||||
if not decimal_places:
|
||||
if decimal_places is None:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
@@ -228,7 +230,7 @@ def format_distance(
|
||||
tx_dist = fmt % d_cm
|
||||
# Millimeters
|
||||
elif unit_length == "MILLIMETERS":
|
||||
if not decimal_places:
|
||||
if decimal_places is None:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
@@ -237,20 +239,20 @@ def format_distance(
|
||||
|
||||
# Otherwise Use Adaptive Units
|
||||
else:
|
||||
if round(value, 2) >= 1.0 and not decimal_places:
|
||||
if round(value, 2) >= 1.0 and decimal_places is None:
|
||||
fmt = "%1.3f"
|
||||
if hide_units is False:
|
||||
fmt += " m"
|
||||
tx_dist = fmt % value
|
||||
else:
|
||||
if round(value, 2) >= 0.01 and not decimal_places:
|
||||
if round(value, 2) >= 0.01 and decimal_places is None:
|
||||
fmt = "%1.1f"
|
||||
if hide_units is False:
|
||||
fmt += " cm"
|
||||
d_cm = value * (100)
|
||||
tx_dist = fmt % d_cm
|
||||
else:
|
||||
if not decimal_places:
|
||||
if decimal_places is None:
|
||||
fmt = "%1.0f"
|
||||
if hide_units is False:
|
||||
fmt += " mm"
|
||||
|
||||
@@ -23,6 +23,7 @@ import json
|
||||
import time
|
||||
import bmesh
|
||||
import shutil
|
||||
import hashlib
|
||||
import shapely
|
||||
import subprocess
|
||||
import webbrowser
|
||||
@@ -32,6 +33,7 @@ import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
@@ -84,6 +86,52 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddAnnotationType(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_annotation_type"
|
||||
bl_label = "Add Annotation Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
object_type = props.object_type
|
||||
has_representation = props.create_representation_for_type
|
||||
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
|
||||
|
||||
if props.create_representation_for_type:
|
||||
obj = tool.Drawing.create_annotation_object(drawing, object_type)
|
||||
else:
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = props.type_name
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=has_representation,
|
||||
context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Annotation", "MODEL_VIEW"),
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
|
||||
class EnableAddAnnotationType(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_add_annotation_type"
|
||||
bl_label = "Enable Add Annotation Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
bpy.context.scene.BIMAnnotationProperties.is_adding_type = True
|
||||
|
||||
|
||||
class DisableAddAnnotationType(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_add_annotation_type"
|
||||
bl_label = "Disable Add Annotation Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
bpy.context.scene.BIMAnnotationProperties.is_adding_type = False
|
||||
|
||||
|
||||
class AddDrawing(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_drawing"
|
||||
bl_label = "Add Drawing"
|
||||
@@ -154,7 +202,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return bool(tool.Ifc.get() and tool.Drawing.is_camera_orthographic() and tool.Drawing.is_drawing_active())
|
||||
return bool(tool.Ifc.get() and tool.Drawing.is_drawing_active())
|
||||
|
||||
def invoke(self, context, event):
|
||||
# printing all drawings on shift+click
|
||||
@@ -168,7 +216,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if self.print_all:
|
||||
original_drawing_id = self.props.active_drawing_id
|
||||
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected]
|
||||
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
|
||||
else:
|
||||
drawings_to_print = [self.props.active_drawing_id]
|
||||
|
||||
@@ -184,7 +232,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
with profile("Drawing generation process"):
|
||||
with profile("Initialize drawing generation process"):
|
||||
self.cprops = self.camera.data.BIMCameraProperties
|
||||
self.drawing_name = self.file.by_id(drawing_id).Name
|
||||
self.drawing = self.file.by_id(drawing_id)
|
||||
self.drawing_name = self.drawing.Name
|
||||
self.metadata = tool.Drawing.get_drawing_metadata(self.camera_element)
|
||||
self.get_scale(context)
|
||||
if self.cprops.update_representation(self.camera):
|
||||
@@ -202,14 +251,20 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
self.svg_writer.setup_drawing_resource_paths(self.camera_element)
|
||||
|
||||
underlay_svg = None
|
||||
linework_svg = None
|
||||
annotation_svg = None
|
||||
|
||||
with profile("Generate underlay"):
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
|
||||
with profile("Generate linework"):
|
||||
linework_svg = self.generate_linework(context)
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
linework_svg = self.generate_linework(context)
|
||||
|
||||
with profile("Generate annotation"):
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
if tool.Drawing.is_camera_orthographic():
|
||||
annotation_svg = self.generate_annotation(context)
|
||||
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
@@ -275,7 +330,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def generate_underlay(self, context):
|
||||
if not self.cprops.has_underlay:
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasUnderlay"):
|
||||
return
|
||||
svg_path = self.get_svg_path(cache_type="underlay")
|
||||
context.scene.render.filepath = svg_path[0:-4] + ".png"
|
||||
@@ -317,7 +372,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
return svg_path
|
||||
|
||||
def generate_linework(self, context):
|
||||
if not self.cprops.has_linework:
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasLinework"):
|
||||
return
|
||||
svg_path = self.get_svg_path(cache_type="linework")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||
@@ -346,10 +401,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
}
|
||||
cached_linework -= edited_guids
|
||||
|
||||
# This is a work in progress. See #1153 and #1564.
|
||||
import hashlib
|
||||
import ifcopenshell.draw
|
||||
|
||||
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
|
||||
|
||||
for ifc_path, ifc in files.items():
|
||||
@@ -434,7 +485,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
@@ -520,7 +571,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
for projection in projections:
|
||||
boundary_lines = []
|
||||
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||
start, end = [co[1:].split(",") for co in path.attrib["d"].split()]
|
||||
# Rounding is necessary to ensure coincident points are coincident
|
||||
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
if start == end:
|
||||
continue
|
||||
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
|
||||
start, end = tool.Drawing.extend_line(start, end, 0.5)
|
||||
boundary_lines.append(shapely.LineString([start, end]))
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
@@ -542,6 +598,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
e
|
||||
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||
if not e.instance.is_a("IfcAnnotation")
|
||||
and tool.Cad.is_point_on_edge(
|
||||
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
|
||||
)
|
||||
]
|
||||
if elements:
|
||||
path = etree.Element("path")
|
||||
@@ -770,7 +829,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, ifc):
|
||||
def setup_serialiser(self, ifc, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
@@ -793,6 +852,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
self.serialiser.setMirrorY(True)
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
@@ -827,6 +889,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
)
|
||||
return classes
|
||||
|
||||
def is_manifold(self, obj):
|
||||
result = self.is_manifold_cache.get(obj.data.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
for edge in bm.edges:
|
||||
if not edge.is_manifold:
|
||||
bm.free()
|
||||
self.is_manifold_cache[obj.data.name] = False
|
||||
return False
|
||||
self.is_manifold_cache[obj.data.name] = True
|
||||
return True
|
||||
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
|
||||
if join_criteria:
|
||||
@@ -837,6 +914,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
joined_paths = {}
|
||||
self.is_manifold_cache = {}
|
||||
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
@@ -846,6 +924,54 @@ class CreateDrawing(bpy.types.Operator):
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not self.is_manifold(obj):
|
||||
continue
|
||||
|
||||
# An element group will contain a bunch of paths representing the
|
||||
# cut of that element. However IfcOpenShell may not correctly
|
||||
# create closed paths. We post-process all paths with shapely to
|
||||
# ensure things that should be closed (i.e.
|
||||
# shapely.polygonize_full) are, and things which aren't are left
|
||||
# alone (e.g. dangles, cuts, invalids). See #3421.
|
||||
line_strings = []
|
||||
old_paths = []
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath = "M" + subpath.strip()
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
line_strings.append(shapely.LineString(coords))
|
||||
old_paths.append(path)
|
||||
unioned_line_strings = shapely.union_all(shapely.GeometryCollection(line_strings))
|
||||
if hasattr(unioned_line_strings, "geoms"):
|
||||
results = shapely.polygonize_full(unioned_line_strings.geoms)
|
||||
else:
|
||||
results = []
|
||||
|
||||
# If we succeeded in generating new path geometry, remove all the
|
||||
# old paths and add new ones.
|
||||
if results:
|
||||
for path in old_paths:
|
||||
path.getparent().remove(path)
|
||||
for result in results:
|
||||
for geom in result.geoms:
|
||||
path = etree.SubElement(el, "{http://www.w3.org/2000/svg}path")
|
||||
if isinstance(geom, shapely.Polygon):
|
||||
d = (
|
||||
"M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in geom.exterior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
for interior in geom.interiors:
|
||||
d += (
|
||||
" M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
elif isinstance(geom, shapely.LineString):
|
||||
d = "M" + " L".join([",".join([str(o) for o in co]) for co in geom.coords]) + " Z"
|
||||
path.attrib["d"] = d
|
||||
|
||||
# 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
|
||||
@@ -873,8 +999,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
is_closed_polygon = False
|
||||
for path in el.findall("{http://www.w3.org/2000/svg}path"):
|
||||
for subpath in path.attrib["d"].split("M")[1:]:
|
||||
subpath = "M" + subpath.strip()
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath.split()]
|
||||
subpath_co = "M" + subpath.strip(" Z")
|
||||
coords = [[round(float(o), 1) for o in co[1:].split(",")] for co in subpath_co.split()]
|
||||
if subpath.strip().lower().endswith("z"):
|
||||
coords.append(coords[0])
|
||||
if len(coords) > 2 and coords[0] == coords[-1]:
|
||||
is_closed_polygon = True
|
||||
polygons.append(shapely.Polygon(coords))
|
||||
@@ -921,7 +1049,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
group.insert(0, projection)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not self.cprops.has_annotation:
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
|
||||
return
|
||||
svg_path = self.get_svg_path(cache_type="annotation")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_annotation_cache:
|
||||
@@ -931,7 +1059,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
filtered_drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
elements = [e for e in elements if e in filtered_drawing_elements]
|
||||
|
||||
annotations = sorted(elements, key=lambda a: tool.Drawing.get_annotation_z_index(a))
|
||||
annotations = sorted(
|
||||
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
|
||||
)
|
||||
|
||||
precision = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "MetricPrecision")
|
||||
if not precision:
|
||||
@@ -1264,7 +1394,25 @@ class ActivateModel(bpy.types.Operator):
|
||||
CutDecorator.uninstall()
|
||||
|
||||
if not bpy.app.background:
|
||||
bpy.ops.object.hide_view_clear()
|
||||
view3d_context = dict()
|
||||
|
||||
for window in bpy.context.window_manager.windows:
|
||||
screen = window.screen
|
||||
for area in screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
view3d_context["window"] = window
|
||||
view3d_context["screen"] = screen
|
||||
view3d_context["area"] = area
|
||||
for region in area.regions:
|
||||
if region.type == "WINDOW":
|
||||
view3d_context["region"] = region
|
||||
break
|
||||
break
|
||||
if view3d_context:
|
||||
break
|
||||
|
||||
if view3d_context:
|
||||
bpy.ops.object.hide_view_clear(view3d_context)
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -1386,6 +1534,9 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
if not self.drawing:
|
||||
self.report({"ERROR"}, "No drawing selected")
|
||||
return {"CANCELLED"}
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
@@ -1632,10 +1783,14 @@ class ActivateDrawingStyle(bpy.types.Operator, Operator):
|
||||
space = self.get_view_3d(context) # Do not remove. It is used in exec later
|
||||
style = json.loads(self.drawing_style.raster_style)
|
||||
for path, value in style.items():
|
||||
if isinstance(value, str):
|
||||
exec(f"{path} = '{value}'")
|
||||
else:
|
||||
exec(f"{path} = {value}")
|
||||
try:
|
||||
if isinstance(value, str):
|
||||
exec(f"{path} = '{value}'")
|
||||
else:
|
||||
exec(f"{path} = {value}")
|
||||
except:
|
||||
# Differences in Blender versions mean result in failures here
|
||||
print(f"Failed to set shading style {path} to {value}")
|
||||
|
||||
def set_query(self, context):
|
||||
self.include_global_ids = []
|
||||
@@ -1732,7 +1887,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
@@ -2273,6 +2428,34 @@ class DisableEditingDrawings(bpy.types.Operator, Operator):
|
||||
core.disable_editing_drawings(tool.Drawing)
|
||||
|
||||
|
||||
class ExpandTargetView(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_target_view"
|
||||
bl_label = "Expand Target View"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
target_view: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
|
||||
drawing.is_expanded = True
|
||||
core.load_drawings(tool.Drawing)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ContractTargetView(bpy.types.Operator):
|
||||
bl_idname = "bim.contract_target_view"
|
||||
bl_label = "Contract Target View"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
target_view: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.DocProperties
|
||||
for drawing in [d for d in props.drawings if d.target_view == self.target_view]:
|
||||
drawing.is_expanded = False
|
||||
core.load_drawings(tool.Drawing)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ExpandSheet(bpy.types.Operator):
|
||||
bl_idname = "bim.expand_sheet"
|
||||
bl_label = "Expand Sheet"
|
||||
|
||||
Binary file not shown.
@@ -165,8 +165,9 @@ def get_diagram_scales(self, context):
|
||||
|
||||
|
||||
def update_drawing_name(self, context):
|
||||
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
if self.ifc_definition_id:
|
||||
drawing = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
core.update_drawing_name(tool.Ifc, tool.Drawing, drawing=drawing, name=self.name)
|
||||
|
||||
|
||||
def get_drawing_style_name(self):
|
||||
@@ -253,6 +254,8 @@ class Drawing(PropertyGroup):
|
||||
name: StringProperty(name="Name", update=update_drawing_name)
|
||||
target_view: StringProperty(name="Target View")
|
||||
is_selected: BoolProperty(name="Is Selected", default=True)
|
||||
is_drawing: BoolProperty(name="Is Drawing", default=False)
|
||||
is_expanded: BoolProperty(name="Is Expanded", default=True)
|
||||
|
||||
|
||||
class Document(PropertyGroup):
|
||||
@@ -297,6 +300,7 @@ class RasterStyleProperty(enum.Enum):
|
||||
SHADING = "scene.display.shading"
|
||||
DISPLAY = "scene.display"
|
||||
OVERLAY = "space.overlay"
|
||||
SPACE_SHADING = "space.shading"
|
||||
|
||||
|
||||
RASTER_STYLE_PROPERTIES_EXCLUDE = (
|
||||
@@ -495,11 +499,10 @@ ANNOTATION_TYPES_DATA = {
|
||||
"TEXT": ("Text", "", "SMALL_CAPS", "empty"),
|
||||
"TEXT_LEADER": ("Leader", "", "TRACKING_BACKWARDS", "curve"),
|
||||
"STAIR_ARROW": ("Stair Arrow", "Add stair arrow annotation.\nIf you have IfcStairFlight object selected, it will be used as a reference for the annotation", "SCREEN_BACK", "curve"),
|
||||
"HIDDEN_LINE": ("Hidden", "", "CON_TRACKTO", "mesh"),
|
||||
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
|
||||
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
|
||||
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
|
||||
"LINEWORK": ("Line", "", "MESH_MONKEY", "mesh"),
|
||||
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "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"),
|
||||
@@ -510,13 +513,10 @@ ANNOTATION_TYPES_DATA = {
|
||||
annotation_classes = [(x, *ANNOTATION_TYPES_DATA[x][:3], i) for i, x in enumerate(ANNOTATION_TYPES_DATA)]
|
||||
|
||||
|
||||
def get_annotation_data_prop(prop_name):
|
||||
def function(self, context):
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
return AnnotationData.data[prop_name]
|
||||
|
||||
return function
|
||||
def get_relating_type_id(self, context):
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
return AnnotationData.data["relating_type_id"]
|
||||
|
||||
|
||||
def update_annotation_object_type(self, context):
|
||||
@@ -533,12 +533,12 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
object_type: bpy.props.EnumProperty(
|
||||
name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type
|
||||
)
|
||||
relating_type_id: bpy.props.EnumProperty(
|
||||
name="Relating Annotation Type", items=get_annotation_data_prop("relating_types")
|
||||
)
|
||||
relating_type_id: bpy.props.EnumProperty(name="Relating Annotation Type", items=get_relating_type_id)
|
||||
create_representation_for_type: bpy.props.BoolProperty(
|
||||
name="Create Representation For Type",
|
||||
default=False,
|
||||
description='Whether "Add type" should define a representation for the type \n'
|
||||
"or allow occurences to have their own",
|
||||
)
|
||||
is_adding_type: bpy.props.BoolProperty(default=False)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
|
||||
@@ -351,7 +351,7 @@ class Scheduler:
|
||||
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]
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
|
||||
@@ -310,10 +310,13 @@ class SheetBuilder:
|
||||
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
|
||||
def replace_urls(text):
|
||||
"""replace urls `url(#marker)` with `url(#prefix-marker)`
|
||||
since `url(#marker.prefix)` doesn't seem to work
|
||||
"""
|
||||
return re.sub(r"url\(#([^\)]+)\)", rf"url(#{prefix}-\1)", text)
|
||||
|
||||
style.text = replace_urls(text)
|
||||
|
||||
for svg_element in svg.findall(f".//*"):
|
||||
if svg_element.tag in (f"{SVG}style", f"{SVG}svg"):
|
||||
@@ -325,6 +328,9 @@ class SheetBuilder:
|
||||
# add class "prefix" to all classes
|
||||
if "class" in attrib:
|
||||
attrib["class"] += f" {prefix}"
|
||||
if "filter" in attrib:
|
||||
# example use "#fill-background" filter
|
||||
attrib["filter"] = replace_urls(attrib["filter"])
|
||||
if svg_element.tag == f"{SVG}use":
|
||||
href_attrib = f"{XLINK}href"
|
||||
if href_attrib in attrib:
|
||||
|
||||
@@ -203,8 +203,6 @@ class SvgWriter:
|
||||
self.draw_section_annotation(obj)
|
||||
elif element.ObjectType == "BREAKLINE":
|
||||
self.draw_break_annotations(obj)
|
||||
elif element.ObjectType == "HIDDEN_LINE":
|
||||
self.draw_line_annotation(obj)
|
||||
elif element.ObjectType == "PLAN_LEVEL":
|
||||
self.draw_plan_level_annotation(obj)
|
||||
elif element.ObjectType == "SECTION_LEVEL":
|
||||
@@ -221,51 +219,42 @@ class SvgWriter:
|
||||
return self
|
||||
|
||||
def draw_section_level_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
matrix_world = obj.matrix_world
|
||||
classes = self.get_attribute_classes(obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
storey = tool.Drawing.get_annotation_element(element)
|
||||
tag = storey.Name if storey else element.Description
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
suppress_zero_inches = dimension_data["suppress_zero_inches"]
|
||||
base_offset_y = 3.5
|
||||
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
[
|
||||
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
|
||||
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 3.5,
|
||||
)
|
||||
)
|
||||
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
|
||||
vector = projected_points_svg[0] - projected_points_svg[1]
|
||||
angle = math.degrees(vector.angle_signed(Vector((1, 0))))
|
||||
|
||||
# TODO: allow metric to be configurable
|
||||
rl_value = (matrix_world @ points[0].co.xyz).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
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 = rl_value / unit_scale
|
||||
rl = ifcopenshell.util.geolocation.auto_z2e(tool.Ifc.get(), rl)
|
||||
rl *= unit_scale
|
||||
rl = "{:.3f}m".format(rl)
|
||||
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,
|
||||
def get_text():
|
||||
z = (matrix_world @ points[0].co.xyz).z
|
||||
rl = helper.format_distance(
|
||||
z,
|
||||
precision=self.precision,
|
||||
decimal_places=self.decimal_places,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
)
|
||||
text = "RL {}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
self.draw_dimension_text(
|
||||
get_text, tag, dimension_data, text_position=text_position, angle=angle, class_str="SECTIONLEVEL"
|
||||
)
|
||||
if tag:
|
||||
self.svg.add(self.svg.text(tag, insert=(text_position[0], text_position[1] - 5), **text_style))
|
||||
|
||||
def draw_stair_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -341,9 +330,10 @@ class SvgWriter:
|
||||
return
|
||||
|
||||
classes = self.get_attribute_classes(obj)
|
||||
if len(obj.data.polygons) == 0:
|
||||
self.draw_edge_annotation(obj, classes)
|
||||
return
|
||||
if len(obj.data.vertices) and not len(obj.data.edges):
|
||||
return self.draw_point_annotation(obj, classes)
|
||||
elif len(obj.data.polygons) == 0:
|
||||
return self.draw_edge_annotation(obj, classes)
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
@@ -510,9 +500,9 @@ class SvgWriter:
|
||||
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)]
|
||||
original_edge = direction * size
|
||||
current_svg_segments = ceil(size / segment_width) * segment_width * direction
|
||||
scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)]
|
||||
return "scale(%f, %f)" % (scale[0], scale[1])
|
||||
|
||||
def poly_to_edges(poly):
|
||||
@@ -566,11 +556,16 @@ class SvgWriter:
|
||||
pattern_dir = pattern_edge.normalized()
|
||||
pattern_length = pattern_edge.length
|
||||
|
||||
segments = int(pattern_length // segment_width)
|
||||
segments = ceil(pattern_length / segment_width)
|
||||
pattern_dir_step = pattern_dir * segment_width
|
||||
points = [pattern_dir_step * i for i in range(segments)]
|
||||
# it takes atleast 2 points to preserve the edge direction
|
||||
# if there is just 1 segment then we still add second point and then hide the "marker-end"
|
||||
n_points = max(segments, 2)
|
||||
points = [pattern_dir_step * i for i in range(n_points)]
|
||||
|
||||
polyline_style = f"marker: url(#{marker_id}); stroke: none;"
|
||||
polyline_style = f"marker: url(#{marker_id}); stroke: none; "
|
||||
if segments == 1:
|
||||
polyline_style += "marker-end: 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
|
||||
@@ -726,15 +721,19 @@ class SvgWriter:
|
||||
drawing = tool.Drawing.get_annotation_element(element)
|
||||
reference = tool.Drawing.get_drawing_reference(drawing)
|
||||
if reference:
|
||||
sheet = tool.Drawing.get_reference_document(reference)
|
||||
if sheet:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_id = reference.ItemReference or "-"
|
||||
sheet_id = sheet.DocumentId or "-"
|
||||
else:
|
||||
reference_id = reference.Identification or "-"
|
||||
sheet_id = sheet.Identification or "-"
|
||||
return (reference_id, sheet_id)
|
||||
for sheet_reference in tool.Ifc.get().by_type("IfcDocumentReference"):
|
||||
if sheet_reference.Description != "DRAWING" or sheet_reference.Location != reference.Location:
|
||||
continue
|
||||
sheet = tool.Drawing.get_reference_document(sheet_reference)
|
||||
if sheet:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
reference_id = sheet_reference.ItemReference or "-"
|
||||
sheet_id = sheet.DocumentId or "-"
|
||||
else:
|
||||
reference_id = sheet_reference.Identification or "-"
|
||||
sheet_id = sheet.Identification or "-"
|
||||
return (reference_id, sheet_id)
|
||||
break
|
||||
return ("-", "-")
|
||||
|
||||
@staticmethod
|
||||
@@ -792,7 +791,14 @@ class SvgWriter:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = self.camera.matrix_world.copy()
|
||||
camera_matrix[0][0] = 1
|
||||
camera_matrix[1][1] = 1
|
||||
camera_matrix[2][2] = 1
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
@@ -828,45 +834,39 @@ class SvgWriter:
|
||||
|
||||
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
|
||||
# and add clases to the tspan tags
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
attribs = {
|
||||
"transform": text_transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
angle,
|
||||
text_literal.BoxAlignment,
|
||||
classes_str,
|
||||
fill_bg="fill-bg" in classes,
|
||||
line_number_start=line_number,
|
||||
)
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
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,
|
||||
**SvgWriter.get_box_alignment_parameters(text_literal.BoxAlignment),
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
def draw_point_annotation(self, obj, classes):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
text_lines = text.replace("\\n", "\n").split("\n")
|
||||
matrix_world = obj.matrix_world
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
|
||||
|
||||
for text_line in text_lines:
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
# 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
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
|
||||
if add_fill_bg:
|
||||
# return line_number back to the original value
|
||||
line_number -= len(text_lines)
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if point_type:
|
||||
svg_id += f"-{point_type}"
|
||||
|
||||
if "fill-bg" in classes:
|
||||
add_text_tag(True)
|
||||
add_text_tag(False)
|
||||
for symbol_position in projected_points:
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
symbol_position_svg = symbol_position * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
|
||||
def draw_break_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -892,53 +892,60 @@ class SvgWriter:
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def draw_plan_level_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
matrix_world = obj.matrix_world
|
||||
classes = self.get_attribute_classes(obj)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
description = element.Description
|
||||
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
suppress_zero_inches = dimension_data["suppress_zero_inches"]
|
||||
base_offset_y = 1.0
|
||||
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
[
|
||||
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
|
||||
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
((y_offset - projected_points[0].y) * self.svg_scale) - 1.0,
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl_value = (matrix_world @ points[0].co).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl_value, precision=self.precision, decimal_places=self.decimal_places)
|
||||
text_position = projected_points_svg[0] - Vector((0, base_offset_y))
|
||||
text_dir = projected_points_svg[1] - projected_points_svg[0]
|
||||
if text_dir.x < 0:
|
||||
box_alignment = "bottom-right"
|
||||
text_dir *= -1
|
||||
else:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
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"
|
||||
angle = math.degrees(text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
box_alignment = "bottom-left" if projected_points[0].x <= projected_points[-1].x else "bottom-right"
|
||||
text_style = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"{}{}".format("" if rl_value < 0 else "+", rl),
|
||||
insert=tuple(text_position),
|
||||
class_="PLANLEVEL",
|
||||
**text_style,
|
||||
# TODO: allow metric to be configurable
|
||||
def get_text():
|
||||
z = (matrix_world @ points[0].co.xyz).z
|
||||
rl = helper.format_distance(
|
||||
z,
|
||||
precision=self.precision,
|
||||
decimal_places=self.decimal_places,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
)
|
||||
text = "{}{}".format("" if z < 0 else "+", rl)
|
||||
return text
|
||||
|
||||
self.draw_dimension_text(
|
||||
get_text,
|
||||
description,
|
||||
dimension_data,
|
||||
text_position=text_position,
|
||||
angle=angle,
|
||||
class_str="PLANLEVEL",
|
||||
box_alignment=box_alignment,
|
||||
)
|
||||
|
||||
def draw_angle_annotations(self, obj):
|
||||
points = obj.data.splines[0].points
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
area = tool.Blender.get_viewport_context()["area"]
|
||||
region_3d = area.spaces.active.region_3d
|
||||
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
|
||||
|
||||
for points_chunk in points_chunked:
|
||||
@@ -959,7 +966,7 @@ class SvgWriter:
|
||||
p0 = points_2d[1] + dir0 * angle_radius
|
||||
p2 = points_2d[1] + dir1 * angle_radius
|
||||
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
|
||||
# points = [p.co.xyz for p in bpy.context.object.data.splines[0].points[:3]]
|
||||
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
@@ -1072,48 +1079,61 @@ class SvgWriter:
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def draw_radius_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
classes = self.get_attribute_classes(obj)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
tag = element.Description
|
||||
matrix_world = obj.matrix_world
|
||||
|
||||
dimension_data = DecoratorData.get_dimension_data(obj)
|
||||
|
||||
for spline in obj.data.splines:
|
||||
points = self.get_spline_points(spline)
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points]
|
||||
d = " ".join(
|
||||
[
|
||||
"L {} {}".format((x_offset + p.x) * self.svg_scale, (y_offset - p.y) * self.svg_scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
projected_points_svg = [(offset + p.xy * Vector((1, -1))) * self.svg_scale for p in projected_points]
|
||||
d = " ".join(["L {} {}".format(*p) for p in projected_points_svg])
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
p0 = projected_points_svg[0]
|
||||
p1 = projected_points_svg[1]
|
||||
|
||||
p0 = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.svg_scale,
|
||||
(y_offset - projected_points[0].y) * self.svg_scale,
|
||||
)
|
||||
)
|
||||
p1 = Vector(
|
||||
(
|
||||
(x_offset + projected_points[1].x) * self.svg_scale,
|
||||
(y_offset - projected_points[1].y) * self.svg_scale,
|
||||
)
|
||||
)
|
||||
text_offset = (p0 - p1).xy.normalized() * 5
|
||||
text_position = projected_points[0]
|
||||
text_position = Vector(
|
||||
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
|
||||
)
|
||||
text_position += text_offset
|
||||
text_offset = (p0 - p1).normalized() * 5
|
||||
text_position = p0 + text_offset
|
||||
|
||||
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}"
|
||||
def get_text():
|
||||
radius = (points[-1].co - points[-2].co).length
|
||||
radius = helper.format_distance(radius, precision=self.precision, decimal_places=self.decimal_places)
|
||||
text = f"R{radius}"
|
||||
return text
|
||||
|
||||
self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style))
|
||||
self.draw_dimension_text(
|
||||
get_text, tag, dimension_data, text_position=text_position, class_str="RADIUS", box_alignment="center"
|
||||
)
|
||||
|
||||
def draw_dimension_text(self, get_text, tag, dimension_data, **create_text_kwargs):
|
||||
prefix = dimension_data["text_prefix"]
|
||||
suffix = dimension_data["text_suffix"]
|
||||
show_description_only = dimension_data["show_description_only"]
|
||||
fill_bg = dimension_data["fill_bg"]
|
||||
|
||||
text_tags = []
|
||||
line_number_start = 0
|
||||
if not show_description_only:
|
||||
text = get_text()
|
||||
full_prefix = ((tag + "\\n") if tag else "") + prefix
|
||||
text = full_prefix + text + suffix
|
||||
line_number_start -= full_prefix.count("\\n")
|
||||
else:
|
||||
if not tag:
|
||||
return
|
||||
text = tag
|
||||
|
||||
text_tags += self.create_text_tag(
|
||||
text, line_number_start=line_number_start, fill_bg=fill_bg, **create_text_kwargs
|
||||
)
|
||||
|
||||
for text in text_tags:
|
||||
self.svg.add(text)
|
||||
|
||||
def draw_fall_annotations(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
@@ -1208,6 +1228,7 @@ class SvgWriter:
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
fill_bg=dimension_data["fill_bg"],
|
||||
)
|
||||
|
||||
def draw_dimension_annotations(self, obj):
|
||||
@@ -1231,6 +1252,7 @@ class SvgWriter:
|
||||
suppress_zero_inches=dimension_data["suppress_zero_inches"],
|
||||
text_prefix=dimension_data["text_prefix"],
|
||||
text_suffix=dimension_data["text_suffix"],
|
||||
fill_bg=dimension_data["fill_bg"],
|
||||
)
|
||||
|
||||
def draw_measureit_arch_dimension_annotations(self):
|
||||
@@ -1256,6 +1278,7 @@ class SvgWriter:
|
||||
suppress_zero_inches=False,
|
||||
text_prefix="",
|
||||
text_suffix="",
|
||||
fill_bg=False,
|
||||
):
|
||||
offset = Vector([self.raw_width, self.raw_height]) / 2
|
||||
v0 = self.project_point_onto_camera(v0_global)
|
||||
@@ -1265,13 +1288,6 @@ class SvgWriter:
|
||||
mid = ((end - start) / 2) + start
|
||||
vector = end - start
|
||||
perpendicular = Vector((vector.y, -vector.x)).normalized()
|
||||
dimension = (v1_global - v0_global).length
|
||||
dimension = helper.format_distance(
|
||||
dimension,
|
||||
precision=self.precision,
|
||||
decimal_places=self.decimal_places,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
)
|
||||
sheet_dimension = (end - start).length
|
||||
|
||||
# if annotation can't fit offset text to the right of marker
|
||||
@@ -1281,81 +1297,109 @@ class SvgWriter:
|
||||
line = self.svg.line(start=start, end=end, class_=" ".join(classes))
|
||||
self.svg.add(line)
|
||||
|
||||
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)
|
||||
text_tags = []
|
||||
text_tag_kwargs = {
|
||||
"angle": angle,
|
||||
"class_str": "DIMENSION",
|
||||
"text_format": text_format,
|
||||
"fill_bg": fill_bg,
|
||||
}
|
||||
|
||||
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,
|
||||
if not show_description_only:
|
||||
dimension = (v1_global - v0_global).length
|
||||
dimension = helper.format_distance(
|
||||
dimension,
|
||||
precision=self.precision,
|
||||
decimal_places=self.decimal_places,
|
||||
suppress_zero_inches=suppress_zero_inches,
|
||||
)
|
||||
self.svg.add(text_tag)
|
||||
text = text_prefix + str(dimension) + text_suffix
|
||||
else:
|
||||
if not dimension_text:
|
||||
return
|
||||
text = dimension_text
|
||||
|
||||
text_tags += self.create_text_tag(
|
||||
text,
|
||||
text_position + perpendicular,
|
||||
box_alignment="bottom-middle",
|
||||
multiline_to_bottom=False,
|
||||
**text_tag_kwargs,
|
||||
)
|
||||
|
||||
if not show_description_only and dimension_text:
|
||||
text_tags += self.create_text_tag(
|
||||
dimension_text,
|
||||
text_position - perpendicular,
|
||||
box_alignment="top-middle",
|
||||
multiline_to_bottom=True,
|
||||
**text_tag_kwargs,
|
||||
)
|
||||
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
|
||||
def create_text_tag(
|
||||
self,
|
||||
text,
|
||||
text_position,
|
||||
angle,
|
||||
box_alignment,
|
||||
class_str,
|
||||
angle=0.0,
|
||||
box_alignment="bottom-left",
|
||||
class_str="",
|
||||
text_format=lambda x: x,
|
||||
multiline=False,
|
||||
multiline_to_bottom=False,
|
||||
multiline=True,
|
||||
multiline_to_bottom=True,
|
||||
fill_bg=False,
|
||||
line_number_start=0,
|
||||
_draw_fill_bg=False,
|
||||
):
|
||||
"""returns list of created text tags"""
|
||||
text_tags = []
|
||||
if fill_bg:
|
||||
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
|
||||
del method_kwargs["self"]
|
||||
del method_kwargs["text_tags"]
|
||||
text_tags += self.create_text_tag(**method_kwargs)
|
||||
|
||||
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
|
||||
|
||||
if not multiline:
|
||||
text_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
return self.svg.text(
|
||||
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
text_tag = self.svg.text(
|
||||
text_format(text),
|
||||
insert=text_position,
|
||||
class_=class_str,
|
||||
**(text_kwargs | SvgWriter.get_box_alignment_parameters(box_alignment)),
|
||||
**(transform_kwargs | base_text_attrs),
|
||||
)
|
||||
text_tags.append(text_tag)
|
||||
return text_tags
|
||||
|
||||
text_position_svg_str = ", ".join(map(str, text_position))
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
# 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
|
||||
# and add clases to the tspan tags
|
||||
# ref: https://github.com/IfcOpenShell/IfcOpenShell/issues/2833#issuecomment-1471584960
|
||||
text_kwargs = {
|
||||
"transform": text_transform,
|
||||
"style": "font-size: 0;",
|
||||
}
|
||||
|
||||
text_tag = self.svg.text("", **text_kwargs, **SvgWriter.get_box_alignment_parameters(box_alignment))
|
||||
text_tag = self.svg.text("", **text_kwargs, **base_text_attrs)
|
||||
text_tags.append(text_tag)
|
||||
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):
|
||||
for line_number, text_line in enumerate(text_lines, line_number_start):
|
||||
# position has to be inserted at tspan to avoid x offset between tspans
|
||||
# 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_format(text_line), class_=class_str, insert=(0, 0))
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
return text_tag
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
# TODO is this needlessly complex?
|
||||
|
||||
@@ -161,13 +161,13 @@ class BIM_PT_drawing_underlay(Panel):
|
||||
class BIM_PT_drawings(Panel):
|
||||
bl_label = "Drawings"
|
||||
bl_idname = "BIM_PT_drawings"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DrawingsData.is_loaded:
|
||||
@@ -199,18 +199,31 @@ class BIM_PT_drawings(Panel):
|
||||
col.alignment = "LEFT"
|
||||
row2 = col.row(align=True)
|
||||
row2.operator("bim.remove_drawing", icon="X", text="").drawing = active_drawing.ifc_definition_id
|
||||
|
||||
row2.operator(
|
||||
"bim.duplicate_drawing", icon="COPYDOWN", text=""
|
||||
).drawing = active_drawing.ifc_definition_id
|
||||
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
|
||||
op = row.operator("bim.select_all_drawings", icon="SELECT_SUBTRACT", text="")
|
||||
op = row.operator("bim.open_drawing", icon="URL", text="")
|
||||
|
||||
open_drawing_button = row.row(align=True)
|
||||
op = open_drawing_button.operator("bim.open_drawing", icon="URL", text="")
|
||||
op.view = active_drawing.name
|
||||
open_drawing_button.enabled = active_drawing.ifc_definition_id > 0
|
||||
|
||||
row.operator("bim.activate_model", icon="VIEW3D", text="")
|
||||
op = row.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
|
||||
|
||||
drawing_button = row.row(align=True)
|
||||
op = drawing_button.operator("bim.activate_drawing", icon="OUTLINER_OB_CAMERA", text="")
|
||||
op.drawing = active_drawing.ifc_definition_id
|
||||
row.operator("bim.create_drawing", text="", icon="OUTPUT")
|
||||
drawing_button.enabled = active_drawing.ifc_definition_id > 0
|
||||
|
||||
create_drawing_button = row.row(align=True)
|
||||
create_drawing_button.operator("bim.create_drawing", text="", icon="OUTPUT")
|
||||
create_drawing_button.enabled = active_drawing.ifc_definition_id > 0
|
||||
self.layout.template_list(
|
||||
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
|
||||
)
|
||||
@@ -229,13 +242,13 @@ class BIM_PT_drawings(Panel):
|
||||
class BIM_PT_schedules(Panel):
|
||||
bl_label = "Schedules"
|
||||
bl_idname = "BIM_PT_schedules"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
@@ -276,21 +289,18 @@ 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"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not DocumentsData.is_loaded:
|
||||
@@ -304,7 +314,7 @@ class BIM_PT_references(Panel):
|
||||
|
||||
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.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
|
||||
row.operator("bim.load_references", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
@@ -328,13 +338,13 @@ class BIM_PT_references(Panel):
|
||||
class BIM_PT_sheets(Panel):
|
||||
bl_label = "Sheets"
|
||||
bl_idname = "BIM_PT_sheets"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return tool.Blender.is_tab(context, "DRAWINGS") and tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not SheetsData.is_loaded:
|
||||
@@ -377,12 +387,13 @@ class BIM_PT_sheets(Panel):
|
||||
|
||||
|
||||
class BIM_PT_product_assignments(Panel):
|
||||
bl_label = "IFC Product Assignments"
|
||||
bl_label = "Product Assignments"
|
||||
bl_idname = "BIM_PT_product_assignments"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -412,12 +423,13 @@ class BIM_PT_product_assignments(Panel):
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
bl_label = "IFC Text"
|
||||
bl_label = "Text"
|
||||
bl_idname = "BIM_PT_text"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 0
|
||||
bl_parent_id = "BIM_PT_tab_object_metadata"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -489,95 +501,22 @@ class BIM_PT_text(Panel):
|
||||
row.label(text=literal_data[attribute])
|
||||
|
||||
|
||||
class BIM_PT_annotation_utilities(Panel):
|
||||
bl_idname = "BIM_PT_annotation_utilities"
|
||||
bl_label = "Annotation"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BIM Documentation"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
self.props = context.scene.DocProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dimension", icon="FIXED_SIZE")
|
||||
op.object_type = "DIMENSION"
|
||||
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Fall", icon="SORT_ASC")
|
||||
op.object_type = "FALL"
|
||||
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")
|
||||
row.enabled = context.scene.camera is not None
|
||||
|
||||
|
||||
class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
if not item:
|
||||
layout.label(text="", translate=False)
|
||||
return
|
||||
|
||||
row = layout.row(align=True)
|
||||
if item.is_drawing:
|
||||
row.label(text="", icon="BLANK1")
|
||||
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
|
||||
row.prop(item, "is_selected", text="", icon=selected_icon)
|
||||
icon = "UV_FACESEL"
|
||||
if item.target_view == "ELEVATION_VIEW":
|
||||
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
|
||||
row.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
if item.target_view == "PLAN_VIEW":
|
||||
icon = "UV_FACESEL"
|
||||
elif item.target_view == "ELEVATION_VIEW":
|
||||
icon = "UV_VERTEXSEL"
|
||||
elif item.target_view == "SECTION_VIEW":
|
||||
icon = "UV_EDGESEL"
|
||||
@@ -585,9 +524,17 @@ class BIM_UL_drawinglist(bpy.types.UIList):
|
||||
icon = "XRAY"
|
||||
elif item.target_view == "MODEL_VIEW":
|
||||
icon = "SNAP_VOLUME"
|
||||
else:
|
||||
icon = "CLIPUV_HLT"
|
||||
if item.is_expanded:
|
||||
row.operator(
|
||||
"bim.contract_target_view", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN"
|
||||
).target_view = item.target_view
|
||||
else:
|
||||
row.operator(
|
||||
"bim.expand_target_view", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT"
|
||||
).target_view = item.target_view
|
||||
row.prop(item, "name", text="", icon=icon, emboss=False)
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
|
||||
class BIM_UL_sheets(bpy.types.UIList):
|
||||
@@ -626,3 +573,31 @@ class BIM_UL_sheets(bpy.types.UIList):
|
||||
else:
|
||||
name = item.name or "Unnamed"
|
||||
row.label(text=name)
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
# We only need filtering, not reordering for sheets.
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "filter_name", text="")
|
||||
row.prop(self, "use_filter_invert", text="", icon="ARROW_LEFTRIGHT")
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
flt_flags = []
|
||||
flt_neworder = []
|
||||
|
||||
if self.filter_name:
|
||||
filter_name = self.filter_name.lower()
|
||||
active_sheet = None
|
||||
for sheet in data.sheets:
|
||||
if sheet.is_sheet:
|
||||
active_sheet = sheet
|
||||
active_sheet_index = len(flt_flags)
|
||||
if filter_name in sheet.name.lower() or filter_name in sheet.identification.lower():
|
||||
flt_flags.append(self.bitflag_filter_item)
|
||||
if not sheet.is_sheet:
|
||||
flt_flags[active_sheet_index] = self.bitflag_filter_item
|
||||
else:
|
||||
flt_flags.append(0)
|
||||
|
||||
if not flt_flags:
|
||||
return flt_flags, flt_neworder
|
||||
return flt_flags, flt_neworder
|
||||
|
||||
@@ -23,14 +23,14 @@ import blenderbim.tool as tool
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from bpy.types import WorkSpaceTool
|
||||
|
||||
# from blenderbim.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData, AnnotationData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.bim.handler
|
||||
|
||||
|
||||
# declaring it here to avoid circular import problems
|
||||
# TODO: Fix circular import.
|
||||
# Declaring it here to avoid circular import problems
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -38,6 +38,77 @@ class Operator:
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LaunchAnnotationTypeManager(bpy.types.Operator):
|
||||
bl_idname = "bim.launch_annotation_type_manager"
|
||||
bl_label = "Launch Annotation Type Manager"
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Manage annotation types and templates"
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self, width=550)
|
||||
|
||||
def draw(self, context):
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
|
||||
columns = self.layout.column_flow(columns=3)
|
||||
row = columns.row()
|
||||
row.alignment = "LEFT"
|
||||
row.label(text=f"{len(AnnotationData.data['relating_types'])} Types", icon="FILE_VOLUME")
|
||||
|
||||
row = columns.row(align=True)
|
||||
row.alignment = "CENTER"
|
||||
# In case you want something here in the future
|
||||
|
||||
row = columns.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
# In case you want something here in the future
|
||||
|
||||
if props.is_adding_type:
|
||||
row = self.layout.row()
|
||||
box = row.box()
|
||||
row = box.row()
|
||||
row.prop(props, "type_name")
|
||||
row = box.row()
|
||||
row.prop(props, "create_representation_for_type", text="Geometric Type")
|
||||
row = box.row(align=True)
|
||||
row.operator("bim.add_annotation_type", icon="CHECKMARK", text="Save New Type")
|
||||
row.operator("bim.disable_add_annotation_type", icon="CANCEL", text="")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_add_annotation_type", icon="ADD", text="Create New Type")
|
||||
|
||||
flow = self.layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
||||
|
||||
for relating_type in AnnotationData.data["relating_types"]:
|
||||
outer_col = flow.column()
|
||||
box = outer_col.box()
|
||||
|
||||
row = box.row()
|
||||
row.alignment = "CENTER"
|
||||
row.label(text=relating_type["name"], icon="FILE_3D")
|
||||
|
||||
row = box.row()
|
||||
row.alignment = "CENTER"
|
||||
row.label(text=relating_type["description"])
|
||||
|
||||
row = box.row(align=True)
|
||||
|
||||
op = row.operator("bim.select_type", icon="OBJECT_DATA")
|
||||
op.relating_type = relating_type["id"]
|
||||
op = row.operator("bim.rename_type", icon="GREASEPENCIL", text="")
|
||||
op.element = relating_type["id"]
|
||||
op = row.operator("bim.duplicate_type", icon="DUPLICATE", text="")
|
||||
op.element = relating_type["id"]
|
||||
op = row.operator("bim.remove_type", icon="X", text="")
|
||||
op.element = relating_type["id"]
|
||||
|
||||
|
||||
class AnnotationTool(WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
@@ -90,41 +161,7 @@ def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
|
||||
|
||||
# TODO: move to operator
|
||||
def create_annotation_type(context):
|
||||
# just empty to store parameters
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
object_type = props.object_type
|
||||
create_representation = props.create_representation_for_type
|
||||
drawing = tool.Ifc.get_entity(bpy.context.scene.camera)
|
||||
|
||||
if props.create_representation_for_type:
|
||||
obj = tool.Drawing.create_annotation_object(drawing, object_type)
|
||||
else:
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
|
||||
obj.name = f"{object_type}_TYPE"
|
||||
obj.location = context.scene.cursor.location
|
||||
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), object_type)
|
||||
|
||||
element = tool.Drawing.run_root_assign_class(
|
||||
obj=obj,
|
||||
ifc_class="IfcTypeProduct",
|
||||
predefined_type=object_type,
|
||||
should_add_representation=create_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=tool.Drawing.get_ifc_representation_class(object_type),
|
||||
)
|
||||
element.ApplicableOccurrence = f"IfcAnnotation/{object_type}"
|
||||
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
|
||||
|
||||
# TODO: move to operator
|
||||
def create_annotation_occurence(context):
|
||||
# object_type = context.scene.BIMAnnotationProperties.object_type
|
||||
def create_annotation_occurrence(context):
|
||||
props = context.scene.BIMAnnotationProperties
|
||||
relating_type = tool.Ifc.get().by_id(int(props.relating_type_id))
|
||||
object_type = props.object_type
|
||||
@@ -152,9 +189,8 @@ def create_annotation_occurence(context):
|
||||
|
||||
def create_annotation():
|
||||
props = bpy.context.scene.BIMAnnotationProperties
|
||||
create_type_occurence = props.relating_type_id != "0"
|
||||
if create_type_occurence:
|
||||
create_annotation_occurence(bpy.context)
|
||||
if props.relating_type_id != "0":
|
||||
create_annotation_occurrence(bpy.context)
|
||||
else:
|
||||
object_type = props.object_type
|
||||
bpy.ops.bim.add_annotation(object_type=object_type, data_type=ANNOTATION_TYPES_DATA[object_type][-1])
|
||||
@@ -171,6 +207,11 @@ class AnnotationToolUI:
|
||||
row.label(text="No IFC Project", icon="ERROR")
|
||||
return
|
||||
|
||||
drawing = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not drawing:
|
||||
row.label(text="No Active Drawing", icon="ERROR")
|
||||
return
|
||||
|
||||
if not AnnotationData.is_loaded:
|
||||
AnnotationData.load()
|
||||
|
||||
@@ -183,13 +224,11 @@ class AnnotationToolUI:
|
||||
@classmethod
|
||||
def draw_create_object_interface(cls):
|
||||
row = cls.layout.row(align=True)
|
||||
op, row = add_layout_hotkey_operator(cls.layout, "Add Type", "S_C", "Create a new annotation type")
|
||||
selected_icon = "CHECKBOX_HLT" if cls.props.create_representation_for_type else "CHECKBOX_DEHLT"
|
||||
row.prop(cls.props, "create_representation_for_type", text="", icon=selected_icon)
|
||||
row.prop(bpy.context.scene.DocProperties, "should_draw_decorations", text="Viewport Annotations")
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
|
||||
|
||||
@classmethod
|
||||
@@ -204,21 +243,20 @@ class AnnotationToolUI:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="FILE_3D")
|
||||
prop_with_search(row, cls.props, "relating_type_id", text="")
|
||||
row.operator("bim.launch_annotation_type_manager", icon="LIGHTPROBE_GRID", text="")
|
||||
|
||||
create_type_occurence = cls.props.relating_type_id != "0"
|
||||
label = "Add Type Occurence" if create_type_occurence else "Add Annotation"
|
||||
add_layout_hotkey_operator(cls.layout, label, "S_A", "Create a new annotation")
|
||||
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
|
||||
|
||||
if object_type in ("TEXT", "STAIR_ARROW"):
|
||||
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout,
|
||||
"Bulk Tag",
|
||||
"S_T",
|
||||
"Create new annotations and automatically adjust them to the selected objects",
|
||||
)
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
|
||||
)
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout, "Readjust", "S_G", "Readjust tags based on the products they are assigned to"
|
||||
)
|
||||
|
||||
|
||||
class Hotkey(bpy.types.Operator, Operator):
|
||||
@@ -250,23 +288,27 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
|
||||
def hotkey_S_T(self):
|
||||
props = bpy.context.scene.BIMAnnotationProperties
|
||||
object_type = props.object_type
|
||||
annotation_type = props.object_type
|
||||
|
||||
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
|
||||
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for tagging.")
|
||||
return
|
||||
|
||||
related_objects = bpy.context.selected_objects
|
||||
for related_object in related_objects:
|
||||
create_annotation()
|
||||
obj = bpy.context.active_object
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
|
||||
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
|
||||
|
||||
def hotkey_S_A(self):
|
||||
create_annotation()
|
||||
|
||||
def hotkey_S_E(self):
|
||||
if not bpy.context.object:
|
||||
if not bpy.context.active_object:
|
||||
return
|
||||
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.object):
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
|
||||
bpy.ops.bim.edit_text_popup()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
@@ -275,12 +317,15 @@ class Hotkey(bpy.types.Operator, Operator):
|
||||
if not element or not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
|
||||
annotation_type = element.ObjectType
|
||||
if annotation_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
|
||||
self.report({"ERROR"}, f"Annotation type {annotation_type} is not supported for readjustment.")
|
||||
continue
|
||||
|
||||
related_product = tool.Drawing.get_assigned_product(element)
|
||||
if not related_product:
|
||||
self.report({"ERROR"}, "Selected annotation has no product assigned.")
|
||||
continue
|
||||
related_object = tool.Ifc.get_object(related_product)
|
||||
|
||||
tool.Drawing.setup_annotation_object(obj, element.ObjectType, related_object)
|
||||
|
||||
def hotkey_S_C(self):
|
||||
create_annotation_type(bpy.context)
|
||||
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
|
||||
|
||||
@@ -29,6 +29,9 @@ classes = (
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverrideDuplicateMoveLinkedMacro,
|
||||
operator.OverrideDuplicateMoveMacro,
|
||||
operator.OverrideDuplicateMoveAggregate,
|
||||
operator.OverrideDuplicateMoveAggregateMacro,
|
||||
operator.RefreshAggregate,
|
||||
operator.OverrideJoin,
|
||||
operator.OverrideModeSetEdit,
|
||||
operator.OverrideModeSetObject,
|
||||
@@ -58,6 +61,8 @@ def register():
|
||||
operator.OverrideDuplicateMoveMacro.define("TRANSFORM_OT_translate")
|
||||
operator.OverrideDuplicateMoveLinkedMacro.define("BIM_OT_override_object_duplicate_move_linked")
|
||||
operator.OverrideDuplicateMoveLinkedMacro.define("TRANSFORM_OT_translate")
|
||||
operator.OverrideDuplicateMoveAggregateMacro.define("BIM_OT_override_object_duplicate_move_aggregate")
|
||||
operator.OverrideDuplicateMoveAggregateMacro.define("TRANSFORM_OT_translate")
|
||||
|
||||
bpy.types.Object.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMObjectGeometryProperties)
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
@@ -69,24 +74,38 @@ def register():
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_object_join", "J", "PRESS", ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_macro", "D", "PRESS", shift=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked_macro", "D", "PRESS", alt=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_duplicate_move_aggregate_macro", "D", "PRESS", ctrl=True, shift=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "X", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_object_delete", "DEL", "PRESS")
|
||||
kmi.properties.confirm = False
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Curve", space_type="EMPTY")
|
||||
kmi = km.keymap_items.new("bim.override_mode_set_object", "TAB", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Outliner", space_type="OUTLINER")
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_outliner_delete", "X", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
kmi = km.keymap_items.new("bim.override_outliner_delete", "DEL", "PRESS")
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -111,10 +111,13 @@ class ConnectionsData:
|
||||
for rel in connected_to:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
related_element = rel.RelatedPort
|
||||
related_element_connection_type = ""
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
@@ -128,10 +131,13 @@ class ConnectionsData:
|
||||
for rel in connected_from:
|
||||
if element.is_a("IfcDistributionPort"):
|
||||
relating_element = rel.RelatingPort
|
||||
relating_element_connection_type = ""
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
|
||||
results.append(
|
||||
{
|
||||
|
||||
@@ -20,18 +20,20 @@ import bpy
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.core.aggregate
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.drawing
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.handler
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -59,22 +61,58 @@ class AddRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
representation_conversion_method: bpy.props.EnumProperty(
|
||||
items=[
|
||||
("OUTLINE", "Trace Outline", ""),
|
||||
("BOX", "Bounding Box", ""),
|
||||
("PROJECT", "Full Representation", ""),
|
||||
],
|
||||
name="Representation Conversion Method",
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_context = int(context.active_object.BIMGeometryProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
obj = context.active_object
|
||||
props = obj.BIMGeometryProperties
|
||||
ifc_context = int(props.contexts or "0") or None
|
||||
if not ifc_context:
|
||||
return
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
|
||||
if self.representation_conversion_method == "OUTLINE":
|
||||
if ifc_context.ContextType == "Plan":
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||
elif ifc_context.ContextIdentifier == "Profile":
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="-Y")
|
||||
else:
|
||||
data = tool.Geometry.generate_outline_mesh(obj, axis="+Z")
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
elif self.representation_conversion_method == "BOX":
|
||||
if ifc_context.ContextType == "Plan":
|
||||
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Z")
|
||||
elif ifc_context.ContextIdentifier == "Profile":
|
||||
data = tool.Geometry.generate_2d_box_mesh(obj, axis="Y")
|
||||
else:
|
||||
data = tool.Geometry.generate_3d_box_mesh(obj)
|
||||
tool.Geometry.change_object_data(obj, data, is_global=True)
|
||||
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=context.active_object,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "representation_conversion_method", text="")
|
||||
|
||||
|
||||
class SelectConnection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.select_connection"
|
||||
@@ -415,7 +453,11 @@ class OverrideDelete(bpy.types.Operator):
|
||||
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):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if ifcopenshell.util.element.get_pset(element, "BBIM_Array"):
|
||||
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
|
||||
return {"FINISHED"}
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
@@ -445,6 +487,7 @@ class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bl_label = "IFC Delete"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hierarchy: bpy.props.BoolProperty(default=False)
|
||||
is_batch: bpy.props.BoolProperty(name="Is Batch", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -477,7 +520,26 @@ class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
bpy.data.collections.remove(collection)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
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)
|
||||
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())
|
||||
objects_to_delete = set()
|
||||
collections_to_delete = set()
|
||||
for item in context.selected_ids:
|
||||
@@ -490,8 +552,20 @@ class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
elif item.bl_rna.identifier == "Object":
|
||||
objects_to_delete.add(bpy.data.objects.get(item.name))
|
||||
for obj in objects_to_delete:
|
||||
# This is the only difference
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
if tool.Ifc.get_entity(obj):
|
||||
tool.Geometry.delete_ifc_object(obj)
|
||||
else:
|
||||
bpy.data.objects.remove(obj)
|
||||
for collection in collections_to_delete:
|
||||
bpy.data.collections.remove(collection)
|
||||
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)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_collection_objects_and_children(self, collection):
|
||||
@@ -506,6 +580,14 @@ class OverrideOutlinerDelete(bpy.types.Operator):
|
||||
children = children.union(collection.children)
|
||||
return {"objects": objects, "children": children}
|
||||
|
||||
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 OverrideDuplicateMoveMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_macro"
|
||||
@@ -552,6 +634,14 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
|
||||
|
||||
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
@@ -620,6 +710,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
@@ -643,6 +737,442 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideDuplicateMoveAggregateMacro(bpy.types.Macro):
|
||||
bl_idname = "bim.override_object_duplicate_move_aggregate_macro"
|
||||
bl_label = "IFC Duplicate Objects Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.override_object_duplicate_move_aggregate"
|
||||
bl_label = "IFC Duplicate Objects Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
if self.new_active_obj:
|
||||
context.view_layer.objects.active = self.new_active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
new_active_obj = None
|
||||
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
if new_active_obj:
|
||||
context.view_layer.objects.active = new_active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.new_active_obj = None
|
||||
old_to_new = {}
|
||||
|
||||
### Adding the assembly data
|
||||
def add_assembly_data(element, parent, data_to_add):
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Aggregate_Data")
|
||||
data = [data_to_add]
|
||||
|
||||
if pset:
|
||||
if parent == None:
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=tool.Ifc.get().by_id(pset["id"]),
|
||||
properties={"Data": json.dumps(data)},
|
||||
)
|
||||
else:
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=tool.Ifc.get().by_id(pset["id"]),
|
||||
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
else:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Aggregate_Data"
|
||||
)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Parent": parent.GlobalId, "Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
def add_child_to_assembly_data(new_entity):
|
||||
pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Aggregate_Data")
|
||||
parent_element = tool.Ifc.get().by_guid(pset["Parent"])
|
||||
parent_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Aggregate_Data")
|
||||
data = json.loads(parent_pset["Data"])
|
||||
data[0]["children"].append(new_entity.GlobalId)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=tool.Ifc.get().by_id(parent_pset["id"]),
|
||||
properties={"Data": json.dumps(data)},
|
||||
)
|
||||
|
||||
def create_data_structure(entity, level=-1):
|
||||
level += 1
|
||||
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [],
|
||||
}
|
||||
|
||||
data_parent = {
|
||||
"children": [],
|
||||
"instance_of": [entity.GlobalId],
|
||||
}
|
||||
|
||||
if not entity.is_a("IfcElementAssembly"):
|
||||
return
|
||||
else:
|
||||
if level == 0:
|
||||
add_assembly_data(entity, entity, data_parent)
|
||||
else:
|
||||
add_assembly_data(entity, None, data_parent)
|
||||
parts = ifcopenshell.util.element.get_parts(entity)
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
add_assembly_data(part, entity, data_children)
|
||||
add_child_to_assembly_data(part)
|
||||
create_data_structure(part, level)
|
||||
continue
|
||||
add_assembly_data(part, entity, data_children)
|
||||
add_child_to_assembly_data(part)
|
||||
|
||||
return
|
||||
|
||||
def recreate_data_structure(entity, level=-1):
|
||||
level += 1
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
|
||||
pset_data = json.loads(pset["Data"])[0]
|
||||
instance_of = pset_data["instance_of"]
|
||||
|
||||
data_children = {
|
||||
"children": [],
|
||||
"instance_of": [],
|
||||
}
|
||||
|
||||
# Keeps instance ID through all copies
|
||||
data_parent = {
|
||||
"children": [],
|
||||
"instance_of": instance_of,
|
||||
}
|
||||
|
||||
if not entity.is_a("IfcElementAssembly"):
|
||||
return
|
||||
else:
|
||||
if level == 0:
|
||||
add_assembly_data(entity, entity, data_parent)
|
||||
else:
|
||||
add_assembly_data(entity, None, data_parent)
|
||||
parts = ifcopenshell.util.element.get_parts(entity)
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
add_child_to_assembly_data(part)
|
||||
recreate_data_structure(part, level)
|
||||
continue
|
||||
add_assembly_data(part, entity, data_children)
|
||||
add_child_to_assembly_data(part)
|
||||
|
||||
return
|
||||
|
||||
def duplicate_all(obj, level=-1, new_parent=None, parents=[]):
|
||||
level += 1
|
||||
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
|
||||
if level == 0:
|
||||
new_parent = duplicate_objects(obj)
|
||||
parents.append(new_parent)
|
||||
else:
|
||||
pair = []
|
||||
pair.append(new_parent)
|
||||
new_parent = duplicate_objects(obj)
|
||||
pair.append(new_parent)
|
||||
parents.append(pair)
|
||||
|
||||
if not entity.is_a("IfcElementAssembly"):
|
||||
return
|
||||
else:
|
||||
parts = ifcopenshell.util.element.get_parts(entity)
|
||||
|
||||
# Ensures that we are duplication all the IfcElementAssembly
|
||||
# before duplicating the nested objects
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
else:
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(new_parent),
|
||||
related_obj=tool.Ifc.get_object(new_part),
|
||||
)
|
||||
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
|
||||
# Recursion Call
|
||||
duplicate_all(part_obj, level, new_parent, parents)
|
||||
|
||||
if level == 0:
|
||||
for p in parents[1:]:
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(p[0]),
|
||||
related_obj=tool.Ifc.get_object(p[1]),
|
||||
)
|
||||
|
||||
return new_parent
|
||||
return
|
||||
|
||||
def duplicate_objects(obj, is_root=False):
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
# This is needed to make sure the new object gets unlink from
|
||||
# the old object assembly collection
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
|
||||
# Copy the actual class
|
||||
new_entity = blenderbim.core.root.copy_class(
|
||||
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
|
||||
)
|
||||
|
||||
if new_entity:
|
||||
# Checks if the object belongs to an Ifc Array
|
||||
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
|
||||
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(selected_root_entity),
|
||||
related_obj=tool.Ifc.get_object(new_entity),
|
||||
)
|
||||
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
|
||||
|
||||
return new_entity
|
||||
|
||||
### Check if only one element and it's assembly
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
|
||||
selected_obj = context.selected_objects[0]
|
||||
selected_root_entity = tool.Ifc.get_entity(selected_obj)
|
||||
|
||||
if not selected_root_entity.is_a("IfcElementAssembly"):
|
||||
return {"FINISHED"}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
||||
if not pset:
|
||||
create_data_structure(selected_root_entity)
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
||||
|
||||
selected_root_parent = selected_root_entity
|
||||
|
||||
new_root_entity = duplicate_all(selected_obj)
|
||||
|
||||
recreate_data_structure(new_root_entity)
|
||||
|
||||
old_objs = []
|
||||
for old, new in old_to_new.items():
|
||||
old_objs.append(tool.Ifc.get_object(old))
|
||||
|
||||
relationships = tool.Root.get_decomposition_relationships(old_objs)
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RefreshAggregate(bpy.types.Operator):
|
||||
bl_idname = "bim.refresh_aggregate"
|
||||
bl_label = "IFC Refresh Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.new_active_obj = None
|
||||
old_to_new = {}
|
||||
|
||||
def remove_objects(entity, level=-1, parents=[]):
|
||||
level += 1
|
||||
if level == 0:
|
||||
parents = [entity]
|
||||
parts = ifcopenshell.util.element.get_parts(entity)
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
parents.append(part)
|
||||
remove_objects(part, level, parents)
|
||||
continue
|
||||
else:
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
if part_obj:
|
||||
tool.Geometry.delete_ifc_object(part_obj)
|
||||
|
||||
return parents
|
||||
|
||||
def duplicate_children(entity):
|
||||
pset = ifcopenshell.util.element.get_pset(entity, "BBIM_Aggregate_Data")
|
||||
pset_data = json.loads(pset["Data"])[0]
|
||||
instance_of = pset_data["instance_of"][0]
|
||||
|
||||
instance_entity = tool.Ifc.get().by_guid(instance_of)
|
||||
|
||||
if not instance_entity.is_a("IfcElementAssembly"):
|
||||
return
|
||||
else:
|
||||
parts = ifcopenshell.util.element.get_parts(instance_entity)
|
||||
for part in parts:
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
pass
|
||||
else:
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
new_part = duplicate_objects(part_obj)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(entity),
|
||||
related_obj=tool.Ifc.get_object(new_part),
|
||||
)
|
||||
|
||||
def duplicate_objects(obj, is_root=False):
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.link(new_obj)
|
||||
obj.select_set(False)
|
||||
new_obj.select_set(True)
|
||||
|
||||
# Copy the actual class
|
||||
new_entity = blenderbim.core.root.copy_class(
|
||||
tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj
|
||||
)
|
||||
|
||||
if new_entity:
|
||||
# Checks if the object belongs to an Ifc Array
|
||||
array_pset = ifcopenshell.util.element.get_pset(new_entity, "BBIM_Array")
|
||||
if array_pset:
|
||||
array_pset = tool.Ifc.get().by_id(array_pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=new_entity, pset=array_pset)
|
||||
|
||||
blenderbim.core.aggregate.unassign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=obj,
|
||||
related_obj=tool.Ifc.get_object(new_entity),
|
||||
)
|
||||
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = [new_entity]
|
||||
|
||||
return new_entity
|
||||
|
||||
if len(context.selected_objects) != 1:
|
||||
return {"FINISHED"}
|
||||
|
||||
selected_root_obj = context.selected_objects[0]
|
||||
selected_root_entity = tool.Ifc.get_entity(selected_root_obj)
|
||||
|
||||
if not selected_root_entity.is_a("IfcElementAssembly"):
|
||||
return {"FINISHED"}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(selected_root_entity, "BBIM_Aggregate_Data")
|
||||
if not pset:
|
||||
return {"FINISHED"}
|
||||
|
||||
pset_data = json.loads(pset["Data"])[0]
|
||||
instance_of = pset_data["instance_of"][0]
|
||||
original_root_entity = tool.Ifc.get().by_guid(instance_of)
|
||||
if original_root_entity == selected_root_entity:
|
||||
return {"FINISHED"}
|
||||
|
||||
parents = remove_objects(selected_root_entity)
|
||||
|
||||
original_root_object = tool.Ifc.get_object(original_root_entity)
|
||||
|
||||
selected_matrix = selected_root_obj.matrix_world
|
||||
original_matrix = original_root_object.matrix_world
|
||||
|
||||
for parent in parents:
|
||||
duplicate_children(parent)
|
||||
|
||||
old_objs = []
|
||||
for old, new in old_to_new.items():
|
||||
old_objs.append(tool.Ifc.get_object(old))
|
||||
|
||||
new_obj = tool.Ifc.get_object(new[0])
|
||||
|
||||
matrix_diff = new_obj.matrix_world @ original_matrix
|
||||
new_matrix = selected_matrix @ matrix_diff
|
||||
|
||||
new_obj.matrix_world = new_matrix
|
||||
|
||||
relationships = tool.Root.get_decomposition_relationships(old_objs)
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideJoin(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_object_join"
|
||||
bl_label = "IFC Join"
|
||||
@@ -753,6 +1283,7 @@ class OverridePasteBuffer(bpy.types.Operator):
|
||||
|
||||
|
||||
class OverrideModeSetEdit(bpy.types.Operator):
|
||||
bl_description = "Switch from Object mode to Edit mode"
|
||||
bl_idname = "bim.override_mode_set_edit"
|
||||
bl_label = "IFC Mode Set Edit"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -761,7 +1292,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objs = context.selected_objects or ([context.active_object] if context.active_object else [])
|
||||
selected_objs = context.selected_objects or ([context.active_object] if context.active_object else [])
|
||||
active_obj = context.active_object
|
||||
|
||||
if context.active_object:
|
||||
@@ -771,19 +1302,15 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
if element and element.is_a("IfcRelSpaceBoundary"):
|
||||
return bpy.ops.bim.enable_editing_boundary_geometry()
|
||||
|
||||
for obj in objs:
|
||||
for obj in selected_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 is not None and usage_type not in ("PROFILE", "LAYER3"):
|
||||
# Parametric objects shall not be edited as meshes as they
|
||||
@@ -791,35 +1318,37 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
# constraints.
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
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")
|
||||
):
|
||||
if tool.Blender.Modifier.is_modifier_with_non_editable_path(element):
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
is_profile = True
|
||||
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 (
|
||||
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
|
||||
elif (
|
||||
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="")
|
||||
operator = lambda: bpy.ops.bim.hotkey(hotkey="S_E")
|
||||
elif tool.Blender.Modifier.is_editing_parameters(obj):
|
||||
# This should go BEFORE the modifiers
|
||||
self.report({"INFO"}, "Can't edit path while the modifier parameters are being modified")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
elif tool.Blender.Modifier.is_roof(element):
|
||||
operator = lambda: bpy.ops.bim.enable_editing_roof_path()
|
||||
elif tool.Blender.Modifier.is_railing(element):
|
||||
operator = lambda: bpy.ops.bim.enable_editing_railing_path()
|
||||
else:
|
||||
is_profile = False
|
||||
if is_profile:
|
||||
if len(context.selected_objects) == 1 and context.active_object == context.selected_objects[0]:
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
operator()
|
||||
return {"FINISHED"}
|
||||
else:
|
||||
self.report({"INFO"}, "Only a single profile-based representation can be edited at a time.")
|
||||
@@ -840,30 +1369,34 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=False,
|
||||
)
|
||||
tool.Geometry.dissolve_triangulated_edges(obj)
|
||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if not context.selected_objects or len(context.selected_objects) != len(objs):
|
||||
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
|
||||
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
|
||||
self.report({"INFO"}, "Only mesh-compatible representations may be edited in edit mode.")
|
||||
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
|
||||
|
||||
if context.active_object not in context.selected_objects:
|
||||
# The active object is non-mesh-like. Set a valid object (or None) as active
|
||||
context.view_layer.objects.active = context.selected_objects[0] if context.selected_objects else None
|
||||
if context.active_object:
|
||||
bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
else:
|
||||
# restore the selection if nothing worked
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = active_obj
|
||||
return tool.Blender.toggle_edit_mode(context)
|
||||
|
||||
# Restore the selection if nothing worked
|
||||
for obj in selected_objs:
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = active_obj
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
|
||||
|
||||
def _invoke(self, context, event):
|
||||
if not tool.Ifc.get():
|
||||
return bpy.ops.object.mode_set(mode="EDIT", toggle=True)
|
||||
return tool.Blender.toggle_edit_mode(context)
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
@@ -911,6 +1444,9 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
row.label(text="No Geometry Found: Object will revert to previous state.")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
|
||||
|
||||
def _invoke(self, context, event):
|
||||
self.is_valid = True
|
||||
self.should_save = True
|
||||
|
||||
@@ -943,10 +1479,10 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
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.Blender.Modifier.is_railing(element):
|
||||
bpy.ops.bim.finish_editing_railing_path()
|
||||
elif tool.Blender.Modifier.is_roof(element):
|
||||
bpy.ops.bim.finish_editing_roof_path()
|
||||
elif tool.Model.get_usage_type(element) == "PROFILE":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
else:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
@@ -29,19 +30,21 @@ def object_menu(self, context):
|
||||
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')
|
||||
self.layout.operator("bim.override_outliner_delete", icon="X")
|
||||
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
bl_label = "IFC Representations"
|
||||
bl_label = "Representations"
|
||||
bl_idname = "BIM_PT_representations"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_context = "scene"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_tab_representations"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -58,20 +61,24 @@ class BIM_PT_representations(Panel):
|
||||
layout = self.layout
|
||||
props = context.active_object.BIMObjectProperties
|
||||
|
||||
if not RepresentationsData.data["representations"]:
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
prop_with_search(row, context.active_object.BIMGeometryProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
if not RepresentationsData.data["representations"]:
|
||||
layout.label(text="No Representations Found")
|
||||
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=representation["ContextType"])
|
||||
row.label(text=representation["ContextIdentifier"])
|
||||
row.label(text=representation["TargetView"])
|
||||
row.label(text=representation["RepresentationType"])
|
||||
op = row.operator("bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH", text="")
|
||||
op = row.operator(
|
||||
"bim.switch_representation",
|
||||
icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",
|
||||
text="",
|
||||
)
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = representation["id"]
|
||||
@@ -80,14 +87,14 @@ class BIM_PT_representations(Panel):
|
||||
|
||||
|
||||
class BIM_PT_connections(Panel):
|
||||
bl_label = "IFC Connections"
|
||||
bl_label = "Connections"
|
||||
bl_idname = "BIM_PT_connections"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_tab_geometric_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -118,11 +125,14 @@ class BIM_PT_connections(Panel):
|
||||
|
||||
|
||||
class BIM_PT_mesh(Panel):
|
||||
bl_label = "IFC Representation"
|
||||
bl_label = "Representation Utilities"
|
||||
bl_idname = "BIM_PT_mesh"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
bl_order = 2
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_parent_id = "BIM_PT_tab_representations"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -136,43 +146,34 @@ class BIM_PT_mesh(Panel):
|
||||
def draw(self, context):
|
||||
if not context.active_object.data:
|
||||
return
|
||||
row = self.layout.row()
|
||||
row.label(text="Advanced Users Only", icon="ERROR")
|
||||
|
||||
layout = self.layout
|
||||
props = context.active_object.data.BIMMeshProperties
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_representation")
|
||||
row.operator("bim.copy_representation", text="Copy Mesh From Active To Selected")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_representation")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Tessellation")
|
||||
op = row.operator("bim.update_representation", text="Convert To Tessellation")
|
||||
op.ifc_representation_class = "IfcTessellatedFaceSet"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Rectangle Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Convert To Rectangle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Circle Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Convert To Circle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcCircleProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion")
|
||||
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Arbitrary Extrusion With Voids")
|
||||
op = row.operator("bim.update_representation", text="Convert To Arbitrary Extrusion With Voids")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.get_representation_ifc_parameters")
|
||||
for index, ifc_parameter in enumerate(props.ifc_parameters):
|
||||
row = layout.row(align=True)
|
||||
row.prop(ifc_parameter, "name", text="")
|
||||
row.prop(ifc_parameter, "value", text="")
|
||||
row.operator("bim.update_parametric_representation", icon="FILE_REFRESH", text="").index = index
|
||||
|
||||
|
||||
def BIM_PT_transform(self, context):
|
||||
if context.active_object and context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
@@ -185,7 +186,7 @@ def BIM_PT_transform(self, context):
|
||||
|
||||
|
||||
class BIM_PT_derived_placements(Panel):
|
||||
bl_label = "IFC Derived Placements"
|
||||
bl_label = "Derived Placements"
|
||||
bl_idname = "BIM_PT_derived_placements"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -220,12 +221,13 @@ class BIM_PT_derived_placements(Panel):
|
||||
|
||||
|
||||
class BIM_PT_workarounds(Panel):
|
||||
bl_label = "IFC Vendor Workarounds"
|
||||
bl_label = "Vendor Workarounds"
|
||||
bl_idname = "BIM_PT_workarounds"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
bl_parent_id = "BIM_PT_tab_geometric_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -33,6 +33,7 @@ classes = (
|
||||
operator.ConvertGlobalToLocal,
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -171,3 +171,19 @@ class ConvertGlobalToLocal(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_global_to_local(tool.Georeference)
|
||||
|
||||
class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.convert_angle_to_coord"
|
||||
bl_label = "Convert Angle To Y Axis"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert angle to Y axis"
|
||||
type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
file = tool.Ifc.get()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
return file and (props.angle_degree_input_x or props.angle_degree_input_y)
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
@@ -37,6 +37,12 @@ class BIMGeoreferenceProperties(PropertyGroup):
|
||||
projected_crs: CollectionProperty(name="Projected CRS", type=Attribute)
|
||||
coordinate_input: StringProperty(name="Coordinate Input", description='Formatted "x,y,z" (without quotes)')
|
||||
coordinate_output: StringProperty(name="Coordinate Output", description='Formatted "x,y,z" (without quotes)')
|
||||
angle_degree_input_x: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to Easting")
|
||||
angle_degree_input_y: FloatProperty(name="Angle Degree Input", description="Angle (in degrees) rel to +Y")
|
||||
x_axis_abscissa_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
x_axis_ordinate_output: StringProperty(name="X Axis Abscissa Ordinate Output", description="X axis abscissa and ordinate", )
|
||||
y_axis_abscissa_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
y_axis_ordinate_output: StringProperty(name="Y Axis Abscissa Ordinate Output", description="Y axis abscissa and ordinate", )
|
||||
has_blender_offset: BoolProperty(name="Has Blender Offset")
|
||||
blender_eastings: StringProperty(name="Blender Eastings", default="0")
|
||||
blender_northings: StringProperty(name="Blender Northings", default="0")
|
||||
|
||||
@@ -23,7 +23,7 @@ from blenderbim.bim.module.georeference.data import GeoreferenceData
|
||||
|
||||
|
||||
class BIM_PT_gis(Panel):
|
||||
bl_label = "IFC Georeferencing"
|
||||
bl_label = "Georeferencing"
|
||||
bl_idname = "BIM_PT_gis"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -167,3 +167,21 @@ class BIM_PT_gis_utilities(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.convert_local_to_global", text="Local to Global")
|
||||
row.operator("bim.convert_global_to_local", text="Global to Local")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Orientation Calculator", icon="TRACKING_REFINE_FORWARDS")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_x", text="Angle (°) rel to Easting")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_x"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_abscissa_output", text="XAxis Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "x_axis_ordinate_output", text="XAxis Ordinate")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "angle_degree_input_y", text="Angle (°) rel to to +Y")
|
||||
row.operator("bim.convert_angle_to_coord", text="", icon="FILE_REFRESH").type = "rel_y"
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_abscissa_output", text="North Abscissa")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "y_axis_ordinate_output", text="North Ordinate")
|
||||
@@ -20,13 +20,13 @@ from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_cityjson_converter(Panel):
|
||||
bl_label = "IFC CityJSON"
|
||||
bl_label = "CityJSON"
|
||||
bl_idname = "BIM_PT_ifccityjson"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration"
|
||||
bl_parent_id = "BIM_PT_tab_collaboration"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -48,7 +48,7 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
|
||||
new = self.props.groups.add()
|
||||
new.ifc_definition_id = group.id()
|
||||
new.name = group.Name or "Unnamed"
|
||||
new.selection_query = group.Description.split("*selector*")[1] if group.Description else ""
|
||||
new.selection_query = ""
|
||||
new.tree_depth = tree_depth
|
||||
new.has_children = False
|
||||
new.is_expanded = group.id() in self.expanded_groups
|
||||
|
||||
@@ -22,13 +22,13 @@ from blenderbim.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
|
||||
|
||||
class BIM_PT_groups(Panel):
|
||||
bl_label = "IFC Groups"
|
||||
bl_label = "Groups"
|
||||
bl_idname = "BIM_PT_groups"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
bl_parent_id = "BIM_PT_tab_grouping_and_filtering"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -69,14 +69,14 @@ class BIM_PT_groups(Panel):
|
||||
|
||||
|
||||
class BIM_PT_object_groups(Panel):
|
||||
bl_label = "IFC Groups"
|
||||
bl_label = "Groups"
|
||||
bl_idname = "BIM_PT_object_groups"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_utilities_object"
|
||||
bl_parent_id = "BIM_PT_tab_misc"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -17,7 +17,7 @@ class CreateRepo(bpy.types.Operator):
|
||||
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
|
||||
@@ -45,7 +45,7 @@ class AddFileToRepo(bpy.types.Operator):
|
||||
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
|
||||
|
||||
@@ -6,7 +6,7 @@ from blenderbim.bim.module.ifcgit.data import IfcGitData
|
||||
class IFCGIT_PT_panel(bpy.types.Panel):
|
||||
"""Scene Properties panel to interact with IFC repository data"""
|
||||
|
||||
bl_label = "IFC Git"
|
||||
bl_label = "Git"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
@@ -211,6 +211,33 @@ class COMMIT_UL_List(bpy.types.UIList):
|
||||
layout.label(text=refs + commit.message.split("\n")[0], icon="DECORATE_ANIMATE")
|
||||
layout.label(text=time.strftime("%c", time.localtime(commit.committed_date)))
|
||||
|
||||
def draw_filter(self, context, layout):
|
||||
|
||||
# We only need filtering and reverse sort, not reordering by name
|
||||
row = layout.row(align=True)
|
||||
row.prop(self, "filter_name", text="")
|
||||
row.prop(self, "use_filter_invert", text="", icon="ARROW_LEFTRIGHT")
|
||||
row.prop(self, "use_filter_sort_reverse", text="", icon="SORT_DESC")
|
||||
|
||||
def filter_items(self, context, data, propname):
|
||||
|
||||
commits = getattr(data, propname)
|
||||
helper_funcs = bpy.types.UI_UL_list
|
||||
|
||||
# Default return values.
|
||||
flt_flags = []
|
||||
flt_neworder = list(range(len(commits)))
|
||||
|
||||
# Filtering by commit message
|
||||
if self.filter_name:
|
||||
flt_flags = helper_funcs.filter_items_by_name(
|
||||
self.filter_name, self.bitflag_filter_item, commits, "message", reverse=False
|
||||
)
|
||||
if not flt_flags:
|
||||
flt_flags = [self.bitflag_filter_item] * len(commits)
|
||||
|
||||
return flt_flags, flt_neworder
|
||||
|
||||
|
||||
class IFCGIT_PT_revision_inspector(bpy.types.Panel):
|
||||
"""Tool panel to interact with revision history"""
|
||||
|
||||
@@ -22,13 +22,13 @@ from blenderbim.bim.module.layer.data import LayersData
|
||||
|
||||
|
||||
class BIM_PT_layers(Panel):
|
||||
bl_label = "IFC Presentation Layers"
|
||||
bl_label = "Presentation Layers"
|
||||
bl_idname = "BIM_PT_layers"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry_object"
|
||||
bl_parent_id = "BIM_PT_tab_geometric_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -27,7 +27,7 @@ class BIM_PT_lca(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_integrations"
|
||||
bl_parent_id = "BIM_PT_tab_sandbox"
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMLCAProperties
|
||||
|
||||
@@ -23,7 +23,7 @@ from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesD
|
||||
|
||||
|
||||
class BIM_PT_libraries(Panel):
|
||||
bl_label = "IFC Libraries"
|
||||
bl_label = "Libraries"
|
||||
bl_idname = "BIM_PT_libraries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -94,14 +94,14 @@ class BIM_PT_libraries(Panel):
|
||||
|
||||
|
||||
class BIM_PT_library_references(Panel):
|
||||
bl_label = "IFC Library References"
|
||||
bl_label = "Library References"
|
||||
bl_idname = "BIM_PT_library_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_order = 1
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
bl_parent_id = "BIM_PT_tab_misc"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -32,9 +32,10 @@ classes = (
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.DisableEditingMaterial,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
operator.DisableEditingMaterialSetItemProfile,
|
||||
operator.DisableEditingMaterials,
|
||||
operator.DuplicateLayer,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EditMaterial,
|
||||
operator.EditMaterialSetItem,
|
||||
|
||||
@@ -217,10 +217,10 @@ class RemoveConstituent(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
constituent = tool.Ifc.get().by_id(self.constituent)
|
||||
for material_set in layer.ToMaterialConstituentSet:
|
||||
for material_set in constituent.ToMaterialConstituentSet:
|
||||
if len(material_set.MaterialConstituents) == 1:
|
||||
self.report({"ERROR"}, "At least one constituent must exist")
|
||||
return {"ERROR"}
|
||||
return {"CANCELLED"}
|
||||
ifcopenshell.api.run("material.remove_constituent", tool.Ifc.get(), constituent=constituent)
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ class RemoveProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for material_set in profile.ToMaterialProfileSet:
|
||||
if len(material_set.MaterialProfiles) == 1:
|
||||
self.report({"ERROR"}, "At least one profile must exist")
|
||||
return {"ERROR"}
|
||||
return {"CANCELLED"}
|
||||
ifcopenshell.api.run("material.remove_profile", tool.Ifc.get(), profile=profile)
|
||||
|
||||
|
||||
@@ -311,7 +311,6 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_layer"
|
||||
bl_label = "Remove Layer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -319,10 +318,25 @@ class RemoveLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for material_set in layer.ToMaterialLayerSet:
|
||||
if len(material_set.MaterialLayers) == 1:
|
||||
self.report({"ERROR"}, "At least one layer must exist")
|
||||
return {"ERROR"}
|
||||
return {"CANCELLED"}
|
||||
ifcopenshell.api.run("material.remove_layer", tool.Ifc.get(), layer=layer)
|
||||
|
||||
|
||||
class DuplicateLayer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.duplicate_layer"
|
||||
bl_label = "Duplicate Layer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
layer: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
layer = tool.Ifc.get().by_id(self.layer)
|
||||
new_layer = ifcopenshell.util.element.copy(tool.Ifc.get(), layer)
|
||||
for material_set in layer.ToMaterialLayerSet:
|
||||
layers = list(material_set.MaterialLayers)
|
||||
layers.insert(layers.index(layer), new_layer)
|
||||
material_set.MaterialLayers = layers
|
||||
|
||||
|
||||
class AddListItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_list_item"
|
||||
bl_label = "Add List Item"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import draw_attributes
|
||||
@@ -25,7 +26,7 @@ from blenderbim.bim.module.material.data import MaterialsData, ObjectMaterialDat
|
||||
|
||||
|
||||
class BIM_PT_materials(Panel):
|
||||
bl_label = "IFC Materials"
|
||||
bl_label = "Materials"
|
||||
bl_idname = "BIM_PT_materials"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -88,7 +89,7 @@ class BIM_PT_materials(Panel):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.material_attributes, self.layout)
|
||||
|
||||
class BIM_PT_material(Panel):
|
||||
bl_label = "IFC Material"
|
||||
bl_label = "Material"
|
||||
bl_idname = "BIM_PT_material"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
@@ -111,15 +112,17 @@ class BIM_PT_material(Panel):
|
||||
|
||||
|
||||
class BIM_PT_object_material(Panel):
|
||||
bl_label = "IFC Object Material"
|
||||
bl_label = "Object Material"
|
||||
bl_idname = "BIM_PT_object_material"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_metadata"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_materials"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Blender.is_tab(context, "GEOMETRY"):
|
||||
return False
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
@@ -272,6 +275,8 @@ class BIM_PT_object_material(Panel):
|
||||
op.material_set_item = set_item["id"]
|
||||
op = row.operator("bim.enable_editing_material_set_item", icon="GREASEPENCIL", text="")
|
||||
op.material_set_item = set_item["id"]
|
||||
if ObjectMaterialData.data["set_item_name"] == "layer":
|
||||
row.operator("bim.duplicate_layer", icon="DUPLICATE", text="").layer = set_item["id"]
|
||||
op = row.operator(f"bim.remove_{ObjectMaterialData.data['set_item_name']}", icon="X", text="")
|
||||
if ObjectMaterialData.data["material_class"] == "IfcMaterialList":
|
||||
setattr(op, "list_item_set", ObjectMaterialData.data["set"]["id"])
|
||||
|
||||
@@ -31,6 +31,7 @@ from . import (
|
||||
stair,
|
||||
window,
|
||||
opening,
|
||||
mep,
|
||||
pie,
|
||||
workspace,
|
||||
profile,
|
||||
@@ -38,6 +39,7 @@ from . import (
|
||||
door,
|
||||
railing,
|
||||
roof,
|
||||
mep,
|
||||
)
|
||||
|
||||
classes = (
|
||||
@@ -45,15 +47,20 @@ classes = (
|
||||
array.DisableEditingArray,
|
||||
array.EditArray,
|
||||
array.EnableEditingArray,
|
||||
array.ApplyArray,
|
||||
array.RemoveArray,
|
||||
array.SelectArrayParent,
|
||||
array.SelectAllArrayObjects,
|
||||
array.Input3DCursorXArray,
|
||||
array.Input3DCursorYArray,
|
||||
array.Input3DCursorZArray,
|
||||
product.AddConstrTypeInstance,
|
||||
product.AddDefaultType,
|
||||
product.AddEmptyType,
|
||||
product.AlignProduct,
|
||||
product.ChangeTypePage,
|
||||
product.DisableAddType,
|
||||
product.EnableAddType,
|
||||
product.LoadTypeThumbnails,
|
||||
product.MirrorElements,
|
||||
workspace.Hotkey,
|
||||
@@ -99,6 +106,8 @@ classes = (
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
mep.FitFlowSegments,
|
||||
mep.RegenerateDistributionElement,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
@@ -167,6 +176,8 @@ classes = (
|
||||
roof.EnableEditingRoofPath,
|
||||
roof.RemoveRoof,
|
||||
roof.SetGableRoofEdgeAngle,
|
||||
mep.MEPAddObstruction,
|
||||
mep.MEPAddTransition,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
@@ -174,7 +185,15 @@ addon_keymaps = []
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"builtin.transform"}, separator=True, group=False)
|
||||
bpy.utils.register_tool(workspace.SlabTool, after={"bim.wall_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DoorTool, after={"bim.slab_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.window_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.Object.BIMArrayProperties = bpy.props.PointerProperty(type=prop.BIMArrayProperties)
|
||||
bpy.types.Object.BIMStairProperties = bpy.props.PointerProperty(type=prop.BIMStairProperties)
|
||||
@@ -201,6 +220,14 @@ def register():
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.WallTool)
|
||||
bpy.utils.unregister_tool(workspace.SlabTool)
|
||||
bpy.utils.unregister_tool(workspace.DoorTool)
|
||||
bpy.utils.unregister_tool(workspace.WindowTool)
|
||||
bpy.utils.unregister_tool(workspace.ColumnTool)
|
||||
bpy.utils.unregister_tool(workspace.BeamTool)
|
||||
bpy.utils.unregister_tool(workspace.DuctTool)
|
||||
bpy.utils.unregister_tool(workspace.PipeTool)
|
||||
bpy.utils.unregister_tool(workspace.BimTool)
|
||||
del bpy.types.Scene.BIMModelProperties
|
||||
del bpy.types.Object.BIMArrayProperties
|
||||
|
||||
@@ -134,6 +134,24 @@ class EditArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
data = json.dumps(data)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.apply_array"
|
||||
bl_label = "Apply Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Apply the array and keep children as separate entities. Only available for the last array"
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
data = json.loads(pset["Data"])
|
||||
bpy.ops.bim.remove_array(item=len(data) - 1, keep_objs=True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -142,6 +160,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Remove Array"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.IntProperty()
|
||||
keep_objs: bpy.props.BoolProperty(name="Keep Objects", default=False)
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
@@ -152,6 +171,12 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
data = json.loads(pset["Data"])
|
||||
data[self.item]["count"] = 1
|
||||
|
||||
if (self.keep_objs) & (self.item < (len(data) - 1)):
|
||||
self.report(
|
||||
{"INFO"}, "Keeping the objects is only allowed when you are removing the last Array of the object"
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
props.is_editing = -1
|
||||
|
||||
try:
|
||||
@@ -159,7 +184,11 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
|
||||
tool.Model.regenerate_array(parent, data)
|
||||
if self.keep_objs:
|
||||
tool.Blender.Modifier.Array.bake_children_transform(element, self.item)
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, self.item, False)
|
||||
|
||||
tool.Model.regenerate_array(parent, data, self.keep_objs)
|
||||
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
if len(data) == 1:
|
||||
@@ -168,6 +197,7 @@ class RemoveArray(bpy.types.Operator, tool.Ifc.Operator):
|
||||
del data[self.item]
|
||||
data = json.dumps(data)
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data})
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -176,17 +206,35 @@ class SelectArrayParent(bpy.types.Operator):
|
||||
bl_idname = "bim.select_array_parent"
|
||||
bl_label = "Select Array Parent"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.StringProperty()
|
||||
parent: bpy.props.StringProperty(description="Parent Element GUID")
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
element = tool.Ifc.get().by_guid(self.parent)
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
|
||||
return {"CANCELLED"}
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
tool.Blender.select_and_activate_single_object(context, active_object=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectAllArrayObjects(bpy.types.Operator):
|
||||
bl_idname = "bim.select_all_array_objects"
|
||||
bl_label = "Select All Array Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
parent: bpy.props.StringProperty(description="Parent Element GUID")
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
parent_element = tool.Ifc.get().by_guid(self.parent)
|
||||
except RuntimeError:
|
||||
self.report({"ERROR"}, f"Couldn't find array parent by guid '{self.parent}'")
|
||||
return {"CANCELLED"}
|
||||
|
||||
array_objects = tool.Blender.Modifier.Array.get_all_objects(parent_element)
|
||||
tool.Blender.set_objects_selection(context, active_object=array_objects[0], selected_objects=array_objects)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -200,7 +248,7 @@ class Input3DCursorXArray(bpy.types.Operator):
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).x
|
||||
props.x = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).x
|
||||
else:
|
||||
props.x = cursor.location.x - obj.location.x
|
||||
return {"FINISHED"}
|
||||
@@ -216,7 +264,7 @@ class Input3DCursorYArray(bpy.types.Operator):
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).y
|
||||
props.y = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).y
|
||||
else:
|
||||
props.y = cursor.location.y - obj.location.y
|
||||
return {"FINISHED"}
|
||||
@@ -232,7 +280,7 @@ class Input3DCursorZArray(bpy.types.Operator):
|
||||
props = obj.BIMArrayProperties
|
||||
cursor = context.scene.cursor
|
||||
if props.use_local_space:
|
||||
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.col[3]).z
|
||||
props.z = (Matrix.inverted(obj.matrix_world) @ cursor.matrix.translation).z
|
||||
else:
|
||||
props.z = cursor.location.z - obj.location.z
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
import bpy
|
||||
import math
|
||||
import json
|
||||
import functools
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc
|
||||
import blenderbim.tool as tool
|
||||
from math import degrees
|
||||
|
||||
|
||||
def refresh():
|
||||
@@ -41,22 +41,29 @@ class AuthoringData:
|
||||
data = {}
|
||||
type_thumbnails = {}
|
||||
types_per_page = 9
|
||||
ifc_element_type = None
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
def load(cls, ifc_element_type=None):
|
||||
cls.is_loaded = True
|
||||
cls.props = bpy.context.scene.BIMModelProperties
|
||||
if ifc_element_type:
|
||||
cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type
|
||||
cls.data["ifc_element_type"] = cls.ifc_element_type
|
||||
cls.data["ifc_classes"] = cls.ifc_classes()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id()
|
||||
cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes()
|
||||
cls.data["type_class"] = cls.type_class()
|
||||
|
||||
# only after .type_class()
|
||||
cls.data["type_predefined_type"] = cls.type_predefined_type()
|
||||
cls.data["total_types"] = cls.total_types()
|
||||
cls.data["total_pages"] = cls.total_pages()
|
||||
cls.data["next_page"] = cls.next_page()
|
||||
cls.data["prev_page"] = cls.prev_page()
|
||||
cls.data["paginated_relating_types"] = cls.paginated_relating_types()
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail()
|
||||
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail() # only after .relating_type_id()
|
||||
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()
|
||||
@@ -64,6 +71,7 @@ class AuthoringData:
|
||||
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()
|
||||
cls.data["selected_material_usages"] = cls.selected_material_usages()
|
||||
|
||||
@classmethod
|
||||
def boundary_class(cls):
|
||||
@@ -112,6 +120,8 @@ class AuthoringData:
|
||||
def type_thumbnail(cls):
|
||||
if not cls.data["relating_type_id"]:
|
||||
return 0
|
||||
if not tool.Blender.enum_property_has_valid_index(cls.props, "relating_type_id", cls.data["relating_type_id"]):
|
||||
return 0
|
||||
element = tool.Ifc.get().by_id(int(cls.props.relating_type_id))
|
||||
return cls.type_thumbnails.get(element.id(), None) or 0
|
||||
|
||||
@@ -206,6 +216,10 @@ class AuthoringData:
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
if cls.data["ifc_element_type"]:
|
||||
if tool.Ifc.get().by_type(cls.data["ifc_element_type"]):
|
||||
return [(cls.data["ifc_element_type"], cls.data["ifc_element_type"], "")]
|
||||
return []
|
||||
results = []
|
||||
classes = {
|
||||
e.is_a() for e in (tool.Ifc.get().by_type("IfcElementType") + tool.Ifc.get().by_type("IfcSpaceType"))
|
||||
@@ -230,12 +244,26 @@ class AuthoringData:
|
||||
if ifc_class:
|
||||
elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name or "Unnamed")
|
||||
results.extend(elements)
|
||||
return [
|
||||
(str(e.id()), e.Name or "Unnamed", e.Description or "")
|
||||
for e in results
|
||||
]
|
||||
return [(str(e.id()), e.Name or "Unnamed", e.Description or "") for e in results]
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def selected_material_usages(cls):
|
||||
selected_usages = {}
|
||||
for obj in bpy.context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
usage = tool.Model.get_usage_type(element)
|
||||
if not usage:
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if representation and representation.RepresentationType == "SweptSolid":
|
||||
usage = "SWEPTSOLID"
|
||||
else:
|
||||
continue
|
||||
selected_usages.setdefault(usage, []).append(obj)
|
||||
return selected_usages
|
||||
|
||||
|
||||
class ArrayData:
|
||||
data = {}
|
||||
@@ -270,17 +298,26 @@ class StairData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
cls.data = {}
|
||||
cls.data["pset_data"] = cls.pset_data()
|
||||
if not cls.data["pset_data"]:
|
||||
return
|
||||
cls.data["general_params"] = cls.general_params()
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Stair", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Stair")
|
||||
|
||||
@classmethod
|
||||
def general_params(cls):
|
||||
props = bpy.context.active_object.BIMStairProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
general_params = {}
|
||||
general_props = props.get_props_kwargs(stair_type=data["stair_type"])
|
||||
for prop_name in general_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
|
||||
general_params[prop_readable_name] = prop_value
|
||||
return general_params
|
||||
|
||||
|
||||
class SverchokData:
|
||||
@@ -290,17 +327,12 @@ class SverchokData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters(), "has_sverchok": cls.has_sverchok()}
|
||||
cls.data = {"pset_data": cls.pset_data(), "has_sverchok": cls.has_sverchok()}
|
||||
|
||||
# NOTE: never used
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Sverchok", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Sverchok")
|
||||
|
||||
@classmethod
|
||||
def has_sverchok(cls):
|
||||
@@ -312,6 +344,13 @@ class SverchokData:
|
||||
return False
|
||||
|
||||
|
||||
def get_prop_from_data(props, data, prop_name):
|
||||
prop_value = data[prop_name]
|
||||
prop_value = round(prop_value, 5) if type(prop_value) is float else prop_value
|
||||
prop_readable_name = props.bl_rna.properties[prop_name].name
|
||||
return prop_readable_name, prop_value
|
||||
|
||||
|
||||
class WindowData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
@@ -319,17 +358,51 @@ class WindowData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
cls.data = {}
|
||||
cls.data["pset_data"] = cls.pset_data()
|
||||
if not cls.data["pset_data"]:
|
||||
return
|
||||
cls.data["general_params"] = cls.general_params()
|
||||
cls.data["lining_params"] = cls.lining_params()
|
||||
cls.data["panel_params"] = cls.panel_params()
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Window", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Window")
|
||||
|
||||
@classmethod
|
||||
def general_params(cls):
|
||||
props = bpy.context.active_object.BIMWindowProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
general_params = {}
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop_name in general_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
|
||||
general_params[prop_readable_name] = prop_value
|
||||
return general_params
|
||||
|
||||
@classmethod
|
||||
def lining_params(cls):
|
||||
props = bpy.context.active_object.BIMWindowProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
lining_data = data["lining_properties"]
|
||||
lining_params = {}
|
||||
lining_props = props.get_lining_kwargs(window_type=data["window_type"])
|
||||
for prop_name in lining_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, lining_data, prop_name)
|
||||
lining_params[prop_readable_name] = prop_value
|
||||
return lining_params
|
||||
|
||||
@classmethod
|
||||
def panel_params(cls):
|
||||
props = bpy.context.active_object.BIMWindowProperties
|
||||
panel_data = cls.data["pset_data"]["data_dict"]["panel_properties"]
|
||||
panel_params = {}
|
||||
panel_props = props.get_panel_kwargs()
|
||||
for prop_name in panel_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, panel_data, prop_name)
|
||||
panel_params[prop_readable_name] = prop_value
|
||||
return panel_params
|
||||
|
||||
|
||||
class DoorData:
|
||||
@@ -339,17 +412,52 @@ class DoorData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
cls.data = {}
|
||||
cls.data["pset_data"] = cls.pset_data()
|
||||
if not cls.data["pset_data"]:
|
||||
return
|
||||
cls.data["general_params"] = cls.general_params()
|
||||
cls.data["lining_params"] = cls.lining_params()
|
||||
cls.data["panel_params"] = cls.panel_params()
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Door", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Door")
|
||||
|
||||
@classmethod
|
||||
def general_params(cls):
|
||||
props = bpy.context.active_object.BIMDoorProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
general_params = {}
|
||||
general_props = props.get_general_kwargs()
|
||||
for prop_name in general_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
|
||||
general_params[prop_readable_name] = prop_value
|
||||
return general_params
|
||||
|
||||
@classmethod
|
||||
def lining_params(cls):
|
||||
props = bpy.context.active_object.BIMDoorProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
lining_data = data["lining_properties"]
|
||||
lining_params = {}
|
||||
lining_props = props.get_lining_kwargs(door_type=data["door_type"], lining_data=lining_data)
|
||||
for prop_name in lining_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, lining_data, prop_name)
|
||||
lining_params[prop_readable_name] = prop_value
|
||||
return lining_params
|
||||
|
||||
@classmethod
|
||||
def panel_params(cls):
|
||||
props = bpy.context.active_object.BIMDoorProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
panel_data = cls.data["pset_data"]["data_dict"]["panel_properties"]
|
||||
panel_params = {}
|
||||
panel_props = props.get_panel_kwargs(lining_data=data["lining_properties"])
|
||||
for prop_name in panel_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, panel_data, prop_name)
|
||||
panel_params[prop_readable_name] = prop_value
|
||||
return panel_params
|
||||
|
||||
|
||||
class RailingData:
|
||||
@@ -359,17 +467,31 @@ class RailingData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
cls.data = {}
|
||||
cls.data["pset_data"] = cls.pset_data()
|
||||
if not cls.data["pset_data"]:
|
||||
return
|
||||
cls.data["general_params"] = cls.general_params()
|
||||
cls.data["path_data"] = cls.path_data()
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Railing", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Railing")
|
||||
|
||||
@classmethod
|
||||
def general_params(cls):
|
||||
props = bpy.context.active_object.BIMRailingProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
general_params = {}
|
||||
general_props = props.get_general_kwargs(railing_type=data["railing_type"])
|
||||
for prop_name in general_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
|
||||
general_params[prop_readable_name] = prop_value
|
||||
return general_params
|
||||
|
||||
@classmethod
|
||||
def path_data(cls):
|
||||
return cls.data["pset_data"]["data_dict"]["path_data"]
|
||||
|
||||
|
||||
class RoofData:
|
||||
@@ -379,14 +501,27 @@ class RoofData:
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"parameters": cls.parameters()}
|
||||
cls.data = {}
|
||||
cls.data["pset_data"] = cls.pset_data()
|
||||
if not cls.data["pset_data"]:
|
||||
return
|
||||
cls.data["general_params"] = cls.general_params()
|
||||
|
||||
@classmethod
|
||||
def parameters(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
parameters = psets.get("BBIM_Roof", None)
|
||||
if parameters:
|
||||
parameters["data_dict"] = json.loads(parameters.get("Data", "[]") or "[]")
|
||||
return parameters
|
||||
def pset_data(cls):
|
||||
return tool.Model.get_modeling_bbim_pset_data(bpy.context.active_object, "BBIM_Roof")
|
||||
|
||||
@classmethod
|
||||
def general_params(cls):
|
||||
props = bpy.context.active_object.BIMRoofProperties
|
||||
data = cls.data["pset_data"]["data_dict"]
|
||||
general_params = {}
|
||||
general_props = props.get_general_kwargs(generation_method=data["generation_method"])
|
||||
for prop_name in general_props:
|
||||
prop_readable_name, prop_value = get_prop_from_data(props, data, prop_name)
|
||||
|
||||
if prop_name in ("angle", "rafter_edge_angle"):
|
||||
prop_value = round(degrees(prop_value), 2)
|
||||
|
||||
general_params[prop_readable_name] = prop_value
|
||||
return general_params
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user