mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Compare commits
428 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92642e6a6a | |||
| 78d33437ea | |||
| 65b3960ab3 | |||
| e81848dfba | |||
| fe9cda8ba1 | |||
| 49221739cf | |||
| c6966f9b39 | |||
| f08ed629b5 | |||
| ba675c0d28 | |||
| 3a80722621 | |||
| 9b77bb6e37 | |||
| 7a6df49348 | |||
| 666e484b2b | |||
| 256f40ed44 | |||
| 8eccb1a5ed | |||
| ce3490824d | |||
| 43c74010a5 | |||
| 6c991ed356 | |||
| c01c2a737a | |||
| c9c11a2905 | |||
| e5037dc41a | |||
| acd9250028 | |||
| a3dc477921 | |||
| 6246dda8d6 | |||
| 2683bcba20 | |||
| 97f2aa976e | |||
| 9b96073ac0 | |||
| 7efd9cea2a | |||
| c917881a8e | |||
| fa46eb140f | |||
| b8ea42540e | |||
| 671ab78c76 | |||
| a8550cea26 | |||
| 4f047274bf | |||
| eb04a7d579 | |||
| 84096529e1 | |||
| 3350f31312 | |||
| 0d79963ed8 | |||
| 43a8fa8bcb | |||
| ca8f3cf1e6 | |||
| 313a1c5705 | |||
| 53f6454784 | |||
| 2c0df1d4f5 | |||
| e384dbd548 | |||
| d22a0c7cb8 | |||
| 02a62a943c | |||
| 0625e4153c | |||
| 03f5feca88 | |||
| 85e3c6588f | |||
| 6e913a0ff9 | |||
| ff4e58a8ff | |||
| 1db028fa7b | |||
| 69a7efdab4 | |||
| 20dcb68009 | |||
| 6b0b64b869 | |||
| 09631e9565 | |||
| e9bb70f56b | |||
| bd008bdefe | |||
| 03d4782cb7 | |||
| 49c555c508 | |||
| c51c5651b2 | |||
| 364c2286d1 | |||
| 37d20eb360 | |||
| dd6912bdaa | |||
| a1fe68a17e | |||
| 3ec79cac3f | |||
| dbc6f521fe | |||
| c52f851d79 | |||
| 3380158f55 | |||
| 4e9a5f246f | |||
| 664c49a07d | |||
| 06b9cc6b38 | |||
| b9ea741e5e | |||
| 0098e38c77 | |||
| ece796fc2d | |||
| de4659570a | |||
| 62236c44ae | |||
| ce5db914db | |||
| 12699f29b7 | |||
| 54e326983f | |||
| 5fb40a390c | |||
| 0cb8716c6a | |||
| 099013fa05 | |||
| 4a8843b6bb | |||
| 33d15b42b7 | |||
| 44581c77ac | |||
| e848b249a6 | |||
| f85c3ae24c | |||
| 7c633fab31 | |||
| f716f8a357 | |||
| d156927e13 | |||
| 2ef1f4d812 | |||
| 78a36ab429 | |||
| 763a38bc9c | |||
| a23c88a53e | |||
| f6bf626349 | |||
| b66792f981 | |||
| 195af0c850 | |||
| ee56e06d86 | |||
| 146dd1e74d | |||
| 6d47cf3a51 | |||
| 9b6d53fe38 | |||
| 6902e2e2c6 | |||
| 82d136b9b3 | |||
| b8dd0450d1 | |||
| 85d76c084b | |||
| c658f7610e | |||
| 3fad28572b | |||
| 455fe80497 | |||
| 3f46d54ad9 | |||
| 89e3276c90 | |||
| bb9eba07c0 | |||
| 6d4573fb58 | |||
| 24890dd001 | |||
| 65d599bbd9 | |||
| ebe5460a9f | |||
| 01bc29c5b2 | |||
| 5ad6d455f9 | |||
| cef1c86f6d | |||
| d1caa3242f | |||
| 4f7bfec026 | |||
| df6ad70345 | |||
| a7a3ccb23a | |||
| b65f2ca062 | |||
| f991b053d3 | |||
| 47a25ca8b9 | |||
| a3f27830c5 | |||
| 1a5b5dd303 | |||
| f529bc37ee | |||
| 825ffe03b0 | |||
| 622ff05247 | |||
| d638405ff8 | |||
| c0f547c6e4 | |||
| 3accc1dbd6 | |||
| 6eab9fb593 | |||
| 2708133493 | |||
| c5e6c3e2c7 | |||
| d7b27c0620 | |||
| 75ffc4454d | |||
| a1817e8a61 | |||
| b4b334145f | |||
| 04535d27ec | |||
| 3c41f6b0f8 | |||
| a17c44c076 | |||
| eccf72ee8a | |||
| 9c9bec9c4e | |||
| 71c1cd6a9e | |||
| 0504556e7a | |||
| e9a18d030e | |||
| 6d725f60fa | |||
| 72206bc441 | |||
| c0eb9addfd | |||
| ff3a98234d | |||
| ebf6b9fc88 | |||
| c2e4b5f1fa | |||
| 9728c4d345 | |||
| f0d6500539 | |||
| 124ccf0404 | |||
| df03f7ad29 | |||
| ff8fc898a1 | |||
| 2d62d8181a | |||
| 6853735bb2 | |||
| 1c3980b6f0 | |||
| 5c224150ec | |||
| 18719c16df | |||
| ddb8abff0f | |||
| f9221717ff | |||
| da6945b352 | |||
| 62cdf76a5a | |||
| fae607f8ce | |||
| 468192e849 | |||
| edd01637e2 | |||
| 604fbc4bc9 | |||
| 4617d9f234 | |||
| 41486ba08a | |||
| 2120653104 | |||
| 43869c33f6 | |||
| fcfdc179df | |||
| 30c164d223 | |||
| b28d8b0cca | |||
| 4b5907f5dd | |||
| 78a27ef44c | |||
| 528e27234c | |||
| 8ac885ced1 | |||
| 63872abaef | |||
| 5ba155eb17 | |||
| 2ec3c4c06b | |||
| 6f55cd5169 | |||
| f5f88f4b97 | |||
| d5b92c0fea | |||
| 9355969110 | |||
| 6c998e0b8a | |||
| 51f8d648ad | |||
| a26539d6a5 | |||
| a9f27fcf17 | |||
| 28e5af853d | |||
| a7b3b5261f | |||
| 0dac56dbdf | |||
| 0992f43f11 | |||
| c1c50f8868 | |||
| d733960068 | |||
| 66fa3f0096 | |||
| cbf7b0a77a | |||
| 91f2f9438f | |||
| fcbb6e60ac | |||
| f33d613f9d | |||
| 849dfbed28 | |||
| 8a4bc34fad | |||
| aa18c1b7d6 | |||
| ae45a9b9a5 | |||
| 7aa75d98d3 | |||
| ff6db5f24d | |||
| de7c54b771 | |||
| 7fbf317ad2 | |||
| 0471f17d9a | |||
| bf134eb0f4 | |||
| 7ccb6ee110 | |||
| 9076043a5c | |||
| 1c0fcf7b40 | |||
| 4fdcb87a28 | |||
| 246460dec4 | |||
| a89a40e637 | |||
| c1fcf2528e | |||
| d4f0495caa | |||
| 669295d4ac | |||
| e4a27d49c9 | |||
| e0e8281a1e | |||
| 1cdf695891 | |||
| 580678c2c1 | |||
| a038ae434b | |||
| 698ef40ada | |||
| 276077964d | |||
| 45080a8a1f | |||
| dd348de0b7 | |||
| 61e510e45c | |||
| 05068754b9 | |||
| de561b78e0 | |||
| e6e1978584 | |||
| 0b7bdc0f9d | |||
| e868186897 | |||
| 5450233f8e | |||
| 8e215382f7 | |||
| 4149292a13 | |||
| 117f663f48 | |||
| 960610325f | |||
| ab50609854 | |||
| c203b78d34 | |||
| 5633445008 | |||
| 22ceb35f47 | |||
| 06a988e141 | |||
| 2dc29978f6 | |||
| 10b502512f | |||
| 2d0c44e25a | |||
| 3e52d93db0 | |||
| 07bee4acfa | |||
| 0da5d807b8 | |||
| 657aafbd2e | |||
| 04449d08e8 | |||
| 2b710ffbde | |||
| 7fbe365599 | |||
| f49b9f6781 | |||
| 901318b73b | |||
| 8545d870c0 | |||
| 3124d306d6 | |||
| 8e529f3f10 | |||
| 1ff45165d9 | |||
| 05256fc0f4 | |||
| be9ef33932 | |||
| fc424ebd6b | |||
| 030f3cf307 | |||
| c8eb933453 | |||
| 208132785f | |||
| cfa435e9c8 | |||
| 515e972906 | |||
| 100b0637ac | |||
| 2d8ec8a6bf | |||
| 02461233a6 | |||
| dcac1e69ca | |||
| b118400cc6 | |||
| bc430b80f6 | |||
| d2d3addf10 | |||
| 683e316088 | |||
| e279fee518 | |||
| 93c5906ad7 | |||
| 6a06607f3f | |||
| 9f73bd43b4 | |||
| 5f7d92aeb0 | |||
| c754d15da8 | |||
| 13f5d2d5de | |||
| 280eac6127 | |||
| ea0e70ef0d | |||
| a5af8807ee | |||
| 0bae861b6a | |||
| b32bbb89a1 | |||
| 109149a0be | |||
| 4bf4622498 | |||
| a4c054ef62 | |||
| 1141fd5784 | |||
| 0aa8a19b6d | |||
| b46f1647d2 | |||
| 72d9bc9256 | |||
| b67d56ada1 | |||
| d013d6ed09 | |||
| 4bb549f3e9 | |||
| d772299871 | |||
| 081ca81a7e | |||
| 2ad2a102cb | |||
| 52e8ac386e | |||
| f69de3324a | |||
| 04b3b9b60e | |||
| f64e1dbd7f | |||
| 341451f59b | |||
| 80b8da629d | |||
| 78e5016619 | |||
| 3406c23f98 | |||
| ee308d294f | |||
| 955a9af461 | |||
| 34e8d9563d | |||
| e5b5403eaa | |||
| 47cf2ea29e | |||
| 9d8074f1fe | |||
| 3fd944a956 | |||
| 566512f7bc | |||
| 1a84967b1b | |||
| 41afb26d31 | |||
| 818e8010a4 | |||
| 58ee049a8f | |||
| 54fa45850a | |||
| fbadcc017b | |||
| 6337698494 | |||
| 2e6a70b338 | |||
| e70f675f20 | |||
| 13d0b27ef4 | |||
| 40b112cd48 | |||
| 14c0400f73 | |||
| 4733c82b82 | |||
| 7bccf10fec | |||
| 918915aeae | |||
| 295568453c | |||
| 56ff0e7472 | |||
| e779a57a09 | |||
| 6d817703aa | |||
| c551275db2 | |||
| 9ffe92eae5 | |||
| f19e334cd5 | |||
| a5b8728aec | |||
| a8c99cebaf | |||
| 9ae2c6352a | |||
| 0bc849be5a | |||
| a99df36351 | |||
| 525e6debd5 | |||
| d6cb94c6f1 | |||
| c437ba8f9d | |||
| aee7fbcc79 | |||
| 456adf453d | |||
| d2e9194636 | |||
| 9907651d4a | |||
| bb2f59e2f8 | |||
| fec295d021 | |||
| cb08c19942 | |||
| b1b121ebc8 | |||
| ba9c2a3280 | |||
| 575de12d79 | |||
| 503f0399ff | |||
| 871ad1ab7f | |||
| 58859ef8c3 | |||
| 52062bc7e8 | |||
| 8fdbe30dc5 | |||
| f438c48c23 | |||
| 6478601f79 | |||
| 816059eefd | |||
| 3daf4203d1 | |||
| c1b53859ee | |||
| 63f85beb7e | |||
| bdc6fcb66f | |||
| 4577b7bf60 | |||
| 33f4461a87 | |||
| c78e7b3ba8 | |||
| 699ccddda3 | |||
| a9a48bdd1d | |||
| 8ed395a29f | |||
| 63bfe761bf | |||
| f8c11e3af7 | |||
| 993ae71968 | |||
| 81b8f7e39b | |||
| b1e897197b | |||
| 46cf1362f6 | |||
| f03992dcde | |||
| aedc1751b6 | |||
| 26cbd82da1 | |||
| 516503b9ca | |||
| 23be5858b5 | |||
| 776842a064 | |||
| bb090bd9a0 | |||
| 0abe9f0559 | |||
| 07bfe296dd | |||
| d666a687dc | |||
| acde4144bc | |||
| b41e90ead6 | |||
| 68fd0c0cf5 | |||
| 199be3363f | |||
| dd3fc79fb9 | |||
| d384b3d7df | |||
| 04209c78b9 | |||
| a7719cc4b1 | |||
| 3d0d2ec8fb | |||
| 1fa1a9e3d3 | |||
| 464b0c2ebc | |||
| 179a970974 | |||
| 7fb77a794e | |||
| 1d74306bc2 | |||
| 0c6a3eafd2 | |||
| 292cbf55ad | |||
| e17ae4ef61 | |||
| dba6a8d0b4 | |||
| 13725411c9 | |||
| b0e9f389a5 | |||
| f6128febe9 | |||
| fc2e12bff6 | |||
| e455f87305 | |||
| 84fc198bbe | |||
| d39e97f6a7 | |||
| 96d509698b | |||
| cefa66cdf9 | |||
| 8198df3fa6 | |||
| 4ebdb5e339 | |||
| 8dca84dd07 | |||
| 4cee76ddc3 |
@@ -28,7 +28,7 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
git cmake gcc g++ libboost-all-dev python3-all-dev swig libpcre3-dev libxml2-dev \
|
||||
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
-
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
python3-all-dev python3-pip \
|
||||
swig libpcre3-dev libxml2-dev \
|
||||
libtbb-dev nlohmann-json3-dev \
|
||||
liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev libcgal-dev
|
||||
|
||||
- name: ccache
|
||||
@@ -53,11 +53,10 @@ jobs:
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt-get install git cmake gcc g++ libboost1.67-all-dev python3-all-dev swig libpcre3-dev libxml2-dev
|
||||
sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev \
|
||||
sudo apt-get install libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev libocct-data-exchange-dev \
|
||||
libhdf5-dev
|
||||
|
||||
- name: Build
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
-DBUILD_PACKAGE=On \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/oce \
|
||||
-DOCC_INCLUDE_DIR=/usr/include/opencascade \
|
||||
-DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 \
|
||||
-DPYTHON_INCLUDE_DIR:PATH=/usr/include/python3.8 \
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
IfcOpenShell
|
||||
============
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with the Industry Foundation Classes ([IFC])
|
||||
file format. Currently supported IFC releases are [IFC2x3 TC1] and [IFC4 Add2 TC1].
|
||||
file format. Extensive geometric support is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1].
|
||||
Support for parsing is provided for IFC4x1, IFC4x2, and the IFC4x3 release candidates. Extending with support for
|
||||
arbitrary IFC schemas is possible at compile-time when using C++ and at run-time when using Python.
|
||||
|
||||
For more information, see
|
||||
* [http://ifcopenshell.org](http://ifcopenshell.org)
|
||||
@@ -13,7 +15,7 @@ Prerequisites
|
||||
-------------
|
||||
* Git
|
||||
* CMake (2.6 or newer)
|
||||
* Windows: [Visual Studio] 2008 or newer with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* Windows: [Visual Studio] 2008 to 2019 (2022 not yet supported by dependency CMake) with C++ toolset (or [Visual C++ Build Tools]) or [MSYS2] + MinGW
|
||||
* *nix: GCC 4.7 or newer, or Clang (any version)
|
||||
|
||||
Dependencies
|
||||
@@ -33,7 +35,9 @@ Dependencies
|
||||
Building IfcOpenShell
|
||||
---------------------
|
||||
|
||||
**Note:** The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
|
||||
Note 1: The path where the source code is cloned to can contain spaces but non-ASCII characters are very likely to cause problems with the build.
|
||||
|
||||
Note 2: If you had not used `git clone --recursive https://github.com/IfcOpenShell/IfcOpenshell.git`, update the submodules by running `git submodule init & git submodule update`.
|
||||
|
||||
### Compiling on Windows
|
||||
The preferred way to fetch and build this project's dependencies is to use the build scripts
|
||||
@@ -81,17 +85,26 @@ The following instructions are for Ubuntu, modify as required for other operatin
|
||||
can be experimented with and studied for pointers for other operating systems, but note that this script is not currently
|
||||
meant to be used for a typical IfcOpenShell workspace setup.
|
||||
|
||||
Note: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
|
||||
Note 1: It is recommeded to use OCCT for IfcOpenShell. You could use OCE as well, but sometimes it may lag behind OCCT and
|
||||
therefore not compile with the latest IfcOpenShell.
|
||||
|
||||
Note 2: where `make -j` is written, add a number roughly equal to the amount of CPU cores + 1.
|
||||
|
||||
**1)** Install most of the prerequisites and dependencies:
|
||||
|
||||
$ sudo apt-get install git cmake gcc g++ libboost-all-dev
|
||||
$ sudo apt-get install git cmake gcc g++ libboost-all-dev libcgal-dev
|
||||
|
||||
**2a)** Either use an OCE package from your operating system's software repository
|
||||
**2a)** Either use an OCCT package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install libocct-data-exchange-dev libocct-draw-dev libocct-foundation-dev libocct-modeling-algorithms-dev libocct-modeling-data-dev libocct-ocaf-dev libocct-visualization-dev
|
||||
|
||||
**2b)** or, if OCCT is not available or the latest code is wanted, obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**2c)** or, if you'd like to try using OCE, use the package from your operating system's software repository
|
||||
|
||||
$ sudo apt-get install liboce-foundation-dev liboce-modeling-dev liboce-ocaf-dev liboce-visualization-dev liboce-ocaf-lite-dev
|
||||
|
||||
**2b)** or (if not available, or the latest code is wanted) compile OCE yourself (note that the build takes a long time):
|
||||
**2d)** or if OCE is not available, or the latest code is wanted, compile OCE yourself (note that the build takes a long time)
|
||||
|
||||
$ sudo apt-get install libftgl-dev libtbb2 libtbb-dev libgl1-mesa-dev libfreetype6-dev
|
||||
$ git clone https://github.com/tpaviot/oce.git
|
||||
@@ -101,8 +114,6 @@ Note: where `make -j` is written, add a number roughly equal to the amount of CP
|
||||
$ make -j
|
||||
$ sudo make install
|
||||
|
||||
**2c)** or obtain and compile OCCT from https://dev.opencascade.org/release
|
||||
|
||||
**3)** For building IfcConvert with COLLADA (.dae) support (on by default), OpenCOLLADA is needed:
|
||||
|
||||
$ sudo apt-get install libpcre3-dev libxml2-dev
|
||||
@@ -128,7 +139,13 @@ dependencies' paths. `OCC_INCLUDE_DIR` might be needed to set also. `OPENCOLLADA
|
||||
$ cmake ../cmake -DOCC_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DOPENCOLLADA_INCLUDE_DIR="/usr/local/include/opencollada" \
|
||||
-DOPENCOLLADA_LIBRARY_DIR="/usr/local/lib/opencollada" \
|
||||
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/
|
||||
-DPCRE_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu/ \
|
||||
-DCGAL_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_INCLUDE_DIR=/usr/include \
|
||||
-DMPFR_INCLUDE_DIR=/usr/include \
|
||||
-DGMP_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DMPFR_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu \
|
||||
-DHDF5_SUPPORT=Off
|
||||
$ make -j
|
||||
|
||||
If all worked out correctly you can now use IfcOpenShell. See the examples below.
|
||||
|
||||
+23
-14
@@ -60,6 +60,7 @@ if (${BUILD_CONVERT})
|
||||
OPTION(GLTF_SUPPORT "Build IfcConvert with glTF support (requires json.hpp)." OFF)
|
||||
endif()
|
||||
OPTION(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
OPTION(ADD_COMMIT_SHA "Add commit sha and branch in version number, warning results in many rebuilds, requires git" OFF)
|
||||
|
||||
# TODO QtViewer is deprecated ATM as it uses the 0.4 API
|
||||
# OPTION(BUILD_QTVIEWER "Build IfcOpenShell Qt GUI Viewer (requires Qt 4 framework)." OFF)
|
||||
@@ -144,6 +145,7 @@ ELSE()
|
||||
MESSAGE(FATAL_ERROR "Unable to find JSON for Modern C++ header file, aborting")
|
||||
ENDIF()
|
||||
add_definitions(-DWITH_GLTF)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_GLTF)
|
||||
endif()
|
||||
|
||||
# Set INSTALL_RPATH for target
|
||||
@@ -200,6 +202,7 @@ find_package(LibXml2 REQUIRED)
|
||||
|
||||
if (IFCXML_SUPPORT)
|
||||
add_definitions(-DWITH_IFCXML)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_IFCXML)
|
||||
endif()
|
||||
|
||||
FIND_PACKAGE(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
@@ -270,6 +273,9 @@ ENDIF()
|
||||
SET(OPENCASCADE_LIBRARY_NAMES
|
||||
TKernel TKMath TKBRep TKGeomBase TKGeomAlgo TKG3d TKG2d TKShHealing TKTopAlgo TKMesh TKPrim TKBool TKBO
|
||||
TKFillet TKSTEP TKSTEPBase TKSTEPAttr TKXSBase TKSTEP209 TKIGES TKOffset TKHLR
|
||||
|
||||
# @todo investigate the exact conditions when this is necessary
|
||||
TKBin
|
||||
)
|
||||
|
||||
IF("${OCC_LIBRARY_DIR}" STREQUAL "")
|
||||
@@ -368,6 +374,8 @@ IF(COLLADA_SUPPORT)
|
||||
IF(COLLADASWStreamWriter_h)
|
||||
MESSAGE(STATUS "OpenCOLLADA header files found")
|
||||
ADD_DEFINITIONS(-DWITH_OPENCOLLADA)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_OPENCOLLADA)
|
||||
|
||||
SET(OPENCOLLADA_LIBRARY_NAMES
|
||||
GeneratedSaxParser MathMLSolver OpenCOLLADABaseUtils OpenCOLLADAFramework OpenCOLLADASaxFrameworkLoader
|
||||
OpenCOLLADAStreamWriter UTF buffer ftoa
|
||||
@@ -450,15 +458,16 @@ if(HDF5_SUPPORT)
|
||||
# debian default
|
||||
|
||||
SET(HDF5_LIBRARIES
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.a
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.a
|
||||
/usr/lib/x86_64-linux-gnu/libsz.a
|
||||
/usr/lib/x86_64-linux-gnu/libaec.a
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5_cpp.so
|
||||
/usr/lib/x86_64-linux-gnu/hdf5/serial/libhdf5.so
|
||||
/usr/lib/x86_64-linux-gnu/libsz.so
|
||||
/usr/lib/x86_64-linux-gnu/libaec.so
|
||||
z dl
|
||||
)
|
||||
endif()
|
||||
|
||||
add_definitions(-DWITH_HDF5)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
endif()
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
@@ -689,7 +698,7 @@ TARGET_LINK_LIBRARIES(IfcParse ${Boost_LIBRARIES} ${BCRYPT_LIBRARIES} ${LIBXML2_
|
||||
if (BUILD_IFCGEOM)
|
||||
|
||||
# IfcGeom
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h)
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/*.h ../src/ifcgeom/*.i)
|
||||
file(GLOB IFCGEOM_CPP_FILES ../src/ifcgeom/*.cpp)
|
||||
set(IFCGEOM_FILES ${IFCGEOM_CPP_FILES} ${IFCGEOM_H_FILES})
|
||||
|
||||
@@ -781,21 +790,20 @@ INSTALL(TARGETS IfcGeomServer
|
||||
|
||||
endif()
|
||||
|
||||
if (ADD_COMMIT_SHA)
|
||||
find_package (Git)
|
||||
if (GIT_FOUND)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} show -s --pretty=%D HEAD
|
||||
COMMAND ${GIT_EXECUTABLE} branch --contains HEAD
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
string(REPLACE ", " ";" git_branch_list "${git_branches}")
|
||||
string(REPLACE " -> " ";" git_branch_list "${git_branch_list}")
|
||||
string(REPLACE "\n" ";" git_branch_list "${git_branches}")
|
||||
foreach(git_branch_candidate IN ITEMS ${git_branch_list})
|
||||
if (NOT git_branch_candidate MATCHES "/")
|
||||
if (NOT git_branch_candidate MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate})
|
||||
endif()
|
||||
string(STRIP "${git_branch_candidate}" git_branch_candidate_2)
|
||||
if (NOT git_branch_candidate_2 MATCHES "^HEAD$")
|
||||
set(git_branch ${git_branch_candidate_2})
|
||||
endif()
|
||||
endforeach()
|
||||
execute_process(
|
||||
@@ -803,11 +811,12 @@ if (GIT_FOUND)
|
||||
OUTPUT_VARIABLE git_sha
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
message(STATUS "IfcOpenShell branch ${git_branch}")
|
||||
message(STATUS "IfcOpenShell commit ${git_sha}")
|
||||
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
|
||||
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Documentation
|
||||
IF(BUILD_DOCUMENTATION)
|
||||
|
||||
+273
-294
@@ -56,16 +56,10 @@ import subprocess as sp
|
||||
import shutil
|
||||
import tarfile
|
||||
import multiprocessing
|
||||
import platform
|
||||
import sysconfig
|
||||
|
||||
PYTHON_MAJOR = sys.version_info[0]
|
||||
|
||||
if PYTHON_MAJOR >= 3:
|
||||
from urllib.request import urlretrieve
|
||||
else:
|
||||
# Not Python 3 - today, it is most likely to be Python 2
|
||||
# But note that this might need an update when Python 4
|
||||
# might be around one day
|
||||
from urllib import urlretrieve
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -74,145 +68,120 @@ ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.INFO)
|
||||
logger.addHandler(ch)
|
||||
|
||||
PROJECT_NAME="IfcOpenShell"
|
||||
PYTHON_VERSIONS=["2.7.18", "3.5.5", "3.6.13", "3.7.10", "3.8.8", "3.9.2"]
|
||||
JSON_VERSION="v3.6.1"
|
||||
OCE_VERSION="0.18"
|
||||
OCCT_VERSION="7.5.3"
|
||||
BOOST_VERSION="1.71.0"
|
||||
PCRE_VERSION="8.41"
|
||||
LIBXML2_VERSION="2.9.9"
|
||||
SWIG_VERSION="3.0.12"
|
||||
OPENCOLLADA_VERSION="v1.6.68"
|
||||
HDF5_VERSION="1.8.22"
|
||||
PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
|
||||
GMP_VERSION="6.1.2"
|
||||
MPFR_VERSION="3.1.5"
|
||||
CGAL_VERSION="5.2"
|
||||
PYTHON_VERSIONS = ["3.6.14", "3.7.12", "3.8.12", "3.9.7", "3.10.0"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.5.3"
|
||||
BOOST_VERSION = "1.71.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
OPENCOLLADA_VERSION = "v1.6.68"
|
||||
HDF5_VERSION = "1.12.1"
|
||||
|
||||
GMP_VERSION = "6.1.2"
|
||||
MPFR_VERSION = "3.1.5" # latest is 4.1.0
|
||||
CGAL_VERSION = "5.3"
|
||||
|
||||
# binaries
|
||||
cp="cp"
|
||||
bash="bash"
|
||||
uname="uname"
|
||||
git="git"
|
||||
bunzip2="bunzip2"
|
||||
tar="tar"
|
||||
cc="cc"
|
||||
cplusplus="c++"
|
||||
autoconf="autoconf"
|
||||
automake="automake"
|
||||
yacc="yacc"
|
||||
make="make"
|
||||
cp = "cp"
|
||||
bash = "bash"
|
||||
git = "git"
|
||||
bunzip2 = "bunzip2"
|
||||
tar = "tar"
|
||||
cc = "cc"
|
||||
cplusplus = "c++"
|
||||
autoconf = "autoconf"
|
||||
automake = "automake"
|
||||
yacc = "yacc"
|
||||
make = "make"
|
||||
date = "date"
|
||||
curl="curl"
|
||||
wget="wget"
|
||||
strip="strip"
|
||||
curl = "curl"
|
||||
wget = "wget"
|
||||
strip = "strip"
|
||||
|
||||
# Helper function for coloured printing
|
||||
|
||||
NO_COLOR="\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
|
||||
BLACK_ON_WHITE="\033[0;30;107m"
|
||||
RED="\033[31m"
|
||||
GREEN="\033[32m"
|
||||
YELLOW="\033[33m"
|
||||
MAGENTA="\033[35m"
|
||||
NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
|
||||
BLACK_ON_WHITE = "\033[0;30;107m"
|
||||
RED = "\033[31m"
|
||||
GREEN = "\033[32m"
|
||||
YELLOW = "\033[33m"
|
||||
MAGENTA = "\033[35m"
|
||||
|
||||
def cecho(message, color=NO_COLOR):
|
||||
"""Logs message `message` in color `color`."""
|
||||
logger.info("%s%s\033[0m" % (color, message))
|
||||
logger.info(f"{color}{message}\033[0m")
|
||||
|
||||
def which(cmd):
|
||||
for path in os.environ["PATH"].split(":"):
|
||||
for path in os.getenv("PATH").split(":"):
|
||||
if os.path.exists(path) and cmd in os.listdir(path):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
def get_os():
|
||||
ret_value = sp.check_output([uname, "-s"]).strip()
|
||||
return ret_value
|
||||
|
||||
def to_pystring(x):
|
||||
""" Python 2 & 3 compatibility function for strings handling
|
||||
(to solve TypeError "Can't mix strings and bytes in path components" for Python 3).
|
||||
Reference https://github.com/hugsy/gef/issues/382 """
|
||||
res = str(x, encoding="utf-8") if PYTHON_MAJOR == 3 else x
|
||||
substs = [("\n","\\n"), ("\r","\\r"), ("\t","\\t"), ("\v","\\v"), ("\b","\\b"), ]
|
||||
for x,y in substs: res = res.replace(x,y)
|
||||
return res
|
||||
|
||||
# Set defaults for missing empty environment variables
|
||||
|
||||
USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"
|
||||
|
||||
TOOLSET = None
|
||||
if get_os() == "Darwin":
|
||||
if platform.system() == "Darwin":
|
||||
# C++11 features used in OCCT 7+ need a more recent stdlib
|
||||
TOOLSET = "10.9" if USE_OCCT else "10.6"
|
||||
|
||||
try:
|
||||
IFCOS_NUM_BUILD_PROCS = os.environ["IFCOS_NUM_BUILD_PROCS"]
|
||||
except KeyError:
|
||||
IFCOS_NUM_BUILD_PROCS=multiprocessing.cpu_count() + 1
|
||||
|
||||
try:
|
||||
TARGET_ARCH = os.environ["TARGET_ARCH"]
|
||||
except KeyError:
|
||||
TARGET_ARCH = sp.check_output([uname, "-m"]).strip()
|
||||
IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)
|
||||
|
||||
CMAKE_DIR=os.path.realpath(os.path.join("..", "cmake"))
|
||||
CMAKE_DIR = os.path.realpath(os.path.join("..", "cmake"))
|
||||
|
||||
try:
|
||||
DEPS_DIR = os.environ["DEPS_DIR"]
|
||||
except KeyError:
|
||||
path = [b"..", b"build", sp.check_output(uname).strip(), TARGET_ARCH]
|
||||
if TOOLSET:
|
||||
path.append(TOOLSET)
|
||||
|
||||
DEPS_DIR = to_pystring(os.path.realpath(os.path.join(*path)))
|
||||
path = ["..", "build", platform.system(), platform.machine()]
|
||||
if TOOLSET:
|
||||
path.append(TOOLSET)
|
||||
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))
|
||||
|
||||
DEPS_DIR = os.getenv("DEPS_DIR", DEFAULT_DEPS_DIR)
|
||||
|
||||
if not os.path.exists(DEPS_DIR):
|
||||
os.makedirs(DEPS_DIR)
|
||||
|
||||
try:
|
||||
BUILD_CFG=os.environ["BUILD_CFG"]
|
||||
except KeyError:
|
||||
BUILD_CFG="RelWithDebInfo"
|
||||
BUILD_CFG = os.getenv("BUILD_CFG", "RelWithDebInfo")
|
||||
|
||||
|
||||
# Print build configuration information
|
||||
|
||||
cecho ("""This script fetches and builds %s and its dependencies
|
||||
""" % (PROJECT_NAME,), BLACK_ON_WHITE)
|
||||
cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
|
||||
""", BLACK_ON_WHITE)
|
||||
cecho("""Script configuration:
|
||||
|
||||
""", GREEN)
|
||||
cecho("""* Target Architecture = %s""" % (TARGET_ARCH,), MAGENTA)
|
||||
cecho(" - Whether 32-bit (i686) or 64-bit (x86_64) will be built.")
|
||||
cecho("""* USE_OCCT = %r""" % (USE_OCCT,), MAGENTA)
|
||||
cecho(f"""* USE_OCCT = {USE_OCCT}""", MAGENTA)
|
||||
if USE_OCCT:
|
||||
cecho(" - Compiling against official Open Cascade")
|
||||
else:
|
||||
cecho(" - Compiling against Open Cascade Community Edition")
|
||||
cecho("* Dependency Directory = %s" % (DEPS_DIR,), MAGENTA)
|
||||
cecho(" - The directory where %s dependencies are installed." % (PROJECT_NAME,))
|
||||
cecho("* Build Config Type = %s" % (BUILD_CFG,), MAGENTA)
|
||||
cecho(f"* Dependency Directory = {DEPS_DIR}", MAGENTA)
|
||||
cecho(f" - The directory where {PROJECT_NAME} dependencies are installed.")
|
||||
cecho(f"* Build Config Type = {BUILD_CFG}", MAGENTA)
|
||||
cecho(""" - The used build configuration type for the dependencies.
|
||||
Defaults to RelWithDebInfo if not specified.""")
|
||||
|
||||
if BUILD_CFG == "MinSizeRel":
|
||||
cecho(" WARNING: MinSizeRel build can suffer from a significant performance loss.", RED)
|
||||
|
||||
cecho("* IFCOS_NUM_BUILD_PROCS = %s" % (IFCOS_NUM_BUILD_PROCS,), MAGENTA)
|
||||
cecho(f"* IFCOS_NUM_BUILD_PROCS = {IFCOS_NUM_BUILD_PROCS}", MAGENTA)
|
||||
cecho(""" - How many compiler processes may be run in parallel.
|
||||
""")
|
||||
|
||||
dependency_tree = {
|
||||
'IfcParse': ('boost', 'libxml2', 'hdf5'),
|
||||
'IfcGeom': ('IfcParse', 'occ', 'OpenCOLLADA', 'json', 'cgal'),
|
||||
'IfcConvert': ('IfcGeom', ),
|
||||
'OpenCOLLADA': ('libxml2', 'pcre'),
|
||||
'IfcParse': ('boost', 'libxml2', 'hdf5'),
|
||||
'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal'),
|
||||
'IfcConvert': ('IfcGeom',),
|
||||
'OpenCOLLADA': ('libxml2', 'pcre'),
|
||||
'IfcGeomServer': ('IfcGeom',),
|
||||
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
|
||||
'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
|
||||
'swig': ('pcre',),
|
||||
'boost': (),
|
||||
'libxml2': (),
|
||||
@@ -252,7 +221,7 @@ print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))
|
||||
|
||||
for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, yacc, make, "patch", "cmake"]:
|
||||
if which(cmd) is None:
|
||||
raise ValueError("Required tool '%s' not installed or not added to PATH" % (cmd,))
|
||||
raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")
|
||||
|
||||
# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
|
||||
download_tool_default = download_tool_py = "py"
|
||||
@@ -263,10 +232,10 @@ download_tool_git = "git"
|
||||
log_dir = os.path.join(DEPS_DIR, "logs")
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
LOG_FILE="%s.log" % (os.path.join(log_dir, to_pystring(sp.check_output([date, "+%Y%m%d"]).strip())),)
|
||||
LOG_FILE = os.path.join(log_dir, sp.check_output([date, "+%Y%m%d"], encoding="utf-8").strip()) + ".log"
|
||||
if not os.path.exists(LOG_FILE):
|
||||
open(LOG_FILE, "w").close()
|
||||
logger.info("using command log file '%s'" % (LOG_FILE,))
|
||||
logger.info(f"using command log file '{LOG_FILE}'")
|
||||
|
||||
def run(cmds, cwd=None):
|
||||
|
||||
@@ -276,43 +245,47 @@ def run(cmds, cwd=None):
|
||||
with leading and trailing whitespace removed.
|
||||
"""
|
||||
|
||||
logger.debug("running command %r in directory %r" % (" ".join(cmds), cwd))
|
||||
log_file_handle = open(LOG_FILE, "ab")
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE)
|
||||
logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
|
||||
log_file_handle = open(LOG_FILE, "a")
|
||||
proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
|
||||
stdout, stderr = proc.communicate()
|
||||
log_file_handle.write(stdout)
|
||||
log_file_handle.write(stderr)
|
||||
log_file_handle.close()
|
||||
logger.debug("command returned %r" % proc.returncode)
|
||||
logger.debug(f"command returned {proc.returncode}")
|
||||
|
||||
if proc.returncode != 0:
|
||||
print("-" * 70)
|
||||
print(stderr)
|
||||
print("-" * 70)
|
||||
raise Exception("Command `%s` returned exit code %d" % (" ".join(cmds), proc.returncode))
|
||||
raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")
|
||||
|
||||
return stdout.strip()
|
||||
|
||||
BOOST_VERSION_UNDERSCORE=BOOST_VERSION.replace(".", "_")
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
OCE_LOCATION="https://github.com/tpaviot/oce/archive/OCE-%s.tar.gz" % (OCE_VERSION,)
|
||||
BOOST_LOCATION="https://boostorg.jfrog.io/artifactory/main/release/%s/source/" % (BOOST_VERSION,)
|
||||
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
|
||||
BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
|
||||
|
||||
# Helper functions
|
||||
|
||||
|
||||
def run_autoconf(arg1, configure_args, cwd):
|
||||
configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
|
||||
if not os.path.exists(configure_path):
|
||||
run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
|
||||
# Using `sh` over `bash` fixes issues with building swig
|
||||
run(["/bin/sh", "../configure"]+configure_args+["--prefix=%s" % (os.path.realpath("%s/install/%s" % (DEPS_DIR, arg1)),)], cwd=cwd)
|
||||
prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")
|
||||
run(["/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
if cmake_dir is None:
|
||||
P=".."
|
||||
P = ".."
|
||||
else:
|
||||
P=cmake_dir
|
||||
run(["cmake", P]+cmake_args+["-DCMAKE_BUILD_TYPE=%s" % (BUILD_CFG,)], cwd=cwd)
|
||||
P = cmake_dir
|
||||
run(["cmake", P] + cmake_args + [f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
"""Lazily clones the `git` repository denoted by `clone_url` into
|
||||
@@ -321,10 +294,10 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
`revision` after cloning or in the existing clone if `revision` is not
|
||||
`None`."""
|
||||
if not os.path.exists(target_dir):
|
||||
logger.info("cloning '%s' into '%s'" % (clone_url, target_dir))
|
||||
logger.info(f"cloning '{clone_url}' into '{target_dir}'")
|
||||
run([git, "clone", "--recursive", clone_url, target_dir])
|
||||
else:
|
||||
logger.info("directory '%s' already cloned. Pulling latest changes." % (target_dir,))
|
||||
logger.info(f"directory '{target_dir}' already cloned. Pulling latest changes.")
|
||||
|
||||
# detect whether we are on a branch and pull
|
||||
if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
|
||||
@@ -341,13 +314,13 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
linker flags."""
|
||||
check_dir = os.path.join(DEPS_DIR, "install", name)
|
||||
if os.path.exists(check_dir):
|
||||
logger.info( "Found existing %s, skipping" % (name,))
|
||||
logger.info(f"Found existing {name}, skipping")
|
||||
return
|
||||
build_dir = os.path.join(DEPS_DIR, "build")
|
||||
if not os.path.exists(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
logger.info("\rFetching %s... " % (name,))
|
||||
logger.info(f"\rFetching {name}... ")
|
||||
|
||||
if download_tool == download_tool_py:
|
||||
if no_append_name:
|
||||
@@ -359,16 +332,16 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
if not os.path.exists(download_path):
|
||||
urlretrieve(url, os.path.join(build_dir, download_path))
|
||||
else:
|
||||
logger.info("Download '%s' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping" % (download_path,))
|
||||
logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
|
||||
elif download_tool == download_tool_git:
|
||||
logger.info("\rChecking %s... " % (name,))
|
||||
logger.info(f"\rChecking {name}... ")
|
||||
git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
|
||||
else:
|
||||
raise ValueError("download tool '%s' is not supported" % (download_tool,))
|
||||
raise ValueError(f"download tool '{download_tool}' is not supported")
|
||||
download_dir = os.path.join(build_dir, download_name)
|
||||
|
||||
if os.path.isdir(download_dir):
|
||||
extract_dir_name=download_name
|
||||
extract_dir_name = download_name
|
||||
extract_dir = os.path.join(build_dir, extract_dir_name)
|
||||
else:
|
||||
download_tarfile_path = os.path.join(build_dir, download_name)
|
||||
@@ -378,12 +351,12 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
compr = "bz2"
|
||||
else:
|
||||
raise RuntimeError("fix source for new download type")
|
||||
download_tarfile = tarfile.open(name=download_tarfile_path, mode="r:%s" % (compr,))
|
||||
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
|
||||
# tarfile seriously doesn't have a function to retrieve the root directory more easily
|
||||
extract_dir_name= os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
|
||||
extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
|
||||
#run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
|
||||
if extract_dir_name is None:
|
||||
extract_dir_name= run([bash, "-c", "tar -tf %s 2> /dev/null | head -n 1 | cut -f1 -d /" % (download_name,)], cwd=build_dir)
|
||||
extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
|
||||
extract_dir = os.path.join(build_dir, extract_dir_name)
|
||||
if not os.path.exists(extract_dir):
|
||||
run([tar, "-xf", download_name], cwd=build_dir)
|
||||
@@ -413,71 +386,66 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
shutil.rmtree(extract_build_dir)
|
||||
os.makedirs(extract_build_dir)
|
||||
|
||||
logger.info("\rConfiguring %s..." % (name,))
|
||||
logger.info(f"\rConfiguring {name}...")
|
||||
if mode == "autoconf":
|
||||
run_autoconf(name, build_tool_args, cwd=extract_build_dir)
|
||||
elif mode == "cmake":
|
||||
run_cmake(name, build_tool_args, cwd=extract_build_dir)
|
||||
else:
|
||||
raise ValueError()
|
||||
logger.info("\rBuilding %s... " % (name,))
|
||||
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "VERBOSE=1"], cwd=extract_build_dir)
|
||||
logger.info( "\rInstalling %s... " % (name,))
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
run([make, "install"], cwd=extract_build_dir)
|
||||
logger.info( "\rInstalled %s \n" % (name,))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
else:
|
||||
logger.info( "\rConfiguring %s..." % (name,))
|
||||
logger.info(f"\rConfiguring {name}...")
|
||||
run([bash, "./bootstrap.sh"], cwd=extract_dir)
|
||||
logger.info("\rBuilding %s... " % (name,))
|
||||
run(["./b2", "-j%s" % (IFCOS_NUM_BUILD_PROCS,)]+build_tool_args, cwd=extract_dir)
|
||||
logger.info("\rInstalling %s... " % (name,))
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", "boost-%s" % BOOST_VERSION, "boost"))
|
||||
logger.info("\rInstalled %s \n" % (name,))
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
|
||||
cecho("Collecting dependencies:", GREEN)
|
||||
|
||||
# Set compiler flags for 32bit builds on 64bit system
|
||||
# TODO: This is untested
|
||||
|
||||
ADDITIONAL_ARGS=[]
|
||||
BOOST_ADDRESS_MODEL=[]
|
||||
if TARGET_ARCH == "i686" and run([uname, "-m"]).strip() == "x86_64":
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS=["-m32", "-arch i386"]
|
||||
else:
|
||||
ADDITIONAL_ARGS=["-m32"]
|
||||
BOOST_ADDRESS_MODEL=["architecture=x86", "address-model=32"]
|
||||
ADDITIONAL_ARGS = []
|
||||
BOOST_ADDRESS_MODEL = []
|
||||
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS=["-mmacosx-version-min=%s" % TOOLSET]+ADDITIONAL_ARGS
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
|
||||
|
||||
# If the linker supports GC sections, set it up to reduce binary file size
|
||||
# -fPIC is required for the shared libraries to work
|
||||
|
||||
CXXFLAGS=os.environ.get("CXXFLAGS", "")
|
||||
CFLAGS=os.environ.get("CFLAGS", "")
|
||||
LDFLAGS=os.environ.get("LDFLAGS", "")
|
||||
CXXFLAGS = os.environ.get("CXXFLAGS", "")
|
||||
CFLAGS = os.environ.get("CFLAGS", "")
|
||||
LDFLAGS = os.environ.get("LDFLAGS", "")
|
||||
|
||||
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
|
||||
if sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
CXXFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS="%s %s -fdata-sections -ffunction-sections -fvisibility=hidden %s"% (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = f"{CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS = f"{CFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS=CXXFLAGS_MINIMAL
|
||||
CFLAGS=CFLAGS_MINIMAL
|
||||
LDFLAGS="%s -Wl,--gc-sections %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = CXXFLAGS_MINIMAL
|
||||
CFLAGS = CFLAGS_MINIMAL
|
||||
LDFLAGS = f"{LDFLAGS} -Wl,--gc-sections {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS_MINIMAL="%s %s %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS_MINIMAL="%s %s %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
CXXFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CXXFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CFLAGS="%s %s -fvisibility=hidden -fvisibility-inlines-hidden %s" % (CFLAGS, PIC, str.join(" ", ADDITIONAL_ARGS))
|
||||
CXXFLAGS = f"{CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS = f"{CFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
|
||||
else:
|
||||
CXXFLAGS=CXXFLAGS_MINIMAL
|
||||
CFLAGS=CFLAGS_MINIMAL
|
||||
LDFLAGS="%s %s" % (LDFLAGS, str.join(" ", ADDITIONAL_ARGS))
|
||||
LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"
|
||||
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS
|
||||
os.environ["CFLAGS"] = CFLAGS
|
||||
@@ -488,21 +456,20 @@ os.environ["LDFLAGS"] = LDFLAGS
|
||||
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))
|
||||
|
||||
if 'hdf5' in targets:
|
||||
HDF5_MAJOR=".".join(HDF5_VERSION.split(".")[:-1])
|
||||
get_os_result = get_os()
|
||||
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
|
||||
build_dependency(
|
||||
name="hdf5-{HDF5_VERSION}".format(**locals()),
|
||||
name=f"hdf5-{HDF5_VERSION}",
|
||||
mode="ctest",
|
||||
build_tool_args=[],
|
||||
download_url="https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/".format(**locals()),
|
||||
download_name="CMake-hdf5-{HDF5_VERSION}.tar.gz".format(**locals()),
|
||||
ctest_result="HDF5-{HDF5_VERSION}-{get_os_result}".format(**locals()),
|
||||
ctest_result_path="HDF_Group/HDF5/{HDF5_VERSION}".format(**locals())
|
||||
download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
|
||||
download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
|
||||
ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
|
||||
ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
|
||||
)
|
||||
|
||||
if "json" in targets:
|
||||
json_url = "https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp".format(**locals())
|
||||
json_install_path = "{DEPS_DIR}/install/json/nlohmann/json.hpp".format(**locals())
|
||||
json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
|
||||
json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
|
||||
if not os.path.exists(os.path.dirname(json_install_path)):
|
||||
os.makedirs(os.path.dirname(json_install_path))
|
||||
if not os.path.exists(json_install_path):
|
||||
@@ -510,11 +477,11 @@ if "json" in targets:
|
||||
|
||||
if "pcre" in targets:
|
||||
build_dependency(
|
||||
name="pcre-{PCRE_VERSION}".format(**locals()),
|
||||
name=f"pcre-{PCRE_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=[DISABLE_FLAG],
|
||||
download_url="https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/".format(**locals()),
|
||||
download_name="pcre-{PCRE_VERSION}.tar.bz2".format(**locals())
|
||||
download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
|
||||
download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
# An issue exists with swig-1.3 and python >= 3.2
|
||||
@@ -523,20 +490,20 @@ if "swig" in targets:
|
||||
build_dependency(
|
||||
name="swig",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-ccache", "--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}".format(**locals())],
|
||||
build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
|
||||
download_url="https://github.com/swig/swig.git",
|
||||
download_name="swig",
|
||||
download_tool=download_tool_git,
|
||||
revision="rel-{SWIG_VERSION}".format(**locals())
|
||||
revision=f"rel-{SWIG_VERSION}"
|
||||
)
|
||||
|
||||
if USE_OCCT and "occ" in targets:
|
||||
build_dependency(
|
||||
name="occt-{OCCT_VERSION}".format(**locals()),
|
||||
name=f"occt-{OCCT_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}".format(**locals()),
|
||||
"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}".format(**locals()),
|
||||
f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
|
||||
f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
|
||||
"-DBUILD_MODULE_Draw=0",
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off"
|
||||
],
|
||||
@@ -548,7 +515,7 @@ if USE_OCCT and "occ" in targets:
|
||||
)
|
||||
elif "occ" in targets:
|
||||
build_dependency(
|
||||
name="oce-{OCE_VERSION}".format(**locals()),
|
||||
name=f"oce-{OCE_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DOCE_DISABLE_TKSERVICE_FONT=ON",
|
||||
@@ -557,15 +524,15 @@ elif "occ" in targets:
|
||||
"-DOCE_DISABLE_X11=ON",
|
||||
"-DOCE_VISUALISATION=OFF",
|
||||
"-DOCE_OCAF=OFF",
|
||||
"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}".format(**locals())
|
||||
f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
|
||||
],
|
||||
download_url="https://github.com/tpaviot/oce/archive/",
|
||||
download_name="OCE-{OCE_VERSION}.tar.gz".format(**locals())
|
||||
download_name=f"OCE-{OCE_VERSION}.tar.gz"
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
build_dependency(
|
||||
"libxml2-{LIBXML2_VERSION}".format(**locals()),
|
||||
f"libxml2-{LIBXML2_VERSION}",
|
||||
"autoconf",
|
||||
build_tool_args=[
|
||||
"--without-python",
|
||||
@@ -576,7 +543,7 @@ if "libxml2" in targets:
|
||||
"--without-lzma"
|
||||
],
|
||||
download_url="ftp://xmlsoft.org/libxml2/",
|
||||
download_name="libxml2-{LIBXML2_VERSION}.tar.gz".format(**locals())
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
|
||||
)
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
@@ -584,12 +551,12 @@ if "OpenCOLLADA" in targets:
|
||||
"OpenCOLLADA",
|
||||
"cmake",
|
||||
build_tool_args=[
|
||||
"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
|
||||
"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include".format(**locals()),
|
||||
"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/".format(**locals())
|
||||
f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
|
||||
f"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include",
|
||||
f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
|
||||
f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
|
||||
],
|
||||
download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
|
||||
download_name="OpenCOLLADA",
|
||||
@@ -598,158 +565,154 @@ if "OpenCOLLADA" in targets:
|
||||
revision=OPENCOLLADA_VERSION
|
||||
)
|
||||
|
||||
if "python" in targets:
|
||||
if "python" in targets and not USE_CURRENT_PYTHON_VERSION:
|
||||
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
|
||||
OLD_CXX_FLAGS=os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS=os.environ["CFLAGS"]
|
||||
os.environ["CXXFLAGS"]=CXXFLAGS_MINIMAL
|
||||
os.environ["CFLAGS"]=CFLAGS_MINIMAL
|
||||
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS = os.environ["CFLAGS"]
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
|
||||
os.environ["CFLAGS"] = CFLAGS_MINIMAL
|
||||
|
||||
# On OSX a dynamic python library is built or it would not be compatible
|
||||
# with the system python because of some threading initialization
|
||||
PYTHON_CONFIGURE_ARGS=[]
|
||||
if get_os() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS=["--disable-static", "--enable-shared"]
|
||||
PYTHON_CONFIGURE_ARGS = []
|
||||
if platform.system() == "Darwin":
|
||||
PYTHON_CONFIGURE_ARGS = ["--disable-static", "--enable-shared"]
|
||||
|
||||
def get_python_unicode_confs(py_ver):
|
||||
if py_ver < "3.3":
|
||||
return [("--enable-unicode=ucs2",""), ("--enable-unicode=ucs4","u")]
|
||||
else: return [("","")]
|
||||
|
||||
def PYTHON_VERSION_CONFS():
|
||||
for v in PYTHON_VERSIONS:
|
||||
for unicode_conf, abi_tag in get_python_unicode_confs(v):
|
||||
yield v, unicode_conf, abi_tag
|
||||
|
||||
for PYTHON_VERSION, unicode_conf, abi_tag in PYTHON_VERSION_CONFS():
|
||||
for PYTHON_VERSION in PYTHON_VERSIONS:
|
||||
try:
|
||||
build_dependency(
|
||||
"python-{PYTHON_VERSION}{abi_tag}".format(**locals()),
|
||||
f"python-{PYTHON_VERSION}",
|
||||
"autoconf",
|
||||
PYTHON_CONFIGURE_ARGS + [unicode_conf],
|
||||
"http://www.python.org/ftp/python/{PYTHON_VERSION}/".format(**locals()),
|
||||
"Python-{PYTHON_VERSION}.tgz".format(**locals())
|
||||
PYTHON_CONFIGURE_ARGS,
|
||||
f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
|
||||
f"Python-{PYTHON_VERSION}.tgz"
|
||||
)
|
||||
except Exception as e:
|
||||
pyver2 = PYTHON_VERSION[:PYTHON_VERSION.rfind('.')]
|
||||
if os.path.exists("{DEPS_DIR}/install/python-{PYTHON_VERSION}{abi_tag}/bin/python{pyver2}".format(**locals())):
|
||||
print("Installation partially failed")
|
||||
else: raise e
|
||||
except RuntimeError as e:
|
||||
# Sometimes setting up modules such as pip/lzma can cause
|
||||
# the python installer script to return a non zero exit
|
||||
# code where actually the headers and dynamic libraries
|
||||
# are installed correctly. This is all we need so we catch
|
||||
# the exception and only reraise if a partially successful
|
||||
# install is not detected.
|
||||
if not os.path.exists(
|
||||
os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
|
||||
):
|
||||
raise e
|
||||
|
||||
os.environ["CXXFLAGS"]=OLD_CXX_FLAGS
|
||||
os.environ["CFLAGS"]=OLD_C_FLAGS
|
||||
os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
|
||||
os.environ["CFLAGS"] = OLD_C_FLAGS
|
||||
|
||||
if "boost" in targets:
|
||||
str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
|
||||
build_dependency(
|
||||
"boost-{BOOST_VERSION}".format(**locals()),
|
||||
f"boost-{BOOST_VERSION}",
|
||||
mode="bjam",
|
||||
build_tool_args=[
|
||||
"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
|
||||
f"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
"--with-system",
|
||||
"--with-program_options",
|
||||
"--with-regex",
|
||||
"--with-thread",
|
||||
"--with-date_time",
|
||||
"--with-iostreams",
|
||||
"link={LINK_TYPE}".format(**locals())
|
||||
f"link={LINK_TYPE}"
|
||||
] + \
|
||||
BOOST_ADDRESS_MODEL + \
|
||||
list(map(str_concat("cxxflags"), CXXFLAGS.strip().split(' '))) + \
|
||||
list(map(str_concat("linkflags"), LDFLAGS.strip().split(' '))) + \
|
||||
["stage", "-s", "NO_BZIP2=1"],
|
||||
download_url=BOOST_LOCATION,
|
||||
download_name="boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2".format(**locals())
|
||||
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
|
||||
)
|
||||
|
||||
if "cgal" in targets:
|
||||
build_dependency(
|
||||
name="gmp-%s" % (GMP_VERSION,),
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", "--with-pic"],
|
||||
download_url="https://ftp.gnu.org/gnu/gmp/",
|
||||
download_name="gmp-{GMP_VERSION}.tar.bz2".format(**locals())
|
||||
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
build_dependency(
|
||||
name="mpfr-%s" % (MPFR_VERSION,),
|
||||
name=f"mpfr-{MPFR_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=["--disable-shared", "--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}".format(**locals())],
|
||||
download_url="http://www.mpfr.org/mpfr-{MPFR_VERSION}/".format(**locals()),
|
||||
download_name="mpfr-{MPFR_VERSION}.tar.bz2".format(**locals())
|
||||
build_tool_args=["--disable-shared", f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
|
||||
download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
|
||||
download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
|
||||
)
|
||||
|
||||
build_dependency(
|
||||
name="cgal-{CGAL_VERSION}".format(**locals()),
|
||||
name=f"cgal-{CGAL_VERSION}",
|
||||
mode="cmake",
|
||||
build_tool_args=[
|
||||
"-DGMP_LIBRARIES=%s/install/gmp-%s/lib/libgmp.a" % (DEPS_DIR, GMP_VERSION),
|
||||
"-DGMP_INCLUDE_DIR=%s/install/gmp-%s/include" % (DEPS_DIR, GMP_VERSION),
|
||||
"-DMPFR_LIBRARIES=%s/install/mpfr-%s/lib/libmpfr.a" % (DEPS_DIR, MPFR_VERSION),
|
||||
"-DMPFR_INCLUDE_DIR=%s/install/mpfr-%s/include" % (DEPS_DIR, MPFR_VERSION),
|
||||
"-DBoost_INCLUDE_DIR=%s/install/boost-%s" % (DEPS_DIR, BOOST_VERSION),
|
||||
"-DCMAKE_INSTALL_PREFIX=%s/install/cgal-%s/" % (DEPS_DIR, CGAL_VERSION),
|
||||
f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.a",
|
||||
f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.a" ,
|
||||
f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
|
||||
"-DCGAL_HEADER_ONLY=On",
|
||||
"-DBUILD_SHARED_LIBS=Off"
|
||||
],
|
||||
download_url="https://github.com/CGAL/cgal.git",
|
||||
download_name="cgal",
|
||||
download_tool=download_tool_git,
|
||||
revision="v{CGAL_VERSION}".format(**locals())
|
||||
revision=f"v{CGAL_VERSION}"
|
||||
)
|
||||
|
||||
cecho("Building IfcOpenShell:", GREEN)
|
||||
|
||||
IFCOS_DIR=os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
if os.path.exists(IFCOS_DIR):
|
||||
shutil.rmtree(IFCOS_DIR)
|
||||
os.makedirs(IFCOS_DIR)
|
||||
|
||||
IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
|
||||
if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
|
||||
if os.path.exists(IFCOS_DIR):
|
||||
shutil.rmtree(IFCOS_DIR)
|
||||
os.makedirs(IFCOS_DIR, exist_ok=True)
|
||||
executables_dir = os.path.join(IFCOS_DIR, "executables")
|
||||
if not os.path.exists(executables_dir):
|
||||
os.makedirs(executables_dir)
|
||||
os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
logger.info("\rConfiguring executables...")
|
||||
|
||||
OFF_ON = ["OFF", "ON"]
|
||||
|
||||
exec_args=[
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
exec_args = [
|
||||
"-DBUILD_IFCGEOM=" +OFF_ON["IfcGeom" in targets],
|
||||
"-DBUILD_GEOMSERVER=" +OFF_ON["IfcGeomServer" in targets],
|
||||
"-DBUILD_CONVERT=" +OFF_ON["IfcConvert" in targets],
|
||||
"-DBUILD_IFCPYTHON=" "OFF",
|
||||
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell",
|
||||
]
|
||||
|
||||
cmake_args=[
|
||||
cmake_args = [
|
||||
"-DUSE_MMAP=" "OFF",
|
||||
"-DBUILD_EXAMPLES=" "OFF",
|
||||
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
|
||||
"-DBOOST_ROOT=" "{DEPS_DIR}/install/boost-{BOOST_VERSION}".format(**locals()),
|
||||
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
|
||||
"-DBOOST_ROOT=" f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
|
||||
"-DGLTF_SUPPORT=" "ON",
|
||||
"-DJSON_INCLUDE_DIR=" "{DEPS_DIR}/install/json".format(**locals()),
|
||||
"-DJSON_INCLUDE_DIR=" f"{DEPS_DIR}/install/json",
|
||||
"-DBoost_NO_BOOST_CMAKE=" "On",
|
||||
]
|
||||
|
||||
if "cgal" in targets:
|
||||
cmake_args.extend([
|
||||
"-DCGAL_INCLUDE_DIR=" "{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include".format(**locals()),
|
||||
"-DGMP_INCLUDE_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/include".format(**locals()),
|
||||
"-DGMP_LIBRARY_DIR=" "{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib".format(**locals()),
|
||||
"-DMPFR_INCLUDE_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include".format(**locals()),
|
||||
"-DMPFR_LIBRARY_DIR=" "{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib".format(**locals()),
|
||||
"-DCGAL_INCLUDE_DIR=" f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
|
||||
"-DGMP_INCLUDE_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
|
||||
"-DGMP_LIBRARY_DIR=" f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
|
||||
"-DMPFR_INCLUDE_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
|
||||
"-DMPFR_LIBRARY_DIR=" f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
|
||||
])
|
||||
|
||||
if "occ" in targets and USE_OCCT:
|
||||
occ_include_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade".format(**locals())
|
||||
occ_library_dir = "{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib".format(**locals())
|
||||
occ_include_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
|
||||
cmake_args.extend([
|
||||
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
|
||||
"-DOCC_LIBRARY_DIR=" +occ_library_dir
|
||||
])
|
||||
|
||||
elif "occ" in targets:
|
||||
occ_include_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce".format(**locals())
|
||||
occ_library_dir = "{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
occ_include_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce"
|
||||
occ_library_dir = f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
|
||||
cmake_args.extend([
|
||||
"-DOCC_INCLUDE_DIR=" +occ_include_dir,
|
||||
"-DOCC_LIBRARY_DIR=" +occ_library_dir
|
||||
@@ -757,82 +720,98 @@ elif "occ" in targets:
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
cmake_args.extend([
|
||||
"-DOPENCOLLADA_INCLUDE_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/include/opencollada".format(**locals()),
|
||||
"-DOPENCOLLADA_LIBRARY_DIR=" "{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada".format(**locals())
|
||||
"-DOPENCOLLADA_INCLUDE_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
|
||||
"-DOPENCOLLADA_LIBRARY_DIR=" f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
|
||||
])
|
||||
else:
|
||||
cmake_args.extend([
|
||||
"-DCOLLADA_SUPPORT=" "Off",
|
||||
])
|
||||
|
||||
if "pcre" in targets:
|
||||
cmake_args.append(
|
||||
"-DPCRE_LIBRARY_DIR=" "{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib".format(**locals())
|
||||
"-DPCRE_LIBRARY_DIR=" f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
|
||||
)
|
||||
|
||||
if "libxml2" in targets:
|
||||
cmake_args.extend([
|
||||
"-DLIBXML2_INCLUDE_DIR=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2".format(**locals()),
|
||||
"-DLIBXML2_LIBRARIES=" "{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}".format(**locals())
|
||||
"-DLIBXML2_INCLUDE_DIR=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
|
||||
"-DLIBXML2_LIBRARIES=" f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
|
||||
])
|
||||
|
||||
if "hdf5" in targets:
|
||||
cmake_args.extend([
|
||||
"-DHDF5_INCLUDE_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include".format(**locals()),
|
||||
"-DHDF5_LIBRARY_DIR=" "{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib".format(**locals())
|
||||
"-DHDF5_INCLUDE_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
|
||||
"-DHDF5_LIBRARY_DIR=" f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
|
||||
])
|
||||
|
||||
run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)
|
||||
|
||||
logger.info("\rBuilding executables... ")
|
||||
|
||||
run([make, "-j{IFCOS_NUM_BUILD_PROCS}".format(**locals())], cwd=executables_dir)
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
|
||||
run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)
|
||||
|
||||
if "IfcOpenShell-Python" in targets:
|
||||
|
||||
# On OSX the actual Python library is not linked against.
|
||||
ADDITIONAL_ARGS=""
|
||||
if get_os() == "Darwin":
|
||||
ADDITIONAL_ARGS="-Wl,-flat_namespace,-undefined,suppress"
|
||||
ADDITIONAL_ARGS = ""
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"
|
||||
|
||||
os.environ["CXXFLAGS"]="%s %s" % (CXXFLAGS_MINIMAL, ADDITIONAL_ARGS)
|
||||
os.environ["CFLAGS"]="%s %s" % (CFLAGS_MINIMAL, ADDITIONAL_ARGS)
|
||||
os.environ["LDFLAGS"]="%s %s" % (LDFLAGS, ADDITIONAL_ARGS)
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
|
||||
|
||||
for PYTHON_VERSION, _, TAG in PYTHON_VERSION_CONFS():
|
||||
logger.info("\rConfiguring python {PYTHON_VERSION}{TAG} wrapper...".format(**locals()))
|
||||
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
|
||||
os.makedirs(python_dir, exist_ok=True)
|
||||
|
||||
# python_dir = os.path.join(IFCOS_DIR, "python-{PYTHON_VERSION}{TAG}".format(**locals()))
|
||||
python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
|
||||
if not os.path.exists(python_dir):
|
||||
os.makedirs(python_dir)
|
||||
def compile_python_wrapper(python_version, python_library, python_include, python_executable):
|
||||
logger.info(f"\rConfiguring python {python_version} wrapper...")
|
||||
|
||||
cache_path = os.path.join(python_dir, "CMakeCache.txt")
|
||||
if os.path.exists(cache_path):
|
||||
os.unlink(cache_path)
|
||||
os.remove(cache_path)
|
||||
|
||||
PYTHON_LIBRARY=run([bash, "-c", "ls {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/lib/libpython*.*".format(**locals())])
|
||||
PYTHON_INCLUDE=run([bash, "-c", "ls -d {DEPS_DIR}/install/python-{PYTHON_VERSION}{TAG}/include/python*".format(**locals())])
|
||||
PYTHON_EXECUTABLE=os.path.join(DEPS_DIR, "install", "python-{PYTHON_VERSION}{TAG}".format(**locals()), "bin", "python{PYTHON_VERSION[0]}".format(**locals()))
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"]=os.path.basename(PYTHON_LIBRARY)
|
||||
os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)
|
||||
|
||||
run_cmake("",
|
||||
cmake_args + [
|
||||
"-DPYTHON_LIBRARY=" +PYTHON_LIBRARY,
|
||||
"-DPYTHON_EXECUTABLE=" +PYTHON_EXECUTABLE,
|
||||
"-DPYTHON_INCLUDE_DIR=" +PYTHON_INCLUDE,
|
||||
"-DSWIG_EXECUTABLE=" "{DEPS_DIR}/install/swig/bin/swig".format(**locals()),
|
||||
"-DCMAKE_INSTALL_PREFIX=" "{DEPS_DIR}/install/ifcopenshell/tmp".format(**locals()),
|
||||
"-DPYTHON_LIBRARY=" +python_library,
|
||||
"-DPYTHON_EXECUTABLE=" +python_executable,
|
||||
"-DPYTHON_INCLUDE_DIR=" +python_include,
|
||||
"-DSWIG_EXECUTABLE=" f"{DEPS_DIR}/install/swig/bin/swig",
|
||||
"-DCMAKE_INSTALL_PREFIX=" f"{DEPS_DIR}/install/ifcopenshell/tmp",
|
||||
"-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}]
|
||||
], cmake_dir=CMAKE_DIR, cwd=python_dir)
|
||||
|
||||
logger.info("\rBuilding python %s%s wrapper... " % (PYTHON_VERSION, TAG))
|
||||
|
||||
run([make, "-j%s" % (IFCOS_NUM_BUILD_PROCS,), "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
logger.info(f"\rBuilding python {python_version} wrapper... ")
|
||||
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
|
||||
run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))
|
||||
|
||||
module_dir = os.path.dirname(run([PYTHON_EXECUTABLE, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))
|
||||
|
||||
if get_os() != "Darwin":
|
||||
if platform.system() != "Darwin":
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)
|
||||
return module_dir
|
||||
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", "python-%s%s" % (PYTHON_VERSION, TAG))])
|
||||
|
||||
if USE_CURRENT_PYTHON_VERSION:
|
||||
python_info = sysconfig.get_paths()
|
||||
python_lib = os.path.join(
|
||||
sysconfig.get_config_var('LIBDIR'),
|
||||
sysconfig.get_config_var('multiarchsubdir').replace("/", ""),
|
||||
sysconfig.get_config_var("INSTSONAME")
|
||||
)
|
||||
compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
|
||||
else:
|
||||
for python_version in PYTHON_VERSIONS:
|
||||
python_library = run([bash, "-c", f"ls {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
|
||||
python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
|
||||
python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")
|
||||
|
||||
module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
|
||||
run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])
|
||||
|
||||
logger.info("\rBuilt IfcOpenShell...\n\n")
|
||||
|
||||
@@ -645,9 +645,10 @@ class BcfXml:
|
||||
|
||||
def get_comments(self, guid):
|
||||
comments = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Comments" not in data["Topics"]:
|
||||
return comments
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
|
||||
for item in data["Topic"]["Comments"].get("Comment", []):
|
||||
comment = bcf.v3.data.Comment()
|
||||
mandatory_keys = {
|
||||
@@ -675,9 +676,9 @@ class BcfXml:
|
||||
|
||||
def get_viewpoints(self, guid):
|
||||
viewpoints = {}
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
if "Viewpoints" not in data["Topic"]:
|
||||
return viewpoints
|
||||
data = self._read_xml(os.path.join(guid, "markup.bcf"), "markup.xsd")
|
||||
for item in data["Topic"]["Viewpoints"]:
|
||||
viewpoint = self.get_viewpoint(item, guid)
|
||||
viewpoints[viewpoint.guid] = viewpoint
|
||||
|
||||
@@ -46,7 +46,7 @@ class RelatedTopic:
|
||||
|
||||
class HeaderFile:
|
||||
def __init__(self):
|
||||
self.file_name = ""
|
||||
self.filename = ""
|
||||
self.date = None
|
||||
self.reference = ""
|
||||
self.ifc_project = None
|
||||
|
||||
+62
-3
@@ -93,7 +93,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-9853d3d-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcblender-python-$(PYNUMBER)-v0.7.0-b9ea741-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcblender*
|
||||
cp -r dist/working/io_import_scene_ifc/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -133,6 +133,8 @@ endif
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc4d/ifc4d dist/blenderbim/libs/site/packages/
|
||||
# Provides IFC5D functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifc5d/ifc5d dist/blenderbim/libs/site/packages/
|
||||
# Provides IFCCityJSON functionality
|
||||
cp -r dist/working/IfcOpenShell-0.7.0/src/ifccityjson dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Mustache templating in construction documentation
|
||||
@@ -240,6 +242,30 @@ endif
|
||||
cp -r dist/working/lark-parser-0.8.5/lark dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFC4D
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/b0/bb/9c4dddd3ca173cb56241cfb2eddfae24690dc676d357ac4cab17d0a36d9d/PyP6Xer-1.13.0.tar.gz
|
||||
cd dist/working && tar -xzvf PyP6Xer*
|
||||
cp -r dist/working/PyP6Xer-1.13.0/xerparser dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by xerparser and IFC4D
|
||||
# TODO: remove this dependency. It's only used to show a progress bar.
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/9f/7b/76c4e5ef1a1b528fcaada4133f972e77d33c252831676cf414119ca6093d/tqdm-4.50.0.tar.gz
|
||||
cd dist/working && tar -xzvf tqdm*
|
||||
cp -r dist/working/tqdm-4.50.0/tqdm dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCityJSON
|
||||
mkdir dist/working
|
||||
cd dist/working && python -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
cd dist/working && . env/bin/activate && pip install cjio --target=./site-packages
|
||||
cd dist/working/site-packages/ && rm -r *dist-info*
|
||||
cp -r dist/working/site-packages/* dist/blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCClash
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(HPPFCL_URL)
|
||||
@@ -345,7 +371,7 @@ endif
|
||||
cd dist/working/ && patch ../blenderbim/libs/site/packages/behave/runner_util.py < runner_util.patch
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by ids
|
||||
# Required by ids - uh, not any more, but I'm trying it out in the drawing module
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(LXML_URL)
|
||||
cd dist/working && cp *.whl lxml.zip && unzip lxml.zip
|
||||
@@ -429,6 +455,39 @@ endif
|
||||
cd dist/working/olca-ipc-0.0.10/ && cp -r olca ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Provides Brickschema fucntionality
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/ca/37/8309da9a72407d2f5eb5489c197ac3cfe96ee3ccba1902d2a0ecc7843f92/brickschema-0.5.1.tar.gz
|
||||
cd dist/working && tar -xzvf brickschema*
|
||||
# This is an evil hack because we don't want to bundle flask
|
||||
echo "" > dist/working/brickschema-0.5.1/brickschema/web.py
|
||||
cd dist/working/brickschema-0.5.1/ && cp -r brickschema ../../blenderbim/libs/site/packages/
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd dist/working && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd dist/working && cp Brick.ttl ../../blenderbim/bim/schema/Brick.ttl
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/c7/22/37bb938be8e5c20d443b0c9ba0a243573b671b01739efb0a19f81bc5b470/pyshacl-0.17.2.tar.gz
|
||||
cd dist/working && tar -xzvf pyshacl*
|
||||
cd dist/working/pyshacl-0.17.2/ && cp -r pyshacl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/d2/a7/be8244688bfcee37c23733ab4fe8e6afa6d4403bd2674a3ae7bd2cecc77b/rdflib-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf rdflib*
|
||||
cd dist/working/rdflib-6.0.2/ && cp -r rdflib ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by brickschema
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/7d/c7/208aece36279e4f1236e437119358786e39530ecc1719d4e1afeddba5288/owlrl-6.0.2.tar.gz
|
||||
cd dist/working && tar -xzvf owlrl*
|
||||
cd dist/working/owlrl-6.0.2/ && cp -r owlrl ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
cd dist/blenderbim && sed -i "s/999999/$(VERSION)/" __init__.py
|
||||
cd dist && zip -r blenderbim-$(VERSION)-$(PYVERSION)-$(PLATFORM).zip ./*
|
||||
rm -rf dist/blenderbim
|
||||
@@ -464,7 +523,7 @@ coverage:
|
||||
|
||||
.PHONY: license
|
||||
license:
|
||||
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2021" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
|
||||
copyright-header --license GPL3 --copyright-holder "Dion Moult <dion@thinkmoult.com>" --copyright-year "2022" --copyright-software "BlenderBIM Add-on" --copyright-software-description "OpenBIM Blender Add-on" -a ./ -o ./
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
+3
-125
@@ -1,127 +1,5 @@
|
||||
# Blender BIM IFC
|
||||
# BlenderBIM Add-on
|
||||
|
||||
_Under heavy development!_
|
||||
An add-on to Blender to allow BIM functionality.
|
||||
|
||||
Let's use Blender to create BIM models, because why not?
|
||||
|
||||
## Features
|
||||
|
||||
- IFC4 is a first-class citizen. IFC4 has been an ISO standard since 2013, with
|
||||
the latest published in 2018. It contains many more features than IFC2X3,
|
||||
published back in 2005.
|
||||
- Available for Windows, Mac, and Linux!
|
||||
- Use open-source Blender 2.8 to do digital modeling! Used by NASA, Hollywood,
|
||||
and Ubisoft, Blender allows real-time modeling in rendered view, animations,
|
||||
file linking, proxy objects, Blender Cloud collaboration, lighting analysis,
|
||||
CFD, energy modeling, and a huge ecosystem of add-ons with a thriving
|
||||
community. No licensing, and with a strong Python API to integrate into your
|
||||
custom workflow.
|
||||
- Create complex spatial structures with multiple sites, buildings, facilities,
|
||||
bridges, and building parts. Sites can be broken down into sub-sites and
|
||||
multiple buildings per site. You can categorise different floors to different
|
||||
buildings.
|
||||
- Classify any geometry as any IFC type. Just because it is a wall doesn't
|
||||
mean you should be limited to modeling it in a certain way. Easily change
|
||||
from one type to another, switch relationships and aggregations, reuse or
|
||||
even remove geometry altogether, without damaging your geometry or breaking
|
||||
your BIM model.
|
||||
- Use collections to create element compositions and decompositions with full
|
||||
control over the classifications of their parts. Most BIM programs don't give
|
||||
this flexibility. Create collection instances to efficiently duplicate
|
||||
composite objects.
|
||||
- Allow BIM objects to instance a shared geometric representations. This allows
|
||||
you to place the same shape in multiple objects without drastically
|
||||
increasing your filesize.
|
||||
- Provide a custom value to any simple IFC attribute of an IFC product.
|
||||
- Create product types that regular IFC products can inherit attributes from.
|
||||
- Define your own property sets with control over exactly how the data in the
|
||||
property sets are stored using property set template definitions which you
|
||||
can download or create yourself. Property sets can apply to both types and
|
||||
objects, and object property sets can selectively override type property
|
||||
sets.
|
||||
- Applies array modifiers to create instances.
|
||||
- Basic quantity take-off support for costing.
|
||||
- Settings to toggle export of quantities or geometric representations.
|
||||
Exporting IFC data without geometric representations is much faster and a
|
||||
fraction of the filesize.
|
||||
- Maintain `GlobalId` between exports, so that you can manipulate the IFC
|
||||
reliably in other software while making changes.
|
||||
- Not all objects need full solid geometry. Create representations as 2D or 3D
|
||||
curves / wireframes for rapid and lightweight BIM development.
|
||||
- Create map conversions and coordinate reference system definitions to link
|
||||
BIM to GIS.
|
||||
- Export basic materials with surface and diffuse colours for representation.
|
||||
You can also link materials to an externally defined file, such as a `.blend`
|
||||
Cycles material, V-Ray material, or even Radiance material for lighting
|
||||
simulation.
|
||||
- Switch between different types of metric units when modeling.
|
||||
- Model multiple representations for different purposes for a single BIM
|
||||
object or type, such as footprints, axis, reference, or clearance geometry.
|
||||
You can export just the type of representation required for your usecase, or
|
||||
export multiple for the user to choose from.
|
||||
- Record qualitative objectives such as design intentions, health and safety
|
||||
strategies, or code compliance, performance solutions, and more, in the BIM
|
||||
model and directly associate them to objects or types.
|
||||
- Classify objects and types to different classification systems such as
|
||||
Uniclass, Omniclass, or your custom system.
|
||||
- Associate external document files such as plans, brochures, specifications,
|
||||
warranties, or anything to objects and types.
|
||||
- Specify swept solid geometries, allowing you to round-trip geometry
|
||||
manipulation with other BIM software which have more limited geometric tools,
|
||||
like Revit.
|
||||
- Create material layer sets, for objects which have materials in predefined
|
||||
layers with thicknesses.
|
||||
- Create material constituent sets, for objects which are made up of
|
||||
arbitrarily mixed ingredients.
|
||||
- Specify nested and hosted element relationships.
|
||||
- Specify predefined door attributes as defined in the IFC4 specification.
|
||||
- Specify predefined window attributes as defined in the IFC4 specification.
|
||||
- Specify surface styles either defined as part of a material or assigned
|
||||
directly to an object's representation.
|
||||
- Colour coding by IFC class to visually spot mistakes
|
||||
|
||||
Some more experimental features:
|
||||
|
||||
- Cut sections for documentation with OpenCascade
|
||||
- Export gbXML files that are linked to your IFC file to do energy analysis
|
||||
- QA module to generate Gherkin unit tests to validate your BIM models
|
||||
- HTML formatter to generate reports from QA results
|
||||
- COBie spreadsheet extraction to import into CAFM systems
|
||||
|
||||
## Demo
|
||||
|
||||
[See demo video](https://www.youtube.com/watch?v=iD3v3eu2AjY)
|
||||
|
||||
It's only just started, so everything can and will change. But if you want to
|
||||
check it out:
|
||||
|
||||
1. Install [Blender](https://www.blender.org/) 64-bit version.
|
||||
2. Download the latest packaged version of Blender BIM from
|
||||
[here](https://blenderbim.org/builds/).
|
||||
3. Launch Blender. Go to `Edit->Preferences->Add-ons->Install...`. Browse to
|
||||
the downloaded `.zip` file from step 2.
|
||||
4. Enable the checkbox next to the Import-Export IFC Blender plugin which will
|
||||
show up.
|
||||
5. All done! You will find an import and export IFC option in the `File` menu.
|
||||
|
||||
By default, Blender BIM will export whatever you have selected. The objects you
|
||||
have selected _must_ belong in an `IfcProject` and spatially contained, such as
|
||||
witihn an `IfcBuilding`. It must also follow the naming convention. Please see
|
||||
the screenshot below for an example scene.
|
||||
|
||||
A naming convention of `IfcClass/IfcName` is enforced. If you name your object
|
||||
`IfcWall/Foo`, it will turn into an `IfcWall` with the name attribute set to
|
||||
`Foo`. You can override object attributes using Blender's `Custom Properties`.
|
||||
Your objects must live in a top level `Collection` defined as a context class,
|
||||
usually something like `IfcProject/Foo`. You can then nest spatial structures
|
||||
underneath to create a hierarchy.
|
||||
|
||||
## Pictures
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
... this Blender files creates this IFC file ...
|
||||
|
||||

|
||||
More information on the [BlenderBIM Add-on website](https://blenderbim.org).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 399 KiB |
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator
|
||||
from . import handler, ui, prop, operator, helper
|
||||
|
||||
modules = {
|
||||
"project": None,
|
||||
@@ -30,6 +30,7 @@ modules = {
|
||||
"georeference": None,
|
||||
"context": None,
|
||||
"drawing": None,
|
||||
"misc": None,
|
||||
"attribute": None,
|
||||
"type": None,
|
||||
"spatial": None,
|
||||
@@ -42,6 +43,7 @@ modules = {
|
||||
"sequence": None,
|
||||
"group": None,
|
||||
"system": None,
|
||||
"brick": None,
|
||||
"structural": None,
|
||||
"boundary": None,
|
||||
"profile": None,
|
||||
@@ -52,6 +54,7 @@ modules = {
|
||||
"pset": None,
|
||||
"qto": None,
|
||||
"classification": None,
|
||||
"library": None,
|
||||
"constraint": None,
|
||||
"document": None,
|
||||
"pset_template": None,
|
||||
@@ -61,33 +64,33 @@ modules = {
|
||||
"bimtester": None,
|
||||
"diff": None,
|
||||
"patch": None,
|
||||
"gis": None,
|
||||
"covetool": None,
|
||||
"augin": None,
|
||||
"debug": None,
|
||||
}
|
||||
|
||||
|
||||
for name in modules.keys():
|
||||
modules[name] = importlib.import_module(f"blenderbim.bim.module.{name}")
|
||||
|
||||
|
||||
classes = [
|
||||
operator.OpenUri,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.ExportIFC,
|
||||
operator.ImportIFC,
|
||||
operator.OpenUpstream,
|
||||
operator.AddSectionPlane,
|
||||
operator.RemoveSectionPlane,
|
||||
operator.ReloadIfcFile,
|
||||
operator.AddIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.SetOverrideColour,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.OverrideDelete,
|
||||
operator.ConfigureVisibility,
|
||||
prop.StrProperty,
|
||||
prop.ObjProperty,
|
||||
prop.Attribute,
|
||||
prop.ModuleVisibility,
|
||||
prop.BIMProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
@@ -99,20 +102,26 @@ classes = [
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
ui.BIM_ADDON_preferences,
|
||||
#scene panel groups -->
|
||||
ui.BIM_PT_project_and_template_setup,
|
||||
ui.BIM_PT_utilities,
|
||||
ui.BIM_PT_collaboration_and_data_exchange,
|
||||
ui.BIM_PT_geometry_and_spatial_structure,
|
||||
ui.BIM_PT_4D5D,
|
||||
ui.BIM_PT_structural,
|
||||
ui.BIM_PT_misc,
|
||||
ui.BIM_PT_quality_control,
|
||||
#object panel groups -->
|
||||
ui.BIM_PT_geometry_and_spatial_structure_object,
|
||||
ui.BIM_PT_object_attributes_properties_and_relationships,
|
||||
ui.BIM_PT_utilities_object,
|
||||
ui.BIM_PT_misc_object
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
classes.extend(mod.classes)
|
||||
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(operator.ExportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcjson)")
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportIFC.bl_idname, text="Industry Foundation Classes (.ifc/.ifczip/.ifcxml)")
|
||||
|
||||
|
||||
def on_register(scene):
|
||||
handler.setDefaultProperties(scene)
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_register)
|
||||
@@ -129,8 +138,6 @@ def register():
|
||||
bpy.app.handlers.load_post.append(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.append(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Object.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
bpy.types.Material.BIMObjectProperties = bpy.props.PointerProperty(type=prop.BIMObjectProperties)
|
||||
@@ -151,8 +158,6 @@ def unregister():
|
||||
bpy.app.handlers.load_post.remove(handler.setDefaultProperties)
|
||||
bpy.app.handlers.load_post.remove(handler.loadIfcStore)
|
||||
bpy.app.handlers.save_pre.remove(handler.ensureIfcExported)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
del bpy.types.Scene.BIMProperties
|
||||
del bpy.types.Object.BIMObjectProperties
|
||||
del bpy.types.Material.BIMObjectProperties
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
*/
|
||||
|
||||
* { stroke-linecap: round; stroke-linejoin: round; }
|
||||
*[id] { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; }
|
||||
text { fill: black; stroke: none; }
|
||||
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
|
||||
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
|
||||
.hidden { stroke-dasharray: 3, 2; }
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
# 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"],
|
||||
}
|
||||
@@ -27,7 +27,12 @@ import addon_utils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.placement
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.aggregate
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.core.style
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
class IfcExporter:
|
||||
@@ -37,8 +42,10 @@ class IfcExporter:
|
||||
def export(self):
|
||||
self.file = IfcStore.get_file()
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
if bpy.context.scene.BIMProjectProperties.is_authoring:
|
||||
self.sync_object_placements_and_deletions()
|
||||
self.sync_deletions()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1]
|
||||
if extension == "ifczip":
|
||||
@@ -79,103 +86,99 @@ class IfcExporter:
|
||||
self.get_application_name(), self.get_application_version()
|
||||
)
|
||||
|
||||
def sync_object_placements_and_deletions(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
to_delete = []
|
||||
def sync_deletions(self):
|
||||
results = []
|
||||
for ifc_definition_id in IfcStore.deleted_ids:
|
||||
try:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
if hasattr(product, "GlobalId"):
|
||||
results.append(product.GlobalId)
|
||||
except:
|
||||
continue
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
return results
|
||||
|
||||
for ifc_definition_id, obj in IfcStore.id_map.items():
|
||||
def sync_all_objects(self):
|
||||
results = []
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
for ifc_definition_id in list(IfcStore.id_map.keys()):
|
||||
obj = IfcStore.id_map[ifc_definition_id]
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
continue
|
||||
self.sync_object_placement(obj)
|
||||
self.sync_object_container(ifc_definition_id, obj)
|
||||
tool.Collector.sync(obj)
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
result = self.sync_object_material(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
if self.should_delete(obj):
|
||||
to_delete.append(ifc_definition_id)
|
||||
|
||||
for ifc_definition_id in to_delete:
|
||||
product = self.file.by_id(ifc_definition_id)
|
||||
IfcStore.unlink_element(product)
|
||||
ifcopenshell.api.run("root.remove_product", self.file, **{"product": product})
|
||||
return results
|
||||
|
||||
def sync_edited_objects(self):
|
||||
results = []
|
||||
for obj in IfcStore.edited_objs.copy():
|
||||
if not obj:
|
||||
continue
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
bpy.ops.bim.update_style_colours(material=obj.name)
|
||||
blenderbim.core.style.update_style_colours(tool.Ifc, tool.Style, obj=obj)
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
results.append(element)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
IfcStore.edited_objs.clear()
|
||||
return results
|
||||
|
||||
def sync_object_material(self, obj):
|
||||
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
if not self.has_changed_materials(obj):
|
||||
return
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def has_changed_materials(self, obj):
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != str([s.id() for s in tool.Geometry.get_styles(obj) if s])
|
||||
|
||||
def sync_object_placement(self, obj):
|
||||
if not self.has_object_moved(obj):
|
||||
return
|
||||
blender_matrix = np.array(obj.matrix_world)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if (obj.scale - Vector((1.0, 1.0, 1.0))).length > 1e-4:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
return element
|
||||
if element.is_a("IfcGridAxis"):
|
||||
return self.sync_grid_axis_object_placement(obj, element)
|
||||
if not hasattr(element, "ObjectPlacement"):
|
||||
return
|
||||
ifc_matrix = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
ifc_matrix[0][3] *= self.unit_scale
|
||||
ifc_matrix[1][3] *= self.unit_scale
|
||||
ifc_matrix[2][3] *= self.unit_scale
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
return element
|
||||
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "OBJECT_PLACEMENT":
|
||||
ifc_matrix = ifcopenshell.util.geolocation.global2local(
|
||||
ifc_matrix,
|
||||
float(props.blender_eastings) * self.unit_scale,
|
||||
float(props.blender_northings) * self.unit_scale,
|
||||
float(props.blender_orthogonal_height) * self.unit_scale,
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
if not np.allclose(ifc_matrix, blender_matrix, atol=0.0001):
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
|
||||
def sync_object_container(self, guid, obj):
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
element_collection = bpy.data.collections.get(obj.name)
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcContext"):
|
||||
return
|
||||
|
||||
if (
|
||||
(element.is_a("IfcElement") and element_collection)
|
||||
or element.is_a("IfcSpatialStructureElement")
|
||||
or element.is_a("IfcGrid")
|
||||
):
|
||||
try:
|
||||
parent_collection = [c for c in bpy.data.collections if c.children.get(element_collection.name)][0]
|
||||
except:
|
||||
return # Out of the spatial tree
|
||||
else:
|
||||
parent_collection = obj.users_collection[0]
|
||||
|
||||
parent_obj = bpy.data.objects.get(parent_collection.name)
|
||||
if not parent_obj or not parent_obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
parent = self.file.by_id(parent_obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if parent.is_a("IfcSpatialStructureElement") and not element.is_a("IfcSpatialStructureElement"):
|
||||
if parent != ifcopenshell.util.element.get_container(element):
|
||||
bpy.ops.bim.assign_container(relating_structure=parent.id(), related_element=obj.name)
|
||||
elif parent != ifcopenshell.util.element.get_aggregate(element):
|
||||
bpy.ops.bim.assign_object(relating_object=parent_obj.name, related_object=obj.name)
|
||||
|
||||
def should_delete(self, obj):
|
||||
try:
|
||||
# This will throw an exception if the Blender object no longer exists
|
||||
foo = obj.name
|
||||
foo
|
||||
def has_object_moved(self, obj):
|
||||
if not obj.BIMObjectProperties.location_checksum:
|
||||
return True # Let's be conservative
|
||||
loc_check = np.frombuffer(eval(obj.BIMObjectProperties.location_checksum))
|
||||
rot_check = np.frombuffer(eval(obj.BIMObjectProperties.rotation_checksum))
|
||||
loc_real = np.array(obj.matrix_world.translation).flatten()
|
||||
rot_real = np.array(obj.matrix_world.to_3x3()).flatten()
|
||||
if np.allclose(loc_check, loc_real, atol=1e-4) and np.allclose(rot_check, rot_real, atol=1e-2):
|
||||
return False
|
||||
except:
|
||||
return True
|
||||
return True
|
||||
|
||||
def sync_grid_axis_object_placement(self, obj, element):
|
||||
grid = (element.PartOfU or element.PartOfV or element.PartOfW)[0]
|
||||
grid_obj = tool.Ifc.get_object(grid)
|
||||
if grid_obj:
|
||||
self.sync_object_placement(grid_obj)
|
||||
if grid_obj.matrix_world != obj.matrix_world:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def get_application_name(self):
|
||||
return "BlenderBIM"
|
||||
|
||||
@@ -50,9 +50,7 @@ def mode_callback(obj, data):
|
||||
):
|
||||
continue
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
representation = IfcStore.get_file().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if representation.RepresentationType in ["Tessellation", "Brep", "Annotation2D"]:
|
||||
IfcStore.edited_objs.add(obj)
|
||||
IfcStore.edited_objs.add(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@@ -107,13 +105,6 @@ def color_callback(obj, data):
|
||||
|
||||
|
||||
def active_object_callback():
|
||||
obj = bpy.context.active_object
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
stored_obj = IfcStore.get_element(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if stored_obj and stored_obj != obj:
|
||||
bpy.ops.bim.copy_class(obj=obj.name)
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
@@ -209,6 +200,7 @@ def ensureIfcExported(scene):
|
||||
|
||||
|
||||
def get_application(ifc):
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
version = get_application_version()
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "BlenderBIM" and element.Version == version:
|
||||
@@ -216,7 +208,7 @@ def get_application(ifc):
|
||||
return ifcopenshell.api.run(
|
||||
"owner.add_application",
|
||||
ifc,
|
||||
version=get_application_version(),
|
||||
version=version,
|
||||
application_full_name="BlenderBIM Add-on",
|
||||
application_identifier="BlenderBIM",
|
||||
)
|
||||
@@ -244,6 +236,7 @@ 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
|
||||
# TODO: Move to drawing module
|
||||
if len(bpy.context.scene.DocProperties.drawing_styles) == 0:
|
||||
drawing_style = bpy.context.scene.DocProperties.drawing_styles.add()
|
||||
drawing_style.name = "Technical"
|
||||
@@ -297,7 +290,7 @@ def setDefaultProperties(scene):
|
||||
RasterStyleProperty.SHADING_SHADOW_INTENSITY.value: 0.5,
|
||||
RasterStyleProperty.DISPLAY_LIGHT_DIRECTION.value: (0.5, 0.5, 0.5),
|
||||
RasterStyleProperty.VIEW_USE_CURVE_MAPPING.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: True,
|
||||
RasterStyleProperty.OVERLAY_SHOW_WIREFRAMES.value: False,
|
||||
RasterStyleProperty.OVERLAY_WIREFRAME_THRESHOLD.value: 0,
|
||||
RasterStyleProperty.OVERLAY_SHOW_FLOOR.value: False,
|
||||
RasterStyleProperty.OVERLAY_SHOW_AXIS_X.value: False,
|
||||
|
||||
@@ -46,7 +46,7 @@ def draw_attribute(attribute, layout, copy_operator=None):
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.data = json.dumps({"name": attribute.name, "value": attribute.get_value(), "is_null": attribute.is_null})
|
||||
op.name = attribute.name
|
||||
|
||||
|
||||
def import_attributes(ifc_class, props, data, callback=None):
|
||||
@@ -97,3 +97,37 @@ def export_attributes(props, callback=None):
|
||||
continue # Our job is done
|
||||
attributes[prop.name] = prop.get_value()
|
||||
return attributes
|
||||
|
||||
|
||||
class IFCHeaderSpecs:
|
||||
def __init__(self, filepath: str):
|
||||
# According to https://www.steptools.com/stds/step/IS_final_p21e3.html#clause-8
|
||||
self.description = ""
|
||||
self.implementation_level = ""
|
||||
self.name = ""
|
||||
self.time_stamp = ""
|
||||
self.author = ""
|
||||
self.organization = ""
|
||||
self.preprocessor_version = ""
|
||||
self.originating_system = ""
|
||||
self.authorization = ""
|
||||
self.schema_name = ""
|
||||
with open(filepath) as ifc_file:
|
||||
max_lines_to_parse = 50
|
||||
for _ in range(max_lines_to_parse):
|
||||
line = next(ifc_file)
|
||||
if line.startswith("FILE_DESCRIPTION"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
self.description = part
|
||||
elif i == 3:
|
||||
self.implementation_level = part
|
||||
elif line.startswith("FILE_NAME"):
|
||||
for i, part in enumerate(line.split("'")):
|
||||
if i == 1:
|
||||
self.name = part
|
||||
elif i == 3:
|
||||
self.time_stamp = part
|
||||
elif line.startswith("FILE_SCHEMA"):
|
||||
self.schema_name = line.split("'")[1]
|
||||
break
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -16,8 +16,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import uuid
|
||||
import hashlib
|
||||
import zipfile
|
||||
import tempfile
|
||||
import ifcopenshell
|
||||
@@ -29,8 +31,11 @@ class IfcStore:
|
||||
path = ""
|
||||
file = None
|
||||
schema = None
|
||||
cache = None
|
||||
cache_path = None
|
||||
id_map = {}
|
||||
guid_map = {}
|
||||
deleted_ids = set()
|
||||
edited_objs = set()
|
||||
pset_template_path = ""
|
||||
pset_template_file = None
|
||||
@@ -42,24 +47,31 @@ class IfcStore:
|
||||
last_transaction = ""
|
||||
history = []
|
||||
future = []
|
||||
schema_identifiers = ["IFC4", "IFC2X3"]
|
||||
|
||||
@staticmethod
|
||||
def purge():
|
||||
IfcStore.path = ""
|
||||
IfcStore.file = None
|
||||
IfcStore.schema = None
|
||||
IfcStore.cache = None
|
||||
IfcStore.cache_path = None
|
||||
IfcStore.id_map = {}
|
||||
IfcStore.guid_map = {}
|
||||
IfcStore.deleted_ids = set()
|
||||
IfcStore.edited_objs = set()
|
||||
IfcStore.pset_template_path = ""
|
||||
IfcStore.pset_template_file = None
|
||||
IfcStore.library_path = ""
|
||||
IfcStore.library_file = None
|
||||
IfcStore.schema_identifiers = ["IFC4", "IFC2X3"]
|
||||
|
||||
@staticmethod
|
||||
def get_file():
|
||||
if IfcStore.file is None:
|
||||
IfcStore.path = bpy.context.scene.BIMProperties.ifc_file
|
||||
if not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
@@ -67,6 +79,30 @@ class IfcStore:
|
||||
pass
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
if IfcStore.cache is None and IfcStore.path:
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
try:
|
||||
IfcStore.cache = ifcopenshell.geom.serializers.hdf5(IfcStore.cache_path, cache_settings)
|
||||
except:
|
||||
return
|
||||
return IfcStore.cache
|
||||
|
||||
@staticmethod
|
||||
def update_cache():
|
||||
if not IfcStore.cache:
|
||||
return
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
os.replace(IfcStore.cache_path, new_cache_path)
|
||||
IfcStore.get_cache()
|
||||
|
||||
@staticmethod
|
||||
def load_file(path):
|
||||
extension = path.split(".")[-1]
|
||||
@@ -85,7 +121,9 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def get_schema():
|
||||
if IfcStore.file is None:
|
||||
return
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
|
||||
bpy.context.scene.BIMProjectProperties.export_schema
|
||||
)
|
||||
elif IfcStore.schema is None:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema)
|
||||
return IfcStore.schema
|
||||
@@ -224,7 +262,9 @@ class IfcStore:
|
||||
except:
|
||||
pass
|
||||
|
||||
if obj:
|
||||
if element and element.is_a("IfcSurfaceStyle"):
|
||||
obj.BIMMaterialProperties.ifc_style_id = 0
|
||||
elif obj:
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
@staticmethod
|
||||
@@ -232,7 +272,6 @@ class IfcStore:
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
active_object = context.active_object
|
||||
IfcStore.begin_transaction(operator)
|
||||
IfcStore.get_file().begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
@@ -248,11 +287,7 @@ class IfcStore:
|
||||
operator, rollback=lambda d: IfcStore.get_file().undo(), commit=lambda d: IfcStore.get_file().redo()
|
||||
)
|
||||
IfcStore.end_transaction(operator)
|
||||
try:
|
||||
active_object.name
|
||||
context.view_layer.objects.active = active_object
|
||||
except:
|
||||
pass
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.geolocation
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.drawing.prop import get_diagram_scales
|
||||
|
||||
@@ -67,6 +68,7 @@ class MaterialCreator:
|
||||
self.add_default_material(element)
|
||||
if self.parse_representations(element):
|
||||
self.assign_material_slots_to_faces()
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
def add_default_material(self, element):
|
||||
element_material = ifcopenshell.util.element.get_material(element)
|
||||
@@ -207,6 +209,8 @@ class IfcImporter:
|
||||
self.profile_code("Create project")
|
||||
self.process_element_filter()
|
||||
self.profile_code("Process element filter")
|
||||
self.process_context_filter()
|
||||
self.profile_code("Process context filter")
|
||||
self.create_collections()
|
||||
self.profile_code("Create collections")
|
||||
self.create_openings_collection()
|
||||
@@ -215,7 +219,7 @@ class IfcImporter:
|
||||
self.profile_code("Create materials")
|
||||
self.create_styles()
|
||||
self.profile_code("Create styles")
|
||||
self.create_annotation()
|
||||
self.create_annotations()
|
||||
self.profile_code("Create annotation")
|
||||
self.parse_native_elements()
|
||||
self.profile_code("Parsing native elements")
|
||||
@@ -252,9 +256,11 @@ class IfcImporter:
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
def is_element_far_away(self, element, is_meters=True):
|
||||
def is_element_far_away(self, element):
|
||||
try:
|
||||
return self.is_point_far_away(element.ObjectPlacement.RelativePlacement.Location, is_meters=is_meters)
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
point = placement[:, 3][0:3]
|
||||
return self.is_point_far_away(point, is_meters=False)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -266,12 +272,38 @@ class IfcImporter:
|
||||
coords = point.Coordinates
|
||||
return abs(coords[0]) > limit or abs(coords[1]) > limit or abs(coords[2]) > limit
|
||||
|
||||
def process_context_filter(self):
|
||||
# Facetation is to accommodate broken Revit files
|
||||
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
|
||||
self.body_contexts = [
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
|
||||
if c.ContextIdentifier in ["Body", "Facetation"]
|
||||
]
|
||||
if self.body_contexts:
|
||||
self.settings.set_context_ids(self.body_contexts)
|
||||
# Annotation is to accommodate broken Revit files
|
||||
# See https://github.com/Autodesk/revit-ifc/issues/187
|
||||
self.plan_contexts = [
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext")
|
||||
if c.ContextType in ["Plan", "Annotation"]
|
||||
]
|
||||
if self.plan_contexts:
|
||||
self.settings_2d.set_context_ids(self.plan_contexts)
|
||||
|
||||
def process_element_filter(self):
|
||||
if self.ifc_import_settings.has_filter:
|
||||
self.elements = set(self.ifc_import_settings.elements)
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
# TODO: enable filtering for annotations
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
else:
|
||||
self.elements = set(self.file.by_type("IfcElement"))
|
||||
self.annotations = set(self.file.by_type("IfcAnnotation"))
|
||||
|
||||
if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements:
|
||||
self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements)
|
||||
else:
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.spatial_elements = set(self.file.by_type("IfcSpatialStructureElement"))
|
||||
else:
|
||||
@@ -352,24 +384,26 @@ class IfcImporter:
|
||||
else:
|
||||
project = self.file.by_type("IfcContext")[0]
|
||||
site = self.find_decomposed_ifc_class(project, "IfcSite")
|
||||
if site and self.is_element_far_away(site[0], is_meters=False):
|
||||
return self.guess_georeferencing(site[0])
|
||||
if site and self.is_element_far_away(site):
|
||||
return self.guess_georeferencing(site)
|
||||
building = self.find_decomposed_ifc_class(project, "IfcBuilding")
|
||||
if building and self.is_element_far_away(building[0], is_meters=False):
|
||||
return self.guess_georeferencing(building[0])
|
||||
if building and self.is_element_far_away(building):
|
||||
return self.guess_georeferencing(building)
|
||||
return self.guess_absolute_coordinate()
|
||||
|
||||
def guess_georeferencing(self, element):
|
||||
if not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
if not element.ObjectPlacement or not element.ObjectPlacement.is_a("IfcLocalPlacement"):
|
||||
return
|
||||
placement = element.ObjectPlacement.RelativePlacement
|
||||
placement = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
props.blender_eastings = str(placement.Location.Coordinates[0])
|
||||
props.blender_northings = str(placement.Location.Coordinates[1])
|
||||
props.blender_orthogonal_height = str(placement.Location.Coordinates[2])
|
||||
if placement.RefDirection:
|
||||
props.blender_x_axis_abscissa = str(placement.RefDirection.DirectionRatios[0])
|
||||
props.blender_x_axis_ordinate = str(placement.RefDirection.DirectionRatios[1])
|
||||
props.blender_eastings = str(placement[0][3])
|
||||
props.blender_northings = str(placement[1][3])
|
||||
props.blender_orthogonal_height = str(placement[2][3])
|
||||
x_axis = mathutils.Vector(placement[:, 0][0:3])
|
||||
default_x_axis = mathutils.Vector((1, 0, 0))
|
||||
if (default_x_axis - x_axis).length > 0.01:
|
||||
props.blender_x_axis_abscissa = str(placement[0][0])
|
||||
props.blender_x_axis_ordinate = str(placement[1][0])
|
||||
props.has_blender_offset = True
|
||||
|
||||
def guess_absolute_coordinate(self):
|
||||
@@ -476,8 +510,8 @@ class IfcImporter:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
|
||||
obj.BIMObjectProperties.ifc_definition_id = axis.id()
|
||||
obj.matrix_world = grid_obj.matrix_world
|
||||
self.link_element(axis, obj)
|
||||
self.set_matrix_world(obj, grid_obj.matrix_world)
|
||||
grid_collection.objects.link(obj)
|
||||
|
||||
def create_type_products(self):
|
||||
@@ -504,6 +538,7 @@ class IfcImporter:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation_map.MappedRepresentation)
|
||||
mesh = self.create_mesh(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
@@ -560,19 +595,26 @@ class IfcImporter:
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self):
|
||||
products = self.create_products(self.spatial_elements)
|
||||
self.spatial_elements -= products
|
||||
products = self.create_curve_products(self.spatial_elements)
|
||||
self.spatial_elements -= products
|
||||
for element in self.spatial_elements:
|
||||
self.create_product(element)
|
||||
self.create_generic_elements(self.spatial_elements)
|
||||
|
||||
def create_annotations(self):
|
||||
self.create_generic_elements(self.annotations)
|
||||
|
||||
def create_elements(self):
|
||||
products = self.create_products(self.elements)
|
||||
self.elements -= products
|
||||
products = self.create_curve_products(self.elements)
|
||||
self.elements -= products
|
||||
for element in self.elements:
|
||||
self.create_generic_elements(self.elements)
|
||||
|
||||
def create_generic_elements(self, elements):
|
||||
# Based on my experience in viewing BIM models, representations are prioritised as follows:
|
||||
# 1. 3D Body, 2. 2D Plans, 3. Point clouds, 4. No representation
|
||||
# If an element has a representation that doesn't follow 1, 2, or 3, it will not show by default.
|
||||
# The user can load them later if they want to view them.
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_curve_products(elements)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
for element in elements:
|
||||
self.create_product(element)
|
||||
|
||||
def create_products(self, products):
|
||||
@@ -585,6 +627,9 @@ class IfcImporter:
|
||||
)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings, self.file, include=products)
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
@@ -601,26 +646,25 @@ class IfcImporter:
|
||||
)
|
||||
)
|
||||
checkpoint = time.time()
|
||||
# self.update_progress(((total / approx_total_products) * progress_range) + start_progress)
|
||||
self.update_progress((iterator.progress() / 100 * progress_range) + start_progress)
|
||||
shape = iterator.get()
|
||||
if shape:
|
||||
product = self.file.by_id(shape.guid)
|
||||
# Facetation is to accommodate broken Revit files
|
||||
# See https://forums.buildingsmart.org/t/suggestions-on-how-to-improve-clarity-of-representation-context-usage-in-documentation/3663/6?u=moult
|
||||
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
|
||||
# We only load a single context, and we prioritise the Body context. See #1290.
|
||||
pass
|
||||
else:
|
||||
if self.body_contexts:
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
else:
|
||||
if shape.context not in ["Body", "Facetation"] and IfcStore.get_element(shape.guid):
|
||||
# We only load a single context, and we prioritise the Body context. See #1290.
|
||||
pass
|
||||
else:
|
||||
self.create_product(product, shape)
|
||||
results.add(product)
|
||||
if not iterator.next():
|
||||
break
|
||||
print("Done creating geometry")
|
||||
return results
|
||||
|
||||
def create_annotation(self):
|
||||
self.create_curve_products(self.file.by_type("IfcAnnotation"))
|
||||
|
||||
def create_structural_items(self):
|
||||
# Create structural collections
|
||||
self.structural_member_collection = bpy.data.collections.new("Members")
|
||||
@@ -658,9 +702,69 @@ class IfcImporter:
|
||||
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
||||
|
||||
obj = bpy.data.objects.new("{}/{}".format(product.is_a(), product.Name), mesh)
|
||||
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, placement_matrix)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
|
||||
def get_pointcloud_representation(self, product):
|
||||
if hasattr(product, "Representation") and hasattr(product.Representation, "Representations"):
|
||||
representations = product.Representation.Representations
|
||||
elif hasattr(product, "RepresentationMaps") and hasattr(product.RepresentationMaps, "RepresentationMaps"):
|
||||
representations = product.RepresentationMaps
|
||||
else:
|
||||
return None
|
||||
|
||||
for representation in representations:
|
||||
if representation.RepresentationType == "PointCloud":
|
||||
return representation
|
||||
|
||||
elif self.file.schema == "IFC2X3" and representation.RepresentationType == "GeometricSet":
|
||||
for item in representation.Items:
|
||||
if not (item.is_a("IfcCartesianPointList") or item.is_a("IfcCartesianPoint")):
|
||||
break
|
||||
else:
|
||||
return representation
|
||||
|
||||
elif representation.RepresentationType == "MappedRepresentation":
|
||||
for item in representation.Items:
|
||||
mapped_representation = self.get_pointcloud_representation(item)
|
||||
if mapped_representation is not None:
|
||||
return mapped_representation
|
||||
return None
|
||||
|
||||
def create_pointclouds(self, products):
|
||||
result = set()
|
||||
for product in products:
|
||||
representation = self.get_pointcloud_representation(product)
|
||||
if representation is not None:
|
||||
pointcloud = self.create_pointcloud(product, representation)
|
||||
if pointcloud is not None:
|
||||
result.add(pointcloud)
|
||||
|
||||
return result
|
||||
|
||||
def create_pointcloud(self, product, representation):
|
||||
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcCartesianPointList"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPoint"):
|
||||
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
|
||||
|
||||
if len(vertex_list) == 0:
|
||||
return None
|
||||
|
||||
mesh_name = f"{representation.ContextOfItems.id()}/{representation.id()}"
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
mesh.from_pydata(vertex_list, [], [])
|
||||
|
||||
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))
|
||||
self.link_element(product, obj)
|
||||
return product
|
||||
|
||||
def create_curve_products(self, products):
|
||||
results = set()
|
||||
if not products:
|
||||
@@ -671,6 +775,9 @@ class IfcImporter:
|
||||
)
|
||||
else:
|
||||
iterator = ifcopenshell.geom.iterator(self.settings_2d, self.file, include=products)
|
||||
cache = IfcStore.get_cache()
|
||||
if cache:
|
||||
iterator.set_cache(cache)
|
||||
valid_file = iterator.initialize()
|
||||
if not valid_file:
|
||||
return results
|
||||
@@ -701,15 +808,16 @@ class IfcImporter:
|
||||
pass
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
mesh = self.create_camera(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
elif element.is_a("IfcAnnotation") and self.is_curve_annotation(element):
|
||||
mesh = self.create_curve(element, shape)
|
||||
self.link_mesh(shape, mesh)
|
||||
elif shape:
|
||||
mesh_name = self.get_mesh_name(shape.geometry)
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
if mesh is None:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
if "-" in shape.geometry.id:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id.split("-")[0])
|
||||
else:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(shape.geometry.id)
|
||||
self.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
else:
|
||||
mesh = None
|
||||
@@ -723,13 +831,13 @@ class IfcImporter:
|
||||
mat = np.array(
|
||||
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
|
||||
)
|
||||
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, mat)
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, mat))
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif mesh:
|
||||
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
|
||||
self.material_creator.create(element, obj, mesh)
|
||||
elif hasattr(element, "ObjectPlacement"):
|
||||
obj.matrix_world = self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element))
|
||||
self.set_matrix_world(obj, self.apply_blender_offset_to_matrix_world(obj, self.get_element_matrix(element)))
|
||||
|
||||
self.add_opening_relation(element, obj)
|
||||
|
||||
@@ -845,6 +953,34 @@ class IfcImporter:
|
||||
curve.bevel_depth = self.unit_scale * item.Radius
|
||||
return curve
|
||||
|
||||
def create_native_annotation(self, element, mesh_name):
|
||||
# TODO: georeferencing?
|
||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
polyline = curve.splines.new("POLY")
|
||||
|
||||
for representation in self.native_data[element.GlobalId]["representations"]:
|
||||
for item in representation["raw"].Items:
|
||||
# TODO: support inner radius, start param, and end param
|
||||
geometry = ifcopenshell.geom.create_shape(self.settings_native, item.Directrix)
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = representation["matrix"] @ mathutils.Vector(v2)
|
||||
|
||||
curve.bevel_depth = self.unit_scale * item.Radius
|
||||
return curve
|
||||
|
||||
def merge_by_class(self):
|
||||
merge_set = {}
|
||||
for obj in self.added_data.values():
|
||||
@@ -993,6 +1129,7 @@ class IfcImporter:
|
||||
def create_spatial_decomposition_collections(self):
|
||||
for rel_aggregate in self.project["ifc"].IsDecomposedBy or []:
|
||||
self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects)
|
||||
self.create_views_collection()
|
||||
self.create_type_collection()
|
||||
|
||||
def create_type_collection(self):
|
||||
@@ -1004,6 +1141,22 @@ class IfcImporter:
|
||||
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)
|
||||
for element in self.file.by_type("IfcAnnotation"):
|
||||
if element.ObjectType == "DRAWING":
|
||||
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
collection = bpy.data.collections.new("IfcGroup/" + group.Name)
|
||||
self.collections[group.GlobalId] = collection
|
||||
view_collection.children.link(collection)
|
||||
|
||||
def create_spatial_decomposition_collection(self, parent, related_objects):
|
||||
for element in related_objects:
|
||||
if element not in self.spatial_elements:
|
||||
@@ -1019,6 +1172,8 @@ class IfcImporter:
|
||||
def create_aggregate_collections(self):
|
||||
if self.ifc_import_settings.has_filter:
|
||||
rel_aggregates = [e.IsDecomposedBy[0] for e in self.elements if e.IsDecomposedBy]
|
||||
rel_aggregates += [e.Decomposes[0] for e in self.elements if e.Decomposes]
|
||||
rel_aggregates = set(rel_aggregates)
|
||||
else:
|
||||
rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")]
|
||||
|
||||
@@ -1117,8 +1272,12 @@ class IfcImporter:
|
||||
def place_object_in_decomposition_collection(self, element, obj):
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
return self.collections[element.GlobalId].objects.link(obj)
|
||||
collection = self.collections[element.GlobalId]
|
||||
collection.name = obj.name
|
||||
return collection.objects.link(obj)
|
||||
elif getattr(element, "Decomposes", None):
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
return self.collections[aggregate.GlobalId].objects.link(obj)
|
||||
@@ -1128,6 +1287,8 @@ class IfcImporter:
|
||||
def place_object_in_spatial_decomposition_collection(self, element, obj):
|
||||
if element.is_a("IfcProject"):
|
||||
return
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
return
|
||||
elif element.GlobalId in self.collections:
|
||||
return self.collections[element.GlobalId].objects.link(obj)
|
||||
elif element.is_a("IfcTypeObject"):
|
||||
@@ -1138,16 +1299,9 @@ class IfcImporter:
|
||||
return self.structural_member_collection.objects.link(obj)
|
||||
elif element.is_a("IfcStructuralConnection"):
|
||||
return self.structural_connection_collection.objects.link(obj)
|
||||
elif element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
view_collection = bpy.data.collections.get("Views")
|
||||
if not view_collection:
|
||||
view_collection = bpy.data.collections.new("Views")
|
||||
bpy.context.scene.collection.children.link(view_collection)
|
||||
elif element.is_a("IfcAnnotation") and self.is_drawing_annotation(element):
|
||||
group = [r for r in element.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
drawing_collection = bpy.data.collections.new("IfcGroup/" + group.Name)
|
||||
view_collection.children.link(drawing_collection)
|
||||
drawing_collection.objects.link(obj)
|
||||
return
|
||||
return self.collections[group.GlobalId].objects.link(obj)
|
||||
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if container:
|
||||
@@ -1163,6 +1317,33 @@ class IfcImporter:
|
||||
self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
|
||||
def is_curve_annotation(self, element):
|
||||
return element.ObjectType in [
|
||||
"DIMENSION",
|
||||
"EQUAL_DIMENSION",
|
||||
"MISC",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT_LEADER",
|
||||
]
|
||||
|
||||
def is_drawing_annotation(self, element):
|
||||
return element.ObjectType in [
|
||||
"BREAKLINE",
|
||||
"DIMENSION",
|
||||
"DRAWING",
|
||||
"EQUAL_DIMENSION",
|
||||
"GRID",
|
||||
"HIDDEN_LINE",
|
||||
"MISC",
|
||||
"PLAN_LEVEL",
|
||||
"SECTION_LEVEL",
|
||||
"STAIR_ARROW",
|
||||
"TEXT",
|
||||
"TEXT_LEADER",
|
||||
]
|
||||
|
||||
def get_element_matrix(self, element):
|
||||
result = ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement)
|
||||
result[0][3] *= self.unit_scale
|
||||
@@ -1289,6 +1470,31 @@ class IfcImporter:
|
||||
camera.BIMCameraProperties.custom_diagram_scale = pset["Scale"]
|
||||
return camera
|
||||
|
||||
def create_curve(self, element, shape):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
|
||||
curve = bpy.data.curves.new(self.get_mesh_name(geometry), type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = mathutils.Vector(v2)
|
||||
return curve
|
||||
|
||||
def create_mesh(self, element, shape):
|
||||
try:
|
||||
if hasattr(shape, "geometry"):
|
||||
@@ -1397,6 +1603,20 @@ class IfcImporter:
|
||||
self.added_data[element.id()] = obj
|
||||
IfcStore.link_element(element, obj)
|
||||
|
||||
def link_mesh(self, shape, mesh):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
if "-" in geometry.id:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id.split("-")[0])
|
||||
else:
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(geometry.id)
|
||||
|
||||
def set_matrix_world(self, obj, matrix_world):
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
def __init__(self):
|
||||
@@ -1413,14 +1633,30 @@ class IfcImportSettings:
|
||||
self.should_offset_model = False
|
||||
self.model_offset_coordinates = (0, 0, 0)
|
||||
self.has_filter = None
|
||||
self.elements = ""
|
||||
self.should_filter_spatial_elements = True
|
||||
self.elements = set()
|
||||
self.collection_mode = "DECOMPOSITION"
|
||||
|
||||
@staticmethod
|
||||
def factory(context, input_file, logger):
|
||||
scene_diff = context.scene.DiffProperties
|
||||
props = context.scene.BIMProjectProperties
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
settings.logger = logger
|
||||
settings.diff_file = scene_diff.diff_json_file
|
||||
settings.collection_mode = props.collection_mode
|
||||
settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing
|
||||
settings.should_merge_by_class = props.should_merge_by_class
|
||||
settings.should_merge_by_material = props.should_merge_by_material
|
||||
settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour
|
||||
settings.should_clean_mesh = props.should_clean_mesh
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.should_offset_model = props.should_offset_model
|
||||
settings.model_offset_coordinates = (
|
||||
[float(o) for o in props.model_offset_coordinates.split(",")]
|
||||
if props.model_offset_coordinates
|
||||
else (0, 0, 0)
|
||||
)
|
||||
return settings
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.aggregate as core
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
@@ -92,10 +95,34 @@ class AddAggregate(bpy.types.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return {"FINISHED"}
|
||||
|
||||
aggregate_collection = bpy.data.collections.new("IfcElementAssembly/Assembly")
|
||||
context.scene.collection.children.link(aggregate_collection)
|
||||
aggregate = bpy.data.objects.new("Assembly", None)
|
||||
aggregate_collection.objects.link(aggregate)
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class="IfcElementAssembly")
|
||||
bpy.ops.bim.assign_object(related_object=obj.name, relating_object=aggregate.name)
|
||||
|
||||
tool.Collector.sync(obj)
|
||||
current_aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
current_container = ifcopenshell.util.element.get_container(element)
|
||||
if current_aggregate:
|
||||
core.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=tool.Ifc.get_object(current_aggregate),
|
||||
related_obj=aggregate,
|
||||
)
|
||||
elif current_container:
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=tool.Ifc.get_object(current_container),
|
||||
element_obj=aggregate,
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -28,6 +28,7 @@ class BIM_PT_aggregate(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -24,6 +24,7 @@ classes = (
|
||||
operator.DisableEditingAttributes,
|
||||
operator.EditAttributes,
|
||||
operator.GenerateGlobalId,
|
||||
operator.CopyAttributeToSelection,
|
||||
prop.BIMAttributeProperties,
|
||||
ui.BIM_PT_object_attributes,
|
||||
ui.BIM_PT_material_attributes,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
AttributesData.is_loaded = False
|
||||
MaterialAttributesData.is_loaded = False
|
||||
|
||||
|
||||
class AttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
data = element.get_info()
|
||||
if hasattr(element, "GlobalId"):
|
||||
excluded_keys = ["id", "type"]
|
||||
else:
|
||||
excluded_keys = ["type"]
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in excluded_keys:
|
||||
continue
|
||||
if key == "id":
|
||||
key = "STEP ID"
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
|
||||
class MaterialAttributesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object.active_material)
|
||||
data = element.get_info()
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in ["id", "type"]:
|
||||
continue
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
@@ -20,10 +20,21 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.attribute as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
@@ -40,22 +51,9 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
new = props.attributes.add()
|
||||
if attribute["type"] == "entity" or (attribute["type"] == "list" and attribute["list_type"] == "entity"):
|
||||
continue
|
||||
new.name = attribute["name"]
|
||||
new.is_null = attribute["is_null"]
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
new.string_value = attribute["value"] or ""
|
||||
elif attribute["type"] == "integer":
|
||||
new.int_value = attribute["value"] or 0
|
||||
elif attribute["type"] == "float":
|
||||
new.float_value = attribute["value"] or 0.0
|
||||
elif attribute["type"] == "enum":
|
||||
new.enum_items = json.dumps(attribute["enum_items"])
|
||||
if attribute["value"]:
|
||||
new.enum_value = attribute["value"]
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
blenderbim.bim.helper.import_attributes2(tool.Ifc.get().by_id(oprops.ifc_definition_id), props.attributes)
|
||||
props.is_editing_attributes = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -77,16 +75,13 @@ class DisableEditingAttributes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditAttributes(bpy.types.Operator):
|
||||
class EditAttributes(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_attributes"
|
||||
bl_label = "Edit Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
if self.obj_type == "Object":
|
||||
@@ -139,3 +134,14 @@ class GenerateGlobalId(bpy.types.Operator):
|
||||
global_id.data_type = "string"
|
||||
global_id.string_value = ifcopenshell.guid.new()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CopyAttributeToSelection(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.copy_attribute_to_selection"
|
||||
bl_label = "Copy Attribute To Selection"
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
|
||||
for obj in context.selected_objects:
|
||||
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
|
||||
|
||||
@@ -16,17 +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 blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.attribute.data import Data
|
||||
from blenderbim.bim.module.attribute.data import AttributesData, MaterialAttributesData
|
||||
|
||||
|
||||
def draw_ui(context, layout, obj_type):
|
||||
def draw_ui(context, layout, obj_type, attributes):
|
||||
obj = context.active_object if obj_type == "Object" else context.active_object.active_material
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
if oprops.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
@@ -37,46 +36,17 @@ def draw_ui(context, layout, obj_type):
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["type"] == "entity":
|
||||
continue
|
||||
row = layout.row(align=True)
|
||||
blender_attribute = props.attributes.get(attribute["name"])
|
||||
if attribute["type"] == "string" or attribute["type"] == "list":
|
||||
row.prop(blender_attribute, "string_value", text=attribute["name"])
|
||||
elif attribute["type"] == "integer":
|
||||
row.prop(blender_attribute, "int_value", text=attribute["name"])
|
||||
elif attribute["type"] == "float":
|
||||
row.prop(blender_attribute, "float_value", text=attribute["name"])
|
||||
elif attribute["type"] == "enum":
|
||||
row.prop(blender_attribute, "enum_value", text=attribute["name"])
|
||||
|
||||
if attribute["name"] == "GlobalId":
|
||||
row.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
if attribute["is_optional"]:
|
||||
row.prop(
|
||||
blender_attribute,
|
||||
"is_null",
|
||||
icon="RADIOBUT_OFF" if blender_attribute.is_null else "RADIOBUT_ON",
|
||||
text="",
|
||||
)
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj_type = obj_type
|
||||
op.obj = obj.name
|
||||
|
||||
if "GlobalId" not in [a["name"] for a in Data.products[oprops.ifc_definition_id]]:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="STEP ID")
|
||||
row.label(text=str(oprops.ifc_definition_id))
|
||||
|
||||
for attribute in Data.products[oprops.ifc_definition_id]:
|
||||
if attribute["value"] is None or attribute["type"] == "entity":
|
||||
continue
|
||||
for attribute in attributes:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=str(attribute["value"]))
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
# TODO: reimplement, see #1222
|
||||
# if "IfcSite/" in context.active_object.name or "IfcBuilding/" in context.active_object.name:
|
||||
@@ -89,6 +59,8 @@ class BIM_PT_object_attributes(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -99,7 +71,9 @@ class BIM_PT_object_attributes(Panel):
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Object")
|
||||
if not AttributesData.is_loaded:
|
||||
AttributesData.load()
|
||||
draw_ui(context, self.layout, "Object", AttributesData.data["attributes"])
|
||||
|
||||
|
||||
class BIM_PT_material_attributes(Panel):
|
||||
@@ -108,6 +82,8 @@ class BIM_PT_material_attributes(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -119,4 +95,6 @@ class BIM_PT_material_attributes(Panel):
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
draw_ui(context, self.layout, "Material")
|
||||
if not MaterialAttributesData.is_loaded:
|
||||
MaterialAttributesData.load()
|
||||
draw_ui(context, self.layout, "Material", MaterialAttributesData.data["attributes"])
|
||||
|
||||
@@ -93,6 +93,7 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
topic = bcfxml.get_topic(self.topic_guid)
|
||||
bcfxml.get_header(self.topic_guid)
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
@@ -841,6 +842,8 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
obj.data.angle = 2 * atan(
|
||||
(0.5 * cam_height) / (0.5 * cam_width / tan(radians(camera.field_of_view) / 2))
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
z_axis = Vector(
|
||||
(-camera.camera_direction.x, -camera.camera_direction.y, -camera.camera_direction.z)
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_bcf(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration_and_data_exchange"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_qa(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.use_property_split = True
|
||||
|
||||
@@ -17,14 +17,28 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui
|
||||
from . import ui, operator, prop
|
||||
|
||||
classes = (ui.BIM_PT_boundary,)
|
||||
classes = (
|
||||
operator.LoadProjectSpaceBoundaries,
|
||||
operator.LoadSpaceBoundaries,
|
||||
operator.LoadBoundary,
|
||||
operator.SelectProjectBoundaries,
|
||||
operator.ColourByRelatedBuildingElement,
|
||||
operator.EnableEditingBoundary,
|
||||
operator.DisableEditingBoundary,
|
||||
operator.EditBoundaryAttributes,
|
||||
operator.UpdateBoundaryGeometry,
|
||||
ui.BIM_PT_Boundary,
|
||||
ui.BIM_PT_SpaceBoundaries,
|
||||
ui.BIM_PT_SceneBoundaries,
|
||||
prop.BIMBoundaryProperties,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
del bpy.types.Object.bim_boundary_properties
|
||||
|
||||
@@ -0,0 +1,320 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import bpy
|
||||
import mathutils
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
|
||||
def get_boundaries_collection(blender_space):
|
||||
space_collection = bpy.data.collections.get(blender_space.name)
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
boundaries_collection = bpy.data.collections.new(collection_name)
|
||||
space_collection.children.link(boundaries_collection)
|
||||
return boundaries_collection
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self):
|
||||
self.ifc_file = None
|
||||
self.logger = None
|
||||
self.ifc_importer = None
|
||||
self.settings = None
|
||||
self.load_settings()
|
||||
self.load_importer()
|
||||
|
||||
def load_settings(self):
|
||||
self.settings = ifcopenshell.geom.settings()
|
||||
self.settings.set(self.settings.EXCLUDE_SOLIDS_AND_SURFACES, False)
|
||||
self.settings.set(self.settings.USE_BREP_DATA, False)
|
||||
|
||||
def load_importer(self):
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
self.logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
|
||||
self.ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
self.ifc_importer.file = self.ifc_file
|
||||
|
||||
def load_boundary(self, boundary_id, blender_space):
|
||||
boundary = self.ifc_file.by_id(boundary_id)
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
return obj
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# workaround for unvalid geometry provided by Revit. See https://github.com/IfcOpenShell/IfcOpenShell/issues/635#issuecomment-770366838
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
|
||||
obj.matrix_world = blender_space.matrix_world
|
||||
boundaries_collection = get_boundaries_collection(blender_space)
|
||||
boundaries_collection.objects.link(obj)
|
||||
tool.Ifc.link(boundary, obj)
|
||||
return obj
|
||||
|
||||
|
||||
class LoadProjectSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_project_space_boundaries"
|
||||
bl_label = "Load all project space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for space_id, boundaries_id in Data.spaces.items():
|
||||
blender_space = tool.Ifc.get_object(tool.Ifc.get().by_id(space_id))
|
||||
for boundary_id in boundaries_id:
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.load_boundary"
|
||||
bl_label = "Load boundary"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
boundary_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
loader = Loader()
|
||||
blender_space = context.active_object
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
obj = loader.load_boundary(self.boundary_id, blender_space)
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadSpaceBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_space_boundaries"
|
||||
bl_label = "Load selected space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
blender_space = context.active_object
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
loader = Loader()
|
||||
if not Data.is_loaded:
|
||||
Data.load(tool.Ifc.get())
|
||||
for boundary_id in Data.spaces.get(oprops.ifc_definition_id, []):
|
||||
loader.load_boundary(boundary_id, blender_space)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectProjectBoundaries(bpy.types.Operator):
|
||||
bl_idname = "bim.select_project_space_boundaries"
|
||||
bl_label = "Select all project space boundaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
obj.select_set(False)
|
||||
ifc_file = tool.Ifc.get()
|
||||
for boundary_id in Data.boundaries:
|
||||
boundary = ifc_file.by_id(boundary_id)
|
||||
obj = tool.Ifc.get_object(boundary)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_colour(ifc_boundary):
|
||||
"""Return a color depending on IfcClass given"""
|
||||
product_colors = {
|
||||
"IfcWall": (0.7, 0.3, 0, 1),
|
||||
"IfcWindow": (0, 0.7, 1, 1),
|
||||
"IfcSlab": (0.7, 0.7, 0.5, 1),
|
||||
"IfcRoof": (0, 0.3, 0, 1),
|
||||
"IfcDoor": (1, 1, 1, 1),
|
||||
}
|
||||
if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL":
|
||||
return (1, 0, 1, 1)
|
||||
|
||||
element = ifc_boundary.RelatedBuildingElement
|
||||
if not element:
|
||||
return (1, 0, 0, 1)
|
||||
for product, colour in product_colors.items():
|
||||
if element.is_a(product):
|
||||
return colour
|
||||
return (0, 0, 0, 1)
|
||||
|
||||
|
||||
class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
bl_idname = "bim.colour_by_related_building_element"
|
||||
bl_label = "Apply colour based on related building elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
self.store_state(context)
|
||||
result = self._execute(context)
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
for obj in context.visible_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if not element.is_a("IfcRelSpaceBoundary"):
|
||||
continue
|
||||
obj.color = get_colour(element)
|
||||
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
|
||||
if areas:
|
||||
areas[0].spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
|
||||
def store_state(self, context):
|
||||
areas = [a for a in context.screen.areas if a.type == "VIEW_3D"]
|
||||
if areas:
|
||||
self.transaction_data = {"area": areas[0], "color_type": areas[0].spaces[0].shading.color_type}
|
||||
|
||||
def rollback(self, data):
|
||||
if data:
|
||||
data["area"].spaces[0].shading.color_type = data["color_type"]
|
||||
|
||||
def commit(self, data):
|
||||
if data:
|
||||
data["area"].spaces[0].shading.color_type = "OBJECT"
|
||||
|
||||
|
||||
EDITABLE_ATTRIBUTES = {
|
||||
"RelatingSpace": "relating_space",
|
||||
"RelatedBuildingElement": "related_building_element",
|
||||
"ParentBoundary": "parent_boundary",
|
||||
"CorrespondingBoundary": "corresponding_boundary",
|
||||
}
|
||||
|
||||
|
||||
class EnableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_boundary"
|
||||
bl_label = "Edit boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = True
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
entity = getattr(boundary, ifc_attribute, None)
|
||||
if not entity:
|
||||
continue
|
||||
obj = tool.Ifc.get_object(entity)
|
||||
if entity and obj:
|
||||
setattr(bprops, blender_property, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingBoundary(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_boundary"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = False
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
setattr(bprops, blender_property, None)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditBoundaryAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_boundary_attributes"
|
||||
bl_label = "Disable editing boundary relations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
attributes = dict()
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
attributes[blender_property] = entity
|
||||
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
|
||||
bpy.ops.bim.disable_editing_boundary()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def polyline_from_indexes(mesh, indexes):
|
||||
return tuple(mesh.vertices[i].co for i in indexes)
|
||||
|
||||
|
||||
def polyline_to_2d(polyline, placement_matrix):
|
||||
matrix_inv = placement_matrix.inverted()
|
||||
return tuple((matrix_inv @ v).to_2d() for v in polyline)
|
||||
|
||||
|
||||
class UpdateBoundaryGeometry(bpy.types.Operator):
|
||||
bl_idname = "bim.update_boundary_geometry"
|
||||
bl_label = "Update boundary geometry"
|
||||
bl_description = """
|
||||
Update boundary connection geometry from mesh.
|
||||
Mesh must lie on a single plane. It should look like a face or a face with holes.
|
||||
"""
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
helper = Helper(ifc_file)
|
||||
mesh = context.active_object.data
|
||||
curves = helper.auto_detect_curve_bounded_plane(mesh)
|
||||
outer_boundary = polyline_from_indexes(mesh, curves["outer_curve"])
|
||||
inner_boundaries = tuple(polyline_from_indexes(mesh, boundary) for boundary in curves["inner_curves"])
|
||||
|
||||
# Create placement matrix
|
||||
location = outer_boundary[0]
|
||||
i = (outer_boundary[1] - outer_boundary[0]).normalized()
|
||||
k = mesh.polygons[0].normal
|
||||
j = k.cross(i)
|
||||
matrix = mathutils.Matrix()
|
||||
matrix[0].xyz = i
|
||||
matrix[1].xyz = j
|
||||
matrix[2].xyz = k
|
||||
matrix.transpose()
|
||||
matrix.translation = location
|
||||
|
||||
settings = {
|
||||
"rel_space_boundary": tool.Ifc.get_entity(context.active_object),
|
||||
"outer_boundary": polyline_to_2d(outer_boundary, matrix),
|
||||
"inner_boundaries": tuple(polyline_to_2d(boundary, matrix) for boundary in inner_boundaries),
|
||||
"location": location,
|
||||
"axis": k,
|
||||
"ref_direction": i,
|
||||
}
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", ifc_file, **settings)
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,60 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def space_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
|
||||
return False
|
||||
|
||||
|
||||
def boundary_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
return False
|
||||
|
||||
|
||||
def element_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcElement")
|
||||
return False
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
|
||||
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
|
||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
@@ -20,16 +20,37 @@ import bpy
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
from ifcopenshell.api.boundary.data import Data
|
||||
|
||||
|
||||
class BIM_PT_boundary(Panel):
|
||||
class BIM_PT_SceneBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_idname = "BIM_PT_boundary"
|
||||
bl_id_name = "BIM_PT_scene_boundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.load_project_space_boundaries")
|
||||
row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
|
||||
row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
|
||||
|
||||
|
||||
class BIM_PT_Boundary(Panel):
|
||||
bl_label = "IFC Space Boundary"
|
||||
bl_idname = "BIM_PT_Boundary"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -40,15 +61,97 @@ class BIM_PT_boundary(Panel):
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
|
||||
return False
|
||||
return True
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
|
||||
def draw(self, context):
|
||||
self.oprops = context.active_object.BIMObjectProperties
|
||||
if not Data.is_loaded:
|
||||
Data.load(IfcStore.get_file())
|
||||
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
|
||||
boundary = Data.boundaries[boundary_id]
|
||||
props = context.active_object.BIMObjectProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
element = ifc_file.by_id(props.ifc_definition_id)
|
||||
boundary = ifc_file.by_id(props.ifc_definition_id)
|
||||
self.bprops = context.active_object.bim_boundary_properties
|
||||
if self.bprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
row.operator("bim.disable_editing_boundary", icon="CANCEL", text="")
|
||||
self.draw_relation_editor(boundary, "RelatingSpace", "relating_space")
|
||||
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
|
||||
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
|
||||
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text=f"{boundary_id}", icon="GHOST_ENABLED")
|
||||
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
|
||||
self.draw_relation_data(boundary, "RelatingSpace")
|
||||
self.draw_relation_data(boundary, "RelatedBuildingElement")
|
||||
self.draw_relation_data(boundary, "ParentBoundary")
|
||||
self.draw_relation_data(boundary, "CorrespondingBoundary")
|
||||
if hasattr(boundary, "InnerBoundaries"):
|
||||
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="InnerBoundaries")
|
||||
row.label(text=f"[{i}]")
|
||||
row.label(text=f"{inner_boundary.is_a()}/{inner_boundary.Name}")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.update_boundary_geometry")
|
||||
|
||||
def draw_relation_data(self, boundary, ifc_attribute: str):
|
||||
row = self.layout.row(align=True)
|
||||
if hasattr(boundary, ifc_attribute):
|
||||
row.label(text=ifc_attribute)
|
||||
entity = getattr(boundary, ifc_attribute)
|
||||
if entity:
|
||||
row.label(text=f"{entity.is_a()}/{entity.Name}")
|
||||
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
|
||||
op.global_id = entity.GlobalId
|
||||
else:
|
||||
row.label(text="")
|
||||
|
||||
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
|
||||
if hasattr(boundary, ifc_attribute):
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, blender_property)
|
||||
|
||||
|
||||
class BIM_PT_SpaceBoundaries(Panel):
|
||||
bl_label = "IFC Space Boundaries"
|
||||
bl_idname = "BIM_PT_SpaceBoundaries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
for ifc_class in valid_classes:
|
||||
if entity.is_a(ifc_class):
|
||||
return True
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.active_object.BIMObjectProperties
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_space_boundaries")
|
||||
if not Data.is_loaded:
|
||||
Data.load(self.ifc_file)
|
||||
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
|
||||
boundary_data = Data.boundaries[boundary_id]
|
||||
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
|
||||
row = self.layout.row()
|
||||
if building_element:
|
||||
bld_el_description = f"{building_element.is_a()}/{building_element.Name}"
|
||||
else:
|
||||
bld_el_description = None
|
||||
row.label(text=f"{boundary_id} > {bld_el_description}", icon="GHOST_ENABLED")
|
||||
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.boundary_id = boundary_id
|
||||
|
||||
+28
-12
@@ -16,17 +16,33 @@
|
||||
# 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 test.bim.bootstrap
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddBrick,
|
||||
operator.AddBrickFeed,
|
||||
operator.AssignBrickReference,
|
||||
operator.CloseBrickProject,
|
||||
operator.ConvertBrickProject,
|
||||
operator.ConvertIfcToBrick,
|
||||
operator.LoadBrickProject,
|
||||
operator.NewBrickFile,
|
||||
operator.RefreshBrickViewer,
|
||||
operator.RewindBrickClass,
|
||||
operator.ViewBrickClass,
|
||||
operator.ViewBrickItem,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
ui.BIM_UL_bricks,
|
||||
)
|
||||
|
||||
|
||||
class TestExecuteIfcPatch(test.bim.bootstrap.NewFile):
|
||||
@test.bim.bootstrap.scenario
|
||||
def test_executing_ifcpatch(self):
|
||||
return """
|
||||
Given I set "scene.BIMPatchProperties.ifc_patch_recipes" to "OffsetObjectPlacements"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_input" to "{cwd}/test/files/basic.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_output" to "{cwd}/test/files/basic-patched.ifc"
|
||||
And I set "scene.BIMPatchProperties.ifc_patch_args" to "[123454321,0,0,0]"
|
||||
When I press "bim.execute_ifc_patch"
|
||||
Then the file "{cwd}/test/files/basic-patched.ifc" should contain "123454321"
|
||||
"""
|
||||
def register():
|
||||
bpy.types.Scene.BIMBrickProperties = bpy.props.PointerProperty(type=prop.BIMBrickProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBrickProperties
|
||||
@@ -0,0 +1,177 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
try:
|
||||
from rdflib import URIRef, BNode
|
||||
except:
|
||||
# See #1860
|
||||
print("Warning: brickschema not available.")
|
||||
|
||||
|
||||
def refresh():
|
||||
BrickschemaData.is_loaded = False
|
||||
BrickschemaReferencesData.is_loaded = False
|
||||
|
||||
|
||||
class BrickschemaData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
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(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
try:
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
except:
|
||||
return []
|
||||
results = []
|
||||
uri = brick.uri
|
||||
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 .
|
||||
OPTIONAL {
|
||||
{ ?name rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?name a brick:EntityProperty . }
|
||||
?value ?sp ?sv }
|
||||
}
|
||||
""".replace(
|
||||
"{uri}", uri
|
||||
)
|
||||
)
|
||||
|
||||
for row in query:
|
||||
name = row.get("name").toPython().split("#")[-1]
|
||||
value = row.get("value")
|
||||
results.append(
|
||||
{
|
||||
"name": name,
|
||||
"value": value.toPython().split("#")[-1],
|
||||
"is_uri": isinstance(value, URIRef),
|
||||
"value_uri": value.toPython(),
|
||||
"is_globalid": name == "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], ""))
|
||||
return results
|
||||
|
||||
|
||||
class BrickschemaReferencesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {"is_loaded": cls.get_is_loaded(), "libraries": cls.libraries(), "references": cls.references()}
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
|
||||
@classmethod
|
||||
def libraries(cls):
|
||||
results = []
|
||||
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
elif ".ttl" in library.Location:
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
|
||||
if rel.is_a("IfcRelAssociatesLibrary"):
|
||||
reference = rel.RelatingLibrary
|
||||
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
|
||||
continue
|
||||
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
|
||||
continue
|
||||
results.append(
|
||||
{
|
||||
"id": reference.id(),
|
||||
"identification": reference.ItemReference
|
||||
if tool.Ifc.get_schema() == "IFC2X3"
|
||||
else reference.Identification,
|
||||
"name": reference.Name or "Unnamed",
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -0,0 +1,174 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
self._execute(context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
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)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_class(tool.Brick, brick_class=self.brick_class)
|
||||
|
||||
|
||||
class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_item"
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item)
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.rewind_brick_class(tool.Brick)
|
||||
|
||||
|
||||
class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.close_brick_project"
|
||||
bl_label = "Close Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.close_brick_project(tool.Brick)
|
||||
|
||||
|
||||
class ConvertBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_brick_project"
|
||||
bl_label = "Convert Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_brick_project(tool.Ifc, tool.Brick)
|
||||
|
||||
|
||||
class AssignBrickReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_brick_reference"
|
||||
bl_label = "Assign Brick Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.assign_brick_reference(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
obj=context.active_object,
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)),
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
|
||||
|
||||
class AddBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick"
|
||||
bl_label = "Add Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
core.add_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
obj=context.active_object,
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_equipment_class,
|
||||
library=library,
|
||||
)
|
||||
|
||||
|
||||
class AddBrickFeed(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_feed"
|
||||
bl_label = "Add Brick Feed"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_brick_feed(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
source=[o for o in context.selected_objects if o != context.active_object][0],
|
||||
destination=context.active_object,
|
||||
)
|
||||
|
||||
|
||||
class ConvertIfcToBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_ifc_to_brick"
|
||||
bl_label = "Convert IFC To Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
core.convert_ifc_to_brick(tool.Brick, namespace=props.namespace, library=library)
|
||||
|
||||
|
||||
class NewBrickFile(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.new_brick_file"
|
||||
bl_label = "New Brick File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.new_brick_file(tool.Brick)
|
||||
|
||||
|
||||
class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
@@ -0,0 +1,71 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
BrickschemaData.is_loaded = False
|
||||
|
||||
|
||||
def get_libraries(self, context):
|
||||
if not BrickschemaReferencesData.is_loaded:
|
||||
BrickschemaReferencesData.load()
|
||||
return BrickschemaReferencesData.data["libraries"]
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["namespaces"]
|
||||
|
||||
|
||||
def get_brick_equipment_classes(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
return BrickschemaData.data["brick_equipment_classes"]
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
label: StringProperty(name="Label")
|
||||
uri: StringProperty(name="URI")
|
||||
total_items: IntProperty(name="Total Items")
|
||||
|
||||
|
||||
class BIMBrickProperties(PropertyGroup):
|
||||
active_brick_class: StringProperty(name="Active Brick Class")
|
||||
brick_breadcrumbs: CollectionProperty(name="Brick Breadcrumbs", type=StrProperty)
|
||||
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)
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_equipment_class: EnumProperty(name="Brick Equipment Class", items=get_brick_equipment_classes)
|
||||
@@ -0,0 +1,128 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.module.brick.data import BrickschemaData, BrickschemaReferencesData
|
||||
|
||||
|
||||
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"
|
||||
|
||||
def draw(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaData.data["is_loaded"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.new_brick_file", text="Create Project")
|
||||
row.operator("bim.load_brick_project", text="Load Project")
|
||||
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")
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "namespace", text="")
|
||||
row.prop(self.props, "brick_equipment_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
row.operator("bim.add_brick_feed", text="", icon="PLUGIN")
|
||||
|
||||
self.layout.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
for attribute in BrickschemaData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
if attribute["is_uri"]:
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = attribute["value_uri"]
|
||||
if attribute["is_globalid"]:
|
||||
op = row.operator("bim.select_global_id", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.global_id = attribute["value"]
|
||||
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
bl_label = "IFC Brickschema References"
|
||||
bl_idname = "BIM_PT_ifc_brickschema_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not BrickschemaReferencesData.is_loaded:
|
||||
BrickschemaReferencesData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaReferencesData.data["is_loaded"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Brickschema Project Loaded")
|
||||
return
|
||||
|
||||
if not BrickschemaReferencesData.data["libraries"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.assign_brick_reference", icon="ADD")
|
||||
row.operator("bim.convert_ifc_to_brick", icon="IMPORT")
|
||||
|
||||
if not BrickschemaReferencesData.data["references"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No References")
|
||||
|
||||
for reference in BrickschemaReferencesData.data["references"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=reference["identification"], icon="ASSET_MANAGER")
|
||||
row.label(text=reference["name"])
|
||||
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
|
||||
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
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))
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_classifications(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_and_template_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -86,6 +87,7 @@ class BIM_PT_classification_references(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_cobie(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration_and_data_exchange"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_misc"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -73,6 +74,7 @@ class BIM_PT_object_constraints(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_context(bpy.types.Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -31,6 +31,7 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_4D5D"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -22,10 +22,13 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.AddCsvAttribute,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportIfcCsv,
|
||||
operator.EyedropIfcCsv,
|
||||
operator.SelectCsvIfcFile,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ExportCsvAttributes,
|
||||
prop.CsvProperties,
|
||||
ui.BIM_PT_ifccsv,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,65 @@ class RemoveCsvAttribute(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_all_csv_attributes"
|
||||
bl_label = "Remove all CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.CsvProperties.csv_attributes.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = context.scene.CsvProperties.csv_attributes
|
||||
csv_attributes.clear()
|
||||
csv_json = json.load(open(self.filepath))
|
||||
i = 0
|
||||
for attribute in csv_json:
|
||||
csv_attributes.add()
|
||||
csv_attributes[i].name = attribute
|
||||
i += 1
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Attributes"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".json"
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_attributes = []
|
||||
with open(self.filepath, "w") as outfile:
|
||||
for attribute in context.scene.CsvProperties.csv_attributes:
|
||||
csv_attributes.append(attribute.name)
|
||||
|
||||
json.dump(csv_attributes, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC to CSV"
|
||||
|
||||
@@ -32,7 +32,7 @@ from bpy.props import (
|
||||
|
||||
|
||||
class CsvProperties(PropertyGroup):
|
||||
csv_ifc_file: StringProperty(default="", name="CSV IFC File")
|
||||
csv_ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
|
||||
csv_delimiter: EnumProperty(
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_ifccsv(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration_and_data_exchange"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -46,15 +47,29 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_selector")
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.add_csv_attribute")
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.add_csv_attribute")
|
||||
c = split.column()
|
||||
c.operator("bim.import_csv_attributes", icon="IMPORT", text="Load Template")
|
||||
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
if props.csv_attributes:
|
||||
row = layout.row()
|
||||
row.label()
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL", text="")
|
||||
row = layout.row()
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="Create Template")
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
@@ -62,6 +77,9 @@ class BIM_PT_ifccsv(Panel):
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
row.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.7)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT")
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -20,17 +20,21 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.PrintIfcFile,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ValidateIfcFile,
|
||||
operator.ProfileImportIFC,
|
||||
operator.CreateAllShapes,
|
||||
operator.CreateShapeFromStepId,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.InspectFromStepId,
|
||||
operator.InspectFromObject,
|
||||
operator.InspectFromStepId,
|
||||
operator.ParseExpress,
|
||||
operator.PrintIfcFile,
|
||||
operator.PrintObjectPlacement,
|
||||
operator.ProfileImportIFC,
|
||||
operator.PurgeHdf5Cache,
|
||||
operator.PurgeIfcLinks,
|
||||
operator.RewindInspector,
|
||||
operator.SelectExpressFile,
|
||||
operator.SelectHighPolygonMeshes,
|
||||
operator.SelectHighestPolygonMeshes,
|
||||
operator.ValidateIfcFile,
|
||||
prop.BIMDebugProperties,
|
||||
ui.BIM_PT_debug,
|
||||
)
|
||||
|
||||
@@ -16,20 +16,25 @@
|
||||
# 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 time
|
||||
import logging
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.debug as core
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.bim.import_ifc as import_ifc
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class PrintIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.print_ifc_file"
|
||||
bl_label = "Print IFC File"
|
||||
bl_description = "Prints the file contents in the system console.\nAccess it with Window > Toggle System Console"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -43,6 +48,7 @@ class PrintIfcFile(bpy.types.Operator):
|
||||
class PurgeIfcLinks(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_ifc_links"
|
||||
bl_label = "Purge IFC Links"
|
||||
bl_description = "Purge all definitions and references from the file.\nWarning : Cannot be undone."
|
||||
|
||||
def execute(self, context):
|
||||
for obj in bpy.data.objects:
|
||||
@@ -96,7 +102,8 @@ class ProfileImportIFC(bpy.types.Operator):
|
||||
|
||||
class CreateAllShapes(bpy.types.Operator):
|
||||
bl_idname = "bim.create_all_shapes"
|
||||
bl_label = "Create All Shapes"
|
||||
bl_label = "Test All Shapes"
|
||||
bl_description = "Look for errors in all the shapes contained in the file"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -126,7 +133,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
except:
|
||||
failures.append(element)
|
||||
print("***** FAILURE *****")
|
||||
print("Failures:")
|
||||
print(f"Failures: {len(failures)}")
|
||||
for failure in failures:
|
||||
print(failure)
|
||||
return {"FINISHED"}
|
||||
@@ -135,6 +142,7 @@ class CreateAllShapes(bpy.types.Operator):
|
||||
class CreateShapeFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.create_shape_from_step_id"
|
||||
bl_label = "Create Shape From STEP ID"
|
||||
bl_description = "Recreate a mesh object from a STEP ID"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_include_curves: bpy.props.BoolProperty()
|
||||
|
||||
@@ -154,9 +162,12 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
if self.should_include_curves:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, element)
|
||||
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
if shape:
|
||||
ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
else:
|
||||
mesh = None
|
||||
obj = bpy.data.objects.new("Debug", mesh)
|
||||
context.scene.collection.objects.link(obj)
|
||||
return {"FINISHED"}
|
||||
@@ -165,20 +176,37 @@ class CreateShapeFromStepId(bpy.types.Operator):
|
||||
class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_high_polygon_meshes"
|
||||
bl_label = "Select High Polygon Meshes"
|
||||
bl_description = "Select objects containing more polygons than the specified number"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
threshold: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
[
|
||||
o.select_set(True)
|
||||
for o in context.view_layer.objects
|
||||
if o.type == "MESH" and len(o.data.polygons) > context.scene.BIMDebugProperties.number_of_polygons
|
||||
]
|
||||
for obj in context.view_layer.objects:
|
||||
if obj.type == "MESH" and len(obj.data.polygons) > self.threshold:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectHighestPolygonMeshes(bpy.types.Operator):
|
||||
bl_idname = "bim.select_highest_polygon_meshes"
|
||||
bl_label = "Select Highest Polygon Meshes"
|
||||
bl_description = "Select objects with a number of polygons superior to the specified percentile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
percentile: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"]
|
||||
if objects:
|
||||
percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100
|
||||
print(f"Selected all Meshes with more than {int(percentile)} polygons")
|
||||
[obj.select_set(True) for obj in objects if len(obj.data.polygons) > percentile]
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RewindInspector(bpy.types.Operator):
|
||||
bl_idname = "bim.rewind_inspector"
|
||||
bl_label = "Rewind Inspector"
|
||||
bl_description = "Rewind the Inspector to the previously inspected element"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMDebugProperties
|
||||
@@ -195,6 +223,7 @@ class RewindInspector(bpy.types.Operator):
|
||||
class InspectFromStepId(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_step_id"
|
||||
bl_label = "Inspect From STEP ID"
|
||||
bl_description = "Inspect the attributes and references by looking up the specified STEP ID"
|
||||
step_id: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
@@ -245,12 +274,21 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
class InspectFromObject(bpy.types.Operator):
|
||||
bl_idname = "bim.inspect_from_object"
|
||||
bl_label = "Inspect From Object"
|
||||
bl_description = "Inspect the Active Object's attributes and references"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
if bpy.app.version >= (3, 0, 0):
|
||||
cls.poll_message_set("No Active Object")
|
||||
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
|
||||
if bpy.app.version >= (3, 0, 0):
|
||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
ifc_definition_id = context.active_object.BIMObjectProperties.ifc_definition_id
|
||||
if not ifc_definition_id:
|
||||
return {"FINISHED"}
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=ifc_definition_id)
|
||||
bpy.ops.bim.inspect_from_step_id(step_id=context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -262,3 +300,40 @@ class PrintObjectPlacement(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
print(ifcopenshell.util.placement.get_local_placement(IfcStore.get_file().by_id(self.step_id)))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ParseExpress(bpy.types.Operator):
|
||||
bl_idname = "bim.parse_express"
|
||||
bl_label = "Parse Express"
|
||||
|
||||
def execute(self, context):
|
||||
core.parse_express(tool.Debug, context.scene.BIMDebugProperties.express_file)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectExpressFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_express_file"
|
||||
bl_label = "Select Express File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC EXPRESS definition"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.exp", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if os.path.exists(self.filepath) and "exp" in os.path.splitext(self.filepath)[1]:
|
||||
context.scene.BIMDebugProperties.express_file = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class PurgeHdf5Cache(bpy.types.Operator):
|
||||
bl_idname = "bim.purge_hdf5_cache"
|
||||
bl_label = "Purge HDF5 Cache"
|
||||
|
||||
def execute(self, context):
|
||||
core.purge_hdf5_cache(tool.Debug)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -33,8 +33,10 @@ from bpy.props import (
|
||||
class BIMDebugProperties(PropertyGroup):
|
||||
step_id: IntProperty(name="STEP ID")
|
||||
number_of_polygons: IntProperty(name="Number of Polygons", min=0)
|
||||
active_step_id: IntProperty(name="STEP ID")
|
||||
percentile_of_polygons: IntProperty(name="Percentile of Polygons", min=0, max=100, default=90, subtype="PERCENTAGE")
|
||||
active_step_id: IntProperty(name="STEP ID", default=1, soft_min=1)
|
||||
step_id_breadcrumb: CollectionProperty(name="STEP ID Breadcrumb", type=StrProperty)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
inverse_attributes: CollectionProperty(name="Inverse Attributes", type=Attribute)
|
||||
inverse_references: CollectionProperty(name="Inverse References", type=Attribute)
|
||||
express_file: StringProperty(name="Express File")
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_debug(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -38,9 +39,17 @@ class BIM_PT_debug(Panel):
|
||||
row.operator("bim.validate_ifc_file", icon="CHECKMARK", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "express_file", text="")
|
||||
row.operator("bim.parse_express", icon="IMPORT", text="")
|
||||
row.operator("bim.select_express_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.print_ifc_file")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_hdf5_cache")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.purge_ifc_links")
|
||||
|
||||
@@ -56,8 +65,13 @@ class BIM_PT_debug(Panel):
|
||||
row.prop(props, "step_id", text="")
|
||||
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator("bim.select_high_polygon_meshes")
|
||||
row.operator("bim.select_high_polygon_meshes").threshold = context.scene.BIMDebugProperties.number_of_polygons
|
||||
row.prop(props, "number_of_polygons", text="")
|
||||
row = layout.split(factor=0.7, align=True)
|
||||
row.operator(
|
||||
"bim.select_highest_polygon_meshes"
|
||||
).percentile = context.scene.BIMDebugProperties.percentile_of_polygons
|
||||
row.prop(props, "percentile_of_polygons", text="")
|
||||
|
||||
layout.label(text="Inspector:")
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class BIM_PT_diff(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_collaboration_and_data_exchange"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -77,6 +78,7 @@ class BIM_PT_object_documents(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -35,7 +35,6 @@ classes = (
|
||||
operator.ResizeText,
|
||||
operator.AddVariable,
|
||||
operator.RemoveVariable,
|
||||
operator.PropagateTextData,
|
||||
operator.RemoveDrawing,
|
||||
operator.AddDrawingStyle,
|
||||
operator.RemoveDrawingStyle,
|
||||
@@ -54,6 +53,9 @@ classes = (
|
||||
operator.CleanWireframes,
|
||||
operator.CopyGrid,
|
||||
operator.AddSectionsAnnotations,
|
||||
operator.EditText,
|
||||
operator.DisableEditingText,
|
||||
operator.EnableEditingText,
|
||||
prop.Variable,
|
||||
prop.Drawing,
|
||||
prop.Schedule,
|
||||
@@ -81,6 +83,7 @@ classes = (
|
||||
def register():
|
||||
bpy.types.Scene.DocProperties = bpy.props.PointerProperty(type=prop.DocProperties)
|
||||
bpy.types.Camera.BIMCameraProperties = bpy.props.PointerProperty(type=prop.BIMCameraProperties)
|
||||
bpy.types.Object.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.types.TextCurve.BIMTextProperties = bpy.props.PointerProperty(type=prop.BIMTextProperties)
|
||||
bpy.app.handlers.load_post.append(handler.toggleDecorationsOnLoad)
|
||||
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
@@ -150,18 +151,30 @@ class Annotator:
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def get_annotation_obj(name, data_type, context):
|
||||
def get_annotation_obj(object_type, data_type, context):
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
if name in obj.name:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new(name)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new(name, type="CURVE")
|
||||
if object_type == "TEXT":
|
||||
obj = bpy.data.objects.new(object_type, None)
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
elif object_type == "TEXT_LEADER":
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(name, data)
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
for obj in collection.objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.ObjectType == object_type:
|
||||
return obj
|
||||
if data_type == "mesh":
|
||||
data = bpy.data.meshes.new(object_type)
|
||||
elif data_type == "curve":
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
collection.objects.link(obj)
|
||||
return obj
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
TextData.is_loaded = False
|
||||
|
||||
|
||||
class TextData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"attributes": cls.attributes()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def attributes(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["TEXT", "TEXT_LEADER"]:
|
||||
return []
|
||||
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
|
||||
return [
|
||||
{"name": "Literal", "value": text_literal.Literal},
|
||||
{"name": "BoxAlignment", "value": text_literal.BoxAlignment},
|
||||
]
|
||||
@@ -16,32 +16,32 @@
|
||||
# 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/>.
|
||||
|
||||
"""Viewport decorations"""
|
||||
import os
|
||||
import gpu
|
||||
import bpy
|
||||
import blf
|
||||
import bgl
|
||||
import math
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from functools import reduce
|
||||
from itertools import chain
|
||||
|
||||
from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
import bpy
|
||||
import bmesh
|
||||
import blf
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
import gpu
|
||||
import bgl
|
||||
from gpu.types import GPUShader, GPUBatch, GPUIndexBuf, GPUVertBuf, GPUVertFormat
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
|
||||
|
||||
class BaseDecorator:
|
||||
# base name of objects to decorate
|
||||
basename = "IfcAnnotation/Something"
|
||||
objecttype = "NOTDEFINED"
|
||||
|
||||
DEF_GLSL = """
|
||||
#define PI 3.141592653589793
|
||||
#define MAX_POINTS 64
|
||||
#define CIRCLE_SEGS 24
|
||||
#define MAX_POINTS 32
|
||||
#define CIRCLE_SEGS 12
|
||||
"""
|
||||
|
||||
LIB_GLSL = """
|
||||
@@ -90,6 +90,7 @@ class BaseDecorator:
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=2) out;
|
||||
@@ -134,13 +135,48 @@ class BaseDecorator:
|
||||
defines=self.DEF_GLSL,
|
||||
)
|
||||
|
||||
# Horrific prototype code to ensure bgl draws at drawing scales
|
||||
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
|
||||
def is_landscape(render):
|
||||
return render.resolution_x > render.resolution_y
|
||||
|
||||
def get_scale(camera):
|
||||
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
return float(numerator) / float(denominator)
|
||||
|
||||
camera = bpy.context.scene.camera
|
||||
render = bpy.context.scene.render
|
||||
if is_landscape(render):
|
||||
camera_width_model = camera.data.ortho_scale
|
||||
else:
|
||||
camera_width_model = camera.data.ortho_scale / render.resolution_y * render.resolution_x
|
||||
self.camera_width_mm = get_scale(camera) * camera_width_model
|
||||
|
||||
self.font_id = blf.load(
|
||||
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
|
||||
)
|
||||
|
||||
def camera_zoom_to_factor(self, zoom):
|
||||
return math.pow(((zoom / 50) + math.sqrt(2)) / 2, 2)
|
||||
|
||||
def get_objects(self, collection):
|
||||
"""find relevant objects
|
||||
using class.basename
|
||||
using class.objecttype
|
||||
|
||||
returns: iterable of blender objects
|
||||
"""
|
||||
return filter(lambda o: self.basename in o.name, collection.all_objects)
|
||||
results = []
|
||||
for obj in collection.all_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if element.is_a("IfcAnnotation") and element.ObjectType == self.objecttype:
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
def get_path_geom(self, obj, topo=True):
|
||||
"""Parses path geometry into line segments
|
||||
@@ -208,7 +244,7 @@ class BaseDecorator:
|
||||
return vertices, indices
|
||||
|
||||
def decorate(self, context, object):
|
||||
"""perform actuall drawing stuff"""
|
||||
"""perform actual drawing stuff"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def draw_lines(self, context, obj, vertices, indices, topology=None):
|
||||
@@ -241,6 +277,15 @@ class BaseDecorator:
|
||||
self.shader.uniform_float("winsize", (region.width, region.height))
|
||||
self.shader.uniform_float("color", color)
|
||||
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
# 0.00025 is a magic constant number I visually discovered to get the right number.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
viewport_drawing_scale = 0.00025 * mm_to_px
|
||||
self.shader.uniform_float("viewportDrawingScale", viewport_drawing_scale)
|
||||
|
||||
batch.draw(self.shader)
|
||||
|
||||
def draw_label(self, context, text, pos, dir, gap=4, center=True, vcenter=False):
|
||||
@@ -251,8 +296,9 @@ class BaseDecorator:
|
||||
|
||||
aligned and centered at segment middle
|
||||
"""
|
||||
font_id = 0
|
||||
font_size = 16
|
||||
# 0 is the default font, but we're fancier than that
|
||||
font_id = self.font_id
|
||||
|
||||
dpi = context.preferences.system.dpi
|
||||
|
||||
color = context.scene.DocProperties.decorations_colour
|
||||
@@ -261,6 +307,16 @@ class BaseDecorator:
|
||||
cos = math.cos(ang)
|
||||
sin = math.sin(ang)
|
||||
|
||||
# Horrific prototype code
|
||||
factor = self.camera_zoom_to_factor(context.space_data.region_3d.view_camera_zoom)
|
||||
camera_width_px = factor * context.region.width
|
||||
mm_to_px = camera_width_px / self.camera_width_mm
|
||||
# 0.004118616 is a magic constant number I visually discovered to get the right number.
|
||||
# In particular it works only for the OpenGOST font and produces a 2.5mm font size.
|
||||
# It probably should be dynamically calculated using system.dpi or something.
|
||||
# font_size = 16 <-- this is a good default
|
||||
font_size = int(0.004118616 * mm_to_px)
|
||||
|
||||
blf.size(font_id, font_size, dpi)
|
||||
|
||||
w, h = 0, 0
|
||||
@@ -284,6 +340,9 @@ class BaseDecorator:
|
||||
|
||||
blf.rotation(font_id, ang)
|
||||
blf.color(font_id, *color)
|
||||
#blf.enable(font_id, blf.SHADOW)
|
||||
#blf.shadow(font_id, 5, 0, 0, 0, 1)
|
||||
#blf.shadow_offset(font_id, 1, -1)
|
||||
blf.draw(font_id, text)
|
||||
blf.disable(font_id, blf.ROTATION)
|
||||
|
||||
@@ -311,7 +370,7 @@ class DimensionDecorator(BaseDecorator):
|
||||
- puts metric text next to each segment
|
||||
"""
|
||||
|
||||
basename = "IfcAnnotation/Dimension"
|
||||
objecttype = "DIMENSION"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -323,6 +382,7 @@ class DimensionDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -339,7 +399,7 @@ class DimensionDecorator(BaseDecorator):
|
||||
vec4 p;
|
||||
|
||||
vec4 head[3];
|
||||
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
// start edge arrow
|
||||
gl_Position = p0;
|
||||
@@ -406,7 +466,7 @@ class EqualityDecorator(DimensionDecorator):
|
||||
- puts 'EQ' label
|
||||
"""
|
||||
|
||||
basename = "IfcAnnotation/Equal"
|
||||
objecttype = "EQUAL_DIMENSION"
|
||||
|
||||
def draw_labels(self, context, obj, vertices, indices):
|
||||
region = context.region
|
||||
@@ -423,12 +483,11 @@ class EqualityDecorator(DimensionDecorator):
|
||||
|
||||
|
||||
class LeaderDecorator(BaseDecorator):
|
||||
"""Decorating stairs
|
||||
"""Decorating text with arrows
|
||||
- head point with arrow
|
||||
- middle points w/out decorations
|
||||
"""
|
||||
|
||||
basename = "IfcAnnotation/Leader"
|
||||
objecttype = "TEXT_LEADER"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -440,6 +499,7 @@ class LeaderDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -461,7 +521,7 @@ class LeaderDecorator(BaseDecorator):
|
||||
// end edge arrow for last segment
|
||||
if (t1 == 2u) {
|
||||
vec4 head[3];
|
||||
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
@@ -475,7 +535,7 @@ class LeaderDecorator(BaseDecorator):
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
gap1 = dir * ARROW_SIZE;
|
||||
gap1 = dir * viewportDrawingScale * ARROW_SIZE;
|
||||
}
|
||||
|
||||
// stem, adjusted for and arrow
|
||||
@@ -488,9 +548,24 @@ class LeaderDecorator(BaseDecorator):
|
||||
}
|
||||
"""
|
||||
|
||||
def get_spline_end(self, obj):
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if not spline_points:
|
||||
return Vector((0, 0, 0))
|
||||
return obj.matrix_world @ spline_points[0].co
|
||||
|
||||
def decorate(self, context, obj):
|
||||
verts, idxs, topo = self.get_path_geom(obj)
|
||||
self.draw_lines(context, obj, verts, idxs, topo)
|
||||
self.draw_labels(context, obj)
|
||||
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, self.get_spline_end(obj))
|
||||
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class StairDecorator(BaseDecorator):
|
||||
@@ -500,12 +575,12 @@ class StairDecorator(BaseDecorator):
|
||||
- middle points w/out decorations
|
||||
"""
|
||||
|
||||
basename = "IfcAnnotation/Stair"
|
||||
objecttype = "STAIR_ARROW"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define CIRCLE_SIZE 8.0
|
||||
#define CIRCLE_SIZE 6.0
|
||||
#define ARROW_ANGLE PI / 3.0
|
||||
#define ARROW_SIZE 24.0
|
||||
"""
|
||||
@@ -513,6 +588,7 @@ class StairDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -534,7 +610,7 @@ class StairDecorator(BaseDecorator):
|
||||
// start edge circle for first segment
|
||||
if (t0 == 1u) {
|
||||
vec4 head[CIRCLE_SEGS];
|
||||
circle_head(CIRCLE_SIZE, head);
|
||||
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
|
||||
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p0w + head[i];
|
||||
@@ -546,13 +622,13 @@ class StairDecorator(BaseDecorator):
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
gap0 = dir * CIRCLE_SIZE;
|
||||
gap0 = dir * viewportDrawingScale * CIRCLE_SIZE;
|
||||
}
|
||||
|
||||
// end edge arrow for last segment
|
||||
if (t1 == 2u) {
|
||||
vec4 head[3];
|
||||
arrow_head(dir, ARROW_SIZE, ARROW_ANGLE, head);
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
@@ -583,7 +659,7 @@ class StairDecorator(BaseDecorator):
|
||||
|
||||
|
||||
class HiddenDecorator(BaseDecorator):
|
||||
basename = "IfcAnnotation/Hidden"
|
||||
objecttype = "HIDDEN_LINE"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -628,13 +704,14 @@ class HiddenDecorator(BaseDecorator):
|
||||
"""
|
||||
|
||||
FRAG_GLSL = """
|
||||
uniform float viewportDrawingScale;
|
||||
uniform vec4 color;
|
||||
in vec2 gl_FragCoord;
|
||||
in float dist;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
uint bit = uint(fract(dist / DASH_SIZE) * 32);
|
||||
uint bit = uint(fract(dist / (viewportDrawingScale * DASH_SIZE)) * 32);
|
||||
if ((DASH_PATTERN & (1U<<bit)) == 0U) discard;
|
||||
fragColor = color;
|
||||
}
|
||||
@@ -649,7 +726,7 @@ class HiddenDecorator(BaseDecorator):
|
||||
|
||||
|
||||
class MiscDecorator(HiddenDecorator):
|
||||
basename = "IfcAnnotation/Misc"
|
||||
objecttype = "MISC"
|
||||
|
||||
FRAG_GLSL = BaseDecorator.FRAG_GLSL
|
||||
|
||||
@@ -677,7 +754,7 @@ class LevelDecorator(BaseDecorator):
|
||||
|
||||
|
||||
class PlanLevelDecorator(LevelDecorator):
|
||||
basename = "IfcAnnotation/Plan Level"
|
||||
objecttype = "PLAN_LEVEL"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -689,6 +766,7 @@ class PlanLevelDecorator(LevelDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -710,7 +788,7 @@ class PlanLevelDecorator(LevelDecorator):
|
||||
// end edge cross+circle for last segment
|
||||
if (t1 == 2u) {
|
||||
vec4 head_o[CIRCLE_SEGS];
|
||||
circle_head(CIRCLE_SIZE, head_o);
|
||||
circle_head(viewportDrawingScale * CIRCLE_SIZE, head_o);
|
||||
|
||||
for(int i=0; i<CIRCLE_SEGS; i++) {
|
||||
p = p1w + head_o[i];
|
||||
@@ -723,7 +801,7 @@ class PlanLevelDecorator(LevelDecorator):
|
||||
EndPrimitive();
|
||||
|
||||
vec4 head_x[3];
|
||||
cross_head(dir, CROSS_SIZE, head_x);
|
||||
cross_head(dir, viewportDrawingScale * CROSS_SIZE, head_x);
|
||||
|
||||
p = p1w + head_x[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
@@ -762,7 +840,7 @@ class PlanLevelDecorator(LevelDecorator):
|
||||
|
||||
|
||||
class SectionLevelDecorator(LevelDecorator):
|
||||
basename = "IfcAnnotation/Section Level"
|
||||
objecttype = "SECTION_LEVEL"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -774,6 +852,7 @@ class SectionLevelDecorator(LevelDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -781,8 +860,8 @@ class SectionLevelDecorator(LevelDecorator):
|
||||
out float dist; // distance from starging point along segment
|
||||
|
||||
void callout_head(in vec4 dir, out vec4 head[4]) {
|
||||
vec2 gap = dir.xy * CALLOUT_GAP;
|
||||
vec2 tail = dir.xy * -CALLOUT_SIZE;
|
||||
vec2 gap = dir.xy * viewportDrawingScale * CALLOUT_GAP;
|
||||
vec2 tail = dir.xy * viewportDrawingScale * -CALLOUT_SIZE;
|
||||
vec2 side = cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy * CALLOUT_GAP;
|
||||
|
||||
head[0] = vec4(tail + side, 0, 0);
|
||||
@@ -850,7 +929,7 @@ class BreakDecorator(BaseDecorator):
|
||||
Uses first two vertices in verts list.
|
||||
"""
|
||||
|
||||
basename = "IfcAnnotation/Break"
|
||||
objecttype = "BREAKLINE"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -862,6 +941,7 @@ class BreakDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -878,8 +958,8 @@ class BreakDecorator(BaseDecorator):
|
||||
|
||||
vec4 p;
|
||||
|
||||
vec4 quart = dir * BREAK_LENGTH * .25;
|
||||
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0) * BREAK_WIDTH;
|
||||
vec4 quart = dir * viewportDrawingScale * BREAK_LENGTH * .25;
|
||||
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0) * viewportDrawingScale * BREAK_WIDTH;
|
||||
|
||||
// TODO: check if there's enough length for zigzag
|
||||
|
||||
@@ -928,7 +1008,7 @@ class BreakDecorator(BaseDecorator):
|
||||
|
||||
|
||||
class GridDecorator(BaseDecorator):
|
||||
basename = "IfcGridAxis/"
|
||||
objecttype = "GRID"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -941,6 +1021,7 @@ class GridDecorator(BaseDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -955,12 +1036,12 @@ class GridDecorator(BaseDecorator):
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
vec4 gap = dir * CIRCLE_SIZE;
|
||||
vec4 gap = dir * CIRCLE_SIZE * viewportDrawingScale;
|
||||
|
||||
vec4 p;
|
||||
|
||||
vec4 head[CIRCLE_SEGS];
|
||||
circle_head(CIRCLE_SIZE, head);
|
||||
circle_head(CIRCLE_SIZE * viewportDrawingScale, head);
|
||||
|
||||
dist = 0;
|
||||
|
||||
@@ -1045,7 +1126,7 @@ class GridDecorator(BaseDecorator):
|
||||
|
||||
|
||||
class SectionViewDecorator(LevelDecorator):
|
||||
basename = "IfcAnnotation/Section"
|
||||
objecttype = "SECTION"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
@@ -1058,6 +1139,7 @@ class SectionViewDecorator(LevelDecorator):
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
@@ -1078,12 +1160,12 @@ class SectionViewDecorator(LevelDecorator):
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
vec4 gap = dir * TRIANGLE_L * .5;
|
||||
vec4 gap = dir * viewportDrawingScale * TRIANGLE_L * .5;
|
||||
vec4 side = vec4(cross(vec3(dir.xy, 0), vec3(0, 0, 1)).xy, 0, 0);
|
||||
vec4 p;
|
||||
|
||||
vec4 head[CIRCLE_SEGS];
|
||||
circle_head(CIRCLE_SIZE, head);
|
||||
circle_head(viewportDrawingScale * CIRCLE_SIZE, head);
|
||||
|
||||
vec4 head3[3];
|
||||
|
||||
@@ -1099,7 +1181,7 @@ class SectionViewDecorator(LevelDecorator):
|
||||
EndPrimitive();
|
||||
|
||||
// start edge triangle
|
||||
triangle_head(dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
|
||||
triangle_head(dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
|
||||
p = p0w + head3[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
@@ -1123,7 +1205,7 @@ class SectionViewDecorator(LevelDecorator):
|
||||
EndPrimitive();
|
||||
|
||||
// end edge triangle
|
||||
triangle_head(-dir, -side, TRIANGLE_L, TRIANGLE_W, head3);
|
||||
triangle_head(-dir, -side, viewportDrawingScale * TRIANGLE_L, viewportDrawingScale * TRIANGLE_W, head3);
|
||||
p = p1w + head3[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
@@ -1149,6 +1231,90 @@ class SectionViewDecorator(LevelDecorator):
|
||||
self.draw_lines(context, obj, verts, [(0, 1)])
|
||||
|
||||
|
||||
class TextDecorator(BaseDecorator):
|
||||
"""Decorator for text objects
|
||||
- draws the text next to the origin
|
||||
"""
|
||||
|
||||
objecttype = "TEXT"
|
||||
|
||||
DEF_GLSL = (
|
||||
BaseDecorator.DEF_GLSL
|
||||
+ """
|
||||
#define ARROW_ANGLE PI / 12.0
|
||||
#define ARROW_SIZE 16.0
|
||||
"""
|
||||
)
|
||||
|
||||
GEOM_GLSL = """
|
||||
uniform vec2 winsize;
|
||||
uniform float viewportDrawingScale;
|
||||
|
||||
layout(lines) in;
|
||||
layout(line_strip, max_vertices=MAX_POINTS) out;
|
||||
|
||||
void main() {
|
||||
vec4 clip2win = matCLIP2WIN();
|
||||
vec4 win2clip = matWIN2CLIP();
|
||||
|
||||
vec4 p0 = gl_in[0].gl_Position, p1 = gl_in[1].gl_Position;
|
||||
|
||||
vec4 p0w = CLIP2WIN(p0), p1w = CLIP2WIN(p1);
|
||||
vec4 edge = p1w - p0w, dir = normalize(edge);
|
||||
|
||||
vec4 p;
|
||||
|
||||
vec4 head[3];
|
||||
arrow_head(dir, viewportDrawingScale * ARROW_SIZE, ARROW_ANGLE, head);
|
||||
|
||||
// start edge arrow
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
p = p0w + head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p0w + head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p0;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// end edge arrow
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
p = p1w - head[1];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[2];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
gl_Position = p1;
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
|
||||
// stem, with gaps for arrows
|
||||
p = p0w + head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
p = p1w - head[0];
|
||||
gl_Position = WIN2CLIP(p);
|
||||
EmitVertex();
|
||||
EndPrimitive();
|
||||
}
|
||||
"""
|
||||
|
||||
def decorate(self, context, obj):
|
||||
self.draw_labels(context, obj)
|
||||
|
||||
def draw_labels(self, context, obj):
|
||||
region = context.region
|
||||
region3d = context.region_data
|
||||
dir = Vector((1, 0))
|
||||
pos = location_3d_to_region_2d(region, region3d, obj.matrix_world.translation)
|
||||
self.draw_label(context, obj.BIMTextProperties.value, pos, dir, gap=0, center=False, vcenter=False)
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
decorators_classes = [
|
||||
DimensionDecorator,
|
||||
@@ -1162,6 +1328,7 @@ class DecorationsHandler:
|
||||
StairDecorator,
|
||||
BreakDecorator,
|
||||
SectionViewDecorator,
|
||||
TextDecorator,
|
||||
]
|
||||
|
||||
installed = None
|
||||
|
||||
@@ -263,7 +263,7 @@ def get_active_drawing(scene):
|
||||
return None, None
|
||||
try:
|
||||
drawing = props.active_drawing
|
||||
return scene.collection.children["Views"].children[f"IfcGroup/{drawing.name}"], drawing.camera
|
||||
return drawing.camera.users_collection[0], drawing.camera
|
||||
except (KeyError, IndexError):
|
||||
raise RuntimeError("missing drawing collection")
|
||||
|
||||
|
||||
@@ -31,13 +31,17 @@ import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.schema
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.drawing as core
|
||||
import blenderbim.bim.module.drawing.svgwriter as svgwriter
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.sheeter as sheeter
|
||||
import blenderbim.bim.module.drawing.scheduler as scheduler
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
import blenderbim.bim.export_ifc
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
from blenderbim.bim.module.drawing.prop import RasterStyleProperty
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.group.data import Data as GroupData
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
@@ -54,6 +58,13 @@ def open_with_user_command(user_command, path):
|
||||
webbrowser.open("file://" + path)
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.add_drawing"
|
||||
bl_label = "Add Drawing"
|
||||
@@ -76,7 +87,7 @@ class AddDrawing(bpy.types.Operator):
|
||||
view_collection = bpy.data.collections.new("IfcGroup/" + new.name)
|
||||
views_collection.children.link(view_collection)
|
||||
camera = bpy.data.objects.new(new.name, bpy.data.cameras.new(new.name))
|
||||
camera.location = (0, 0, 1.7) # The view shall be 1.7m above the origin
|
||||
camera.location = (0, 0, 1.5) # The view shall be 1.5m above the origin
|
||||
camera.data.type = "ORTHO"
|
||||
camera.data.ortho_scale = 50 # The default of 6m is too small
|
||||
camera.data.clip_end = 10 # A slightly more reasonable default
|
||||
@@ -140,12 +151,16 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.camera = context.scene.camera
|
||||
self.camera_element = tool.Ifc.get_entity(self.camera)
|
||||
self.file = IfcStore.get_file()
|
||||
self.time = None
|
||||
start = time.time()
|
||||
self.profile_code("Start drawing generation process")
|
||||
self.props = context.scene.DocProperties
|
||||
self.drawing_name = self.file.by_id(self.camera.BIMObjectProperties.ifc_definition_id).Name
|
||||
self.get_scale()
|
||||
if self.camera.data.BIMCameraProperties.update_representation(self.camera):
|
||||
bpy.ops.bim.update_representation(obj=self.camera.name, ifc_representation_class="")
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
self.profile_code("Generate underlay")
|
||||
linework_svg = self.generate_linework(context)
|
||||
@@ -190,7 +205,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
elif "<defs>" in line:
|
||||
should_skip = True
|
||||
continue
|
||||
elif "</style>" in line:
|
||||
elif "</defs>" in line:
|
||||
should_skip = False
|
||||
continue
|
||||
elif should_skip:
|
||||
@@ -248,21 +263,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
bpy.data.objects[name].hide_set(value)
|
||||
|
||||
svg_writer = svgwriter.SvgWriter()
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
if self.camera.data.BIMCameraProperties.is_nts:
|
||||
svg_writer.human_scale = "NTS"
|
||||
else:
|
||||
svg_writer.human_scale = human_scale
|
||||
render = context.scene.render
|
||||
if self.is_landscape():
|
||||
if self.is_landscape(render):
|
||||
width = self.camera.data.ortho_scale
|
||||
height = width / render.resolution_x * render.resolution_y
|
||||
else:
|
||||
height = self.camera.data.ortho_scale
|
||||
width = height / render.resolution_y * render.resolution_x
|
||||
svg_writer.human_scale = self.human_scale
|
||||
svg_writer.scale = self.scale
|
||||
svg_writer.output = svg_path
|
||||
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
svg_writer.vector_style = drawing_style.vector_style
|
||||
@@ -280,62 +289,160 @@ class CreateDrawing(bpy.types.Operator):
|
||||
svg_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", self.drawing_name + "-linework.svg")
|
||||
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
|
||||
return svg_path
|
||||
|
||||
# All very hackish whilst prototyping
|
||||
exporter = blenderbim.bim.export_ifc.IfcExporter(None)
|
||||
exporter.file = tool.Ifc.get()
|
||||
invalidated_guids = exporter.sync_deletions()
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
[invalidated_guids.append(e.GlobalId) for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
|
||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||
edited_guids = set()
|
||||
for obj in IfcStore.edited_objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
edited_guids.add(element.GlobalId) if hasattr(element, "GlobalId") else None
|
||||
cached_linework = set()
|
||||
if os.path.isfile(svg_path):
|
||||
tree = etree.parse(svg_path)
|
||||
root = tree.getroot()
|
||||
cached_linework = {
|
||||
el.get("{http://www.ifcopenshell.org/ns}guid")
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]")
|
||||
}
|
||||
cached_linework -= edited_guids
|
||||
|
||||
# This is a work in progress. See #1153 and #1564.
|
||||
# Switch from old to new if you are testing v0.7.0
|
||||
self.generate_linework_old(context, svg_path)
|
||||
# self.generate_linework_new(svg_path)
|
||||
import hashlib
|
||||
import ifcopenshell.draw
|
||||
|
||||
files = {context.scene.BIMProperties.ifc_file: tool.Ifc.get()}
|
||||
|
||||
for ifc_path, ifc in files.items():
|
||||
# Don't use draw.main() just whilst we're prototyping and experimenting
|
||||
ifc_hash = hashlib.md5(ifc_path.encode("utf-8")).hexdigest()
|
||||
ifc_cache_path = os.path.join(context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
|
||||
# Get all representation contexts to see what we're dealing with
|
||||
# A drawing prioritises a target view context first, followed by a body context as a fallback
|
||||
body_contexts = []
|
||||
target_view_contexts = []
|
||||
for rep_context in ifc.by_type("IfcGeometricRepresentationContext"):
|
||||
if rep_context.is_a("IfcGeometricRepresentationSubContext"):
|
||||
if rep_context.TargetView == context.scene.camera.data.BIMCameraProperties.target_view:
|
||||
target_view_contexts.append(rep_context.id())
|
||||
continue
|
||||
if rep_context.ContextIdentifier in ["Body", "Facetation"]:
|
||||
body_contexts.append(rep_context.id())
|
||||
continue
|
||||
if rep_context.is_a() == "IfcGeometricRepresentationContext" and rep_context.ContextType == "Model":
|
||||
body_contexts.append(rep_context.id())
|
||||
continue
|
||||
|
||||
elements = set(ifc.by_type("IfcElement")) - set(ifc.by_type("IfcOpeningElement"))
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
cache_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
|
||||
if target_view_contexts and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
|
||||
geom_settings.set_context_ids(target_view_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
processed = set()
|
||||
for elem in self.yield_from_iterator(it):
|
||||
processed.add(ifc.by_id(elem.id))
|
||||
self.serialiser.write(elem)
|
||||
elements -= processed
|
||||
|
||||
if body_contexts and elements:
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True,
|
||||
)
|
||||
geom_settings.set_context_ids(body_contexts)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, multiprocessing.cpu_count(), include=elements)
|
||||
it.set_cache(cache)
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
geom_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True,
|
||||
)
|
||||
it = ifcopenshell.geom.iterator(geom_settings, ifc, include=[self.camera_element])
|
||||
for elem in self.yield_from_iterator(it):
|
||||
self.serialiser.write(elem)
|
||||
|
||||
self.serialiser.finalize()
|
||||
results = self.svg_buffer.get_value()
|
||||
|
||||
root = etree.fromstring(results)
|
||||
self.enrich_linework_with_metadata(root)
|
||||
self.move_projection_to_bottom(root)
|
||||
with open(svg_path, "wb") as svg:
|
||||
svg.write(etree.tostring(root))
|
||||
|
||||
return svg_path
|
||||
|
||||
def generate_linework_new(self, svg_path):
|
||||
settings = ifcopenshell.geom.settings(
|
||||
APPLY_DEFAULT_MATERIALS=True,
|
||||
DISABLE_TRIANGULATION=True,
|
||||
INCLUDE_CURVES=True,
|
||||
EXCLUDE_SOLIDS_AND_SURFACES=False,
|
||||
)
|
||||
buffer = ifcopenshell.geom.serializers.buffer()
|
||||
serialiser = ifcopenshell.geom.serializers.svg(buffer, settings)
|
||||
serialiser.setFile(self.file)
|
||||
serialiser.setElevationRef("DRAWING")
|
||||
serialiser.setUseNamespace(True)
|
||||
serialiser.setAlwaysProject(True)
|
||||
serialiser.setUseHlrPoly(True)
|
||||
serialiser.setWithoutStoreys(True)
|
||||
excluded_elements = []
|
||||
for ifc_class in ["IfcSpace", "IfcOpeningElement", "IfcDoor", "IfcWindow"]:
|
||||
excluded_elements += self.file.by_type(ifc_class)
|
||||
for element in ifcopenshell.geom.iterate(
|
||||
settings, self.file, multiprocessing.cpu_count(), exclude=excluded_elements
|
||||
):
|
||||
serialiser.write(element)
|
||||
serialiser.finalize()
|
||||
with open(svg_path, "w") as svg:
|
||||
svg.write(buffer.get_value())
|
||||
def setup_serialiser(self, ifc):
|
||||
self.svg_settings = ifcopenshell.geom.settings(DISABLE_TRIANGULATION=True, INCLUDE_CURVES=True)
|
||||
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
|
||||
self.serialiser = ifcopenshell.geom.serializers.svg(self.svg_buffer, self.svg_settings)
|
||||
self.serialiser.setFile(ifc)
|
||||
self.serialiser.setWithoutStoreys(True)
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseNamespace(True)
|
||||
self.serialiser.setAlwaysProject(True)
|
||||
self.serialiser.setAutoElevation(False)
|
||||
self.serialiser.setAutoSection(False)
|
||||
self.serialiser.setPrintSpaceNames(False)
|
||||
self.serialiser.setPrintSpaceAreas(False)
|
||||
self.serialiser.setDrawDoorArcs(False)
|
||||
self.serialiser.setNoCSS(True)
|
||||
self.serialiser.setElevationRefGuid(self.camera_element.GlobalId)
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
# 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()
|
||||
# tree.enable_face_styles(True)
|
||||
|
||||
def generate_linework_old(self, context, svg_path):
|
||||
ifcconvert_path = os.path.join(cwd, "..", "..", "..", "libs", "IfcConvert")
|
||||
subprocess.run(
|
||||
[
|
||||
ifcconvert_path,
|
||||
context.scene.BIMProperties.ifc_file,
|
||||
"-yv",
|
||||
svg_path,
|
||||
"--plan",
|
||||
"--model",
|
||||
"--elevation-ref=DRAWING",
|
||||
"--svg-xmlns",
|
||||
"--svg-project",
|
||||
"--svg-poly",
|
||||
"--svg-without-storeys",
|
||||
"--exclude",
|
||||
"entities",
|
||||
"IfcSpace",
|
||||
"IfcOpeningElement",
|
||||
"IfcDoor",
|
||||
"IfcWindow",
|
||||
]
|
||||
)
|
||||
return svg_path
|
||||
def yield_from_iterator(self, it):
|
||||
if it.initialize():
|
||||
while True:
|
||||
yield it.get()
|
||||
if not it.next():
|
||||
break
|
||||
|
||||
def enrich_linework_with_metadata(self, root):
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
classes = ["cut"]
|
||||
try:
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
except:
|
||||
# See #1861 - freshly created guids cannot be extracted unfortunately
|
||||
continue
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material:
|
||||
if material.is_a("IfcMaterialLayerSet"):
|
||||
name = material.LayerSetName or "null"
|
||||
else:
|
||||
name = getattr(material, "Name", "null") or "null"
|
||||
name = self.canonicalise_class_name(name)
|
||||
classes.append(f"material-{name}")
|
||||
el.set("class", (el.get("class", "") + " " + " ".join(classes)).strip())
|
||||
|
||||
def move_projection_to_bottom(self, root):
|
||||
# https://stackoverflow.com/questions/36018627/sorting-child-elements-with-lxml-based-on-attribute-value
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
# group[:] = sorted(group, key=lambda e : "projection" in e.get("class"))
|
||||
group[:] = reversed(group)
|
||||
|
||||
def canonicalise_class_name(self, name):
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not self.props.has_annotation:
|
||||
@@ -347,18 +454,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
camera = self.camera
|
||||
svg_writer = svgwriter.SvgWriter()
|
||||
|
||||
if camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
|
||||
numerator, denominator = fraction.split("/")
|
||||
|
||||
if camera.data.BIMCameraProperties.is_nts:
|
||||
svg_writer.human_scale = "NTS"
|
||||
else:
|
||||
svg_writer.human_scale = human_scale
|
||||
|
||||
drawing_style = context.scene.DocProperties.drawing_styles[
|
||||
camera.data.BIMCameraProperties.active_drawing_style_index
|
||||
]
|
||||
@@ -371,7 +466,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
height = camera.data.ortho_scale
|
||||
width = height / render.resolution_y * render.resolution_x
|
||||
|
||||
svg_writer.scale = float(numerator) / float(denominator)
|
||||
svg_writer.human_scale = self.human_scale
|
||||
svg_writer.scale = self.scale
|
||||
svg_writer.output = svg_path
|
||||
svg_writer.data_dir = context.scene.BIMProperties.data_dir
|
||||
svg_writer.vector_style = drawing_style.vector_style
|
||||
@@ -381,43 +477,55 @@ class CreateDrawing(bpy.types.Operator):
|
||||
svg_writer.camera_projection = tuple(camera.matrix_world.to_quaternion() @ Vector((0, 0, -1)))
|
||||
|
||||
for obj in camera.users_collection[0].objects:
|
||||
if "IfcGrid" in obj.name:
|
||||
if "IfcAnnotation/" not in obj.name:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element.ObjectType == "GRID":
|
||||
svg_writer.annotations.setdefault("grid_objs", []).append(obj)
|
||||
elif obj.type == "CAMERA":
|
||||
continue
|
||||
|
||||
if "IfcAnnotation/" not in obj.name:
|
||||
continue
|
||||
|
||||
if "Leader" in obj.name:
|
||||
svg_writer.annotations["leader_obj"] = (obj, obj.data)
|
||||
elif "Stair" in obj.name:
|
||||
elif element.ObjectType == "TEXT_LEADER":
|
||||
svg_writer.annotations.setdefault("leader_objs", []).append(obj)
|
||||
elif element.ObjectType == "STAIR_ARROW":
|
||||
svg_writer.annotations["stair_obj"] = obj
|
||||
elif "Equal" in obj.name:
|
||||
elif element.ObjectType == "EQUAL_DIMENSION":
|
||||
svg_writer.annotations.setdefault("equal_objs", []).append(obj)
|
||||
elif "Dimension" in obj.name:
|
||||
elif element.ObjectType == "DIMENSION":
|
||||
svg_writer.annotations.setdefault("dimension_objs", []).append(obj)
|
||||
elif "Break" in obj.name:
|
||||
elif element.ObjectType == "BREAKLINE":
|
||||
svg_writer.annotations["break_obj"] = obj
|
||||
elif "Hidden" in obj.name:
|
||||
elif element.ObjectType == "HIDDEN_LINE":
|
||||
svg_writer.annotations.setdefault("hidden_objs", []).append((obj, obj.data))
|
||||
elif "Solid" in obj.name:
|
||||
elif element.ObjectType == "SOLID_LINE":
|
||||
svg_writer.annotations.setdefault("solid_objs", []).append((obj, obj.data))
|
||||
elif "Plan Level" in obj.name:
|
||||
svg_writer.annotations["plan_level_obj"] = obj
|
||||
elif "Section Level" in obj.name:
|
||||
elif element.ObjectType == "PLAN_LEVEL":
|
||||
svg_writer.annotations.setdefault("plan_level_objs", []).append(obj)
|
||||
elif element.ObjectType == "SECTION_LEVEL":
|
||||
svg_writer.annotations["section_level_obj"] = obj
|
||||
elif obj.type == "FONT":
|
||||
elif element.ObjectType == "TEXT":
|
||||
svg_writer.annotations.setdefault("text_objs", []).append(obj)
|
||||
else:
|
||||
elif element.ObjectType == "MISC":
|
||||
svg_writer.annotations.setdefault("misc_objs", []).append(obj)
|
||||
|
||||
svg_writer.annotations["attributes"] = [a.name for a in drawing_style.attributes]
|
||||
svg_writer.annotations["annotation_objs"] = self.get_annotation(svg_writer)
|
||||
# TODO: This was the old 2D annotation box checking system, prepare to deprecate
|
||||
svg_writer.annotations["annotation_objs"] = []
|
||||
|
||||
svg_writer.write("annotation")
|
||||
return svg_writer.output
|
||||
|
||||
def get_scale(self):
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
self.human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
|
||||
if self.camera.data.BIMCameraProperties.is_nts:
|
||||
self.human_scale = "NTS"
|
||||
|
||||
numerator, denominator = fraction.split("/")
|
||||
self.scale = float(numerator) / float(denominator)
|
||||
|
||||
def is_landscape(self, render):
|
||||
return render.resolution_x > render.resolution_y
|
||||
|
||||
@@ -430,56 +538,6 @@ class CreateDrawing(bpy.types.Operator):
|
||||
continue
|
||||
return space
|
||||
|
||||
def get_annotation(self, svg_writer):
|
||||
results = []
|
||||
x = svg_writer.camera_width / 2
|
||||
y = svg_writer.camera_height / 2
|
||||
z = 0.01
|
||||
camera_box = helper.BoundingBox(
|
||||
self.camera,
|
||||
[
|
||||
self.camera.matrix_world @ Vector((-x, -y, -z)),
|
||||
self.camera.matrix_world @ Vector((-x, -y, 0)),
|
||||
self.camera.matrix_world @ Vector((-x, y, 0)),
|
||||
self.camera.matrix_world @ Vector((-x, y, -z)),
|
||||
self.camera.matrix_world @ Vector((x, -y, -z)),
|
||||
self.camera.matrix_world @ Vector((x, -y, 0)),
|
||||
self.camera.matrix_world @ Vector((x, y, 0)),
|
||||
self.camera.matrix_world @ Vector((x, y, -z)),
|
||||
],
|
||||
)
|
||||
# This should probably be also part of IfcConvert in the future, here is a Python prototype
|
||||
settings_2d = ifcopenshell.geom.settings()
|
||||
settings_2d.set(settings_2d.INCLUDE_CURVES, True)
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
if obj == self.camera:
|
||||
continue
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
continue
|
||||
representation = ifcopenshell.util.representation.get_representation(
|
||||
element, "Plan", "Annotation", self.camera.data.BIMCameraProperties.target_view
|
||||
)
|
||||
if not representation:
|
||||
continue
|
||||
if not camera_box.intersect(helper.BoundingBox(obj)):
|
||||
continue
|
||||
shape = ifcopenshell.geom.create_shape(settings_2d, representation)
|
||||
geometry = shape
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
results.append(
|
||||
{
|
||||
"raw": element,
|
||||
"classes": self.get_classes(element, "annotation", svg_writer),
|
||||
"edges": [[e[i], e[i + 1]] for i in range(0, len(e), 2)],
|
||||
"vertices": [obj.matrix_world @ Vector((v[i], v[i + 1], v[i + 2])) for i in range(0, len(v), 3)],
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
def get_classes(self, element, position, svg_writer):
|
||||
classes = [position, element.is_a()]
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
@@ -506,7 +564,7 @@ class AddAnnotation(bpy.types.Operator):
|
||||
bl_idname = "bim.add_annotation"
|
||||
bl_label = "Add Annotation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj_name: bpy.props.StringProperty()
|
||||
object_type: bpy.props.StringProperty()
|
||||
data_type: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
@@ -517,33 +575,51 @@ class AddAnnotation(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
context.scene.DocProperties.should_draw_decorations = True
|
||||
subcontext = ifcopenshell.util.representation.get_context(
|
||||
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
|
||||
)
|
||||
if not subcontext:
|
||||
return {"FINISHED"}
|
||||
if self.data_type == "text":
|
||||
if context.selected_objects:
|
||||
for selected_object in context.selected_objects:
|
||||
obj = annotation.Annotator.add_text(context, related_element=selected_object)
|
||||
else:
|
||||
obj = annotation.Annotator.add_text(context)
|
||||
else:
|
||||
obj = annotation.Annotator.get_annotation_obj(self.obj_name, self.data_type, context)
|
||||
if self.obj_name == "Break":
|
||||
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
|
||||
else:
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj, context)
|
||||
# TODO: reimplement bulk smart tagging
|
||||
# if self.data_type == "text":
|
||||
# if context.selected_objects:
|
||||
# for selected_object in context.selected_objects:
|
||||
# obj = annotation.Annotator.add_text(context, related_element=selected_object)
|
||||
# else:
|
||||
# obj = annotation.Annotator.add_text(context)
|
||||
# else:
|
||||
obj = annotation.Annotator.get_annotation_obj(self.object_type, self.data_type, context)
|
||||
if self.object_type == "BREAKLINE":
|
||||
obj = annotation.Annotator.add_plane_to_annotation(obj, context)
|
||||
elif self.object_type != "TEXT":
|
||||
obj = annotation.Annotator.add_line_to_annotation(obj, context)
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
|
||||
ifc_representation_class = ""
|
||||
if self.object_type == "TEXT":
|
||||
ifc_representation_class = "IfcTextLiteral"
|
||||
elif self.object_type == "TEXT_LEADER":
|
||||
ifc_representation_class = "IfcGeometricCurveSet/IfcTextLiteral"
|
||||
bpy.ops.bim.assign_class(
|
||||
obj=obj.name,
|
||||
ifc_class="IfcAnnotation",
|
||||
context_id=subcontext.id(),
|
||||
ifc_representation_class=ifc_representation_class,
|
||||
)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
element.ObjectType = self.object_type
|
||||
camera = tool.Ifc.get_entity(context.scene.camera)
|
||||
group = [r for r in camera.HasAssignments if r.is_a("IfcRelAssignsToGroup")][0].RelatingGroup
|
||||
bpy.ops.bim.assign_group(product=obj.name, group=group.id())
|
||||
else:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
if obj.data:
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -717,9 +793,8 @@ class ActivateView(bpy.types.Operator):
|
||||
):
|
||||
project_collection.children["Views"].children[collection.name].hide_viewport = True
|
||||
bpy.data.collections.get(collection.name).hide_render = True
|
||||
context.view_layer.layer_collection.children["Views"].children[
|
||||
camera.users_collection[0].name
|
||||
].hide_viewport = False
|
||||
|
||||
project_collection.children["Views"].children[camera.users_collection[0].name].hide_viewport = False
|
||||
bpy.data.collections.get(camera.users_collection[0].name).hide_render = False
|
||||
bpy.ops.bim.activate_drawing_style()
|
||||
return {"FINISHED"}
|
||||
@@ -787,12 +862,6 @@ class GenerateReferences(bpy.types.Operator):
|
||||
level_obj = annotation.Annotator.get_annotation_obj("Section Level", "curve", context)
|
||||
|
||||
width_in_mm = width * 1000
|
||||
if self.camera.data.BIMCameraProperties.diagram_scale == "CUSTOM":
|
||||
human_scale, fraction = self.camera.data.BIMCameraProperties.custom_diagram_scale.split("|")
|
||||
else:
|
||||
human_scale, fraction = self.camera.data.BIMCameraProperties.diagram_scale.split("|")
|
||||
numerator, denominator = fraction.split("/")
|
||||
scale = float(numerator) / float(denominator)
|
||||
real_world_width_in_mm = width_in_mm * scale
|
||||
offset_in_mm = 20
|
||||
offset_percentage = offset_in_mm / real_world_width_in_mm
|
||||
@@ -844,29 +913,6 @@ class RemoveVariable(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class PropagateTextData(bpy.types.Operator):
|
||||
bl_idname = "bim.propagate_text_data"
|
||||
bl_label = "Propagate Text Data"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
source = context.active_object
|
||||
for obj in context.selected_objects:
|
||||
if obj == source:
|
||||
continue
|
||||
obj.data.body = source.data.body
|
||||
obj.data.align_x = source.data.align_x
|
||||
obj.data.align_y = source.data.align_y
|
||||
obj.data.BIMTextProperties.font_size = source.data.BIMTextProperties.font_size
|
||||
obj.data.BIMTextProperties.symbol = source.data.BIMTextProperties.symbol
|
||||
obj.data.BIMTextProperties.variables.clear()
|
||||
for variable in source.data.BIMTextProperties.variables:
|
||||
new_variable = obj.data.BIMTextProperties.variables.add()
|
||||
new_variable.name = variable.name
|
||||
new_variable.prop_key = variable.prop_key
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemoveDrawing(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_drawing"
|
||||
bl_label = "Remove Drawing"
|
||||
@@ -1186,6 +1232,12 @@ class CopyGrid(bpy.types.Operator):
|
||||
return helper.get_active_drawing(context.scene)[0] is not None
|
||||
|
||||
def execute(self, context):
|
||||
subcontext = ifcopenshell.util.representation.get_context(
|
||||
IfcStore.get_file(), "Plan", "Annotation", context.scene.camera.data.BIMCameraProperties.target_view
|
||||
)
|
||||
if not subcontext:
|
||||
return {"FINISHED"}
|
||||
|
||||
proj_coll = helper.get_project_collection(context.scene)
|
||||
view_coll, camera = helper.get_active_drawing(context.scene)
|
||||
is_ortho = camera.data.type == "ORTHO"
|
||||
@@ -1194,11 +1246,19 @@ class CopyGrid(bpy.types.Operator):
|
||||
elevating = is_ortho and camera.data.BIMCameraProperties.target_view in ("ELEVATION_VIEW", "SECTION_VIEW")
|
||||
|
||||
def grep(coll):
|
||||
return [obj for obj in coll.all_objects if obj.name.startswith("IfcGridAxis")]
|
||||
results = []
|
||||
for obj in coll.all_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType == "GRID":
|
||||
results.append(obj)
|
||||
return results
|
||||
|
||||
def clone(src):
|
||||
dst = src.copy()
|
||||
dst.data = dst.data.copy()
|
||||
dst.name = dst.name.replace("IfcGridAxis/", "")
|
||||
dst.BIMObjectProperties.ifc_definition_id = 0
|
||||
dst.data.BIMMeshProperties.ifc_definition_id = 0
|
||||
return dst
|
||||
|
||||
def disassemble(obj):
|
||||
@@ -1245,17 +1305,25 @@ class CopyGrid(bpy.types.Operator):
|
||||
for obj in grep(view_coll):
|
||||
view_coll.objects.unlink(obj)
|
||||
|
||||
grid = [localize(*disassemble(clone(obj))) for obj in grep(proj_coll)]
|
||||
grids = []
|
||||
for element in tool.Ifc.get().by_type("IfcGridAxis"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not obj:
|
||||
continue
|
||||
grids.append((*localize(*disassemble(clone(obj))), element))
|
||||
|
||||
if clipping:
|
||||
grid = [(obj, clip(mesh)) for obj, mesh in grid]
|
||||
grids = [(obj, clip(mesh), element) for obj, mesh, element in grids]
|
||||
elif elevating:
|
||||
grid = [(obj, elev(mesh)) for obj, mesh in grid]
|
||||
grids = [(obj, elev(mesh), element) for obj, mesh, element in grids]
|
||||
|
||||
for obj, mesh in grid:
|
||||
for obj, mesh, element in grids:
|
||||
if mesh is not None:
|
||||
view_coll.objects.link(assemble(obj, mesh))
|
||||
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcAnnotation", context_id=subcontext.id())
|
||||
annotation = tool.Ifc.get_entity(obj)
|
||||
annotation.Name = element.AxisTag
|
||||
annotation.ObjectType = "GRID"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1314,3 +1382,30 @@ class AddSectionsAnnotations(bpy.types.Operator):
|
||||
view_coll.objects.link(obj)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditText(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_text"
|
||||
bl_label = "Edit Text"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_text(tool.Ifc, tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class EnableEditingText(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_text"
|
||||
bl_label = "Enable Editing Text"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_text(tool.Drawing, obj=context.active_object)
|
||||
|
||||
|
||||
class DisableEditingText(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_text"
|
||||
bl_label = "Disable Editing Text"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_text(tool.Drawing, obj=context.active_object)
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
import blenderbim.bim.module.drawing.decoration as decoration
|
||||
import enum
|
||||
@@ -53,6 +56,22 @@ def purge():
|
||||
vector_styles_enum = []
|
||||
|
||||
|
||||
def update_diagram_scale(self, context):
|
||||
scale = self.diagram_scale
|
||||
if scale == "CUSTOM":
|
||||
scale = self.custom_diagram_scale
|
||||
scale = scale.split("|")[1]
|
||||
element = tool.Ifc.get_entity(context.scene.camera)
|
||||
if not element:
|
||||
return
|
||||
pset = ifcopenshell.util.element.get_psets(element).get("EPset_Drawing")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Scale": scale})
|
||||
|
||||
|
||||
def get_diagram_scales(self, context):
|
||||
global diagram_scales_enum
|
||||
if (
|
||||
@@ -150,6 +169,10 @@ def refreshTitleblocks(self, context):
|
||||
def toggleDecorations(self, context):
|
||||
toggle = self.should_draw_decorations
|
||||
if toggle:
|
||||
# TODO: design a proper text variable templating renderer
|
||||
collection = context.scene.camera.users_collection[0]
|
||||
for obj in collection.objects:
|
||||
tool.Drawing.update_text_value(obj)
|
||||
decoration.DecorationsHandler.install(context)
|
||||
else:
|
||||
decoration.DecorationsHandler.uninstall()
|
||||
@@ -265,7 +288,7 @@ class DocProperties(PropertyGroup):
|
||||
drawing_styles: CollectionProperty(name="Drawing Styles", type=DrawingStyle)
|
||||
should_draw_decorations: BoolProperty(name="Should Draw Decorations", update=toggleDecorations)
|
||||
decorations_colour: FloatVectorProperty(
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
||||
name="Decorations Colour", subtype="COLOR", default=(1, 1, 1, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -282,6 +305,7 @@ class DocProperties(PropertyGroup):
|
||||
|
||||
|
||||
class BIMCameraProperties(PropertyGroup):
|
||||
representation: StringProperty(name="Representation")
|
||||
view_name: StringProperty(name="View Name")
|
||||
target_view: EnumProperty(
|
||||
items=[
|
||||
@@ -294,8 +318,8 @@ class BIMCameraProperties(PropertyGroup):
|
||||
name="Target View",
|
||||
default="PLAN_VIEW",
|
||||
)
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale")
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale")
|
||||
diagram_scale: EnumProperty(items=get_diagram_scales, name="Drawing Scale", update=update_diagram_scale)
|
||||
custom_diagram_scale: StringProperty(name="Custom Scale", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
is_nts: BoolProperty(name="Is NTS")
|
||||
@@ -314,8 +338,28 @@ class BIMCameraProperties(PropertyGroup):
|
||||
cut_objects_custom: StringProperty(name="Custom Cut")
|
||||
active_drawing_style_index: IntProperty(name="Active Drawing Style Index")
|
||||
|
||||
# For now, this JSON dump are all the parameters that determine a camera's "Block representation"
|
||||
# By checking this, you will know whether or not the camera IFC representation needs to be refreshed
|
||||
def update_representation(self, obj):
|
||||
representation = json.dumps(
|
||||
{
|
||||
"matrix": [list(x) for x in obj.matrix_world],
|
||||
"raster_x": self.raster_x,
|
||||
"raster_y": self.raster_y,
|
||||
"ortho_scale": obj.data.ortho_scale,
|
||||
"clip_end": obj.data.clip_end,
|
||||
}
|
||||
)
|
||||
if self.representation != representation:
|
||||
self.representation = representation
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
value: StringProperty(name="Value", default="TEXT")
|
||||
font_size: EnumProperty(
|
||||
items=[
|
||||
("1.8", "1.8 - Small", ""),
|
||||
@@ -324,6 +368,7 @@ class BIMTextProperties(PropertyGroup):
|
||||
("5.0", "5.0 - Header", ""),
|
||||
("7.0", "7.0 - Title", ""),
|
||||
],
|
||||
default="2.5",
|
||||
update=refreshFontSize,
|
||||
name="Font Size",
|
||||
)
|
||||
|
||||
@@ -24,6 +24,8 @@ import pystache
|
||||
import xml.etree.ElementTree as ET
|
||||
import svgwrite
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.drawing.helper as helper
|
||||
import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from mathutils import Vector
|
||||
@@ -168,7 +170,7 @@ class SvgWriter:
|
||||
line["marker-start"] = "url(#grid-marker)"
|
||||
line["marker-end"] = "url(#grid-marker)"
|
||||
line["stroke-dasharray"] = "12.5, 3, 3, 3"
|
||||
axis_tag = IfcStore.get_file().by_id(grid_obj.BIMObjectProperties.ifc_definition_id).AxisTag
|
||||
axis_tag = tool.Ifc.get_entity(grid_obj).Name
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
axis_tag,
|
||||
@@ -207,53 +209,10 @@ class SvgWriter:
|
||||
for obj_data in self.annotations.get("solid_objs", []):
|
||||
self.draw_line_annotation(obj_data, ["solid"])
|
||||
|
||||
if self.annotations.get("leader_obj"):
|
||||
self.draw_line_annotation(self.annotations["leader_obj"], ["leader"])
|
||||
self.draw_leader_annotations()
|
||||
|
||||
if self.annotations.get("plan_level_obj"):
|
||||
matrix_world = self.annotations["plan_level_obj"].matrix_world
|
||||
for spline in self.annotations["plan_level_obj"].data.splines:
|
||||
classes = ["annotation", "plan-level"]
|
||||
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.scale, (y_offset - p.y) * self.scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
path["marker-end"] = "url(#plan-level-marker)"
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.scale,
|
||||
((y_offset - projected_points[0].y) * self.scale) - 2.5,
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = ((matrix_world @ points[0].co).xyz + self.annotations["plan_level_obj"].location).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl)
|
||||
else:
|
||||
rl = "{:.3f}m".format(rl)
|
||||
if projected_points[0].x > projected_points[-1].x:
|
||||
text_anchor = "end"
|
||||
else:
|
||||
text_anchor = "start"
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
insert=tuple(text_position),
|
||||
**{
|
||||
"font-size": annotation.Annotator.get_svg_text_size(2.5),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "baseline",
|
||||
},
|
||||
)
|
||||
)
|
||||
for obj in self.annotations.get("plan_level_objs", []):
|
||||
self.draw_plan_level_annotation(obj)
|
||||
|
||||
if self.annotations.get("section_level_obj"):
|
||||
matrix_world = self.annotations["section_level_obj"].matrix_world
|
||||
@@ -455,72 +414,99 @@ class SvgWriter:
|
||||
self.svg.line(start=tuple(start * self.scale), end=tuple(end * self.scale), class_=" ".join(classes))
|
||||
)
|
||||
|
||||
def draw_leader_annotations(self):
|
||||
for obj in self.annotations.get("leader_objs", []):
|
||||
self.draw_line_annotation((obj, obj.data), ["leader"])
|
||||
spline = obj.data.splines[0]
|
||||
spline_points = spline.bezier_points if spline.bezier_points else spline.points
|
||||
if spline_points:
|
||||
position = obj.matrix_world @ spline_points[0].co.xyz
|
||||
else:
|
||||
position = Vector((0, 0, 0))
|
||||
self.draw_text_annotation(obj, position)
|
||||
|
||||
def draw_text_annotations(self):
|
||||
for text_obj in self.annotations.get("text_objs", []):
|
||||
self.draw_text_annotation(text_obj, text_obj.location)
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
element = tool.Ifc.get_entity(text_obj)
|
||||
rep = ifcopenshell.util.representation.get_representation(element, "Plan", "Annotation")
|
||||
text_literal = [i for i in rep.Items if i.is_a("IfcTextLiteral")][0]
|
||||
|
||||
for text_obj in self.annotations.get("text_objs", []):
|
||||
text_position = self.project_point_onto_camera(text_obj.location)
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
text_position = self.project_point_onto_camera(position)
|
||||
text_position = Vector(((x_offset + text_position.x), (y_offset - text_position.y)))
|
||||
|
||||
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
|
||||
projected_x_axis = self.project_point_onto_camera(text_obj.location + local_x_axis)
|
||||
angle = math.degrees(
|
||||
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
|
||||
Vector((1, 0))
|
||||
)
|
||||
local_x_axis = text_obj.matrix_world.to_quaternion() @ Vector((1, 0, 0))
|
||||
projected_x_axis = self.project_point_onto_camera(position + local_x_axis)
|
||||
angle = math.degrees(
|
||||
(Vector((x_offset + projected_x_axis.x, y_offset - projected_x_axis.y)) - text_position).angle_signed(
|
||||
Vector((1, 0))
|
||||
)
|
||||
)
|
||||
|
||||
transform = "rotate({}, {}, {})".format(
|
||||
angle,
|
||||
(text_position * self.scale)[0],
|
||||
(text_position * self.scale)[1],
|
||||
)
|
||||
|
||||
if text_obj.BIMTextProperties.symbol != "None":
|
||||
self.svg.add(
|
||||
self.svg.use("#{}".format(text_obj.BIMTextProperties.symbol), insert=tuple(text_position * self.scale))
|
||||
)
|
||||
|
||||
transform = "rotate({}, {}, {})".format(
|
||||
angle,
|
||||
(text_position * self.scale)[0],
|
||||
(text_position * self.scale)[1],
|
||||
if text_literal.BoxAlignment == "top-left":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "top-middle":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "top-right":
|
||||
alignment_baseline = "hanging"
|
||||
text_anchor = "end"
|
||||
elif text_literal.BoxAlignment == "middle-left":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "center":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "middle-right":
|
||||
alignment_baseline = "middle"
|
||||
text_anchor = "end"
|
||||
elif text_literal.BoxAlignment == "bottom-left":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "start"
|
||||
elif text_literal.BoxAlignment == "bottom-middle":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "middle"
|
||||
elif text_literal.BoxAlignment == "bottom-right":
|
||||
alignment_baseline = "baseline"
|
||||
text_anchor = "end"
|
||||
|
||||
text_body = text_literal.Literal
|
||||
if text_obj.name in self.annotations.get("template_variables", {}):
|
||||
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
|
||||
|
||||
for line_number, text_line in enumerate(text_body.split("\n")):
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
text_line,
|
||||
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
|
||||
class_=" ".join(self.get_attribute_classes(text_obj)),
|
||||
**{
|
||||
"font-size": annotation.Annotator.get_svg_text_size(text_obj.BIMTextProperties.font_size),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": alignment_baseline,
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"transform": transform,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if text_obj.data.BIMTextProperties.symbol != "None":
|
||||
self.svg.add(
|
||||
self.svg.use(
|
||||
"#{}".format(text_obj.data.BIMTextProperties.symbol), insert=tuple(text_position * self.scale)
|
||||
)
|
||||
)
|
||||
|
||||
if text_obj.data.align_x == "CENTER":
|
||||
text_anchor = "middle"
|
||||
elif text_obj.data.align_x == "RIGHT":
|
||||
text_anchor = "end"
|
||||
else:
|
||||
text_anchor = "start"
|
||||
|
||||
if text_obj.data.align_y == "CENTER":
|
||||
alignment_baseline = "middle"
|
||||
elif text_obj.data.align_y == "TOP":
|
||||
alignment_baseline = "hanging"
|
||||
else:
|
||||
alignment_baseline = "baseline"
|
||||
|
||||
text_body = text_obj.data.body
|
||||
if text_obj.name in self.annotations.get("template_variables", {}):
|
||||
text_body = pystache.render(text_body, self.annotations["template_variables"][text_obj.name])
|
||||
|
||||
for line_number, text_line in enumerate(text_body.split("\n")):
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
text_line,
|
||||
insert=tuple((text_position * self.scale) + Vector((0, 3.5 * line_number))),
|
||||
class_=" ".join(self.get_attribute_classes(text_obj)),
|
||||
**{
|
||||
"font-size": annotation.Annotator.get_svg_text_size(
|
||||
text_obj.data.BIMTextProperties.font_size
|
||||
),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": alignment_baseline,
|
||||
"dominant-baseline": alignment_baseline,
|
||||
"transform": transform,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def draw_break_annotations(self, break_obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -549,6 +535,54 @@ class SvgWriter:
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(["breakline"])))
|
||||
|
||||
def draw_plan_level_annotation(self, obj):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
matrix_world = obj.matrix_world
|
||||
for spline in obj.data.splines:
|
||||
classes = ["annotation", "plan-level"]
|
||||
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.scale, (y_offset - p.y) * self.scale)
|
||||
for p in projected_points
|
||||
]
|
||||
)
|
||||
d = "M{}".format(d[1:])
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
path["marker-end"] = "url(#plan-level-marker)"
|
||||
text_position = Vector(
|
||||
(
|
||||
(x_offset + projected_points[0].x) * self.scale,
|
||||
((y_offset - projected_points[0].y) * self.scale) - 2.5,
|
||||
)
|
||||
)
|
||||
# TODO: allow metric to be configurable
|
||||
rl = ((matrix_world @ points[0].co).xyz + obj.location).z
|
||||
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
||||
rl = helper.format_distance(rl)
|
||||
else:
|
||||
rl = "{:.3f}m".format(rl)
|
||||
if projected_points[0].x > projected_points[-1].x:
|
||||
text_anchor = "end"
|
||||
else:
|
||||
text_anchor = "start"
|
||||
self.svg.add(
|
||||
self.svg.text(
|
||||
"RL +{}".format(rl),
|
||||
insert=tuple(text_position),
|
||||
**{
|
||||
"font-size": annotation.Annotator.get_svg_text_size(2.5),
|
||||
"font-family": "OpenGost Type B TT",
|
||||
"text-anchor": text_anchor,
|
||||
"alignment-baseline": "baseline",
|
||||
"dominant-baseline": "baseline",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def draw_dimension_annotations(self, dimension_obj, text_override=None):
|
||||
matrix_world = dimension_obj.matrix_world
|
||||
for spline in dimension_obj.data.splines:
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.drawing.data import TextData
|
||||
|
||||
|
||||
class BIM_PT_camera(Panel):
|
||||
@@ -233,45 +236,63 @@ class BIM_PT_sheets(Panel):
|
||||
|
||||
|
||||
class BIM_PT_text(Panel):
|
||||
bl_label = "Text Paper Space"
|
||||
bl_label = "IFC Text"
|
||||
bl_idname = "BIM_PT_text"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "data"
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return type(context.curve) is bpy.types.TextCurve
|
||||
if not tool.Ifc.get() or not context.active_object:
|
||||
return
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
if not element:
|
||||
return
|
||||
return element.is_a("IfcAnnotation") and element.ObjectType in ["TEXT", "TEXT_LEADER"]
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
props = context.active_object.data.BIMTextProperties
|
||||
if not TextData.is_loaded:
|
||||
TextData.load()
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.propagate_text_data")
|
||||
self.layout.use_property_split = True
|
||||
props = context.active_object.BIMTextProperties
|
||||
|
||||
row = layout.row()
|
||||
if props.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_text", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_text", icon="CANCEL", text="")
|
||||
blenderbim.bim.helper.draw_attributes(props.attributes, self.layout)
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
|
||||
|
||||
for attribute in TextData.data["attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "font_size")
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.prop(props, "symbol")
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.prop(props, "related_element")
|
||||
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_variable")
|
||||
|
||||
for index, variable in enumerate(props.variables):
|
||||
row = layout.row(align=True)
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(variable, "name")
|
||||
row.operator("bim.remove_variable", icon="X", text="").index = index
|
||||
row = layout.row()
|
||||
row = self.layout.row()
|
||||
row.prop(variable, "prop_key")
|
||||
|
||||
|
||||
class BIM_PT_annotation_utilities(Panel):
|
||||
bl_idname = "BIM_PT_annotation_utilities"
|
||||
bl_label = "Annotation"
|
||||
bl_label = "Drawing"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
@@ -289,41 +310,42 @@ class BIM_PT_annotation_utilities(Panel):
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Dim", icon="ARROW_LEFTRIGHT")
|
||||
op.obj_name = "Dimension"
|
||||
op.object_type = "DIMENSION"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Dim (Eq)", icon="ARROW_LEFTRIGHT")
|
||||
op.obj_name = "Equal"
|
||||
op.object_type = "EQUAL_DIMENSION"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Text", icon="SMALL_CAPS")
|
||||
op.data_type = "text"
|
||||
op.object_type = "TEXT"
|
||||
op.data_type = "empty"
|
||||
op = row.operator("bim.add_annotation", text="Leader", icon="TRACKING_BACKWARDS")
|
||||
op.obj_name = "Leader"
|
||||
op.object_type = "TEXT_LEADER"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Stair Arrow", icon="SCREEN_BACK")
|
||||
op.obj_name = "Stair"
|
||||
op.object_type = "STAIR_ARROW"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Hidden", icon="CON_TRACKTO")
|
||||
op.obj_name = "Hidden"
|
||||
op.object_type = "HIDDEN_LINE"
|
||||
op.data_type = "mesh"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Level (Plan)", icon="SORTBYEXT")
|
||||
op.obj_name = "Plan Level"
|
||||
op.object_type = "PLAN_LEVEL"
|
||||
op.data_type = "curve"
|
||||
op = row.operator("bim.add_annotation", text="Level (Section)", icon="TRIA_DOWN")
|
||||
op.obj_name = "Section Level"
|
||||
op.object_type = "SECTION_LEVEL"
|
||||
op.data_type = "curve"
|
||||
|
||||
row = layout.row(align=True)
|
||||
op = row.operator("bim.add_annotation", text="Breakline", icon="FCURVE")
|
||||
op.obj_name = "Break"
|
||||
op.object_type = "BREAKLINE"
|
||||
op.data_type = "mesh"
|
||||
op = row.operator("bim.add_annotation", text="Misc", icon="MESH_MONKEY")
|
||||
op.obj_name = "Misc"
|
||||
op.object_type = "MISC"
|
||||
op.data_type = "mesh"
|
||||
|
||||
props = context.scene.DocProperties
|
||||
|
||||
@@ -20,13 +20,19 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.EditObjectPlacement,
|
||||
operator.AddRepresentation,
|
||||
operator.SwitchRepresentation,
|
||||
operator.RemoveRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.CopyRepresentation,
|
||||
operator.EditObjectPlacement,
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideOutlinerDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverridePasteBuffer,
|
||||
operator.RemoveRepresentation,
|
||||
operator.SwitchRepresentation,
|
||||
operator.UpdateParametricRepresentation,
|
||||
operator.UpdateRepresentation,
|
||||
prop.BIMGeometryProperties,
|
||||
ui.BIM_PT_derived_placements,
|
||||
ui.BIM_PT_representations,
|
||||
@@ -35,11 +41,30 @@ classes = (
|
||||
)
|
||||
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMGeometryProperties = bpy.props.PointerProperty(type=prop.BIMGeometryProperties)
|
||||
bpy.types.OBJECT_PT_transform.append(ui.BIM_PT_transform)
|
||||
wm = bpy.context.window_manager
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="3D View", space_type="VIEW_3D")
|
||||
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
|
||||
kmi.properties["name"] = "bim.override_paste_buffer"
|
||||
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)
|
||||
kmi.properties["name"] = "bim.override_paste_buffer"
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.OBJECT_PT_transform.remove(ui.BIM_PT_transform)
|
||||
del bpy.types.Scene.BIMGeometryProperties
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.placement
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def refresh():
|
||||
DerivedPlacementsData.is_loaded = False
|
||||
RepresentationsData.is_loaded = False
|
||||
|
||||
|
||||
class RepresentationsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"representations": cls.representations()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def representations(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
representations = []
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
representations = element.Representation.Representations
|
||||
elif element.is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in element.RepresentationMaps or []]
|
||||
for representation in representations:
|
||||
representation_type = representation.RepresentationType
|
||||
if representation_type == "MappedRepresentation":
|
||||
representation_type = representation.Items[0].MappingSource.MappedRepresentation.RepresentationType
|
||||
representation_type += "*"
|
||||
data = {
|
||||
"id": representation.id(),
|
||||
"ContextType": representation.ContextOfItems.ContextType or "",
|
||||
"ContextIdentifier": "",
|
||||
"TargetView": "",
|
||||
"RepresentationType": representation_type or "",
|
||||
}
|
||||
if representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext"):
|
||||
data["ContextIdentifier"] = representation.ContextOfItems.ContextIdentifier or ""
|
||||
data["TargetView"] = representation.ContextOfItems.TargetView or ""
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
class DerivedPlacementsData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.load_z_values()
|
||||
cls.load_collection()
|
||||
cls.data = {
|
||||
"is_storey": cls.is_storey(),
|
||||
"storey_height": cls.storey_height(),
|
||||
"min_global_z": cls.min_global_z(),
|
||||
"max_global_z": cls.max_global_z(),
|
||||
"has_collection": cls.has_collection(),
|
||||
"min_decomposed_z": cls.min_decomposed_z(),
|
||||
"max_decomposed_z": cls.max_decomposed_z(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def load_z_values(cls):
|
||||
cls.z_values = [
|
||||
(bpy.context.active_object.matrix_world @ Vector(co))[2] for co in bpy.context.active_object.bound_box
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def load_collection(cls):
|
||||
cls.collection = bpy.data.objects.get(bpy.context.active_object.users_collection[0].name)
|
||||
cls.collection_z = 0
|
||||
if cls.collection:
|
||||
cls.collection_z = cls.collection.matrix_world.translation.z
|
||||
|
||||
@classmethod
|
||||
def has_collection(cls):
|
||||
return bool(cls.collection)
|
||||
|
||||
@classmethod
|
||||
def min_global_z(cls):
|
||||
return "{0:.3f}".format(round(min(cls.z_values), 3))
|
||||
|
||||
@classmethod
|
||||
def max_global_z(cls):
|
||||
return "{0:.3f}".format(round(max(cls.z_values), 3))
|
||||
|
||||
@classmethod
|
||||
def min_decomposed_z(cls):
|
||||
return "{0:.3f}".format(round(min(cls.z_values) - cls.collection_z, 3))
|
||||
|
||||
@classmethod
|
||||
def max_decomposed_z(cls):
|
||||
return "{0:.3f}".format(round(max(cls.z_values) - cls.collection_z, 3))
|
||||
|
||||
@classmethod
|
||||
def is_storey(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if element:
|
||||
return element.is_a("IfcBuildingStorey")
|
||||
|
||||
@classmethod
|
||||
def storey_height(cls):
|
||||
if not cls.is_storey():
|
||||
return
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
storeys = [
|
||||
(s, ifcopenshell.util.placement.get_local_placement(s.ObjectPlacement)[2][3])
|
||||
for s in tool.Ifc.get().by_type("IfcBuildingStorey")
|
||||
]
|
||||
storeys = sorted(storeys, key=lambda s: s[1])
|
||||
for i, storey in enumerate(storeys):
|
||||
if storey[0] != element:
|
||||
continue
|
||||
if i >= len(storeys):
|
||||
return "N/A"
|
||||
return "{0:.3f}".format(round(storeys[i + 1][1] - storey[1], 3))
|
||||
@@ -22,7 +22,7 @@ import mathutils
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
from math import pi
|
||||
from mathutils import Vector, Matrix
|
||||
from mathutils import Vector, Matrix, geometry
|
||||
|
||||
|
||||
class Helper:
|
||||
@@ -215,6 +215,95 @@ class Helper:
|
||||
|
||||
return {"profile": outer_loop, "inner_curves": inner_loops, "extrusion": extrusion}
|
||||
|
||||
# An arbitrary face with voids is similar to a profile with voids for extrusion.
|
||||
# Before we begin we check that all faces are coplanar instead of finding suitable face.
|
||||
# Then we process the same way.
|
||||
# Only 2 parameters are returned as there is no extrusion.
|
||||
def auto_detect_curve_bounded_plane(self, mesh, tolerance=0.001):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
faces = bm.faces
|
||||
normal = faces[0].normal
|
||||
point = faces[0].verts[0].co
|
||||
for face in faces:
|
||||
pt_f = face.verts[0].co
|
||||
if (
|
||||
normal.dot(face.normal) < 1 - tolerance
|
||||
or abs(geometry.distance_point_to_plane(pt_f, point, normal)) > tolerance
|
||||
):
|
||||
raise ValueError("All faces must be coplanar and have same orientation")
|
||||
|
||||
loop_edges = set()
|
||||
for face in faces:
|
||||
potential_edges = set(face.edges)
|
||||
for face2 in faces:
|
||||
if face == face2:
|
||||
continue
|
||||
potential_edges -= set(face2.edges)
|
||||
loop_edges |= potential_edges
|
||||
|
||||
# Create loops from edges
|
||||
loops = []
|
||||
while loop_edges:
|
||||
edge = loop_edges.pop()
|
||||
loop = [edge]
|
||||
has_found_connected_edge = True
|
||||
while has_found_connected_edge:
|
||||
has_found_connected_edge = False
|
||||
for edge in loop_edges.copy():
|
||||
edge_verts = set(edge.verts)
|
||||
if edge_verts & set(loop[0].verts):
|
||||
loop.insert(0, edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
elif edge_verts & set(loop[-1].verts):
|
||||
loop.append(edge)
|
||||
loop_edges.remove(edge)
|
||||
has_found_connected_edge = True
|
||||
loops.append(loop)
|
||||
|
||||
# Determine outer loop
|
||||
max_area = 0
|
||||
outer_loop = None
|
||||
inner_loops = []
|
||||
|
||||
for loop in loops:
|
||||
loop_vertices = []
|
||||
total_edges = len(loop)
|
||||
for i, edge in enumerate(loop):
|
||||
if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[0] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[1])
|
||||
elif edge.verts[1] in loop[i + 1].verts:
|
||||
loop_vertices.append(edge.verts[0])
|
||||
|
||||
loop_bm = bmesh.new()
|
||||
for vert in loop_vertices:
|
||||
loop_bm.verts.new(vert.co)
|
||||
face = loop_bm.faces.new(loop_bm.verts)
|
||||
|
||||
loop_vertex_indices = [v.index for v in loop_vertices]
|
||||
face_area = face.calc_area()
|
||||
if face_area > max_area:
|
||||
max_area = face_area
|
||||
outer_loop = loop_vertex_indices
|
||||
inner_loops.append(loop_vertex_indices)
|
||||
loop_bm.free()
|
||||
|
||||
inner_loops.remove(outer_loop)
|
||||
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
|
||||
return {"outer_curve": outer_loop, "inner_curves": inner_loops}
|
||||
|
||||
# An extrusion edge is an edge that shares a single vertex with a profile
|
||||
# face and is not on the plane of the face.
|
||||
def detect_extrusion_edge(self, bm, profile_face):
|
||||
|
||||
@@ -17,26 +17,32 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import logging
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.geometry as core
|
||||
import blenderbim.core.style
|
||||
import blenderbim.core.root
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim import import_ifc
|
||||
import blenderbim.bim.handler
|
||||
from mathutils import Vector
|
||||
from ifcopenshell.api.geometry.data import Data
|
||||
from ifcopenshell.api.context.data import Data as ContextData
|
||||
from ifcopenshell.api.void.data import Data as VoidData
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim import import_ifc
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.root.prop import get_contexts
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -49,10 +55,10 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
|
||||
def _execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator):
|
||||
class AddRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -61,94 +67,26 @@ class AddRepresentation(bpy.types.Operator):
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
profile_set_usage: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
core.edit_object_placement(tool.Ifc, tool.Surveyor, obj=obj)
|
||||
|
||||
if not obj.data:
|
||||
return {"FINISHED"}
|
||||
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
context_id = self.context_id or int(context.scene.BIMProperties.contexts)
|
||||
context_of_items = self.file.by_id(context_id)
|
||||
|
||||
gprop = context.scene.BIMGeoreferenceProperties
|
||||
coordinate_offset = None
|
||||
if gprop.has_blender_offset and obj.BIMObjectProperties.blender_offset_type == "CARTESIAN_POINT":
|
||||
coordinate_offset = Vector(
|
||||
(
|
||||
float(gprop.blender_eastings),
|
||||
float(gprop.blender_northings),
|
||||
float(gprop.blender_orthogonal_height),
|
||||
)
|
||||
)
|
||||
|
||||
representation_data = {
|
||||
"context": context_of_items,
|
||||
"blender_object": obj,
|
||||
"geometry": obj.data,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": max(1, len(obj.material_slots)),
|
||||
"should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep,
|
||||
"should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation,
|
||||
"ifc_representation_class": self.ifc_representation_class,
|
||||
"profile_set_usage": self.file.by_id(self.profile_set_usage) if self.profile_set_usage else None,
|
||||
}
|
||||
|
||||
result = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
if not result:
|
||||
print("Failed to write shape representation")
|
||||
return {"FINISHED"}
|
||||
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
for s in obj.material_slots
|
||||
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
|
||||
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons):
|
||||
ifcopenshell.api.run(
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
**{
|
||||
"shape_representation": result,
|
||||
"styles": [
|
||||
self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id)
|
||||
for s in obj.material_slots
|
||||
if s.material
|
||||
],
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation", self.file, **{"product": product, "representation": result}
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
profile_set_usage=tool.Ifc.get().by_id(self.profile_set_usage) if self.profile_set_usage else None,
|
||||
)
|
||||
|
||||
mesh = obj.data.copy()
|
||||
mesh.name = "{}/{}".format(context_id, result.id())
|
||||
mesh.BIMMeshProperties.ifc_definition_id = int(result.id())
|
||||
obj.data = mesh
|
||||
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
if product.is_a("IfcTypeProduct"):
|
||||
if self.file.schema == "IFC2X3":
|
||||
types = product.ObjectTypeOf
|
||||
else:
|
||||
types = product.Types
|
||||
if types:
|
||||
for element in types[0].RelatedObjects:
|
||||
Data.load(self.file, element.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SwitchRepresentation(bpy.types.Operator):
|
||||
class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.switch_representation"
|
||||
bl_label = "Switch Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -158,117 +96,31 @@ class SwitchRepresentation(bpy.types.Operator):
|
||||
disable_opening_subtractions: bpy.props.BoolProperty()
|
||||
should_switch_all_meshes: bpy.props.BoolProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.element_obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
self.oprops = self.element_obj.BIMObjectProperties
|
||||
|
||||
self.file = IfcStore.get_file()
|
||||
self.context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems
|
||||
self.mesh_name = self.get_mesh_name()
|
||||
|
||||
mesh = bpy.data.meshes.get(self.mesh_name)
|
||||
if mesh:
|
||||
self.switch_mesh(mesh)
|
||||
if not mesh or self.should_reload:
|
||||
self.pull_mesh_from_ifc(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def switch_mesh(self, mesh):
|
||||
if self.should_switch_all_meshes or self.file.by_id(self.oprops.ifc_definition_id).is_a("IfcTypeProduct"):
|
||||
self.element_obj.data.user_remap(mesh)
|
||||
else:
|
||||
self.element_obj.data = mesh
|
||||
|
||||
def get_mesh_name(self):
|
||||
representation = self.resolve_mapped_representation(self.file.by_id(self.ifc_definition_id))
|
||||
return "{}/{}".format(self.context_of_items.id(), representation.id())
|
||||
|
||||
def resolve_mapped_representation(self, representation):
|
||||
if representation.RepresentationType == "MappedRepresentation":
|
||||
return self.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
def pull_mesh_from_ifc(self, context):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(context, IfcStore.path, logger)
|
||||
element = self.file.by_id(self.oprops.ifc_definition_id)
|
||||
settings = ifcopenshell.geom.settings()
|
||||
|
||||
if self.context_of_items.ContextIdentifier == "Body":
|
||||
if element.is_a("IfcTypeProduct") or self.disable_opening_subtractions:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
|
||||
else:
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.oprops.ifc_definition_id))
|
||||
else:
|
||||
settings.set(settings.INCLUDE_CURVES, True)
|
||||
shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id))
|
||||
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = self.file
|
||||
mesh = ifc_importer.create_mesh(element, shape)
|
||||
mesh.name = self.mesh_name
|
||||
mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id
|
||||
self.switch_mesh(mesh)
|
||||
material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer)
|
||||
material_creator.load_existing_materials()
|
||||
material_creator.create(element, self.element_obj, mesh)
|
||||
|
||||
if self.disable_opening_subtractions and self.context_of_items.ContextIdentifier == "Body":
|
||||
if self.oprops.ifc_definition_id not in VoidData.products:
|
||||
VoidData.load(self.file, self.oprops.ifc_definition_id)
|
||||
for opening_id in VoidData.products[self.oprops.ifc_definition_id]:
|
||||
opening = IfcStore.get_element(opening_id)
|
||||
if not opening:
|
||||
continue
|
||||
modifier = self.element_obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
|
||||
modifier.operation = "DIFFERENCE"
|
||||
modifier.object = opening
|
||||
modifier.solver = "EXACT"
|
||||
modifier.use_self = True
|
||||
else:
|
||||
for modifier in self.element_obj.modifiers:
|
||||
if modifier.type == "BOOLEAN" and "IfcOpeningElement" in modifier.name:
|
||||
self.element_obj.modifiers.remove(modifier)
|
||||
def _execute(self, context):
|
||||
core.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=bpy.data.objects.get(self.obj) if self.obj else context.active_object,
|
||||
representation=tool.Ifc.get().by_id(self.ifc_definition_id),
|
||||
should_reload=self.should_reload,
|
||||
enable_dynamic_voids=self.disable_opening_subtractions,
|
||||
is_global=self.should_switch_all_meshes,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
class RemoveRepresentation(bpy.types.Operator):
|
||||
class RemoveRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_representation"
|
||||
bl_label = "Remove Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
representation_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
representation = self.file.by_id(self.representation_id)
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
is_mapped_representation = representation.RepresentationType == "MappedRepresentation"
|
||||
if is_mapped_representation:
|
||||
mesh_name = "{}/{}".format(
|
||||
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
|
||||
)
|
||||
else:
|
||||
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
|
||||
mesh = bpy.data.meshes.get(mesh_name)
|
||||
if mesh:
|
||||
if obj.data == mesh:
|
||||
# TODO we can do better than this
|
||||
void_mesh = bpy.data.meshes.get("Void")
|
||||
if not void_mesh:
|
||||
void_mesh = bpy.data.meshes.new("Void")
|
||||
obj.data = void_mesh
|
||||
if not is_mapped_representation:
|
||||
bpy.data.meshes.remove(mesh)
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", self.file, **{"product": product, "representation": representation}
|
||||
core.remove_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=context.active_object,
|
||||
representation=tool.Ifc.get().by_id(self.representation_id),
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
|
||||
Data.load(self.file, product.id())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UpdateRepresentation(bpy.types.Operator):
|
||||
@@ -304,7 +156,7 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
ifcopenshell.api.run("grid.create_axis_curve", self.file, **{"axis_curve": obj, "grid_axis": product})
|
||||
return
|
||||
|
||||
bpy.ops.bim.edit_object_placement(obj=obj.name)
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
@@ -331,10 +183,17 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
"ifc_representation_class": self.ifc_representation_class,
|
||||
}
|
||||
|
||||
if not self.ifc_representation_class:
|
||||
representation_data["ifc_representation_class"] = tool.Geometry.get_ifc_representation_class(
|
||||
product, old_representation
|
||||
)
|
||||
representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product)
|
||||
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
|
||||
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
|
||||
[
|
||||
bpy.ops.bim.add_style(material=s.material.name)
|
||||
blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=s.material)
|
||||
for s in obj.material_slots
|
||||
if s.material and not s.material.BIMMaterialProperties.ifc_style_id
|
||||
]
|
||||
@@ -353,6 +212,7 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
"should_use_presentation_style_assignment": context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
},
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
# TODO: move this into a replace_representation usecase or something
|
||||
for inverse in self.file.get_inverse(old_representation):
|
||||
@@ -360,10 +220,10 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
|
||||
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
|
||||
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
|
||||
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name)
|
||||
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
|
||||
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
||||
if obj.data.BIMMeshProperties.ifc_parameters:
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
|
||||
|
||||
class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
@@ -383,34 +243,307 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
parameter = props.ifc_parameters[self.index]
|
||||
self.file.by_id(parameter.step_id)[parameter.index] = parameter.value
|
||||
show_representation_parameters = bool(props.ifc_parameters)
|
||||
bpy.ops.bim.switch_representation(
|
||||
ifc_definition_id=props.ifc_definition_id, should_reload=True, should_switch_all_meshes=True
|
||||
core.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=tool.Ifc.get().by_id(props.ifc_definition_id),
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if show_representation_parameters:
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator):
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.get_representation_ifc_parameters"
|
||||
bl_label = "Get Representation IFC Parameters"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
|
||||
|
||||
|
||||
class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.copy_representation"
|
||||
bl_label = "Copy Representation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
if not context.active_object:
|
||||
return
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(context.active_object.data)
|
||||
geometric_context = tool.Root.get_representation_context(
|
||||
tool.Root.get_object_representation(context.active_object)
|
||||
)
|
||||
for obj in context.selected_objects:
|
||||
if obj == context.active_object:
|
||||
continue
|
||||
if obj.data:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
bm.to_mesh(obj.data)
|
||||
old_rep = self.get_representation_by_context(element, geometric_context)
|
||||
if old_rep:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", tool.Ifc.get(), representation=old_rep)
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
obj=obj,
|
||||
context=geometric_context,
|
||||
ifc_representation_class=None,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
def get_representation_by_context(self, element, context):
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
for r in element.Representation.Representations:
|
||||
if r.ContextOfItems == context:
|
||||
return r
|
||||
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
|
||||
for r in element.RepresentationMaps:
|
||||
if r.MappedRepresentation.ContextOfItems == context:
|
||||
return r.MappedRepresentation
|
||||
|
||||
|
||||
class OverrideDeleteTrait:
|
||||
def delete_ifc_object(self, obj):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
IfcStore.deleted_ids.add(element.id())
|
||||
if getattr(element, "FillsVoids", None):
|
||||
self.remove_filling(element)
|
||||
if element.is_a("IfcOpeningElement"):
|
||||
for rel in element.HasFillings:
|
||||
self.remove_filling(rel.RelatedBuildingElement)
|
||||
if element.VoidsElements:
|
||||
self.delete_opening_element(element)
|
||||
elif getattr(element, "HasOpenings", None):
|
||||
for rel in element.HasOpenings:
|
||||
self.delete_opening_element(rel.RelatedOpeningElement)
|
||||
|
||||
def delete_opening_element(self, element):
|
||||
obj = IfcStore.get_element(element.VoidsElements[0].RelatingBuildingElement.id())
|
||||
bpy.ops.bim.remove_opening(opening_id=element.id(), obj=obj.name)
|
||||
|
||||
def remove_filling(self, element):
|
||||
obj = IfcStore.get_element(element.id())
|
||||
bpy.ops.bim.remove_filling(obj=obj.name)
|
||||
|
||||
|
||||
class OverrideDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "object.delete"
|
||||
bl_label = "Delete"
|
||||
use_global: bpy.props.BoolProperty(default=False)
|
||||
confirm: bpy.props.BoolProperty(default=True)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) > 0
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
elements = self.file.traverse(self.file.by_id(props.ifc_definition_id))
|
||||
props.ifc_parameters.clear()
|
||||
for element in elements:
|
||||
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
for obj in context.selected_objects:
|
||||
bpy.data.objects.remove(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_confirm(self, event)
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
self.delete_ifc_object(obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverrideOutlinerDelete(bpy.types.Operator, OverrideDeleteTrait):
|
||||
bl_idname = "outliner.delete"
|
||||
bl_label = "Delete"
|
||||
hierarchy: bpy.props.BoolProperty(default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_ids) > 0
|
||||
|
||||
def execute(self, context):
|
||||
# In this override, we don't check self.hierarchy. This effectively
|
||||
# makes Delete and Delete Hierarchy identical. This is on purpose, since
|
||||
# non-hierarchical deletion may imply a whole bunch of potentially
|
||||
# unintended IFC spatial modifications. To make life less confusing for
|
||||
# the user, Delete means Delete. End of story.
|
||||
# Deep magick from the dawn of time
|
||||
if IfcStore.get_file():
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
# https://blender.stackexchange.com/questions/203729/python-get-selected-objects-in-outliner
|
||||
objects_to_delete = set()
|
||||
collections_to_delete = set()
|
||||
for item in context.selected_ids:
|
||||
if item.bl_rna.identifier == "Collection":
|
||||
collection = bpy.data.collections.get(item.name)
|
||||
collection_data = self.get_collection_objects_and_children(collection)
|
||||
print(collection_data)
|
||||
objects_to_delete |= collection_data["objects"]
|
||||
collections_to_delete |= collection_data["children"]
|
||||
collections_to_delete.add(collection)
|
||||
elif item.bl_rna.identifier == "Object":
|
||||
objects_to_delete.add(bpy.data.objects.get(item.name))
|
||||
for obj in objects_to_delete:
|
||||
bpy.data.objects.remove(obj)
|
||||
for collection in collections_to_delete:
|
||||
bpy.data.collections.remove(collection)
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
objects_to_delete = set()
|
||||
collections_to_delete = set()
|
||||
for item in context.selected_ids:
|
||||
if item.bl_rna.identifier == "Collection":
|
||||
collection = bpy.data.collections.get(item.name)
|
||||
collection_data = self.get_collection_objects_and_children(collection)
|
||||
objects_to_delete |= collection_data["objects"]
|
||||
collections_to_delete |= collection_data["children"]
|
||||
collections_to_delete.add(collection)
|
||||
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
|
||||
self.delete_ifc_object(obj)
|
||||
bpy.data.objects.remove(obj)
|
||||
for collection in collections_to_delete:
|
||||
bpy.data.collections.remove(collection)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_collection_objects_and_children(self, collection):
|
||||
objects = set()
|
||||
children = set()
|
||||
queue = [collection]
|
||||
while queue:
|
||||
collection = queue.pop()
|
||||
for obj in collection.objects:
|
||||
print('obj is', obj)
|
||||
objects.add(obj)
|
||||
queue.extend(collection.children)
|
||||
children = children.union(collection.children)
|
||||
return {"objects": objects, "children": children}
|
||||
|
||||
|
||||
class OverrideDuplicateMove(bpy.types.Operator):
|
||||
bl_idname = "object.duplicate_move"
|
||||
bl_label = "Duplicate Objects"
|
||||
|
||||
@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
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.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:
|
||||
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 the only difference
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
bl_idname = "object.duplicate_move_linked"
|
||||
bl_label = "Duplicate Linked"
|
||||
|
||||
@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 == 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
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.new_active_obj = None
|
||||
for obj in context.selected_objects:
|
||||
new_obj = obj.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 the only difference
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
bpy.ops.transform.translate("INVOKE_DEFAULT")
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class OverridePasteBuffer(bpy.types.Operator):
|
||||
bl_idname = "bim.override_paste_buffer"
|
||||
bl_label = "Paste BIM Objects"
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.view3d.pastebuffer()
|
||||
if IfcStore.get_file():
|
||||
for obj in context.selected_objects:
|
||||
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel
|
||||
from ifcopenshell.api.geometry.data import Data
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData
|
||||
|
||||
|
||||
class BIM_PT_representations(Panel):
|
||||
@@ -29,6 +29,7 @@ class BIM_PT_representations(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -39,33 +40,31 @@ class BIM_PT_representations(Panel):
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
if not RepresentationsData.is_loaded:
|
||||
RepresentationsData.load()
|
||||
|
||||
layout = self.layout
|
||||
props = context.active_object.BIMObjectProperties
|
||||
|
||||
if props.ifc_definition_id not in Data.products:
|
||||
Data.load(IfcStore.get_file(), props.ifc_definition_id)
|
||||
|
||||
representations = Data.products[props.ifc_definition_id]
|
||||
if not representations:
|
||||
if not RepresentationsData.data["representations"]:
|
||||
layout.label(text="No representations found")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(context.scene.BIMProperties, "contexts", text="")
|
||||
row.operator("bim.add_representation", icon="ADD", text="")
|
||||
|
||||
for ifc_definition_id in representations:
|
||||
representation = Data.representations[ifc_definition_id]
|
||||
for representation in RepresentationsData.data["representations"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=representation["ContextOfItems"]["ContextType"])
|
||||
row.label(text=representation["ContextOfItems"]["ContextIdentifier"])
|
||||
row.label(text=representation["ContextOfItems"]["TargetView"])
|
||||
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="OUTLINER_DATA_MESH", text="")
|
||||
op.should_switch_all_meshes = True
|
||||
op.should_reload = True
|
||||
op.ifc_definition_id = ifc_definition_id
|
||||
op.ifc_definition_id = representation["id"]
|
||||
op.disable_opening_subtractions = False
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = ifc_definition_id
|
||||
row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"]
|
||||
|
||||
|
||||
class BIM_PT_mesh(Panel):
|
||||
@@ -102,9 +101,16 @@ class BIM_PT_mesh(Panel):
|
||||
op.ifc_definition_id = props.ifc_definition_id
|
||||
op.disable_opening_subtractions = True
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.copy_representation")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("bim.update_representation")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Tessellation")
|
||||
op.ifc_representation_class = "IfcTessellatedFaceSet"
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.update_representation", text="Update Mesh As Rectangle Extrusion")
|
||||
op.ifc_representation_class = "IfcExtrudedAreaSolid/IfcRectangleProfileDef"
|
||||
@@ -150,24 +156,28 @@ class BIM_PT_derived_placements(Panel):
|
||||
bl_parent_id = "OBJECT_PT_transform"
|
||||
|
||||
def draw(self, context):
|
||||
z = context.active_object.matrix_world.translation.z
|
||||
z_values = [co[2] for co in context.active_object.bound_box]
|
||||
if not DerivedPlacementsData.is_loaded:
|
||||
DerivedPlacementsData.load()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Global Z")
|
||||
row.label(text="{0:.3f}".format(min(z_values) + z))
|
||||
row.label(text=DerivedPlacementsData.data["min_global_z"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Global Z")
|
||||
row.label(text="{0:.3f}".format(max(z_values) + z))
|
||||
row.label(text=DerivedPlacementsData.data["max_global_z"])
|
||||
|
||||
collection = bpy.data.objects.get(context.active_object.users_collection[0].name)
|
||||
if collection:
|
||||
collection_z = collection.matrix_world.translation.z
|
||||
if DerivedPlacementsData.data["has_collection"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Min Local Z")
|
||||
row.label(text="{0:.3f}".format(min(z_values) + z - collection_z))
|
||||
row.label(text="Min Decomposed Z")
|
||||
row.label(text=DerivedPlacementsData.data["min_decomposed_z"])
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Max Local Z")
|
||||
row.label(text="{0:.3f}".format(max(z_values) + z - collection_z))
|
||||
row.label(text="Max Decomposed Z")
|
||||
row.label(text=DerivedPlacementsData.data["max_decomposed_z"])
|
||||
|
||||
if DerivedPlacementsData.data["is_storey"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Storey Height")
|
||||
row.label(text=DerivedPlacementsData.data["storey_height"])
|
||||
|
||||
|
||||
class BIM_PT_workarounds(Panel):
|
||||
|
||||
@@ -30,6 +30,7 @@ class BIM_PT_gis(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import prop
|
||||
from . import operator
|
||||
from . import ui
|
||||
|
||||
classes = (
|
||||
prop.BIMCityJsonProperties,
|
||||
operator.BIM_OT_cityjson2ifc,
|
||||
operator.BIM_OT_find_cityjson_lod,
|
||||
ui.BIM_PT_cityjson_converter,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.ifccityjson_properties = bpy.props.PointerProperty(type=prop.BIMCityJsonProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.ifccityjson_properties
|
||||
@@ -0,0 +1,69 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
|
||||
class BIM_OT_cityjson2ifc(Operator):
|
||||
bl_idname = "bim.convert_cityjson2ifc"
|
||||
bl_label = "Convert CityJSON to IFC"
|
||||
bl_context = "scene"
|
||||
|
||||
def execute(self, context):
|
||||
from cjio import cityjson
|
||||
from ifccityjson.cityjson2ifc.cityjson2ifc import Cityjson2ifc
|
||||
|
||||
props = context.scene.ifccityjson_properties
|
||||
city_model = cityjson.load(props.input, transform=False)
|
||||
data = {
|
||||
"file_destination": props.output,
|
||||
"lod": props.lod,
|
||||
"split": props.split_lod,
|
||||
}
|
||||
if props.name != "":
|
||||
data["name_attribute"] = props.name
|
||||
|
||||
converter = Cityjson2ifc()
|
||||
converter.configuration(**data)
|
||||
converter.convert(city_model)
|
||||
if props.load_after_convert:
|
||||
bpy.ops.bim.load_project(filepath=props.output)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_find_cityjson_lod(Operator):
|
||||
bl_idname = "bim.find_cityjson_lod"
|
||||
bl_label = "Find LODs in CityJSON file"
|
||||
bl_context = "scene"
|
||||
|
||||
def execute(self, context):
|
||||
from cjio import cityjson
|
||||
|
||||
props = context.scene.ifccityjson_properties
|
||||
city_model = cityjson.load(props.input)
|
||||
lods = set()
|
||||
for obj_id, obj in city_model.get_cityobjects().items():
|
||||
for geometry in obj.geometry:
|
||||
lod = str(geometry.lod)
|
||||
if lod not in lods:
|
||||
lods.add(lod)
|
||||
props.lods.add()
|
||||
props.lods[-1].name = lod
|
||||
props.is_lod_found = True
|
||||
return {"FINISHED"}
|
||||
@@ -0,0 +1,36 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
|
||||
|
||||
class BIMCityJsonProperties(PropertyGroup):
|
||||
def get_lods(self, context):
|
||||
return [(item.name, "LOD" + item.name, "Level of Detail " + item.name) for item in self.lods]
|
||||
|
||||
# TODO: instead of subtype it would be nice to have a helper operator that allows filtered file browsing
|
||||
input: StringProperty(name="CityJSON Input", default="", subtype="FILE_PATH")
|
||||
output: StringProperty(name="IFC Output", default="", subtype="FILE_PATH")
|
||||
name: StringProperty(name="Identifier", default="")
|
||||
split_lod: BoolProperty(name="Should Split LOD", default=True)
|
||||
lods: CollectionProperty(name="LODs", type=StrProperty)
|
||||
lod: EnumProperty(name="LOD", description="", items=get_lods, options={"ANIMATABLE"}, default=None)
|
||||
is_lod_found: BoolProperty(name="Is LOD Found", default=False)
|
||||
load_after_convert: BoolProperty(name="Load After Converting", default=True)
|
||||
@@ -0,0 +1,52 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from bpy.types import Panel
|
||||
|
||||
|
||||
class BIM_PT_cityjson_converter(Panel):
|
||||
bl_label = "IFC 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_and_data_exchange"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
props = scene.ifccityjson_properties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "input")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.find_cityjson_lod")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "output")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "name")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "split_lod")
|
||||
row.prop(props, "load_after_convert")
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "lod")
|
||||
if not props.is_lod_found:
|
||||
row.enabled = False
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.convert_cityjson2ifc")
|
||||
@@ -28,6 +28,7 @@ class BIM_PT_groups(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_utilities"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -74,6 +75,7 @@ class BIM_PT_object_groups(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_utilities_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -28,6 +28,7 @@ class BIM_PT_layers(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_geometry_and_spatial_structure_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -21,6 +21,7 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CalculateLCA,
|
||||
operator.ConnectOpenLCA,
|
||||
prop.BIMLCAProperties,
|
||||
ui.BIM_PT_lca,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import olca
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
LcaData.is_loaded = False
|
||||
|
||||
|
||||
class LcaData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"product_systems": cls.product_systems(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def product_systems(cls):
|
||||
if not bpy.context.scene.BIMLCAProperties.is_connected:
|
||||
return []
|
||||
results = []
|
||||
client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
|
||||
try:
|
||||
results = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)]
|
||||
except:
|
||||
pass
|
||||
return results
|
||||
@@ -43,3 +43,13 @@ class CalculateLCA(bpy.types.Operator):
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ConnectOpenLCA(bpy.types.Operator):
|
||||
bl_idname = "bim.connect_openlca"
|
||||
bl_label = "Connect OpenLCA"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.context.scene.BIMLCAProperties.is_connected = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import olca
|
||||
from blenderbim.bim.module.lca.data import LcaData
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -31,25 +31,14 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
productsystems_enum = []
|
||||
|
||||
|
||||
def purge():
|
||||
global productsystems_enum
|
||||
productsystems_enum = []
|
||||
|
||||
|
||||
def get_product_systems(self, context):
|
||||
global productsystems_enum
|
||||
if not len(productsystems_enum):
|
||||
client = olca.Client(context.preferences.addons["blenderbim"].preferences.openlca_port)
|
||||
try:
|
||||
productsystems_enum = [(ps.name, ps.name, "") for ps in client.get_all(olca.ProductSystem)]
|
||||
except:
|
||||
pass
|
||||
return productsystems_enum
|
||||
if not LcaData.is_loaded:
|
||||
LcaData.load()
|
||||
return LcaData.data["product_systems"]
|
||||
|
||||
|
||||
class BIMLCAProperties(PropertyGroup):
|
||||
is_connected: BoolProperty(name="Is Connected")
|
||||
amount: FloatProperty(name="Amount")
|
||||
product_systems: EnumProperty(items=get_product_systems, name="Product Systems")
|
||||
|
||||
@@ -30,9 +30,13 @@ class BIM_PT_lca(Panel):
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMLCAProperties
|
||||
row = self.layout.row()
|
||||
row.prop(props, "product_systems", text="")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "amount", text="")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.calculate_lca")
|
||||
if props.is_connected:
|
||||
row = self.layout.row()
|
||||
row.prop(props, "product_systems", text="")
|
||||
row = self.layout.row()
|
||||
row.prop(props, "amount", text="")
|
||||
row = self.layout.row()
|
||||
row.operator("bim.calculate_lca")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.connect_openlca")
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddLibrary,
|
||||
operator.AddLibraryReference,
|
||||
operator.AssignLibraryReference,
|
||||
operator.DisableEditingLibrary,
|
||||
operator.DisableEditingLibraryReference,
|
||||
operator.DisableEditingLibraryReferences,
|
||||
operator.EditLibrary,
|
||||
operator.EditLibraryReference,
|
||||
operator.EnableEditingLibrary,
|
||||
operator.EnableEditingLibraryReference,
|
||||
operator.EnableEditingLibraryReferences,
|
||||
operator.RemoveLibrary,
|
||||
operator.RemoveLibraryReference,
|
||||
operator.UnassignLibraryReference,
|
||||
prop.LibraryReference,
|
||||
prop.BIMLibraryProperties,
|
||||
ui.BIM_PT_libraries,
|
||||
ui.BIM_PT_library_references,
|
||||
ui.BIM_UL_library_references,
|
||||
ui.BIM_UL_object_library_references,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMLibraryProperties = bpy.props.PointerProperty(type=prop.BIMLibraryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMLibraryProperties
|
||||
@@ -0,0 +1,110 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
LibrariesData.is_loaded = False
|
||||
LibraryReferencesData.is_loaded = False
|
||||
|
||||
|
||||
class LibrariesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"libraries": cls.libraries(),
|
||||
"library_attributes": cls.library_attributes(),
|
||||
"reference_attributes": cls.reference_attributes(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def libraries(cls):
|
||||
results = []
|
||||
for library in tool.Ifc.get().by_type("IfcLibraryInformation"):
|
||||
results.append({"id": library.id(), "name": library.Name})
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def library_attributes(cls):
|
||||
library_id = bpy.context.scene.BIMLibraryProperties.active_library_id
|
||||
if not library_id:
|
||||
return []
|
||||
results = []
|
||||
data = tool.Ifc.get().by_id(library_id).get_info()
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
del data["VersionDate"]
|
||||
for key, value in data.items():
|
||||
if key in ["id", "type"]:
|
||||
continue
|
||||
if value is not None:
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def reference_attributes(cls):
|
||||
props = bpy.context.scene.BIMLibraryProperties
|
||||
try:
|
||||
reference_id = props.references[props.active_reference_index].ifc_definition_id
|
||||
except:
|
||||
return []
|
||||
if not reference_id:
|
||||
return []
|
||||
results = []
|
||||
data = tool.Ifc.get().by_id(reference_id).get_info()
|
||||
del data["ReferencedLibrary"]
|
||||
for key, value in data.items():
|
||||
if key in ["id", "type"]:
|
||||
continue
|
||||
if value is not None:
|
||||
results.append({"name": key, "value": str(value)})
|
||||
return results
|
||||
|
||||
|
||||
class LibraryReferencesData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"references": cls.references(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
|
||||
if rel.is_a("IfcRelAssociatesLibrary"):
|
||||
library = rel.RelatingLibrary
|
||||
results.append(
|
||||
{
|
||||
"id": library.id(),
|
||||
"identification": library.ItemReference
|
||||
if tool.Ifc.get_schema() == "IFC2X3"
|
||||
else library.Identification,
|
||||
"name": library.Name or "Unnamed",
|
||||
}
|
||||
)
|
||||
return results
|
||||
@@ -0,0 +1,167 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.library as core
|
||||
import blenderbim.bim.handler
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddLibrary(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_library"
|
||||
bl_label = "Add Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_library(tool.Ifc)
|
||||
|
||||
|
||||
class RemoveLibrary(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_library"
|
||||
bl_label = "Remove Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
library: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_library(tool.Ifc, library=tool.Ifc.get().by_id(self.library))
|
||||
|
||||
|
||||
class EnableEditingLibraryReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_library_references"
|
||||
bl_label = "Enable Editing Library References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
library: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_library_references(tool.Library, library=tool.Ifc.get().by_id(self.library))
|
||||
|
||||
|
||||
class DisableEditingLibraryReferences(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_library_references"
|
||||
bl_label = "Disable Editing Library References"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_library_references(tool.Library)
|
||||
|
||||
|
||||
class EnableEditingLibrary(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_library"
|
||||
bl_label = "Enable Editing Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_library(tool.Library)
|
||||
|
||||
|
||||
class DisableEditingLibrary(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_library"
|
||||
bl_label = "Disable Editing Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_library(tool.Library)
|
||||
|
||||
|
||||
class EditLibrary(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_library"
|
||||
bl_label = "Edit Library"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_library(tool.Ifc, tool.Library)
|
||||
|
||||
|
||||
class AddLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_library_reference"
|
||||
bl_label = "Add Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_library_reference(tool.Ifc, tool.Library)
|
||||
|
||||
|
||||
class RemoveLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_library_reference"
|
||||
bl_label = "Remove Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_library_reference(tool.Ifc, tool.Library, reference=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
class EnableEditingLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.enable_editing_library_reference"
|
||||
bl_label = "Enable Editing Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.enable_editing_library_reference(tool.Library, reference=tool.Ifc.get().by_id(self.reference))
|
||||
|
||||
|
||||
class DisableEditingLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.disable_editing_library_reference"
|
||||
bl_label = "Disable Editing Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_library_reference(tool.Library)
|
||||
|
||||
|
||||
class EditLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.edit_library_reference"
|
||||
bl_label = "Edit Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_library_reference(tool.Ifc, tool.Library)
|
||||
|
||||
|
||||
class AssignLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_library_reference"
|
||||
bl_label = "Assign Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_library_reference(
|
||||
tool.Ifc, obj=context.active_object, reference=tool.Ifc.get().by_id(self.reference)
|
||||
)
|
||||
|
||||
|
||||
class UnassignLibraryReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unassign_library_reference"
|
||||
bl_label = "Unassign Library Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
reference: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.unassign_library_reference(
|
||||
tool.Ifc, obj=context.active_object, reference=tool.Ifc.get().by_id(self.reference)
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from blenderbim.bim.module.library.data import LibrariesData
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
def update_active_reference_index(self, context):
|
||||
LibrariesData.is_loaded = False
|
||||
|
||||
|
||||
class LibraryReference(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class BIMLibraryProperties(PropertyGroup):
|
||||
editing_mode: StringProperty(name="Editing Mode")
|
||||
library_attributes: CollectionProperty(name="Library Attributes", type=Attribute)
|
||||
active_library_id: IntProperty(name="Active Library Id")
|
||||
reference_attributes: CollectionProperty(name="Library Attributes", type=Attribute)
|
||||
active_reference_id: IntProperty(name="Active Reference Id")
|
||||
references: CollectionProperty(type=LibraryReference, name="References")
|
||||
active_reference_index: IntProperty(name="Active Reference Index", update=update_active_reference_index)
|
||||
@@ -0,0 +1,147 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.module.library.data import LibrariesData, LibraryReferencesData
|
||||
|
||||
|
||||
class BIM_PT_libraries(Panel):
|
||||
bl_label = "IFC Libraries"
|
||||
bl_idname = "BIM_PT_libraries"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_and_template_setup"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not LibrariesData.is_loaded:
|
||||
LibrariesData.load()
|
||||
self.props = context.scene.BIMLibraryProperties
|
||||
|
||||
if self.props.editing_mode == "LIBRARY":
|
||||
self.draw_editable_library_ui()
|
||||
elif self.props.editing_mode == "REFERENCE":
|
||||
self.draw_editable_reference_ui()
|
||||
elif self.props.editing_mode == "REFERENCES":
|
||||
self.draw_editable_references_ui()
|
||||
else:
|
||||
self.draw_readonly_library_ui()
|
||||
|
||||
def draw_editable_library_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_library", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_library", text="", icon="CANCEL")
|
||||
blenderbim.bim.helper.draw_attributes(self.props.library_attributes, self.layout)
|
||||
|
||||
def draw_editable_references_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.enable_editing_library", icon="GREASEPENCIL")
|
||||
row.operator("bim.disable_editing_library_references", text="", icon="CANCEL")
|
||||
|
||||
for attribute in LibrariesData.data["library_attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_library_reference", icon="ADD")
|
||||
|
||||
self.layout.template_list(
|
||||
"BIM_UL_library_references", "", self.props, "references", self.props, "active_reference_index"
|
||||
)
|
||||
|
||||
for attribute in LibrariesData.data["reference_attributes"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
row.label(text=attribute["value"])
|
||||
|
||||
def draw_editable_reference_ui(self):
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_library_reference", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_library_reference", text="", icon="CANCEL")
|
||||
blenderbim.bim.helper.draw_attributes(self.props.reference_attributes, self.layout)
|
||||
|
||||
def draw_readonly_library_ui(self):
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_library", icon="ADD")
|
||||
for library in LibrariesData.data["libraries"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=library["name"], icon="ASSET_MANAGER")
|
||||
row.operator("bim.enable_editing_library_references", text="", icon="OUTLINER").library = library["id"]
|
||||
row.operator("bim.remove_library", text="", icon="X").library = library["id"]
|
||||
|
||||
|
||||
class BIM_PT_library_references(Panel):
|
||||
bl_label = "IFC Library References"
|
||||
bl_idname = "BIM_PT_library_references"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_misc_object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
|
||||
def draw(self, context):
|
||||
if not LibraryReferencesData.is_loaded:
|
||||
LibraryReferencesData.load()
|
||||
self.props = context.scene.BIMLibraryProperties
|
||||
|
||||
if self.props.editing_mode == "REFERENCES":
|
||||
self.layout.template_list(
|
||||
"BIM_UL_object_library_references", "", self.props, "references", self.props, "active_reference_index"
|
||||
)
|
||||
|
||||
if not LibraryReferencesData.data["references"]:
|
||||
row = self.layout.row()
|
||||
row.label(text="No References")
|
||||
|
||||
for reference in LibraryReferencesData.data["references"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=reference["identification"], icon="ASSET_MANAGER")
|
||||
row.label(text=reference["name"])
|
||||
row.operator("bim.unassign_library_reference", text="", icon="X").reference = reference["id"]
|
||||
|
||||
|
||||
class BIM_UL_library_references(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.enable_editing_library_reference", text="", icon="GREASEPENCIL")
|
||||
op.reference = item.ifc_definition_id
|
||||
op = row.operator("bim.remove_library_reference", text="", icon="X")
|
||||
op.reference = item.ifc_definition_id
|
||||
|
||||
|
||||
class BIM_UL_object_library_references(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
op = row.operator("bim.assign_library_reference", text="", icon="ADD")
|
||||
op.reference = item.ifc_definition_id
|
||||
@@ -20,27 +20,29 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.CopyMaterial,
|
||||
operator.AddMaterial,
|
||||
operator.RemoveMaterial,
|
||||
operator.AssignMaterial,
|
||||
operator.UnassignMaterial,
|
||||
operator.AddConstituent,
|
||||
operator.RemoveConstituent,
|
||||
operator.AddProfile,
|
||||
operator.RemoveProfile,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.AddDefaultMaterial,
|
||||
operator.AddLayer,
|
||||
operator.RemoveLayer,
|
||||
operator.ReorderMaterialSetItem,
|
||||
operator.AddListItem,
|
||||
operator.RemoveListItem,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.AddMaterial,
|
||||
operator.AddProfile,
|
||||
operator.AssignMaterial,
|
||||
operator.AssignParameterizedProfile,
|
||||
operator.CopyMaterial,
|
||||
operator.DisableEditingAssignedMaterial,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.DisableEditingMaterialSetItem,
|
||||
operator.EditAssignedMaterial,
|
||||
operator.EditMaterialSetItem,
|
||||
operator.EnableEditingAssignedMaterial,
|
||||
operator.EnableEditingMaterialSetItem,
|
||||
operator.RemoveConstituent,
|
||||
operator.RemoveLayer,
|
||||
operator.RemoveListItem,
|
||||
operator.RemoveMaterial,
|
||||
operator.RemoveProfile,
|
||||
operator.ReorderMaterialSetItem,
|
||||
operator.UnassignMaterial,
|
||||
operator.UnlinkMaterial,
|
||||
prop.BIMObjectMaterialProperties,
|
||||
ui.BIM_PT_material,
|
||||
ui.BIM_PT_object_material,
|
||||
|
||||
@@ -23,12 +23,21 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.material as core
|
||||
from blenderbim.bim.module.material.prop import purge as material_prop_purge
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.material.data import Data
|
||||
from ifcopenshell.api.profile.data import Data as ProfileData
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignParameterizedProfile(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_parameterized_profile"
|
||||
bl_label = "Assign Parameterized Profile"
|
||||
@@ -59,6 +68,16 @@ class AssignParameterizedProfile(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDefaultMaterial(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_default_material"
|
||||
bl_label = "Add Default Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_default_material(tool.Ifc, tool.Material)
|
||||
Data.load(IfcStore.get_file())
|
||||
|
||||
|
||||
class AddMaterial(bpy.types.Operator):
|
||||
bl_idname = "bim.add_material"
|
||||
bl_label = "Add Material"
|
||||
@@ -112,6 +131,15 @@ class RemoveMaterial(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnlinkMaterial(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.unlink_material"
|
||||
bl_label = "Unlink Material"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.unlink_material(tool.Ifc, obj=context.active_object.active_material)
|
||||
|
||||
|
||||
class AssignMaterial(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_material"
|
||||
bl_label = "Assign Material"
|
||||
|
||||
@@ -36,9 +36,10 @@ class BIM_PT_material(Panel):
|
||||
return IfcStore.get_file() and context.active_object and context.active_object.active_material
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id):
|
||||
row.operator("bim.remove_material", icon="X", text="Remove IFC Material")
|
||||
row.operator("bim.unlink_material", icon="UNLINKED", text="")
|
||||
else:
|
||||
row.operator("bim.add_material", icon="ADD", text="Create IFC Material")
|
||||
|
||||
@@ -49,6 +50,7 @@ class BIM_PT_object_material(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_parent_id = "BIM_PT_object_attributes_properties_and_relationships"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -76,8 +78,9 @@ class BIM_PT_object_material(Panel):
|
||||
self.product_data = Data.products[self.oprops.ifc_definition_id]
|
||||
|
||||
if not Data.materials:
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No Materials Available")
|
||||
row.operator("bim.add_default_material", icon="ADD", text="")
|
||||
return
|
||||
|
||||
if self.product_data:
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.DrawSystemArrows,
|
||||
operator.GetConnectedSystemElements,
|
||||
operator.ResizeToStorey,
|
||||
operator.SetOverrideColour,
|
||||
operator.SetViewportShadowFromSun,
|
||||
operator.SnapSpacesTogether,
|
||||
operator.SplitAlongEdge,
|
||||
prop.BIMMiscProperties,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMMiscProperties = bpy.props.PointerProperty(type=prop.BIMMiscProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMMiscProperties
|
||||
@@ -0,0 +1,262 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.misc as core
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from mathutils import Vector, Matrix, Euler
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetOverrideColour(bpy.types.Operator):
|
||||
bl_idname = "bim.set_override_colour"
|
||||
bl_label = "Set Override Colour"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
obj.color = context.scene.BIMMiscProperties.override_colour
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].shading.color_type = "OBJECT"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetViewportShadowFromSun(bpy.types.Operator):
|
||||
bl_idname = "bim.set_viewport_shadow_from_sun"
|
||||
bl_label = "Set Viewport Shadow from Sun"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object
|
||||
|
||||
def execute(self, context):
|
||||
# Does this belong in the drawing module? Perhaps.
|
||||
# The vector used for the light direction is a bit funny
|
||||
mat = Matrix(((-1.0, 0.0, 0.0, 0.0), (0.0, 0, 1.0, 0.0), (-0.0, -1.0, 0, 0.0), (0.0, 0.0, 0.0, 1.0)))
|
||||
context.scene.display.light_direction = mat.inverted() @ (
|
||||
context.active_object.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SnapSpacesTogether(bpy.types.Operator):
|
||||
bl_idname = "bim.snap_spaces_together"
|
||||
bl_label = "Snap Spaces Together"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects
|
||||
|
||||
def execute(self, context):
|
||||
threshold = 0.5
|
||||
processed_polygons = set()
|
||||
selected_mesh_objects = [o for o in context.selected_objects if o.type == "MESH"]
|
||||
for obj in selected_mesh_objects:
|
||||
for polygon in obj.data.polygons:
|
||||
center = obj.matrix_world @ polygon.center
|
||||
distance = None
|
||||
for obj2 in selected_mesh_objects:
|
||||
if obj2 == obj:
|
||||
continue
|
||||
result = obj2.ray_cast(obj2.matrix_world.inverted() @ center, polygon.normal, distance=threshold)
|
||||
if not result[0]:
|
||||
continue
|
||||
hit = obj2.matrix_world @ result[1]
|
||||
distance = (hit - center).length / 2
|
||||
if distance < 0.01:
|
||||
distance = None
|
||||
break
|
||||
|
||||
if (obj2.name, result[3]) in processed_polygons:
|
||||
distance *= 2
|
||||
continue
|
||||
|
||||
offset = polygon.normal * distance * -1
|
||||
processed_polygons.add((obj2.name, result[3]))
|
||||
for v in obj2.data.polygons[result[3]].vertices:
|
||||
obj2.data.vertices[v].co += offset
|
||||
break
|
||||
if distance:
|
||||
offset = polygon.normal * distance
|
||||
processed_polygons.add((obj.name, polygon.index))
|
||||
for v in polygon.vertices:
|
||||
obj.data.vertices[v].co += offset
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ResizeToStorey(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.resize_to_storey"
|
||||
bl_label = "Resize To Storey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
total_storeys: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
core.resize_to_storey(tool.Misc, obj=obj, total_storeys=self.total_storeys)
|
||||
|
||||
|
||||
class SplitAlongEdge(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.split_along_edge"
|
||||
bl_label = "Split Along Edge"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
core.split_along_edge(tool.Misc, cutter=context.active_object, objs=context.selected_objects)
|
||||
|
||||
|
||||
class GetConnectedSystemElements(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.get_connected_system_elements"
|
||||
bl_label = "Get Connected System Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
# Just dumped here for now before the system module gets properly planned
|
||||
def pprint_element(e):
|
||||
return "{} ({})".format(e.Name, e.GlobalId)
|
||||
|
||||
start = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
|
||||
connected_elements = []
|
||||
|
||||
# Note: this code is for IFC2X3. IFC4 has a different approach.
|
||||
print("Investigating element:", pprint_element(start))
|
||||
for rel in start.HasPorts:
|
||||
for rel2 in rel.RelatingPort.ConnectedTo:
|
||||
print(
|
||||
"{} is connected as via {} ({}) TO {} ({}), contained in {}".format(
|
||||
pprint_element(start),
|
||||
rel.RelatingPort.FlowDirection,
|
||||
rel.RelatingPort.GlobalId,
|
||||
rel2.RelatedPort.FlowDirection,
|
||||
rel2.RelatedPort.GlobalId,
|
||||
[pprint_element(r.RelatedElement) for r in rel2.RelatedPort.ContainedIn],
|
||||
)
|
||||
)
|
||||
connected_elements.extend([r.RelatedElement for r in rel2.RelatedPort.ContainedIn])
|
||||
for rel2 in rel.RelatingPort.ConnectedFrom:
|
||||
print(
|
||||
"{} is connected as via {} ({}) FROM {} ({}), contained in {}".format(
|
||||
pprint_element(start),
|
||||
rel.RelatingPort.FlowDirection,
|
||||
rel.RelatingPort.GlobalId,
|
||||
rel2.RelatingPort.FlowDirection,
|
||||
rel2.RelatingPort.GlobalId,
|
||||
[pprint_element(r.RelatedElement) for r in rel2.RelatingPort.ContainedIn],
|
||||
)
|
||||
)
|
||||
connected_elements.extend([r.RelatedElement for r in rel2.RelatingPort.ContainedIn])
|
||||
|
||||
for element in connected_elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
|
||||
|
||||
class DrawSystemArrows(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.draw_system_arrows"
|
||||
bl_label = "Draw System Arrows"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.selected_objects and tool.Ifc.get()
|
||||
|
||||
def _execute(self, context):
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
curve = bpy.data.objects.new("System Arrows", bpy.data.curves.new("System Arrows", "CURVE"))
|
||||
curve.data.dimensions = "3D"
|
||||
context.scene.collection.objects.link(curve)
|
||||
for obj in bpy.context.selected_objects:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
sources = []
|
||||
sinks = []
|
||||
for rel in getattr(element, "HasPorts", []) or []:
|
||||
if rel.RelatingPort.FlowDirection == "SOURCE":
|
||||
sources.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
elif rel.RelatingPort.FlowDirection == "SINK":
|
||||
sinks.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
else:
|
||||
sources.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
sinks.append(
|
||||
self.get_absolute_matrix(
|
||||
ifcopenshell.util.placement.get_local_placement(rel.RelatingPort.ObjectPlacement)
|
||||
)
|
||||
)
|
||||
for sink in sinks:
|
||||
for source in sources:
|
||||
polyline = curve.data.splines.new("POLY")
|
||||
polyline.points.add(1)
|
||||
polyline.points[0].co = (Matrix(sink).translation * unit_scale).to_4d()
|
||||
polyline.points[1].co = (Matrix(source).translation * unit_scale).to_4d()
|
||||
|
||||
def get_absolute_matrix(self, matrix):
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
matrix = np.array(
|
||||
ifcopenshell.util.geolocation.global2local(
|
||||
matrix,
|
||||
float(props.blender_eastings),
|
||||
float(props.blender_northings),
|
||||
float(props.blender_orthogonal_height),
|
||||
float(props.blender_x_axis_abscissa),
|
||||
float(props.blender_x_axis_ordinate),
|
||||
)
|
||||
)
|
||||
return matrix
|
||||
@@ -0,0 +1,38 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
EnumProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
|
||||
|
||||
class BIMMiscProperties(PropertyGroup):
|
||||
total_storeys: IntProperty(name="Total Storeys", default=1)
|
||||
override_colour: FloatVectorProperty(
|
||||
name="Override Colour", subtype="COLOR", default=(1, 0, 0, 1), min=0.0, max=1.0, size=4
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
bl_idname = "BIM_PT_misc_utilities"
|
||||
bl_label = "Miscellaneous"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = context.scene.BIMMiscProperties
|
||||
|
||||
row = layout.split(factor=0.2, align=True)
|
||||
row.prop(props, "override_colour", text="")
|
||||
row.operator("bim.set_override_colour")
|
||||
row = layout.row()
|
||||
row.operator("bim.set_viewport_shadow_from_sun")
|
||||
row = layout.row()
|
||||
row.operator("bim.snap_spaces_together")
|
||||
row = layout.split(factor=0.2, align=True)
|
||||
row.prop(props, "total_storeys", text="")
|
||||
row.operator("bim.resize_to_storey").total_storeys = props.total_storeys
|
||||
row = layout.row()
|
||||
row.operator("bim.split_along_edge")
|
||||
row = layout.row()
|
||||
row.operator("bim.get_connected_system_elements")
|
||||
row = layout.row()
|
||||
row.operator("bim.draw_system_arrows")
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from . import handler, prop, ui, grid, product, wall, slab, stair, door, window, opening, pie, workspace
|
||||
from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace
|
||||
|
||||
classes = (
|
||||
product.AddEmptyType,
|
||||
@@ -34,11 +34,8 @@ classes = (
|
||||
prop.BIMModelProperties,
|
||||
ui.BIM_PT_authoring,
|
||||
ui.BIM_PT_authoring_architectural,
|
||||
ui.BIM_PT_misc_utilities,
|
||||
grid.BIM_OT_add_object,
|
||||
stair.BIM_OT_add_object,
|
||||
door.BIM_OT_add_object,
|
||||
window.BIM_OT_add_object,
|
||||
opening.BIM_OT_add_object,
|
||||
pie.OpenPieClass,
|
||||
pie.PieUpdateContainer,
|
||||
@@ -56,8 +53,6 @@ def register():
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.append(product.add_empty_type_button)
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
@@ -76,8 +71,6 @@ def unregister():
|
||||
bpy.app.handlers.load_post.remove(handler.load_post)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(door.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(window.add_object_button)
|
||||
bpy.types.VIEW3D_MT_mesh_add.remove(opening.add_object_button)
|
||||
bpy.types.VIEW3D_MT_add.remove(product.add_empty_type_button)
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
def refresh():
|
||||
AuthoringData.is_loaded = False
|
||||
|
||||
|
||||
class AuthoringData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data = {
|
||||
"ifc_classes": cls.ifc_classes(),
|
||||
"relating_types": cls.relating_types(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def ifc_classes(cls):
|
||||
results = []
|
||||
classes = {e.is_a() for e in tool.Ifc.get().by_type("IfcElementType")}
|
||||
results.extend([(c, c, "") for c in sorted(classes)])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def relating_types(cls):
|
||||
ifc_classes = cls.ifc_classes()
|
||||
if not ifc_classes:
|
||||
return []
|
||||
results = []
|
||||
ifc_class = bpy.context.scene.BIMModelProperties.ifc_class
|
||||
if not ifc_class and ifc_classes:
|
||||
ifc_class = ifc_classes[0][0]
|
||||
if ifc_class:
|
||||
elements = [(str(e.id()), e.Name, "") for e in tool.Ifc.get().by_type(ifc_class)]
|
||||
results.extend(sorted(elements, key=lambda s: s[1]))
|
||||
return results
|
||||
return []
|
||||
@@ -1,226 +0,0 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import ifcopenshell
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty
|
||||
from mathutils import Vector
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def add_object(self, context):
|
||||
guid = ifcopenshell.guid.new()
|
||||
leaf_width = self.overall_width - 0.045 - 0.045
|
||||
# TODO reimplement 2D. See #1222.
|
||||
# verts = [
|
||||
# # Left lining
|
||||
# Vector((0, 0, 0)),
|
||||
# Vector((0, self.depth, 0)),
|
||||
# Vector((0.04, self.depth, 0)),
|
||||
# Vector((0.04, self.depth - 0.04, 0)),
|
||||
# Vector((0.065, self.depth - 0.04, 0)),
|
||||
# Vector((0.065, 0, 0)),
|
||||
# # Right lining
|
||||
# Vector((self.overall_width, 0, 0)),
|
||||
# Vector((self.overall_width, self.depth, 0)),
|
||||
# Vector((self.overall_width - 0.04, self.depth, 0)),
|
||||
# Vector((self.overall_width - 0.04, self.depth - 0.04, 0)),
|
||||
# Vector((self.overall_width - 0.065, self.depth - 0.04, 0)),
|
||||
# Vector((self.overall_width - 0.065, 0, 0)),
|
||||
# # Door panel
|
||||
# Vector((0.045, self.depth, 0)),
|
||||
# Vector((0.045, self.depth + leaf_width, 0)),
|
||||
# Vector((0.080, self.depth + leaf_width, 0)),
|
||||
# Vector((0.080, self.depth, 0)),
|
||||
# ]
|
||||
# edges = [
|
||||
# [0, 1],
|
||||
# [1, 2],
|
||||
# [2, 3],
|
||||
# [3, 4],
|
||||
# [4, 5], # Left lining
|
||||
# [6, 7],
|
||||
# [7, 8],
|
||||
# [8, 9],
|
||||
# [9, 10],
|
||||
# [10, 11], # Right lining
|
||||
# [12, 13],
|
||||
# [13, 14],
|
||||
# [14, 15],
|
||||
# [15, 12], # Door panel
|
||||
# ]
|
||||
## Door swing
|
||||
# for i in range(0, 9):
|
||||
# verts.append(
|
||||
# Vector(
|
||||
# (
|
||||
# 0.045 + (leaf_width * math.cos((math.pi / 2) / 8 * i)),
|
||||
# self.depth + (leaf_width * math.sin((math.pi / 2) / 8 * i)),
|
||||
# 0,
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
# edges.append([16 + i, 17 + i])
|
||||
# edges.pop()
|
||||
# faces = []
|
||||
# mesh = bpy.data.meshes.new(name="Plan/Annotation/PLAN_VIEW/" + guid)
|
||||
# mesh.use_fake_user = True
|
||||
# mesh.from_pydata(verts, edges, faces)
|
||||
|
||||
# Door lining profile
|
||||
verts = [
|
||||
Vector((0, 0, 0)),
|
||||
Vector((0, -self.depth, 0)),
|
||||
Vector((0.04, -self.depth, 0)),
|
||||
Vector((0.04, -self.depth + 0.04, 0)),
|
||||
Vector((0.065, -self.depth + 0.04, 0)),
|
||||
Vector((0.065, 0, 0)),
|
||||
]
|
||||
edges = []
|
||||
faces = [[0, 1, 2, 3, 4, 5]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Profile")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj = bpy.data.objects.new("Door Profile", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj)
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
|
||||
# Door lining sweep
|
||||
verts = [
|
||||
Vector((0, 0, 0)),
|
||||
Vector((0, self.overall_height, 0)),
|
||||
Vector((self.overall_width, self.overall_height, 0)),
|
||||
Vector((self.overall_width, 0, 0)),
|
||||
]
|
||||
edges = [[0, 1], [1, 2], [2, 3]]
|
||||
faces = []
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj2 = bpy.data.objects.new("Door", mesh)
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj2)
|
||||
context.view_layer.objects.active = obj2
|
||||
obj2.select_set(True)
|
||||
bpy.ops.object.convert(target="CURVE")
|
||||
|
||||
obj2.data.dimensions = "2D"
|
||||
obj2.data.bevel_mode = "OBJECT"
|
||||
obj2.data.bevel_object = obj
|
||||
|
||||
obj2.rotation_euler[0] = math.pi / 2
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
bpy.ops.object.transform_apply(location=False)
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
# Door panel
|
||||
verts = [
|
||||
Vector((0.045, self.depth, 0)),
|
||||
Vector((0.045, self.depth - 0.035, 0)),
|
||||
Vector((self.overall_width - 0.045, self.depth - 0.035, 0)),
|
||||
Vector((self.overall_width - 0.045, self.depth, 0)),
|
||||
]
|
||||
edges = []
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Panel")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj3 = bpy.data.objects.new("Door Panel", mesh)
|
||||
modifier = obj3.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = 1
|
||||
modifier.thickness = self.overall_height - 0.045
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj3)
|
||||
context.view_layer.objects.active = obj3
|
||||
obj3.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
|
||||
ctx = context.copy()
|
||||
ctx["active_object"] = obj2
|
||||
ctx["selected_editable_objects"] = [obj2, obj3]
|
||||
bpy.ops.object.join(ctx)
|
||||
|
||||
# Door Opening
|
||||
verts = [
|
||||
Vector((0, -0.1, -0.1)),
|
||||
Vector((0, self.depth + 0.1, -0.1)),
|
||||
Vector((self.overall_width, self.depth + 0.1, -0.1)),
|
||||
Vector((self.overall_width, -0.1, -0.1)),
|
||||
]
|
||||
edges = []
|
||||
faces = [[0, 1, 2, 3]]
|
||||
mesh = bpy.data.meshes.new(name="Dumb Door Opening")
|
||||
mesh.from_pydata(verts, edges, faces)
|
||||
obj4 = bpy.data.objects.new("Door Opening", mesh)
|
||||
modifier = obj4.modifiers.new("Panel Height", "SOLIDIFY")
|
||||
modifier.offset = -1
|
||||
modifier.thickness = self.overall_height + 0.1
|
||||
|
||||
context.view_layer.active_layer_collection.collection.objects.link(obj4)
|
||||
context.view_layer.objects.active = obj4
|
||||
obj4.select_set(True)
|
||||
bpy.ops.object.convert(target="MESH")
|
||||
|
||||
obj4.display_type = "WIRE"
|
||||
obj4.parent = obj2
|
||||
obj4.matrix_parent_inverse = obj2.matrix_world.inverted()
|
||||
obj4.hide_render = True
|
||||
obj4.name = "Door Opening"
|
||||
|
||||
obj2.name = "Door"
|
||||
|
||||
if IfcStore.get_file():
|
||||
bpy.ops.bim.assign_class(obj=obj2.name, ifc_class="IfcDoor")
|
||||
|
||||
obj2.location = context.scene.cursor.location
|
||||
# obj2.data.name = "Model/Body/MODEL_VIEW/" + guid
|
||||
# obj2.data.use_fake_user = True
|
||||
|
||||
# TODO: reimplement. See #1222.
|
||||
# rep = obj2.BIMObjectProperties.representation_contexts.add()
|
||||
# rep.context = "Model"
|
||||
# rep.name = "Body"
|
||||
# rep.target_view = "MODEL_VIEW"
|
||||
|
||||
# rep = obj2.BIMObjectProperties.representation_contexts.add()
|
||||
# rep.context = "Plan"
|
||||
# rep.name = "Annotation"
|
||||
# rep.target_view = "PLAN_VIEW"
|
||||
|
||||
|
||||
class BIM_OT_add_object(Operator):
|
||||
bl_idname = "mesh.add_door"
|
||||
bl_label = "Dumb Door"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
overall_width: FloatProperty(name="Overall Width", default=0.85)
|
||||
overall_height: FloatProperty(name="Overall Height", default=2.1)
|
||||
depth: FloatProperty(name="Depth", default=0.2)
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
add_object(self, context)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def add_object_button(self, context):
|
||||
self.layout.operator(BIM_OT_add_object.bl_idname, icon="PLUGIN")
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import blenderbim.core.spatial
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Operator
|
||||
from bpy.props import FloatProperty, IntProperty
|
||||
from mathutils import Vector
|
||||
@@ -51,8 +53,8 @@ def add_object(self, context):
|
||||
if has_site_collection:
|
||||
site_obj = bpy.data.objects.get(grandchild.name)
|
||||
if site_obj and site_obj.BIMObjectProperties.ifc_definition_id:
|
||||
bpy.ops.bim.assign_container(
|
||||
relating_structure=site_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=site_obj, element_obj=obj
|
||||
)
|
||||
|
||||
axes_collection = bpy.data.collections.new("UAxes")
|
||||
|
||||
@@ -60,41 +60,32 @@ def load_post(*args):
|
||||
IfcStore.add_element_listener(slab.element_listener)
|
||||
IfcStore.add_element_listener(profile.element_listener)
|
||||
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbWall.EnsureSolid", wall.ensure_solid
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbWall.GenerateAxis", wall.generate_axis
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbWall.CalculateQuantities", wall.calculate_quantities
|
||||
)
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_layer", "BlenderBIM.DumbWall.RegenerateFromLayer", wall.DumbWallPlaner().regenerate_from_layer
|
||||
)
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type", "BlenderBIM.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbSlab.EnsureSolid", slab.ensure_solid
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbSlab.GenerateFootprint", slab.generate_footprint
|
||||
)
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbSlab.CalculateQuantities", slab.calculate_quantities
|
||||
)
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"material.edit_layer", "BlenderBIM.DumbSlab.RegenerateFromLayer", slab.DumbSlabPlaner().regenerate_from_layer
|
||||
)
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
ifcopenshell.api.add_post_listener(
|
||||
"type.assign_type", "BlenderBIM.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
|
||||
)
|
||||
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
"geometry.add_representation", "BlenderBIM.DumbProfile.EnsureSolid", profile.ensure_solid
|
||||
)
|
||||
ifcopenshell.api.add_pre_listener(
|
||||
"material.edit_profile",
|
||||
"BlenderBIM.DumbProfile.SyncObjectFromProfile",
|
||||
|
||||
@@ -21,6 +21,8 @@ import bmesh
|
||||
import blenderbim.bim.handler
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi
|
||||
from mathutils import Vector
|
||||
@@ -57,12 +59,14 @@ def mode_callback(obj, data):
|
||||
)
|
||||
if not representation:
|
||||
continue
|
||||
bpy.ops.bim.switch_representation(
|
||||
obj=building_element_obj.name,
|
||||
should_switch_all_meshes=True,
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=building_element_obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
ifc_definition_id=representation.id(),
|
||||
disable_opening_subtractions=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
IfcStore.edited_objs.add(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.spatial
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -67,8 +69,8 @@ class PieUpdateContainer(bpy.types.Operator):
|
||||
for collection in obj.users_collection:
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
bpy.ops.bim.assign_container(
|
||||
relating_structure=spatial_obj.BIMObjectProperties.ifc_definition_id, related_element=obj.name
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=spatial_obj, element_obj=obj
|
||||
)
|
||||
break
|
||||
return {"FINISHED"}
|
||||
@@ -80,7 +82,7 @@ class VIEW3D_MT_PIE_bim(bpy.types.Menu):
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
pie.operator("bim.edit_object_placement")
|
||||
pie.operator("bim.update_representation")
|
||||
pie.operator("bim.update_representation").ifc_representation_class = ""
|
||||
pie.operator("bim.pie_add_opening")
|
||||
pie.operator("bim.pie_update_container")
|
||||
pie.operator("bim.open_pie_class", text="Assign IFC Class")
|
||||
|
||||
@@ -22,6 +22,9 @@ import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
from . import wall, slab, profile
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from ifcopenshell.api.pset.data import Data as PsetData
|
||||
@@ -36,14 +39,7 @@ class AddEmptyType(bpy.types.Operator, AddObjectHelper):
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.new("TYPEX", None)
|
||||
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
|
||||
if not [c for c in project.children if "Types" in c.name]:
|
||||
types = bpy.data.collections.new("Types")
|
||||
project.collection.children.link(types)
|
||||
for collection in [c for c in project.children if "Types" in c.name]:
|
||||
collection.collection.objects.link(obj)
|
||||
break
|
||||
break
|
||||
context.scene.collection.objects.link(obj)
|
||||
context.scene.BIMRootProperties.ifc_product = "IfcElementType"
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
context.view_layer.objects.active = obj
|
||||
@@ -59,6 +55,7 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
bl_idname = "bim.add_type_instance"
|
||||
bl_label = "Add Type Instance"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add the selected Type Instance to the model"
|
||||
ifc_class: bpy.props.StringProperty()
|
||||
relating_type: bpy.props.IntProperty()
|
||||
|
||||
@@ -66,9 +63,9 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
tprops = context.scene.BIMTypeProperties
|
||||
ifc_class = self.ifc_class or tprops.ifc_class
|
||||
relating_type_id = self.relating_type or tprops.relating_type
|
||||
props = context.scene.BIMModelProperties
|
||||
ifc_class = self.ifc_class or props.ifc_class
|
||||
relating_type_id = self.relating_type or props.relating_type
|
||||
|
||||
if not ifc_class or not relating_type_id:
|
||||
return {"FINISHED"}
|
||||
@@ -117,7 +114,12 @@ class AddTypeInstance(bpy.types.Operator):
|
||||
collection.objects.link(obj)
|
||||
collection_obj = bpy.data.objects.get(collection.name)
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
bpy.ops.bim.assign_type(relating_type=int(tprops.relating_type), related_object=obj.name)
|
||||
blenderbim.core.type.assign_type(
|
||||
tool.Ifc,
|
||||
tool.Type,
|
||||
element=tool.Ifc.get_entity(obj),
|
||||
type=tool.Ifc.get().by_id(int(props.relating_type)),
|
||||
)
|
||||
|
||||
if building_obj:
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
@@ -229,12 +231,14 @@ class DynamicallyVoidProduct(bpy.types.Operator):
|
||||
was_edit_mode = obj.mode == "EDIT"
|
||||
if was_edit_mode:
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.bim.switch_representation(
|
||||
obj=obj.name,
|
||||
should_switch_all_meshes=True,
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
ifc_definition_id=representation.id(),
|
||||
disable_opening_subtractions=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if was_edit_mode:
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
@@ -252,7 +256,7 @@ def generate_box(usecase_path, ifc_file, settings):
|
||||
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
|
||||
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
|
||||
if old_box:
|
||||
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name)
|
||||
blenderbim.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_box)
|
||||
|
||||
new_settings = settings.copy()
|
||||
new_settings["context"] = box_context
|
||||
@@ -286,16 +290,22 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
continue
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if representation:
|
||||
bpy.ops.bim.switch_representation(
|
||||
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
if usecase_path == "material.assign_material":
|
||||
if not settings.get("Material", None):
|
||||
if not settings.get("material", None):
|
||||
return
|
||||
elements = [self.settings["product"]]
|
||||
elements = [settings["product"]]
|
||||
else:
|
||||
elements = []
|
||||
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
|
||||
@@ -321,4 +331,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):
|
||||
if material[0].id() in object_material_ids:
|
||||
continue
|
||||
|
||||
if len(obj.data.materials) == 1:
|
||||
obj.data.materials.clear()
|
||||
|
||||
obj.data.materials.append(IfcStore.get_element(material[0].id()))
|
||||
|
||||
@@ -17,14 +17,17 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bmesh
|
||||
import math
|
||||
import bmesh
|
||||
import mathutils.geometry
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import mathutils.geometry
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.geometry
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from math import pi, degrees, inf
|
||||
from mathutils import Vector, Matrix
|
||||
@@ -89,19 +92,6 @@ def mode_callback(obj, data):
|
||||
obj.matrix_world.translation = new_origin
|
||||
|
||||
|
||||
def ensure_solid(usecase_path, ifc_file, settings):
|
||||
product = ifc_file.by_id(settings["blender_object"].BIMObjectProperties.ifc_definition_id)
|
||||
parametric = ifcopenshell.util.element.get_psets(product).get("EPset_Parametric")
|
||||
if not parametric or parametric["Engine"] != "BlenderBIM.DumbProfile":
|
||||
return
|
||||
material = ifcopenshell.util.element.get_material(product)
|
||||
if material and material.is_a("IfcMaterialProfileSetUsage"):
|
||||
settings["profile_set_usage"] = material
|
||||
else:
|
||||
return
|
||||
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
|
||||
|
||||
|
||||
class DumbProfileGenerator:
|
||||
def __init__(self, relating_type):
|
||||
self.relating_type = relating_type
|
||||
@@ -167,18 +157,8 @@ class DumbProfileGenerator:
|
||||
obj.rotation_euler[2] = math.pi / 2
|
||||
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
bpy.ops.bim.assign_type(relating_type=self.relating_type.id(), related_object=obj.name)
|
||||
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=self.relating_type)
|
||||
profile_set_usage = ifcopenshell.util.element.get_material(element)
|
||||
bpy.ops.bim.add_representation(
|
||||
obj=obj.name,
|
||||
context_id=ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW").id(),
|
||||
ifc_representation_class="IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
|
||||
profile_set_usage=profile_set_usage.id(),
|
||||
)
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
bpy.ops.bim.switch_representation(
|
||||
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
|
||||
)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
|
||||
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.DumbProfile"})
|
||||
MaterialData.load(self.file)
|
||||
@@ -233,8 +213,14 @@ class DumbProfileRegenerator:
|
||||
return
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if representation:
|
||||
bpy.ops.bim.switch_representation(
|
||||
obj=obj.name, ifc_definition_id=representation.id(), should_reload=True, should_switch_all_meshes=True
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def sync_object(self, element):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user