mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-21 12:36:00 +00:00
Compare commits
365 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e93ff7a6b1 | |||
| 098ae2b69f | |||
| a58a10e93d | |||
| 9be7d951c3 | |||
| 6cf482dc63 | |||
| aaa4683462 | |||
| 0b5535b6d4 | |||
| 45feaeaec4 | |||
| 90f92cd139 | |||
| 0285d97e3f | |||
| bf80e4b5e1 | |||
| d4e4717857 | |||
| fca90e9b0f | |||
| ffbb91216f | |||
| 09620e060f | |||
| 0ea38384cc | |||
| aae659c5fd | |||
| b7a481ec79 | |||
| 763eaf83da | |||
| 2843d1e13e | |||
| e897797955 | |||
| f1c3a90bc5 | |||
| c90c438324 | |||
| ae1ff3f6f7 | |||
| e2224386bf | |||
| 6ad51ebd18 | |||
| 0fb9f584cb | |||
| 9a34b94568 | |||
| 7bca3bcac3 | |||
| de27554043 | |||
| 306ee3dfa4 | |||
| 3593b6b5c2 | |||
| e06acab39b | |||
| 6dd4e0f53a | |||
| 2c2a37cacb | |||
| 7935bd22c2 | |||
| 6887210a1a | |||
| 33dc3a4b06 | |||
| ba4610155f | |||
| 28eeba49ba | |||
| 0a00e529c3 | |||
| c5024cf6be | |||
| 0f3b416c3f | |||
| addacd9ce5 | |||
| 5582bb2d4c | |||
| 8b27c0e755 | |||
| ca4efa6e50 | |||
| cfac835607 | |||
| 3aa72ae74a | |||
| 07579ee9a1 | |||
| fe4823681f | |||
| 6816ad5c0f | |||
| aace987149 | |||
| eee844e204 | |||
| 46cfe70181 | |||
| 294935ceed | |||
| f2097b9036 | |||
| e48a3a06c0 | |||
| c07371cb76 | |||
| 8d450aa500 | |||
| 726c792fbe | |||
| 83dda3e73f | |||
| 5c78e29849 | |||
| e6d48cd37f | |||
| a3ecde3ad9 | |||
| 2bb64a6cae | |||
| 764c818d10 | |||
| 2d18185928 | |||
| 43468166d2 | |||
| fbfd6bc485 | |||
| 1578efe366 | |||
| 76948c5156 | |||
| 562ed5908d | |||
| 9caac14014 | |||
| 695f75f3a7 | |||
| 7d7c04ddc0 | |||
| 863bf8ebbe | |||
| 0fcc28f50c | |||
| 04e9a08f3d | |||
| 837c401116 | |||
| b087d5125f | |||
| a6bff09c78 | |||
| 4716c0b7b7 | |||
| 1d040a170e | |||
| 951026a95c | |||
| c6b82bfa56 | |||
| 01b6d90432 | |||
| 504a116ebf | |||
| be785a878c | |||
| 5c2b7404d2 | |||
| 84e9e2396a | |||
| 7687d5e26e | |||
| 25ce3b039e | |||
| 9c4c42e27e | |||
| 26dd850ac1 | |||
| 0380c286a5 | |||
| 10aca77de4 | |||
| b6f2fdef77 | |||
| 9cc1f5f0e3 | |||
| adcf22f281 | |||
| ad0c49ac37 | |||
| 73d77b8382 | |||
| 3e45f39ea2 | |||
| a2bb015c79 | |||
| b231f43fc7 | |||
| 383038fdb4 | |||
| fbd8ea1edb | |||
| 26e292dd34 | |||
| c520e16031 | |||
| 101972fbad | |||
| f23ebc4031 | |||
| 44431db092 | |||
| d1b6c1ffe4 | |||
| 4d98151b62 | |||
| 5e8e18e3b5 | |||
| 808ff548ec | |||
| bc9b482373 | |||
| 4506eb0df9 | |||
| 921d412fc7 | |||
| 6089d3af78 | |||
| b8168ca606 | |||
| 61a7325bc0 | |||
| c483116b32 | |||
| 55a623dc97 | |||
| ad420108b0 | |||
| 9944c7d2de | |||
| 1a8f2be4fd | |||
| 13f64fdfe2 | |||
| 38a9e29102 | |||
| 3c23a57138 | |||
| 7baed872c6 | |||
| 9a7a284561 | |||
| e9c24a2656 | |||
| 5c73ef0a6d | |||
| dadcbe61ea | |||
| 93502a05ea | |||
| 5ebde4c79a | |||
| cf21ae22f1 | |||
| cc500ae833 | |||
| 256c0baa14 | |||
| 66decc604e | |||
| 356bc18078 | |||
| 1344cbc192 | |||
| e00ffb07ad | |||
| dba43590a5 | |||
| a0613cfee6 | |||
| c3de72b71a | |||
| 39fc60a482 | |||
| 0d7e71a269 | |||
| 78a2f65ec3 | |||
| 802bd74c52 | |||
| 33cf40debb | |||
| ff63fd9f6f | |||
| 5b207e6f54 | |||
| 695dc6ec5a | |||
| d3bcb8c6b9 | |||
| 6226aea3c3 | |||
| a30ad9b886 | |||
| 2b1e17681f | |||
| 8f25a31f19 | |||
| 1c7e6cf1b9 | |||
| fc7459c07f | |||
| 8f6932de64 | |||
| b708100c03 | |||
| d4e2d3d2dd | |||
| 13309cb8c4 | |||
| 5a4bc0909b | |||
| 39540e5794 | |||
| 0a0f977b5c | |||
| 13a75e7ad7 | |||
| 99574d6255 | |||
| 968cceb725 | |||
| 002985a446 | |||
| 5adc29b0b3 | |||
| 52ea4ff44d | |||
| 4e31a87c20 | |||
| 5d85390715 | |||
| 3e13eb8942 | |||
| 262c6d1fcb | |||
| 12b5a88861 | |||
| 2ccc9d886e | |||
| a78769c8ff | |||
| 1a142ad5eb | |||
| 2bc22e150c | |||
| aad5d280fd | |||
| 7ab07ed149 | |||
| 3690998dbc | |||
| 1b83fcc1c7 | |||
| 5cf25c45f9 | |||
| efa734c06e | |||
| e242244ec0 | |||
| 6f9da163fb | |||
| a3fd169bb6 | |||
| 899056f929 | |||
| 132fef1b76 | |||
| 422f772c40 | |||
| 66f5081ada | |||
| 839e35f1da | |||
| 40d9713326 | |||
| 17b04ad697 | |||
| 2f99dd8e98 | |||
| 49f825ba82 | |||
| 3f18ad0bc5 | |||
| 991c42e816 | |||
| 20d6eae50a | |||
| 9740582a84 | |||
| a8046a6d69 | |||
| d6655a5c14 | |||
| 0a3a2db4ae | |||
| 499eb5ea14 | |||
| b8ca287883 | |||
| 52f3392ece | |||
| d53641ce5e | |||
| 7a3ad6c3a1 | |||
| 0b2007011e | |||
| eae6983ca5 | |||
| 0ec337748f | |||
| 2699278ba3 | |||
| 0406166d8a | |||
| 2128eda87b | |||
| 21f4fb169f | |||
| 30fc52ebd9 | |||
| 64bc7af4ef | |||
| b3f1fc667a | |||
| fcbb28f9d8 | |||
| 240a31db3a | |||
| c1bde09585 | |||
| 5dd2b5e555 | |||
| 37dcb92220 | |||
| 6c43c60d04 | |||
| b8d191ac1c | |||
| 57173c7740 | |||
| 2d3295ec54 | |||
| 941ede117c | |||
| 4210b8333f | |||
| 1723d1e5a9 | |||
| 3ee06ee7a0 | |||
| d3e8da58dc | |||
| 29e000b939 | |||
| 6686a72d1f | |||
| 7dd20f69d7 | |||
| 0044ef5588 | |||
| ffa5686b22 | |||
| ab8d6bea99 | |||
| e7bb89f582 | |||
| c80b0464e5 | |||
| 7e3547b309 | |||
| 45c7432ff7 | |||
| c98c20d403 | |||
| d26a2580ea | |||
| f84c4ab2e5 | |||
| de6676a516 | |||
| bfbb7bec94 | |||
| ac00aaae4d | |||
| 422222a7cb | |||
| e1c7b37d62 | |||
| dec978743b | |||
| 10fae033c8 | |||
| b23180d287 | |||
| 99ccd81b3f | |||
| e2d3f3a351 | |||
| 5e6a444aa2 | |||
| 72cf5cfc59 | |||
| 75cb68d2f1 | |||
| 7f40a9d723 | |||
| dcc9d0e9cd | |||
| 7228329f5e | |||
| d8786717b6 | |||
| b73bd3506f | |||
| bdb2791b35 | |||
| 549ea11945 | |||
| aed01b5703 | |||
| 13eb8562e0 | |||
| c022a12835 | |||
| 04eb47ff65 | |||
| 10ecd16f0c | |||
| 75852bfa75 | |||
| 750c720c9b | |||
| 4697681fcb | |||
| 9398363cbf | |||
| ae45e6b960 | |||
| 88c002703e | |||
| 51c78a89bd | |||
| e36a958c0d | |||
| a8126a6f90 | |||
| 9b857cdefa | |||
| 90e6400115 | |||
| 2dcfcf9708 | |||
| f4971da42e | |||
| 008f953b6c | |||
| 5481237480 | |||
| 4f7564e5ae | |||
| d214cbaece | |||
| d0c5f1e69f | |||
| 4ba3240fb4 | |||
| 6d8e5e4c83 | |||
| cd1402570b | |||
| 6c0e528dd2 | |||
| 297a513d21 | |||
| 9f0a4ba064 | |||
| 8813ed66e0 | |||
| fafcd5dfca | |||
| f523870b57 | |||
| 2ed24434c8 | |||
| 6878b2c072 | |||
| 0ed8dc970d | |||
| bc85f09109 | |||
| 793b28e43b | |||
| ed4abacd1e | |||
| 8bf55ab46c | |||
| 7edabce657 | |||
| 56187e4da3 | |||
| 31a12faef1 | |||
| eb94c16465 | |||
| e8437a9c54 | |||
| 840039ef1e | |||
| 84a749605e | |||
| c1283d2ab0 | |||
| 07b5c4918b | |||
| 703f0a6b70 | |||
| 5e98dee46f | |||
| b2eddf55d2 | |||
| db7be640d4 | |||
| 04fa597844 | |||
| 694dbeb96c | |||
| 7afd358c3c | |||
| b0a5a570a9 | |||
| c45b84c850 | |||
| d575a43aee | |||
| 0b151ea559 | |||
| 926970b256 | |||
| d58af0bfa5 | |||
| 429da7c6f3 | |||
| 60a7433573 | |||
| 08a5d444d4 | |||
| 1982069b69 | |||
| 7c06c65df3 | |||
| 6263766202 | |||
| 66fbbeff54 | |||
| 3c5ad1328f | |||
| 3345d7e088 | |||
| 2bb25cb874 | |||
| 444a728e7a | |||
| 70bea0c865 | |||
| 6eea1e9035 | |||
| 4deb1836ad | |||
| 4c8007dcfe | |||
| 000116bb3d | |||
| a2b6f21c7a | |||
| a374ee39d3 | |||
| 0f5c82dffd | |||
| 268fed761e | |||
| fe01d252db | |||
| dc36d74337 | |||
| 7a0da7bd8e | |||
| 530d5a4e63 | |||
| 2d847d1704 | |||
| 631d8fcfb1 | |||
| a8888efb88 | |||
| a51ecea9b1 | |||
| 4d49517111 | |||
| d5d1b3567b | |||
| 7827ce3448 | |||
| a05059db01 | |||
| b78490ca56 |
@@ -0,0 +1,57 @@
|
||||
name: ci-bcf-pypi
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# ┌───────────── minute (0 - 59)
|
||||
# │ ┌───────────── hour (0 - 23)
|
||||
# │ │ ┌───────────── day of the month (1 - 31)
|
||||
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||
# * * * * *
|
||||
- cron: "0 0 18 * *"
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/ci-bcf-pypi.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
major: 0
|
||||
minor: 0
|
||||
name: ifcopenshell
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
name: ${{ matrix.config.name }}-${{ matrix.pyver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v2 # https://github.com/actions/checkout
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.10' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
|
||||
- run: echo ${{ env.DATE }}
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%y%m%d')"
|
||||
- name: Compile
|
||||
run: |
|
||||
pip install build
|
||||
cd src/bcf &&
|
||||
make dist
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: ortega2247/pypi-upload-action@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
@@ -1,37 +0,0 @@
|
||||
name: ci-bcf
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
contains(github.event.head_commit.message, '[bcf release]')
|
||||
steps:
|
||||
- run: echo ok go
|
||||
upload:
|
||||
needs: activate
|
||||
name: Upload BCF package to Pypi
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Build package
|
||||
run: |
|
||||
cd src/bcf
|
||||
pip install build
|
||||
python -m build
|
||||
- name: Test
|
||||
run: |
|
||||
cd src/bcf
|
||||
make test
|
||||
- name: Publish package
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
packages_dir: src/bcf/dist
|
||||
@@ -88,6 +88,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install networkx
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
sudo /usr/bin/python -m pip install mathutils
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
@@ -114,6 +114,7 @@ jobs:
|
||||
sudo /usr/bin/python -m pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
sudo /usr/bin/python -m pip install tabulate
|
||||
sudo /usr/bin/python -m pip install python-dateutil
|
||||
sudo /usr/bin/python -m pip install mathutils
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
||||
+4
-1
@@ -96,5 +96,8 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
# apple
|
||||
.DS_Store
|
||||
|
||||
# clangd cache
|
||||
.cache
|
||||
|
||||
# Brickschema
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
src/blenderbim/blenderbim/bim/schema/Brick.ttl
|
||||
|
||||
+631
-585
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
################################################################################
|
||||
# #
|
||||
# This file is part of IfcOpenShell. #
|
||||
# #
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the Lesser GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3.0 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# IfcOpenShell 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 #
|
||||
# Lesser GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the Lesser GNU General Public License #
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
|
||||
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
|
||||
endif()
|
||||
|
||||
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
|
||||
string(REGEX REPLACE "\n" ";" files "${files}")
|
||||
|
||||
foreach(file ${files})
|
||||
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
|
||||
|
||||
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
|
||||
exec_program(
|
||||
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||
OUTPUT_VARIABLE rm_out
|
||||
RETURN_VALUE rm_retval
|
||||
)
|
||||
|
||||
if(NOT "${rm_retval}" STREQUAL 0)
|
||||
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
|
||||
endif()
|
||||
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
|
||||
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -4,6 +4,7 @@ set MY_PY_VER=%PY_VER:.=%
|
||||
set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
|
||||
@@ -13,6 +13,7 @@ if [ `uname` == Darwin ]; then
|
||||
fi
|
||||
|
||||
cmake -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3;4x3_add1" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
|
||||
+3
-3
@@ -36,10 +36,10 @@ requirements:
|
||||
|
||||
run:
|
||||
- python
|
||||
- boost-cpp
|
||||
- occt ==7.7.0
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x.x') }}
|
||||
- {{ pin_compatible('boost-cpp', max_pin='x.x.x') }}
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- mpfr
|
||||
- gmp # [unix]
|
||||
|
||||
+14
-1
@@ -1,3 +1,9 @@
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
SED:=sed -i
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED:=sed -i '' -e
|
||||
endif
|
||||
|
||||
.PHONY: ci
|
||||
ci:
|
||||
tox
|
||||
@@ -12,6 +18,13 @@ models:
|
||||
cd src && xsdata generate -p bcf.v2.model --unnest-classes --kw-only --slots -ds Google bcf/v2/xsd
|
||||
cd src && xsdata generate -p bcf.v3.model --unnest-classes --kw-only --slots -ds Google bcf/v3/xsd
|
||||
|
||||
.PHONY: dist
|
||||
dist:
|
||||
rm -rf dist
|
||||
$(SED) "s/999999/$(VERSION)/" pyproject.toml
|
||||
python -m build
|
||||
$(SED) "s/$(VERSION)/999999/" pyproject.toml
|
||||
|
||||
# .PHONY
|
||||
# api:
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
# openapi-python-client generate --url https://api.swaggerhub.com/apis/buildingSMART/BCF/3.0
|
||||
@@ -17,7 +17,7 @@ dependencies = [
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
]
|
||||
version = "0.0.1"
|
||||
version = "0.0.999999"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
"Operating System :: OS Independent",
|
||||
@@ -76,7 +76,7 @@ deps =
|
||||
black
|
||||
isort
|
||||
pylint
|
||||
commands =
|
||||
commands =
|
||||
black {posargs:.}
|
||||
isort {posargs:.}
|
||||
pylint {posargs:.} --output-format=colorized
|
||||
@@ -138,4 +138,4 @@ max-attributes = 10
|
||||
|
||||
[tool.pylint.format]
|
||||
expected-line-ending-format = "LF"
|
||||
max-line-length = 120
|
||||
max-line-length = 120
|
||||
+22
-26
@@ -38,22 +38,25 @@ endif
|
||||
VERSION:=`date '+%y%m%d'`
|
||||
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||
PYVERSION:=py310
|
||||
PYPI_IMP:=cp
|
||||
|
||||
ifeq ($(PYVERSION), py39)
|
||||
PYLIBDIR:=python3.9
|
||||
PYNUMBER:=39
|
||||
PYPI_VERSION:=3.9
|
||||
endif
|
||||
ifeq ($(PYVERSION), py310)
|
||||
PYLIBDIR:=python3.10
|
||||
PYNUMBER:=310
|
||||
PYPI_VERSION:=3.10
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), linux)
|
||||
PYPI_PLATFORM:=--platform manylinux_2_17_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py39h40a70d0_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py39hdfdd6bb_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py39h7c9e3ff_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/19/d9/a69c6aff5673554df48120565a14a50eaa41d29ae03b02faa0b023666318/lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/2d/f2/8ec281d357e8bb7d08dc8d727f0e4c8ef3dae7d3fa75c69c8e452bb82d50/shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/01/61/3ff85fb4bb596ce3d223c8fcf93c8df5c12bc8899dfb4fb3cb1c5b20dd5f/Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl
|
||||
@@ -62,23 +65,23 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/linux-64/hpp-fcl-2.3.4-py310h995690b_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/linux-64/eigenpy-3.1.0-py310hf02b7e0_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/linux-64/boost-1.78.0-py310hc4a4660_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/25/1e/19b46d8e8881fe0df2e20945d51919eeb1817836d62a90efa8506530e45c/lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/a8/a5/403728b5614b28083f6424dfdefec5fcf58068495fb03bb08532671c642f/shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/f6/51/320986ebd6d46a0e95c2240468ced73153b691ce07617078bcdf30c609ec/Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/linux-64/assimp-5.0.1-hedfc422_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/linux-64/qhull-2020.2-h4bd325d_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/linux-64/octomap-1.9.8-h924138e_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/linux-64/boost-cpp-1.78.0-h6582d0a_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/linux-64/zlib-1.2.11-h516909a_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macos)
|
||||
PYPI_PLATFORM:=--platform macosx_10_9_x86_64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py39hd85b194_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py39hc4d6e28_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py39h953a6b8_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/b8/74/a71f7ad72e8db54ce899efab84507b801660750cbbfa6a39e6717557d36a/lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/36/a4/7e542a209f862f967d7cb8e939eff155f4294a27d17e16441fb8bdd51a2c/shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/88/7a/ddfe28b485b623361457d4783007c1f9ba83a87f93e7fec32f64793efb6c/Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl
|
||||
@@ -87,23 +90,23 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-64/hpp-fcl-2.3.4-py310h1db6f5f_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-64/eigenpy-3.1.0-py310h43da829_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-64/boost-1.78.0-py310h3e792ce_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
LXML_URL:=https://files.pythonhosted.org/packages/a1/44/17b7dac7a18807d30e2fe10c3328c152808f5464565e230bfd0e77f178c6/lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/1f/2a/dc3353c2431cf53e8d04bb8fba27e584410ca3435c9c85f76d71bf0c0e80/shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/d8/60/b13c00d403f34110e96c1b5c0afa73ce461efe3fe960c3a7e3e7fe190d82/Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-64/assimp-5.0.1-h1224e73_6.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-64/qhull-2020.2-h940c156_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-64/octomap-1.9.8-hb8565cd_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-64/boost-cpp-1.78.0-hf5ba120_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-64/zlib-1.2.11-h7795811_1010.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
PYPI_PLATFORM:=--platform macosx_11_0_arm64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py39hc34188a_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py39h13cfc01_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py39h99de9ae_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py39h9eb174b_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ea/aa/45fbd031edf3149cb767d8b9f9db45d5faf0324d743c6b8fb0298cc022d0/shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/aa/bc/21097cd891dd2fa02f2b3d767e02e883e026482e59d29975d1bc30024aa3/Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl
|
||||
@@ -112,18 +115,19 @@ ifeq ($(PYVERSION), py310)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/osx-arm64/hpp-fcl-2.3.4-py310h46fc4cd_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/osx-arm64/eigenpy-3.1.0-py310ha2643af_0.conda
|
||||
BOOST_URL:=https://anaconda.org/conda-forge/boost/1.78.0/download/osx-arm64/boost-1.78.0-py310h629746b_4.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
LXML_URL:=https://anaconda.org/conda-forge/lxml/4.9.1/download/osx-arm64/lxml-4.9.1-py310h02f21da_0.tar.bz2
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/ec/41/d59208743e737184e1b403e95a937aebb022b8459e99efbcd5208fc8be46/shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/0c/5f/117b653cad585f3aedfe0de996c292e67d4b020ed77f652e5a6c8c24f908/Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/osx-arm64/assimp-5.0.1-h0f81e16_7.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/osx-arm64/qhull-2020.2-hc021e02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/osx-arm64/octomap-1.9.8-hffc8910_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/osx-arm64/boost-cpp-1.78.0-h9ed8d21_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/osx-arm64/zlib-1.2.11-h90dfc92_1014.tar.bz2
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), win)
|
||||
PYPI_PLATFORM:=--platform win_amd64
|
||||
ifeq ($(PYVERSION), py39)
|
||||
HPPFCL_URL:=https://anaconda.org/conda-forge/hpp-fcl/2.3.4/download/win-64/hpp-fcl-2.3.4-py39h39b25cb_0.conda
|
||||
EIGENPY_URL:=https://anaconda.org/conda-forge/eigenpy/3.1.0/download/win-64/eigenpy-3.1.0-py39hb6c915b_0.conda
|
||||
@@ -140,7 +144,8 @@ LXML_URL:=https://files.pythonhosted.org/packages/f6/71/65c80a4caa1617a4c6e8fe15
|
||||
SHAPELY_URL:=https://files.pythonhosted.org/packages/81/8a/7ac076a86b2632f1872284c5e60ed5f2fc26094875a85b35d9fa17b52504/shapely-2.0.1-cp310-cp310-win_amd64.whl
|
||||
PILLOW_URL:=https://files.pythonhosted.org/packages/02/55/67a3c17b9e7d972ed8c246f104da99ca4f3ea42fba566697e479011b84b6/Pillow-9.2.0-cp310-cp310-win_amd64.whl
|
||||
endif
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.0.1/download/win-64/assimp-5.0.1-hc2aa0de_6.tar.bz2
|
||||
ASSIMP_URL:=https://anaconda.org/conda-forge/assimp/5.2.5/download/win-64/assimp-5.2.5-h4dcb625_0.tar.bz2
|
||||
QHULL_URL:=https://anaconda.org/conda-forge/qhull/2020.2/download/win-64/qhull-2020.2-h70d2c02_2.tar.bz2
|
||||
OCTOMAP_URL:=https://anaconda.org/conda-forge/octomap/1.9.8/download/win-64/octomap-1.9.8-h91493d7_0.tar.bz2
|
||||
BOOSTCPP_URL:=https://anaconda.org/conda-forge/boost-cpp/1.78.0/download/win-64/boost-cpp-1.78.0-h9f4b32c_3.conda
|
||||
ZLIB_URL:=https://anaconda.org/conda-forge/zlib/1.2.11/download/win-64/zlib-1.2.11-h62dcd97_1010.tar.bz2
|
||||
@@ -164,7 +169,7 @@ endif
|
||||
cp -r blenderbim/* dist/blenderbim/
|
||||
|
||||
# Provides IfcOpenShell Python functionality
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-fc50bdd-$(PLATFORM)64.zip
|
||||
cd dist/working && wget https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-$(PYNUMBER)-v0.7.0-9cc1f5f-$(PLATFORM)64.zip
|
||||
cd dist/working && unzip ifcopenshell-python*
|
||||
cp -r dist/working/ifcopenshell dist/blenderbim/libs/site/packages/
|
||||
|
||||
@@ -376,6 +381,10 @@ endif
|
||||
mkdir dist/working
|
||||
cd dist/working && $(PYTHON) -m venv env
|
||||
cd dist/working && mkdir site-packages
|
||||
# Required by IfcCSV
|
||||
cd dist/working && . env/bin/activate && $(PIP) install pandas $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install openpyxl --target=./site-packages
|
||||
cd dist/working && . env/bin/activate && $(PIP) install odfpy --target=./site-packages
|
||||
# Required by IFCCityJSON
|
||||
cd dist/working && . env/bin/activate && $(PIP) install cjio --target=./site-packages
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
@@ -426,8 +435,7 @@ ifeq ($(PLATFORM), win)
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl except on Windows
|
||||
ifneq ($(PLATFORM), win)
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
cd dist/working && wget $(QHULL_URL)
|
||||
cd dist/working && tar -xf qhull*
|
||||
@@ -440,8 +448,10 @@ endif
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
ifeq ($(PLATFORM), win)
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by hpp-fcl
|
||||
mkdir dist/working
|
||||
@@ -476,7 +486,7 @@ ifeq ($(PLATFORM), macosm1)
|
||||
cp -r dist/working/lib/*.dylib dist/blenderbim/libs/
|
||||
endif
|
||||
ifeq ($(PLATFORM), win)
|
||||
# Uh, do nothing, apparently? No DLLs are shipped.
|
||||
cp -r dist/working/Library/bin/*.dll dist/blenderbim/libs/site/packages/hppfcl/
|
||||
endif
|
||||
rm -rf dist/working
|
||||
|
||||
@@ -590,20 +600,6 @@ endif
|
||||
cd dist/working/XlsxWriter-1.2.9/ && cp -r xlsxwriter ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by IFCCOBie for ODS support
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/97/73/8ade73f6749177003f7ce3304f524774adda96e6aaab30ea79fd8fda7934/odfpy-1.4.1.tar.gz
|
||||
cd dist/working && tar -xzvf odfpy*
|
||||
cd dist/working/odfpy-1.4.1/ && cp -r odf ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by odfpy
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz
|
||||
cd dist/working && tar -xzvf defusedxml*
|
||||
cd dist/working/defusedxml-0.6.0/ && cp -r defusedxml ../../blenderbim/libs/site/packages/
|
||||
rm -rf dist/working
|
||||
|
||||
# Required by augin
|
||||
mkdir dist/working
|
||||
cd dist/working && wget https://files.pythonhosted.org/packages/76/b4/b7baffbda025efd5dc8fcd8d2e953e3aa939c236a484084fa8f4c3588ee9/boto3-1.17.17.tar.gz
|
||||
|
||||
@@ -145,11 +145,17 @@ classes = [
|
||||
ui.BIM_PT_tab_services_object,
|
||||
# Structural analysis
|
||||
ui.BIM_PT_tab_structural,
|
||||
# Construction scheduling
|
||||
ui.BIM_PT_tab_status,
|
||||
ui.BIM_PT_tab_resources,
|
||||
ui.BIM_PT_tab_cost,
|
||||
ui.BIM_PT_tab_sequence,
|
||||
# Facility management
|
||||
ui.BIM_PT_tab_handover,
|
||||
ui.BIM_PT_tab_operations,
|
||||
# Quality and coordination
|
||||
ui.BIM_PT_tab_quality_control,
|
||||
ui.BIM_PT_tab_clash_detection,
|
||||
ui.BIM_PT_tab_collaboration,
|
||||
ui.BIM_PT_tab_sandbox,
|
||||
# TODO: move this somewhere else and clean it up
|
||||
|
||||
@@ -44,17 +44,29 @@
|
||||
<circle r="5" fill="white" stroke="black" style="stroke-width: 0.25;" />
|
||||
<line x1="-5" y1="0" x2="5" y2="0" style="stroke: black; stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea">
|
||||
<g id="dot">
|
||||
<circle r="1" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-CONTROLPOINT">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.25;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
|
||||
<g id="setout-tag">
|
||||
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
|
||||
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
|
||||
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
|
||||
<text x="3" y="-8" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
|
||||
<text x="3" y="-4" class="regular" text-anchor="start" dominant-baseline="auto" data-type="text-template"></text>
|
||||
</g>
|
||||
<g id="setout-point">
|
||||
<circle fill="white" stroke="black" r="2.5" style="stroke-width: 0.5"/>
|
||||
<path d="M 0 0 L -2.5 0 A 2.5 2.5 0 0 1 0 -2.5 Z" fill="black"/>
|
||||
<path d="M 0 0 L 2.5 0 A 2.5 2.5 0 0 1 0 2.5 Z" fill="black"/>
|
||||
</g>
|
||||
<g id="control-point">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 2.286165,1.4798666 H -1.0000316e-7 -2.2861651 L -1.1430826,-0.5000101 -1.0000316e-7,-2.479888 1.1430827,-0.5000101 Z" />
|
||||
<circle r="0.5" fill="black" stroke="black" style="stroke-width: 0.25;" />
|
||||
</g>
|
||||
<g id="SurveyArea-TRAVERSEPOINT">
|
||||
<g id="traverse-point">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M -2,-2 2,2 M -2,2 2,-2" />
|
||||
</g>
|
||||
<g id="SurveyArea-SPOTELEVATION">
|
||||
<g id="spot-elevation">
|
||||
<path style="fill: white; stroke: black; stroke-width: 0.5;" d="M 0,-2 0,2 M -2,0 2,0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -51,5 +51,5 @@ var json_data = `{{{json_data}}}`;
|
||||
<script>
|
||||
var data = `{{{data}}}`;
|
||||
setupPage(data);
|
||||
create_gantt_chart(json_data)
|
||||
</script>
|
||||
create_gantt_chart(atob(json_data))
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2023-07-30T21:07:33+10:00',(),(),'IfcOpenShell v0.7.0-8f41ae0c1','IfcOpenShell v0.7.0-8f41ae0c1','Nobody');
|
||||
FILE_NAME('/dev/null','2023-09-07T13:40:06+10:00',(),(),'IfcOpenShell v0.7.0-fbd8ea1ed','IfcOpenShell v0.7.0-fbd8ea1ed','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROJECT('0TszJmTqLCyxcP10DtK_MP',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('0Y_CJXSsbCgBlSGFw0mbLa',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3IxQaOPH5Fx90CGmGm1gtQ',$,$,$,#1,(#2));
|
||||
#1=IFCPROJECT('1arVsMni92iPUaaryGTUz2',$,'BlenderBIM Demo',$,$,$,$,(#12,#16),#7);
|
||||
#2=IFCPROJECTLIBRARY('1FfDG5b21DuBDOO4eUE9rF',$,'BlenderBIM Demo Library',$,$,$,$,$,$);
|
||||
#3=IFCRELDECLARES('3tfDIC3Tf9wP8EUOoFeycK',$,$,$,#1,(#2));
|
||||
#4=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#5=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#6=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#7=IFCUNITASSIGNMENT((#6,#4,#5));
|
||||
#7=IFCUNITASSIGNMENT((#5,#6,#4));
|
||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#9=IFCDIRECTION((0.,0.,1.));
|
||||
#10=IFCDIRECTION((1.,0.,0.));
|
||||
@@ -25,82 +25,82 @@ DATA;
|
||||
#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#16,$,.PLAN_VIEW.,$);
|
||||
#20=IFCMATERIAL('Unknown',$,$);
|
||||
#21=IFCWALLTYPE('3U0gAUglf9WBlcfpB15EK0',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#21=IFCWALLTYPE('2o8NqQQbHA5BeErrgwfr7d',$,'WAL50',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#22=IFCMATERIALLAYERSET((#24),$,$);
|
||||
#23=IFCRELASSOCIATESMATERIAL('3J36H22Gz1ovVzdiKgApaM',$,$,$,(#21),#22);
|
||||
#23=IFCRELASSOCIATESMATERIAL('1Q$5DZKr98n82Eitz2dWey',$,$,$,(#21),#22);
|
||||
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
|
||||
#25=IFCRELDECLARES('28ApgMJzH10h$Ng$fmCpYf',$,$,$,#2,(#63,#21,#54,#87,#153,#34,#67,#210,#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96));
|
||||
#26=IFCWALLTYPE('1wGfdUdC10TuASJo4jnO99',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#25=IFCRELDECLARES('1KXKUEn8z0g8lvyEsCTWD3',$,$,$,#2,(#38,#82,#77,#42,#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#153,#34,#67,#210));
|
||||
#26=IFCWALLTYPE('3KlIOv_P9A79tkt8D_jq_m',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#27=IFCMATERIALLAYERSET((#29),$,$);
|
||||
#28=IFCRELASSOCIATESMATERIAL('0NRoxSQPzCgQHfsu5ru91z',$,$,$,(#26),#27);
|
||||
#28=IFCRELASSOCIATESMATERIAL('32B9Wx8Z1FxhIN7m77MiHG',$,$,$,(#26),#27);
|
||||
#29=IFCMATERIALLAYER(#20,0.1,$,$,$,$,$);
|
||||
#30=IFCWALLTYPE('1lt5MuGLLDfwmZd0tNruF7',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#30=IFCWALLTYPE('2DqrSeel1AGw6h3b$ujafZ',$,'WAL200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#31=IFCMATERIALLAYERSET((#33),$,$);
|
||||
#32=IFCRELASSOCIATESMATERIAL('21rGVxPODC$xmWYFaDy1T6',$,$,$,(#30),#31);
|
||||
#32=IFCRELASSOCIATESMATERIAL('2FeYUpt3D4tAvwgGmwXWZn',$,$,$,(#30),#31);
|
||||
#33=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#34=IFCWALLTYPE('0NZm1D8h12xAxyW7fL5nc1',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#34=IFCWALLTYPE('2jNdEOFIP1eQb3WZePlFGY',$,'WAL300',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#35=IFCMATERIALLAYERSET((#37),$,$);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2yvmN42crCJevtirp5cMeV',$,$,$,(#34),#35);
|
||||
#36=IFCRELASSOCIATESMATERIAL('2bFeM6xevAwucuwMlauX77',$,$,$,(#34),#35);
|
||||
#37=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#38=IFCCOVERINGTYPE('1eb4wZ_5n6xRGrs3rlKMiM',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#38=IFCCOVERINGTYPE('3310gCgH59w9tu$DfmiCEN',$,'COV10',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#39=IFCMATERIALLAYERSET((#41),$,$);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3HKIKf06T6xRqM6Lp69$OM',$,$,$,(#38),#39);
|
||||
#40=IFCRELASSOCIATESMATERIAL('3pTNmWd_jBoOWyHc8Yf4qI',$,$,$,(#38),#39);
|
||||
#41=IFCMATERIALLAYER(#20,0.01,$,$,$,$,$);
|
||||
#42=IFCCOVERINGTYPE('2E89aYmtrEfxhX_jsNqviN',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#42=IFCCOVERINGTYPE('2sksWFuPzCgeNk1ofXS6PZ',$,'COV20',$,$,(#46),$,$,$,.NOTDEFINED.);
|
||||
#43=IFCMATERIALLAYERSET((#45),$,$);
|
||||
#44=IFCRELASSOCIATESMATERIAL('2115lEhhP4Z9G1Yhd_DqSC',$,$,$,(#42),#43);
|
||||
#44=IFCRELASSOCIATESMATERIAL('36rl5NddT1ux6ejkQAkT2z',$,$,$,(#42),#43);
|
||||
#45=IFCMATERIALLAYER(#20,0.02,$,$,$,$,$);
|
||||
#46=IFCPROPERTYSET('0vtU63YmjAgg8DeHlTgNx7',$,'EPset_Parametric',$,(#47));
|
||||
#46=IFCPROPERTYSET('2VZJ9xfdn1NeOY6uEmnnYW',$,'EPset_Parametric',$,(#47));
|
||||
#47=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS2'),$);
|
||||
#48=IFCCOVERINGTYPE('0XiSBI57L9Uf8f2NaQ_ksi',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#48=IFCCOVERINGTYPE('1uU6rxt95AC91lZ0jTRG6G',$,'COV30',$,$,(#52),$,$,$,.NOTDEFINED.);
|
||||
#49=IFCMATERIALLAYERSET((#51),$,$);
|
||||
#50=IFCRELASSOCIATESMATERIAL('3U3JdGAmbABA4mB$Fgpq1l',$,$,$,(#48),#49);
|
||||
#50=IFCRELASSOCIATESMATERIAL('2lFv4xQYr5bwtiSsMTVCgz',$,$,$,(#48),#49);
|
||||
#51=IFCMATERIALLAYER(#20,0.03,$,$,$,$,$);
|
||||
#52=IFCPROPERTYSET('17nhQ5I4b9ZvhDu4gRJLKO',$,'EPset_Parametric',$,(#53));
|
||||
#52=IFCPROPERTYSET('0HnrRhcd16Yfbgtci4sqCG',$,'EPset_Parametric',$,(#53));
|
||||
#53=IFCPROPERTYSINGLEVALUE('LayerSetDirection',$,IFCLABEL('AXIS3'),$);
|
||||
#54=IFCRAMPTYPE('1BTHjVDP19tgb3MDCSkWCJ',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#54=IFCRAMPTYPE('3nonXCVn58jhVV08N8c97B',$,'RAM200',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#55=IFCMATERIALLAYERSET((#57),$,$);
|
||||
#56=IFCRELASSOCIATESMATERIAL('3HN21bHtL16vvHA2GnnxH1',$,$,$,(#54),#55);
|
||||
#56=IFCRELASSOCIATESMATERIAL('2NdIrmbUP6bh4x$p_RY31C',$,$,$,(#54),#55);
|
||||
#57=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#58=IFCCIRCLEPROFILEDEF(.AREA.,$,$,0.3);
|
||||
#59=IFCPILETYPE('04_2WZSqH8TgT7ET6DCCpd',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#59=IFCPILETYPE('2uuf5kq5TDDfO9jGN7XIHh',$,'P1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#60=IFCMATERIALPROFILESET($,$,(#62),$);
|
||||
#61=IFCRELASSOCIATESMATERIAL('1qTZZRXsPF9hmYwT$tgFgp',$,$,$,(#59),#60);
|
||||
#61=IFCRELASSOCIATESMATERIAL('3hi821Ffj8YR9yFjp226LU',$,$,$,(#59),#60);
|
||||
#62=IFCMATERIALPROFILE($,$,#20,#58,$,$);
|
||||
#63=IFCSLABTYPE('3_OV9$Sd52KRhIW2TWIxbW',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#63=IFCSLABTYPE('2janmz3nH4P9IHaOVvGY_B',$,'FLR150',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#64=IFCMATERIALLAYERSET((#66),$,$);
|
||||
#65=IFCRELASSOCIATESMATERIAL('1agyf45$X1NhR127pUL0Iw',$,$,$,(#63),#64);
|
||||
#65=IFCRELASSOCIATESMATERIAL('0og5Zg4ib67g6XWgOgqBWy',$,$,$,(#63),#64);
|
||||
#66=IFCMATERIALLAYER(#20,0.2,$,$,$,$,$);
|
||||
#67=IFCSLABTYPE('0LvmpIGf9AVPcG$Kt06TEk',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#67=IFCSLABTYPE('26UKpEaTb9fh4qrqi3Ymzj',$,'FLR250',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#68=IFCMATERIALLAYERSET((#70),$,$);
|
||||
#69=IFCRELASSOCIATESMATERIAL('0EKQPSL656secN86SgeutH',$,$,$,(#67),#68);
|
||||
#69=IFCRELASSOCIATESMATERIAL('1BeBXPqen9TQEE42SxzhjF',$,$,$,(#67),#68);
|
||||
#70=IFCMATERIALLAYER(#20,0.3,$,$,$,$,$);
|
||||
#71=IFCRECTANGLEPROFILEDEF(.AREA.,'500x600',$,0.5,0.6);
|
||||
#72=IFCCOLUMNTYPE('1SI8m9m813tAXfSnM1fCbm',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#72=IFCCOLUMNTYPE('1KnWzBBoLCNfJ3_J79LT7d',$,'C1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#73=IFCMATERIALPROFILESET($,$,(#75),$);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1EhwSTRtPBQgBt2x6dhuT$',$,$,$,(#72),#73);
|
||||
#74=IFCRELASSOCIATESMATERIAL('1W8OWQjEL05gH5hEpve6Lh',$,$,$,(#72),#73);
|
||||
#75=IFCMATERIALPROFILE($,$,#20,#71,$,$);
|
||||
#76=IFCCIRCLEHOLLOWPROFILEDEF(.AREA.,'500.0x5.0 CHS',$,0.25,0.005);
|
||||
#77=IFCCOLUMNTYPE('25GZwy2zLFcgi57r5S8iY3',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#77=IFCCOLUMNTYPE('33jPFERfL9bfTeWfTL5kZ7',$,'C2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#78=IFCMATERIALPROFILESET($,$,(#80),$);
|
||||
#79=IFCRELASSOCIATESMATERIAL('2VURAOhz93ZQqT9YJUA1DO',$,$,$,(#77),#78);
|
||||
#79=IFCRELASSOCIATESMATERIAL('3czOrQw2v9BfS0PeQCrzWq',$,$,$,(#77),#78);
|
||||
#80=IFCMATERIALPROFILE($,$,#20,#76,$,$);
|
||||
#81=IFCRECTANGLEHOLLOWPROFILEDEF(.AREA.,'150x75x2.0 RHS',$,0.075,0.15,0.002,0.005,0.005);
|
||||
#82=IFCCOLUMNTYPE('1Wg$I84s9BTh$iDuZnmSkH',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#82=IFCCOLUMNTYPE('21YcNdKNj2$95U55yNx1Nw',$,'C3',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#83=IFCMATERIALPROFILESET($,$,(#85),$);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3fIIwEe7DAle7x_zISvMUD',$,$,$,(#82),#83);
|
||||
#84=IFCRELASSOCIATESMATERIAL('3n6Cru3xr6jvDptU9u7QEF',$,$,$,(#82),#83);
|
||||
#85=IFCMATERIALPROFILE($,$,#20,#81,$,$);
|
||||
#86=IFCISHAPEPROFILEDEF(.AREA.,'DEMO-I',$,0.1,0.2,0.005,0.01,0.005,$,$);
|
||||
#87=IFCBEAMTYPE('2yg2Irs7L26gFsD_AvvFUu',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#87=IFCBEAMTYPE('2CsGpD$6nDCxWv9jXTtvq9',$,'B1',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#88=IFCMATERIALPROFILESET($,$,(#90),$);
|
||||
#89=IFCRELASSOCIATESMATERIAL('1PU_gbkifF9hYpi9DvXTAU',$,$,$,(#87),#88);
|
||||
#89=IFCRELASSOCIATESMATERIAL('2LJExc74j9rgBSBx51HDPK',$,$,$,(#87),#88);
|
||||
#90=IFCMATERIALPROFILE($,$,#20,#86,$,$);
|
||||
#91=IFCCSHAPEPROFILEDEF(.AREA.,'DEMO-C',$,0.2,0.1,0.0015,0.03,0.005);
|
||||
#92=IFCBEAMTYPE('2Nf2L5qpz8K8KhUNFBg_fT',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#92=IFCBEAMTYPE('32pHN2b0P0awGw$U8PpneO',$,'B2',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#93=IFCMATERIALPROFILESET($,$,(#95),$);
|
||||
#94=IFCRELASSOCIATESMATERIAL('1SgarGNCzEQQpfs29NMq0y',$,$,$,(#92),#93);
|
||||
#94=IFCRELASSOCIATESMATERIAL('2wg8R1Drb2xhQ2Y_pRSh3c',$,$,$,(#92),#93);
|
||||
#95=IFCMATERIALPROFILE($,$,#20,#91,$,$);
|
||||
#96=IFCWINDOWTYPE('2zRUFP8Ef81xDOR2iJx1zK',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#96=IFCWINDOWTYPE('1Gg9HfZEr69u8SdWfDs2J3',$,'WT01',$,$,$,(#135,#152),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#97=IFCINDEXEDPOLYGONALFACE((13,17,18,14));
|
||||
#98=IFCINDEXEDPOLYGONALFACE((5,6,3,4));
|
||||
#99=IFCINDEXEDPOLYGONALFACE((7,8,2,1));
|
||||
@@ -157,7 +157,7 @@ DATA;
|
||||
#150=IFCDIRECTION((0.,0.,1.));
|
||||
#151=IFCAXIS2PLACEMENT3D(#148,#150,#149);
|
||||
#152=IFCREPRESENTATIONMAP(#151,#147);
|
||||
#153=IFCDOORTYPE('0oekOIA$z8lxWiQs9_tFWU',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#153=IFCDOORTYPE('0NBUmPKyT9WecsIeYJrEqg',$,'DT01',$,$,$,(#196,#209),$,$,.NOTDEFINED.,.NOTDEFINED.,$,$);
|
||||
#154=IFCINDEXEDPOLYGONALFACE((17,16,15,14));
|
||||
#155=IFCINDEXEDPOLYGONALFACE((2,3,29,28));
|
||||
#156=IFCINDEXEDPOLYGONALFACE((27,28,29,30,32,31));
|
||||
@@ -214,7 +214,7 @@ DATA;
|
||||
#207=IFCDIRECTION((0.,0.,1.));
|
||||
#208=IFCAXIS2PLACEMENT3D(#205,#207,#206);
|
||||
#209=IFCREPRESENTATIONMAP(#208,#204);
|
||||
#210=IFCFURNITURETYPE('0I2mc$m3T60hLXZYg9Y1wu',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#210=IFCFURNITURETYPE('3Kyc6IyarAUw3_8fkNtXIg',$,'BUN01',$,$,$,(#931,#952),$,$,.NOTDEFINED.,.NOTDEFINED.);
|
||||
#211=IFCINDEXEDPOLYGONALFACE((187,278,44));
|
||||
#212=IFCINDEXEDPOLYGONALFACE((21,52,60));
|
||||
#213=IFCINDEXEDPOLYGONALFACE((91,100,31));
|
||||
@@ -957,131 +957,161 @@ DATA;
|
||||
#950=IFCDIRECTION((0.,0.,1.));
|
||||
#951=IFCAXIS2PLACEMENT3D(#948,#950,#949);
|
||||
#952=IFCREPRESENTATIONMAP(#951,#947);
|
||||
#953=IFCTYPEPRODUCT('2be705eyD4DPBr6j4K7ZEx',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('0uEfutIxDDIxMIBUJOZpSM',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#956=IFCTYPEPRODUCT('1M7mm9wUHFoxdM0MdAMyqM',$,'FINE',$,'IfcAnnotation/LINEWORK',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3AZ8m1Bdn1gfbvagzKr2c3',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#959=IFCTYPEPRODUCT('3LdrNIFOvAkBzrNnqWAZfA',$,'THIN',$,'IfcAnnotation/LINEWORK',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0$9iwgSID7xBul4Tb9r2du',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#962=IFCTYPEPRODUCT('3fFNO0t1TB$wYRaIkKwUCH',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('33Fm$9zgb1cgsCOy859RlR',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#965=IFCTYPEPRODUCT('1CpMBhemj5VQC43bU9J7zG',$,'THICK',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('1OLDsFSDz2TPz5ifkQvWo8',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#968=IFCTYPEPRODUCT('0NITtya3b16hoA3sh17SL4',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('1_kfqWVDn0puI29Ah6eitX',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#971=IFCTYPEPRODUCT('0rRN9EsvL5AQ3PorxrZSKM',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#972),(#991),$);
|
||||
#972=IFCPROPERTYSET('3lRZ$d2VH8cxZYh$mm45iq',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#974=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#975=IFCDIRECTION((0.,0.,1.));
|
||||
#976=IFCDIRECTION((1.,0.,0.));
|
||||
#977=IFCAXIS2PLACEMENT3D(#974,#975,#976);
|
||||
#978=IFCPLANAREXTENT(1000.,1000.);
|
||||
#979=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#977,.RIGHT.,#978,'center');
|
||||
#980=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#981=IFCDIRECTION((0.,0.,1.));
|
||||
#982=IFCDIRECTION((1.,0.,0.));
|
||||
#983=IFCAXIS2PLACEMENT3D(#980,#981,#982);
|
||||
#984=IFCPLANAREXTENT(1000.,1000.);
|
||||
#985=IFCTEXTLITERALWITHEXTENT('{{Name}}',#983,.RIGHT.,#984,'center');
|
||||
#986=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#979,#985));
|
||||
#987=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#988=IFCDIRECTION((1.,0.,0.));
|
||||
#989=IFCDIRECTION((0.,0.,1.));
|
||||
#990=IFCAXIS2PLACEMENT3D(#987,#989,#988);
|
||||
#991=IFCREPRESENTATIONMAP(#990,#986);
|
||||
#992=IFCTYPEPRODUCT('0bFZUWHjbA2e03hRHNinTt',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#993),(#1006),$);
|
||||
#993=IFCPROPERTYSET('3TJOWv5NDBXBNCshYFjsFV',$,'EPset_Annotation',$,(#994));
|
||||
#994=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#995=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#996=IFCDIRECTION((0.,0.,1.));
|
||||
#953=IFCTYPEPRODUCT('0TBMBnD_b66QLUWKD9HLsc',$,'SETOUT-POINT',$,'IfcAnnotation/SYMBOL',(#954),$,$);
|
||||
#954=IFCPROPERTYSET('2dQzX_K4r1Xet6dz9zBlgs',$,'EPset_Annotation',$,(#955));
|
||||
#955=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-point'),$);
|
||||
#956=IFCTYPEPRODUCT('0vo_7PU3H9ygTnrIipqPRI',$,'CONTROL-POINT',$,'IfcAnnotation/SYMBOL',(#957),$,$);
|
||||
#957=IFCPROPERTYSET('3APQOw$FP1ivxp08UxHsl6',$,'EPset_Annotation',$,(#958));
|
||||
#958=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('control-point'),$);
|
||||
#959=IFCTYPEPRODUCT('32V27G3$T1OO3TbXg6948F',$,'TRAVERSE-POINT',$,'IfcAnnotation/SYMBOL',(#960),$,$);
|
||||
#960=IFCPROPERTYSET('0cTSYXMc9B$PTXVDrhBYYW',$,'EPset_Annotation',$,(#961));
|
||||
#961=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('traverse-point'),$);
|
||||
#962=IFCTYPEPRODUCT('2PXIC7Bg914gJhRh2XeGnN',$,'DASHED',$,'IfcAnnotation/LINEWORK',(#963),$,$);
|
||||
#963=IFCPROPERTYSET('0RiYsOxp529gXnCkw44wF8',$,'EPset_Annotation',$,(#964));
|
||||
#964=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('dashed'),$);
|
||||
#965=IFCTYPEPRODUCT('1Ou1kA3Vb4HhuNBvM0uGe0',$,'FINE',$,'IfcAnnotation/LINEWORK',(#966),$,$);
|
||||
#966=IFCPROPERTYSET('2gzly9D0L1qPK2MExeXgRO',$,'EPset_Annotation',$,(#967));
|
||||
#967=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('fine'),$);
|
||||
#968=IFCTYPEPRODUCT('3lx$KQPRbEZwbQ7Xdfm5gw',$,'THIN',$,'IfcAnnotation/LINEWORK',(#969),$,$);
|
||||
#969=IFCPROPERTYSET('0vsVpqs6zArvA2bfBtvQew',$,'EPset_Annotation',$,(#970));
|
||||
#970=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thin'),$);
|
||||
#971=IFCTYPEPRODUCT('00j$y97p903w2HOb35lAQ2',$,'MEDIUM',$,'IfcAnnotation/LINEWORK',(#972),$,$);
|
||||
#972=IFCPROPERTYSET('240rEBDOn8PAvfnm_43s55',$,'EPset_Annotation',$,(#973));
|
||||
#973=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('medium'),$);
|
||||
#974=IFCTYPEPRODUCT('2tVdFGorj6dfrL4uA1kyW8',$,'THICK',$,'IfcAnnotation/LINEWORK',(#975),$,$);
|
||||
#975=IFCPROPERTYSET('2IEGHncr1D$R8fKA9zCEJP',$,'EPset_Annotation',$,(#976));
|
||||
#976=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('thick'),$);
|
||||
#977=IFCTYPEPRODUCT('1T5C$$ONTBB8A9a7vv0Gn6',$,'STRONG',$,'IfcAnnotation/LINEWORK',(#978),$,$);
|
||||
#978=IFCPROPERTYSET('1gRomAS1L2WguA51ZI0E40',$,'EPset_Annotation',$,(#979));
|
||||
#979=IFCPROPERTYSINGLEVALUE('Classes',$,IFCLABEL('strong'),$);
|
||||
#980=IFCTYPEPRODUCT('2LmKYrAEr2K8EwJyCAsyc4',$,'SETOUT-TAG',$,'IfcAnnotation/TEXT',(#981),(#1000),$);
|
||||
#981=IFCPROPERTYSET('0nF9du8qDAF9aldqjCUsbX',$,'EPset_Annotation',$,(#982));
|
||||
#982=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('setout-tag'),$);
|
||||
#983=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#984=IFCDIRECTION((0.,0.,1.));
|
||||
#985=IFCDIRECTION((1.,0.,0.));
|
||||
#986=IFCAXIS2PLACEMENT3D(#983,#984,#985);
|
||||
#987=IFCPLANAREXTENT(1000.,1000.);
|
||||
#988=IFCTEXTLITERALWITHEXTENT('E ``round({{easting}}, 0.001)``',#986,.RIGHT.,#987,'center');
|
||||
#989=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#990=IFCDIRECTION((0.,0.,1.));
|
||||
#991=IFCDIRECTION((1.,0.,0.));
|
||||
#992=IFCAXIS2PLACEMENT3D(#989,#990,#991);
|
||||
#993=IFCPLANAREXTENT(1000.,1000.);
|
||||
#994=IFCTEXTLITERALWITHEXTENT('N ``round({{northing}}, 0.001)``',#992,.RIGHT.,#993,'center');
|
||||
#995=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#988,#994));
|
||||
#996=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#997=IFCDIRECTION((1.,0.,0.));
|
||||
#998=IFCAXIS2PLACEMENT3D(#995,#996,#997);
|
||||
#999=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1000=IFCTEXTLITERALWITHEXTENT('{{Name}}',#998,.RIGHT.,#999,'center');
|
||||
#1001=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1000));
|
||||
#1002=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1003=IFCDIRECTION((1.,0.,0.));
|
||||
#1004=IFCDIRECTION((0.,0.,1.));
|
||||
#1005=IFCAXIS2PLACEMENT3D(#1002,#1004,#1003);
|
||||
#1006=IFCREPRESENTATIONMAP(#1005,#1001);
|
||||
#1007=IFCTYPEPRODUCT('2DlmDdasX05un_uMP4O23U',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1008),(#1033),$);
|
||||
#1008=IFCPROPERTYSET('1$OAHURHr3GON7ZzAJnv72',$,'EPset_Annotation',$,(#1009));
|
||||
#1009=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#998=IFCDIRECTION((0.,0.,1.));
|
||||
#999=IFCAXIS2PLACEMENT3D(#996,#998,#997);
|
||||
#1000=IFCREPRESENTATIONMAP(#999,#995);
|
||||
#1001=IFCTYPEPRODUCT('1cdhVmqEPF6e13_TFOdlQw',$,'DOOR-TAG',$,'IfcAnnotation/TEXT',(#1002),(#1021),$);
|
||||
#1002=IFCPROPERTYSET('1MUPGQolnDEgp0NxcMN56E',$,'EPset_Annotation',$,(#1003));
|
||||
#1003=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('door-tag'),$);
|
||||
#1004=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1005=IFCDIRECTION((0.,0.,1.));
|
||||
#1006=IFCDIRECTION((1.,0.,0.));
|
||||
#1007=IFCAXIS2PLACEMENT3D(#1004,#1005,#1006);
|
||||
#1008=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1009=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1007,.RIGHT.,#1008,'center');
|
||||
#1010=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1011=IFCDIRECTION((0.,0.,1.));
|
||||
#1012=IFCDIRECTION((1.,0.,0.));
|
||||
#1013=IFCAXIS2PLACEMENT3D(#1010,#1011,#1012);
|
||||
#1014=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1015=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1013,.RIGHT.,#1014,'center');
|
||||
#1016=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1017=IFCDIRECTION((0.,0.,1.));
|
||||
#1016=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1009,#1015));
|
||||
#1017=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1018=IFCDIRECTION((1.,0.,0.));
|
||||
#1019=IFCAXIS2PLACEMENT3D(#1016,#1017,#1018);
|
||||
#1020=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1021=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1019,.RIGHT.,#1020,'center');
|
||||
#1022=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1023=IFCDIRECTION((0.,0.,1.));
|
||||
#1024=IFCDIRECTION((1.,0.,0.));
|
||||
#1025=IFCAXIS2PLACEMENT3D(#1022,#1023,#1024);
|
||||
#1026=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1027=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}} or 0., 2)``',#1025,.RIGHT.,#1026,'center');
|
||||
#1028=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1015,#1021,#1027));
|
||||
#1029=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1030=IFCDIRECTION((1.,0.,0.));
|
||||
#1031=IFCDIRECTION((0.,0.,1.));
|
||||
#1032=IFCAXIS2PLACEMENT3D(#1029,#1031,#1030);
|
||||
#1033=IFCREPRESENTATIONMAP(#1032,#1028);
|
||||
#1034=IFCTYPEPRODUCT('2vNx3CIlrFs8bLFlmGvJF3',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1035),(#1048),$);
|
||||
#1035=IFCPROPERTYSET('1l$T4lUqnBb93zcgfbSPvx',$,'EPset_Annotation',$,(#1036));
|
||||
#1036=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1037=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1038=IFCDIRECTION((0.,0.,1.));
|
||||
#1039=IFCDIRECTION((1.,0.,0.));
|
||||
#1040=IFCAXIS2PLACEMENT3D(#1037,#1038,#1039);
|
||||
#1041=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1042=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1040,.RIGHT.,#1041,'center');
|
||||
#1043=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1042));
|
||||
#1044=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1045=IFCDIRECTION((1.,0.,0.));
|
||||
#1046=IFCDIRECTION((0.,0.,1.));
|
||||
#1047=IFCAXIS2PLACEMENT3D(#1044,#1046,#1045);
|
||||
#1048=IFCREPRESENTATIONMAP(#1047,#1043);
|
||||
#1049=IFCTYPEPRODUCT('3hqzFtbSb56RjhAZNaB4Vh',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1050),(#1063),$);
|
||||
#1050=IFCPROPERTYSET('3U7kaKt_nBUvD$U2ryif_H',$,'EPset_Annotation',$,(#1051));
|
||||
#1051=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1019=IFCDIRECTION((0.,0.,1.));
|
||||
#1020=IFCAXIS2PLACEMENT3D(#1017,#1019,#1018);
|
||||
#1021=IFCREPRESENTATIONMAP(#1020,#1016);
|
||||
#1022=IFCTYPEPRODUCT('0OuDi3gRH2CxW9mrtE0vXw',$,'WINDOW-TAG',$,'IfcAnnotation/TEXT',(#1023),(#1036),$);
|
||||
#1023=IFCPROPERTYSET('2X7dAo_1P5ruRnlKA4kIl6',$,'EPset_Annotation',$,(#1024));
|
||||
#1024=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('window-tag'),$);
|
||||
#1025=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1026=IFCDIRECTION((0.,0.,1.));
|
||||
#1027=IFCDIRECTION((1.,0.,0.));
|
||||
#1028=IFCAXIS2PLACEMENT3D(#1025,#1026,#1027);
|
||||
#1029=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1030=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1028,.RIGHT.,#1029,'center');
|
||||
#1031=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1030));
|
||||
#1032=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1033=IFCDIRECTION((1.,0.,0.));
|
||||
#1034=IFCDIRECTION((0.,0.,1.));
|
||||
#1035=IFCAXIS2PLACEMENT3D(#1032,#1034,#1033);
|
||||
#1036=IFCREPRESENTATIONMAP(#1035,#1031);
|
||||
#1037=IFCTYPEPRODUCT('3WEV_9wQn6AQTESgjW36PH',$,'SPACE-TAG',$,'IfcAnnotation/TEXT',(#1038),(#1063),$);
|
||||
#1038=IFCPROPERTYSET('02FAX8dIzAlunWD5ak$6sU',$,'EPset_Annotation',$,(#1039));
|
||||
#1039=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('space-tag'),$);
|
||||
#1040=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1041=IFCDIRECTION((0.,0.,1.));
|
||||
#1042=IFCDIRECTION((1.,0.,0.));
|
||||
#1043=IFCAXIS2PLACEMENT3D(#1040,#1041,#1042);
|
||||
#1044=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1045=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1043,.RIGHT.,#1044,'center');
|
||||
#1046=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1047=IFCDIRECTION((0.,0.,1.));
|
||||
#1048=IFCDIRECTION((1.,0.,0.));
|
||||
#1049=IFCAXIS2PLACEMENT3D(#1046,#1047,#1048);
|
||||
#1050=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1051=IFCTEXTLITERALWITHEXTENT('{{Description}}',#1049,.RIGHT.,#1050,'center');
|
||||
#1052=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1053=IFCDIRECTION((0.,0.,1.));
|
||||
#1054=IFCDIRECTION((1.,0.,0.));
|
||||
#1055=IFCAXIS2PLACEMENT3D(#1052,#1053,#1054);
|
||||
#1056=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1057=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1055,.RIGHT.,#1056,'center');
|
||||
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1057));
|
||||
#1057=IFCTEXTLITERALWITHEXTENT('``round({{Qto_SpaceBaseQuantities.NetFloorArea}}, 0.01)``',#1055,.RIGHT.,#1056,'center');
|
||||
#1058=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1045,#1051,#1057));
|
||||
#1059=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1060=IFCDIRECTION((1.,0.,0.));
|
||||
#1061=IFCDIRECTION((0.,0.,1.));
|
||||
#1062=IFCAXIS2PLACEMENT3D(#1059,#1061,#1060);
|
||||
#1063=IFCREPRESENTATIONMAP(#1062,#1058);
|
||||
#1064=IFCTYPEPRODUCT('2SnSxJPiD9sh$NDx92OCSS',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('3Lu86QVuv5nfjkuPLvdQSH',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1064=IFCTYPEPRODUCT('1evqvQLLL1zxdsIWqEn0lK',$,'MATERIAL-TAG',$,'IfcAnnotation/TEXT',(#1065),(#1078),$);
|
||||
#1065=IFCPROPERTYSET('2KPJlGyer0UBmphfR7952k',$,'EPset_Annotation',$,(#1066));
|
||||
#1066=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('rectangle-tag'),$);
|
||||
#1067=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1068=IFCDIRECTION((0.,0.,1.));
|
||||
#1069=IFCDIRECTION((1.,0.,0.));
|
||||
#1070=IFCAXIS2PLACEMENT3D(#1067,#1068,#1069);
|
||||
#1071=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1072=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1070,.RIGHT.,#1071,'center');
|
||||
#1072=IFCTEXTLITERALWITHEXTENT('{{material.Name}}',#1070,.RIGHT.,#1071,'center');
|
||||
#1073=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1072));
|
||||
#1074=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1075=IFCDIRECTION((1.,0.,0.));
|
||||
#1076=IFCDIRECTION((0.,0.,1.));
|
||||
#1077=IFCAXIS2PLACEMENT3D(#1074,#1076,#1075);
|
||||
#1078=IFCREPRESENTATIONMAP(#1077,#1073);
|
||||
#1079=IFCTYPEPRODUCT('3Nem6d4xX87O3deyWDi3AW',$,'TYPE-TAG',$,'IfcAnnotation/TEXT',(#1080),(#1093),$);
|
||||
#1080=IFCPROPERTYSET('15bspgnA9CEuFox6xFjoTL',$,'EPset_Annotation',$,(#1081));
|
||||
#1081=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1082=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1083=IFCDIRECTION((0.,0.,1.));
|
||||
#1084=IFCDIRECTION((1.,0.,0.));
|
||||
#1085=IFCAXIS2PLACEMENT3D(#1082,#1083,#1084);
|
||||
#1086=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1087=IFCTEXTLITERALWITHEXTENT('{{type.Name}}',#1085,.RIGHT.,#1086,'center');
|
||||
#1088=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1087));
|
||||
#1089=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1090=IFCDIRECTION((1.,0.,0.));
|
||||
#1091=IFCDIRECTION((0.,0.,1.));
|
||||
#1092=IFCAXIS2PLACEMENT3D(#1089,#1091,#1090);
|
||||
#1093=IFCREPRESENTATIONMAP(#1092,#1088);
|
||||
#1094=IFCTYPEPRODUCT('0klFX9AjnEnPBkdIURv8XD',$,'NAME-TAG',$,'IfcAnnotation/TEXT',(#1095),(#1108),$);
|
||||
#1095=IFCPROPERTYSET('1d53tifbv2rwcoFFJosiHf',$,'EPset_Annotation',$,(#1096));
|
||||
#1096=IFCPROPERTYSINGLEVALUE('Symbol',$,IFCLABEL('capsule-tag'),$);
|
||||
#1097=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1098=IFCDIRECTION((0.,0.,1.));
|
||||
#1099=IFCDIRECTION((1.,0.,0.));
|
||||
#1100=IFCAXIS2PLACEMENT3D(#1097,#1098,#1099);
|
||||
#1101=IFCPLANAREXTENT(1000.,1000.);
|
||||
#1102=IFCTEXTLITERALWITHEXTENT('{{Name}}',#1100,.RIGHT.,#1101,'center');
|
||||
#1103=IFCSHAPEREPRESENTATION(#19,'Annotation','Annotation2D',(#1102));
|
||||
#1104=IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#1105=IFCDIRECTION((1.,0.,0.));
|
||||
#1106=IFCDIRECTION((0.,0.,1.));
|
||||
#1107=IFCAXIS2PLACEMENT3D(#1104,#1106,#1105);
|
||||
#1108=IFCREPRESENTATIONMAP(#1107,#1103);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -5,27 +5,27 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#5=IFCSIMPLEPROPERTYTEMPLATE('2T$a4OFsv2LeD5JeBKEV4f',$,'IsNTS','Whether or not the scale is intended to be significant',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#6=IFCSIMPLEPROPERTYTEMPLATE('0AK5C2UpL4$eaac2LszAx$',$,'HasUnderlay','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#14=IFCSIMPLEPROPERTYTEMPLATE('3mRF52q81FQB$h4oTh7M45',$,'Markers','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#15=IFCSIMPLEPROPERTYTEMPLATE('1rhr_0N3LDtuORcEJP0KXM',$,'Symbols','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#16=IFCSIMPLEPROPERTYTEMPLATE('2sHDBuW7P4TROy$hL2w7ct',$,'Patterns','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#17=IFCSIMPLEPROPERTYTEMPLATE('1$xfo9EVb26QLqmPll2_RK',$,'MetricPrecision','',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
|
||||
#18=IFCSIMPLEPROPERTYTEMPLATE('38uAtrp9nD_901NO42zd$7',$,'ImperialPrecision','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#19=IFCSIMPLEPROPERTYTEMPLATE('1MX0uffTL6TOvtvEJpxFmk',$,'DecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#20=IFCSIMPLEPROPERTYTEMPLATE('0joEq0Rd10cxweEh0NeHT6',$,'JoinCriteria','Comma separated selection keys which determine what cut objects are to be joined.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Status.ifc','2020-01-01T00:00:00',(),(),'EPset_Status','EPset_Status',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3JoyTt$cz4fhYuQvJeTMhs',$,'EPset_Status','',.PSET_TYPEDRIVENOVERRIDE.,'IfcProduct',(#2,#4));
|
||||
#2= IFCSIMPLEPROPERTYTEMPLATE('2HBOdOfz5E3hZ7IBLCq$$D',$,'Status','Status of the element, predominately used in renovation or retrofitting projects. The status can be assigned to as "New" - element designed as new addition, "Existing" - element exists and remains, "Demolish" - element existed but is to be demolished, "Temporary" - element will exists only temporary (like a temporary support structure).',.P_ENUMERATEDVALUE.,'IfcLabel',$,#3,$,$,$,.READWRITE.);
|
||||
#3= IFCPROPERTYENUMERATION('EPEnum_ElementStatus',(IFCLABEL('NEW'),IFCLABEL('EXISTING'),IFCLABEL('DEMOLISH'),IFCLABEL('TEMPORARY'),IFCLABEL('OTHER'),IFCLABEL('NOTKNOWN'),IFCLABEL('UNSET')),$);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3ttXxqmlz62BlxjzEPpqvH',$,'UserDefinedStatus','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
@@ -82,7 +82,7 @@ class IfcExporter:
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
self.get_application_name(), self.get_application_version()
|
||||
self.get_application_name(), tool.Blender.get_blenderbim_version()
|
||||
)
|
||||
|
||||
def sync_all_objects(self):
|
||||
@@ -93,6 +93,8 @@ class IfcExporter:
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
continue
|
||||
if obj.library:
|
||||
continue
|
||||
tool.Collector.sync(obj)
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
@@ -162,7 +164,7 @@ class IfcExporter:
|
||||
return "BlenderBIM"
|
||||
|
||||
def get_application_version(self):
|
||||
return ".".join(
|
||||
version = ".".join(
|
||||
[
|
||||
str(x)
|
||||
for x in [
|
||||
@@ -172,6 +174,9 @@ class IfcExporter:
|
||||
][0]
|
||||
]
|
||||
)
|
||||
if blenderbim.bim.last_commit_hash != "8888888":
|
||||
version += f"-{blenderbim.bim.last_commit_hash[:7]}"
|
||||
return version
|
||||
|
||||
|
||||
class IfcExportSettings:
|
||||
|
||||
@@ -27,6 +27,7 @@ from bpy.app.handlers import persistent
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
from blenderbim.bim.module.model.data import AuthoringData
|
||||
from blenderbim.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
|
||||
from mathutils import Vector
|
||||
from math import cos, degrees
|
||||
|
||||
@@ -35,24 +36,6 @@ cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
global_subscription_owner = object()
|
||||
|
||||
|
||||
def mode_callback(obj, data):
|
||||
objects = bpy.context.selected_objects
|
||||
if bpy.context.active_object:
|
||||
objects += [bpy.context.active_object]
|
||||
for obj in objects:
|
||||
if (
|
||||
obj.mode != "EDIT"
|
||||
or not obj.data
|
||||
or not isinstance(obj.data, (bpy.types.Mesh, bpy.types.Curve, bpy.types.TextCurve))
|
||||
or not obj.BIMObjectProperties.ifc_definition_id
|
||||
):
|
||||
continue
|
||||
if obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
tool.Ifc.edit(obj)
|
||||
elif IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id).is_a("IfcGridAxis"):
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
def name_callback(obj, data):
|
||||
try:
|
||||
obj.name
|
||||
@@ -73,17 +56,27 @@ def name_callback(obj, data):
|
||||
refresh_ui_data()
|
||||
return
|
||||
|
||||
if not obj.BIMObjectProperties.ifc_definition_id or "/" not in obj.name:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if "/" in obj.name:
|
||||
object_name = obj.name
|
||||
element_name = obj.name.split("/", 1)[1]
|
||||
else:
|
||||
element_name = obj.name
|
||||
object_name = element.is_a() + f"/{element_name}"
|
||||
obj.name = object_name # NOTE: doesn't trigger infinite recursion
|
||||
|
||||
if element.is_a("IfcGridAxis"):
|
||||
element.AxisTag = obj.name.split("/")[1]
|
||||
element.AxisTag = object_name.split("/")[1]
|
||||
refresh_ui_data()
|
||||
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
element.Name = element_name
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = obj.name
|
||||
element.Name = "/".join(obj.name.split("/")[1:])
|
||||
obj.BIMObjectProperties.collection.name = object_name
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
@@ -106,7 +99,7 @@ def update_bim_tool_props():
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname != "bim.bim_tool":
|
||||
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
@@ -114,6 +107,16 @@ def update_bim_tool_props():
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
is_bim_tool = current_tool.idname == "bim.bim_tool"
|
||||
if is_bim_tool:
|
||||
props.ifc_class = element_type.is_a()
|
||||
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
|
||||
props.relating_type_id = str(element_type.id())
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
if not extrusion:
|
||||
return
|
||||
@@ -124,7 +127,6 @@ def update_bim_tool_props():
|
||||
return x_angle
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
|
||||
@@ -175,30 +177,20 @@ def refresh_ui_data():
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
def purge_module_data():
|
||||
from blenderbim.bim import modules
|
||||
|
||||
refresh_ui_data()
|
||||
for name, value in modules.items():
|
||||
try:
|
||||
getattr(getattr(getattr(ifcopenshell.api, name), "data"), "Data").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# TODO: deprecate prop purge functions and refactor into data classes.
|
||||
try:
|
||||
getattr(value, "prop").purge()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.purge()
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
IfcStore.get_schema()
|
||||
@@ -210,7 +202,7 @@ def undo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
@@ -219,7 +211,7 @@ def redo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
purge_module_data()
|
||||
refresh_ui_data()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
|
||||
@@ -273,6 +273,81 @@ def convert_property_group_from_si(property_group, skip_props=()):
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
def draw_filter(layout, props, data, module):
|
||||
if not data.is_loaded:
|
||||
data.load()
|
||||
|
||||
sprops = bpy.context.scene.BIMSearchProperties
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
|
||||
|
||||
if data.data["saved_searches"]:
|
||||
row.operator("bim.load_search", text="", icon="IMPORT").module = module
|
||||
row.operator("bim.save_search", text="", icon="EXPORT").module = module
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
|
||||
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
|
||||
|
||||
for i, filter_group in enumerate(props.filter_groups):
|
||||
box = layout.box()
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(sprops, "facet", text="")
|
||||
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
|
||||
op.type = sprops.facet
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
for j, ifc_filter in enumerate(filter_group.filters):
|
||||
if ifc_filter.type == "entity":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_3D")
|
||||
elif ifc_filter.type == "attribute":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="COPY_ID")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "type":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME")
|
||||
elif ifc_filter.type == "material":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="MATERIAL")
|
||||
elif ifc_filter.type == "property":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "pset", text="", icon="PROPERTIES")
|
||||
row.prop(ifc_filter, "name", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "classification":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
|
||||
elif ifc_filter.type == "location":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="PACKAGE")
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="GRIP")
|
||||
op = row.operator("bim.select_filter_elements", text="", icon="EYEDROPPER")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
op.module = module
|
||||
op = row.operator("bim.remove_filter", text="", icon="X")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
op.module = module
|
||||
|
||||
row = box.row()
|
||||
op = row.operator("bim.remove_filter_group", icon="X")
|
||||
op.index = i
|
||||
op.module = module
|
||||
|
||||
|
||||
# TODO this should move into ifcopenshell.util
|
||||
|
||||
|
||||
|
||||
@@ -220,8 +220,9 @@ class IfcStore:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
|
||||
if IfcStore.history:
|
||||
data = {"id": element.id(), "guid": getattr(element, "GlobalId", None), "obj": obj.name}
|
||||
@@ -247,8 +248,9 @@ class IfcStore:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "diffuse_color", blenderbim.bim.handler.color_callback)
|
||||
elif isinstance(obj, bpy.types.Object):
|
||||
blenderbim.bim.handler.subscribe_to(obj, "mode", blenderbim.bim.handler.mode_callback)
|
||||
blenderbim.bim.handler.subscribe_to(obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback)
|
||||
blenderbim.bim.handler.subscribe_to(
|
||||
obj, "active_material_index", blenderbim.bim.handler.active_material_index_callback
|
||||
)
|
||||
# TODO Listeners are not re-registered. Does this cause nasty problems to debug later on?
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@@ -321,7 +323,7 @@ class IfcStore:
|
||||
IfcStore.begin_transaction(operator)
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().begin_transaction()
|
||||
if BrickStore.graph:
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.begin_transaction()
|
||||
# This empty transaction ensures that each operator has at least one transaction
|
||||
IfcStore.add_transaction_operation(operator, rollback=lambda data: True, commit=lambda data: True)
|
||||
@@ -339,7 +341,7 @@ class IfcStore:
|
||||
IfcStore.add_transaction_operation(
|
||||
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
|
||||
)
|
||||
if BrickStore.graph:
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.end_transaction()
|
||||
IfcStore.end_transaction(operator)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -227,16 +227,16 @@ class IfcImporter:
|
||||
self.settings.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings.set(self.settings.STRICT_TOLERANCE, True)
|
||||
self.settings_curve = ifcopenshell.geom.settings()
|
||||
self.settings_curve.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings_curve.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings_curve.set(self.settings_curve.STRICT_TOLERANCE, True)
|
||||
self.settings_curve.set(self.settings_curve.INCLUDE_CURVES, True)
|
||||
self.settings_body_2d = ifcopenshell.geom.settings()
|
||||
self.settings_body_2d.set_deflection_tolerance(self.ifc_import_settings.deflection_tolerance)
|
||||
self.settings_body_2d.set_angular_tolerance(self.ifc_import_settings.angular_tolerance)
|
||||
self.settings_body_2d.set(self.settings_body_2d.STRICT_TOLERANCE, True)
|
||||
self.settings_body_2d.set(self.settings_body_2d.INCLUDE_CURVES, True)
|
||||
self.settings_native = ifcopenshell.geom.settings()
|
||||
self.settings_native.set(self.settings_native.INCLUDE_CURVES, True)
|
||||
self.settings_2d = ifcopenshell.geom.settings()
|
||||
self.settings_2d.set(self.settings_2d.INCLUDE_CURVES, True)
|
||||
self.settings_2d.set(self.settings_2d.STRICT_TOLERANCE, True)
|
||||
self.settings_plan_2d = ifcopenshell.geom.settings()
|
||||
self.settings_plan_2d.set(self.settings_plan_2d.INCLUDE_CURVES, True)
|
||||
self.settings_plan_2d.set(self.settings_plan_2d.STRICT_TOLERANCE, True)
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
self.collections = {}
|
||||
@@ -318,6 +318,7 @@ class IfcImporter:
|
||||
self.profile_code("Merging by colour")
|
||||
self.set_default_context()
|
||||
self.profile_code("Setting default context")
|
||||
self.setup_viewport_camera()
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
@@ -348,13 +349,14 @@ class IfcImporter:
|
||||
if c.ContextIdentifier in ["Body", "Facetation"]
|
||||
]
|
||||
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
|
||||
self.body_contexts.extend(
|
||||
[
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
if c.ContextType == "Model"
|
||||
]
|
||||
)
|
||||
if not self.body_contexts:
|
||||
self.body_contexts.extend(
|
||||
[
|
||||
c.id()
|
||||
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
if c.ContextType == "Model"
|
||||
]
|
||||
)
|
||||
if self.body_contexts:
|
||||
self.settings.set_context_ids(self.body_contexts)
|
||||
# Annotation ContextType is to accommodate broken Revit files
|
||||
@@ -365,7 +367,7 @@ class IfcImporter:
|
||||
if c.ContextType in ["Plan", "Annotation"] or c.ContextIdentifier == "Annotation"
|
||||
]
|
||||
if self.plan_contexts:
|
||||
self.settings_2d.set_context_ids(self.plan_contexts)
|
||||
self.settings_plan_2d.set_context_ids(self.plan_contexts)
|
||||
|
||||
def process_element_filter(self):
|
||||
offset = self.ifc_import_settings.element_offset
|
||||
@@ -573,7 +575,13 @@ class IfcImporter:
|
||||
return
|
||||
if not self.does_element_likely_have_geometry_far_away(element):
|
||||
continue
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, element)
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, element)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_body_2d, element)
|
||||
except:
|
||||
continue
|
||||
m = shape.transformation.matrix.data
|
||||
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])
|
||||
@@ -645,7 +653,7 @@ class IfcImporter:
|
||||
self.ifc_import_settings.logger.error("An invalid grid was found %s", grid)
|
||||
continue
|
||||
if grid.Representation:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, grid)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, grid)
|
||||
grid_obj = self.create_product(grid, shape)
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
|
||||
grid_obj.lock_location = (True, True, True)
|
||||
@@ -664,7 +672,7 @@ class IfcImporter:
|
||||
|
||||
def create_grid_axes(self, axes, grid_collection, grid_obj):
|
||||
for axis in axes:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, axis.AxisCurve)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(f"IfcGridAxis/{axis.AxisTag}", mesh)
|
||||
if bpy.context.preferences.addons["blenderbim"].preferences.lock_grids_on_import:
|
||||
@@ -698,7 +706,7 @@ class IfcImporter:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, representation)
|
||||
except:
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_2d, representation)
|
||||
shape = ifcopenshell.geom.create_shape(self.settings_plan_2d, representation)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
if shape:
|
||||
@@ -764,9 +772,9 @@ class IfcImporter:
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
products = self.create_products(elements)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_curve)
|
||||
products = self.create_products(elements, settings=self.settings_body_2d)
|
||||
elements -= products
|
||||
products = self.create_products(elements, settings=self.settings_2d)
|
||||
products = self.create_products(elements, settings=self.settings_plan_2d)
|
||||
elements -= products
|
||||
products = self.create_pointclouds(elements)
|
||||
elements -= products
|
||||
@@ -901,10 +909,10 @@ class IfcImporter:
|
||||
self.structural_collection.children.link(self.structural_connection_collection)
|
||||
self.project["blender"].children.link(self.structural_collection)
|
||||
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveMember"), settings=self.settings_plan_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralCurveConnection"), settings=self.settings_plan_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceMember"), settings=self.settings_plan_2d)
|
||||
self.create_products(self.file.by_type("IfcStructuralSurfaceConnection"), settings=self.settings_plan_2d)
|
||||
self.create_structural_point_connections()
|
||||
|
||||
def create_structural_point_connections(self):
|
||||
@@ -973,10 +981,14 @@ class IfcImporter:
|
||||
placement_matrix = self.get_element_matrix(product)
|
||||
vertex_list = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcCartesianPointList"):
|
||||
if item.is_a("IfcCartesianPointList3D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)) * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPointList2D"):
|
||||
vertex_list.extend(
|
||||
mathutils.Vector(list(coordinates)).to_3d() * self.unit_scale for coordinates in item.CoordList
|
||||
)
|
||||
elif item.is_a("IfcCartesianPoint"):
|
||||
vertex_list.append(mathutils.Vector(list(item.Coordinates)) * self.unit_scale)
|
||||
|
||||
@@ -1902,6 +1914,13 @@ class IfcImporter:
|
||||
obj.matrix_world = matrix_world
|
||||
tool.Geometry.record_object_position(obj)
|
||||
|
||||
def setup_viewport_camera(self):
|
||||
context_override = tool.Blender.get_viewport_context()
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.object.select_all(action="SELECT")
|
||||
bpy.ops.view3d.view_selected()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
def __init__(self):
|
||||
|
||||
@@ -35,13 +35,16 @@ classes = (
|
||||
operator.ViewBrickItem,
|
||||
operator.SerializeBrick,
|
||||
operator.AddBrickNamespace,
|
||||
operator.SetBrickListRoot,
|
||||
operator.RemoveBrickRelation,
|
||||
prop.Brick,
|
||||
prop.BIMBrickProperties,
|
||||
ui.BIM_PT_brickschema,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
ui.BIM_PT_brickschema_project_info,
|
||||
ui.BIM_PT_brickschema_namespaces,
|
||||
ui.BIM_PT_brickschema_create_entity,
|
||||
ui.BIM_PT_brickschema_viewport,
|
||||
ui.BIM_UL_bricks,
|
||||
ui.BIM_PT_ifc_brickschema_references,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class BrickschemaData:
|
||||
|
||||
@classmethod
|
||||
def get_is_loaded(cls):
|
||||
return BrickStore.graph is not None
|
||||
return BrickStore.graph is not None # `if BrickStore.graph` by itself takes ages.
|
||||
|
||||
@classmethod
|
||||
def active_relations(cls):
|
||||
@@ -67,45 +67,63 @@ class BrickschemaData:
|
||||
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 ?predicate ?object ?sp ?sv WHERE {
|
||||
<{uri}> ?predicate ?object .
|
||||
OPTIONAL {
|
||||
{ ?predicate rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?predicate a brick:EntityProperty . }
|
||||
?object ?sp ?sv }
|
||||
SELECT DISTINCT ?predicate ?object ?label ?sp ?sv WHERE {
|
||||
<{uri}> ?predicate ?object .
|
||||
OPTIONAL {
|
||||
?object rdfs:label ?label .
|
||||
}
|
||||
OPTIONAL {
|
||||
{ ?predicate rdfs:range brick:TimeseriesReference . }
|
||||
UNION
|
||||
{ ?predicate a brick:EntityProperty . }
|
||||
?object ?sp ?sv .
|
||||
}
|
||||
}
|
||||
GROUP BY ?object
|
||||
""".replace(
|
||||
"{uri}", uri
|
||||
)
|
||||
)
|
||||
for row in query:
|
||||
predicate = row.get("predicate")
|
||||
predicate_name = predicate.toPython().split("#")[-1]
|
||||
object = row.get("object")
|
||||
object_name = object.toPython().split("#")[-1]
|
||||
predicate_uri = row.get("predicate")
|
||||
predicate_name = predicate_uri.toPython().split("#")[-1]
|
||||
object_uri = row.get("object")
|
||||
object_name = row.get("label")
|
||||
if not object_name:
|
||||
if isinstance(object_uri, BNode):
|
||||
object_name = "[]"
|
||||
else:
|
||||
try:
|
||||
object_name = object_uri.toPython().split("#")[-1]
|
||||
except:
|
||||
object_name = str(object_uri)
|
||||
results.append(
|
||||
{
|
||||
"predicate": predicate,
|
||||
"predicate_uri": predicate_uri,
|
||||
"predicate_name": predicate_name,
|
||||
"object": object,
|
||||
"object_uri": object_uri,
|
||||
"object_name": object_name,
|
||||
"is_uri": isinstance(object, URIRef),
|
||||
"object_uri": object.toPython(),
|
||||
"is_globalid": predicate == "globalID",
|
||||
"is_uri": isinstance(object_uri, URIRef),
|
||||
"is_globalid": predicate_name == "ifcGlobalID",
|
||||
}
|
||||
)
|
||||
# if isinstance(row.get("object"), BNode):
|
||||
# for s, p, o in BrickStore.graph.triples((object, None, None)):
|
||||
# results.append(
|
||||
# {
|
||||
# "predicate": predicate + ":" + p.toPython().split("#")[-1],
|
||||
# "object": o.toPython().split("#")[-1],
|
||||
# "is_uri": isinstance(o, URIRef),
|
||||
# "object_uri": o.toPython(),
|
||||
# "is_globalid": p.toPython().split("#")[-1] == "globalID",
|
||||
# }
|
||||
# )
|
||||
if isinstance(object_uri, BNode):
|
||||
for subject2, predicate2, object2 in BrickStore.graph.triples((object_uri, None, None)):
|
||||
predicate2_name = predicate2.toPython().split("#")[-1]
|
||||
try:
|
||||
object2_name = object2.toPython().split("#")[-1]
|
||||
except:
|
||||
object2_name = str(object2)
|
||||
results.append(
|
||||
{
|
||||
"predicate_uri": None,
|
||||
"predicate_name": predicate_name + ":" + predicate2_name,
|
||||
"object_uri": object2,
|
||||
"object_name": object2_name,
|
||||
"is_uri": isinstance(object2, URIRef),
|
||||
"is_globalid": predicate2_name == "ifcGlobalID",
|
||||
}
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@@ -131,7 +149,7 @@ class BrickschemaReferencesData:
|
||||
for library in ifc.by_type("IfcLibraryInformation"):
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
elif ".ttl" in library.Location:
|
||||
elif library.Location and ".ttl" in library.Location:
|
||||
results.append((str(library.id()), library.Name or "Unnamed", ""))
|
||||
return results
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class LoadBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load in a Brick project from a file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
|
||||
@@ -56,6 +57,7 @@ class ViewBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
bl_label = "View Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect the subclasses of this class"
|
||||
brick_class: bpy.props.StringProperty(name="Brick Class")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
@@ -67,17 +69,22 @@ class ViewBrickItem(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.view_brick_item"
|
||||
bl_label = "View Brick Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Inspect this entity in the viewer"
|
||||
item: bpy.props.StringProperty(name="Brick Item")
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
try:
|
||||
core.view_brick_item(tool.Brick, item=self.item, split_screen=self.split_screen)
|
||||
except:
|
||||
self.report({'ERROR'}, f'Could not find {self.item}')
|
||||
|
||||
|
||||
class RewindBrickClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.rewind_brick_class"
|
||||
bl_label = "Rewind Brick Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Go back to the previous list view"
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
@@ -88,6 +95,7 @@ class CloseBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.close_brick_project"
|
||||
bl_label = "Close Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Close the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
core.close_brick_project(tool.Brick)
|
||||
@@ -97,6 +105,7 @@ class ConvertBrickProject(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_brick_project"
|
||||
bl_label = "Convert Brick Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create an Ifc library for this Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_brick_project(tool.Ifc, tool.Brick)
|
||||
@@ -106,9 +115,18 @@ class AssignBrickReference(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_brick_reference"
|
||||
bl_label = "Assign Brick Reference"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Assign the selected Ifc entity to the selected Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
if not context.active_object:
|
||||
self.report({'ERROR'}, f'No Ifc selected')
|
||||
return
|
||||
props = context.scene.BIMBrickProperties
|
||||
try:
|
||||
props.bricks[props.active_brick_index]
|
||||
except:
|
||||
self.report({'ERROR'}, f'No Brick selected')
|
||||
return
|
||||
core.assign_brick_reference(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
@@ -122,19 +140,17 @@ class AddBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick"
|
||||
bl_label = "Add Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
core.add_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
element=tool.Ifc.get_entity(context.active_object) if context.selected_objects else None,
|
||||
namespace=props.namespace,
|
||||
brick_class=props.brick_entity_class,
|
||||
library=library,
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)) if props.libraries else None,
|
||||
label=props.new_brick_label,
|
||||
)
|
||||
|
||||
@@ -143,6 +159,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_relation"
|
||||
bl_label = "Add Brick Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create the Brick relationship"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -152,7 +169,7 @@ class AddBrickRelation(bpy.types.Operator, Operator):
|
||||
elif props.split_screen_toggled:
|
||||
object = props.split_screen_bricks[props.split_screen_active_brick_index].uri
|
||||
else:
|
||||
object = props.new_brick_relation_namespace + props.new_brick_relation_object
|
||||
object = props.namespace + props.new_brick_relation_object
|
||||
core.add_brick_relation(
|
||||
tool.Brick,
|
||||
brick_uri=brick.uri,
|
||||
@@ -165,6 +182,7 @@ class ConvertIfcToBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.convert_ifc_to_brick"
|
||||
bl_label = "Convert IFC To Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Convert Ifc entities and relations to Brick entities and relations"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -178,6 +196,7 @@ class NewBrickFile(bpy.types.Operator):
|
||||
bl_idname = "bim.new_brick_file"
|
||||
bl_label = "New Brick File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create a Brick project from scratch"
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
@@ -210,16 +229,17 @@ class RefreshBrickViewer(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.refresh_brick_viewer"
|
||||
bl_label = "Refresh Brick Viewer"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
bl_description = "Refresh the list view"
|
||||
|
||||
def _execute(self, context):
|
||||
core.refresh_brick_viewer(tool.Brick, split_screen=self.split_screen)
|
||||
core.refresh_brick_viewer(tool.Brick)
|
||||
|
||||
|
||||
class RemoveBrick(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick"
|
||||
bl_label = "Remove Brick"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Delete this entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
@@ -262,32 +282,20 @@ class SerializeBrick(bpy.types.Operator):
|
||||
class AddBrickNamespace(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_brick_namespace"
|
||||
bl_label = "Add Brick Namespace"
|
||||
bl_description = "Bind a new namespace to the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBrickProperties
|
||||
alias = props.new_brick_namespace_alias
|
||||
uri = props.new_brick_namespace_uri
|
||||
core.add_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
|
||||
class SetBrickListRoot(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.set_brick_list_root"
|
||||
bl_label = "Set Brick View Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
split_screen: bpy.props.BoolProperty(name="Split Screen", default=False, options={"HIDDEN"})
|
||||
|
||||
def _execute(self, context):
|
||||
if self.split_screen:
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
else:
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=self.split_screen)
|
||||
core.add_brick_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
|
||||
|
||||
class RemoveBrickRelation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.remove_brick_relation"
|
||||
bl_label = "Remove Relation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Delete this relationship"
|
||||
predicate: bpy.props.StringProperty(name="Relation")
|
||||
object: bpy.props.StringProperty(name="Object")
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
import blenderbim.core.brick as core
|
||||
import blenderbim.tool.brick as tool
|
||||
from blenderbim.tool.brick import BrickStore
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
@@ -43,12 +45,12 @@ def get_libraries(self, context):
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
return BrickStore.namespaces
|
||||
return [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
||||
|
||||
|
||||
def get_brick_entity_classes(self, context):
|
||||
entity = self.brick_entity_create_type
|
||||
return BrickStore.entity_classes[entity]
|
||||
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
||||
|
||||
|
||||
def get_brick_roots(self, context):
|
||||
@@ -56,13 +58,22 @@ def get_brick_roots(self, context):
|
||||
|
||||
|
||||
def get_brick_relations(self, context):
|
||||
def is_label(relation):
|
||||
return relation["predicate_name"] == "label"
|
||||
if not list(filter(is_label, BrickschemaData.data["active_relations"])):
|
||||
new_relations = BrickStore.relationships.copy()
|
||||
new_relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return new_relations
|
||||
return BrickStore.relationships
|
||||
relations = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
if relation["predicate_name"] == "label":
|
||||
return relations
|
||||
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return relations
|
||||
|
||||
|
||||
|
||||
def update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
|
||||
|
||||
def split_screen_update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
|
||||
|
||||
|
||||
class Brick(PropertyGroup):
|
||||
@@ -79,10 +90,9 @@ class BIMBrickProperties(PropertyGroup):
|
||||
active_brick_index: IntProperty(name="Active Brick Index", update=update_active_brick_index)
|
||||
libraries: EnumProperty(name="Libraries", items=get_libraries)
|
||||
set_list_root_toggled: BoolProperty(name="Set List Root Toggled", default=False)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots)
|
||||
brick_list_root: EnumProperty(name="Brick List Root", items=get_brick_roots, update=update_view)
|
||||
# namespace manager
|
||||
namespace: EnumProperty(name="Namespace", items=get_namespaces)
|
||||
brick_settings_toggled: BoolProperty(name="Brick Settings Toggled", default=False)
|
||||
new_brick_namespace_alias: StringProperty(name="New Brick Namespace Alias")
|
||||
new_brick_namespace_uri: StringProperty(name="New Brick Namespace URI")
|
||||
# create brick entity
|
||||
@@ -93,13 +103,12 @@ class BIMBrickProperties(PropertyGroup):
|
||||
brick_create_relations_toggled: BoolProperty(name="Brick Create Relations Toggled", default=False)
|
||||
brick_edit_relations_toggled: BoolProperty(name="Brick Edit Relations Toggled", default=False)
|
||||
new_brick_relation_type: EnumProperty(name="New Brick Relation Type", items=get_brick_relations)
|
||||
new_brick_relation_namespace: EnumProperty(name="New Brick Relation Namespace", items=get_namespaces)
|
||||
new_brick_relation_object: StringProperty(name="New Brick Relation Object")
|
||||
add_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
add_brick_relation_failed: BoolProperty(name="Add Relation Failed", default=False)
|
||||
# create relations split screen
|
||||
split_screen_toggled: BoolProperty(name="Split Screen Toggled", default=False)
|
||||
split_screen_bricks: CollectionProperty(name="Split Screen Bricks", type=Brick)
|
||||
split_screen_active_brick_index: IntProperty(name="Split Screen Active Brick Index", update=update_active_brick_index)
|
||||
split_screen_active_brick_class: StringProperty(name="Split Screen Active Brick Class")
|
||||
split_screen_brick_breadcrumbs: CollectionProperty(name="Split Screen Brick Breadcrumbs", type=StrProperty)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots)
|
||||
split_screen_brick_list_root: EnumProperty(name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view)
|
||||
@@ -34,17 +34,36 @@ class BIM_PT_brickschema(Panel):
|
||||
def draw(self, context):
|
||||
if not BrickschemaData.is_loaded:
|
||||
BrickschemaData.load()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
|
||||
class BIM_PT_brickschema_project_info(Panel):
|
||||
bl_label = "Project Info"
|
||||
bl_idname = "BIM_PT_brickschema_project_info"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
def draw(self, context):
|
||||
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 BrickStore.path:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=BrickStore.path, icon="FILEBROWSER")
|
||||
else:
|
||||
row.label(text="No file", icon="FILEBROWSER")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if BrickStore.last_saved:
|
||||
row.label(text=BrickStore.last_saved, icon="TIME")
|
||||
else:
|
||||
row.label(text="Not saved", icon="TIME")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
op = row.operator("bim.serialize_brick", icon="EXPORT", text="Save")
|
||||
@@ -53,134 +72,173 @@ class BIM_PT_brickschema(Panel):
|
||||
op.should_save_as = True
|
||||
row.operator("bim.close_brick_project", text="", icon="CANCEL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_settings_toggled", text="", icon="PREFERENCES")
|
||||
|
||||
if self.props.brick_settings_toggled:
|
||||
box = self.layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="Active Namespace:")
|
||||
|
||||
row = box.row(align=True)
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
class BIM_PT_brickschema_namespaces(Panel):
|
||||
bl_label = "Namespaces"
|
||||
bl_idname = "BIM_PT_brickschema_namespaces"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
row = box.row(align=True)
|
||||
row.label(text="Bind New Namespace:")
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return BrickStore.graph != None
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Entity:")
|
||||
row.label(text="Active Namespace:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_entity_create_type", text="")
|
||||
prop_with_search(row, self.props, "namespace", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_label", text="")
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="")
|
||||
row.operator("bim.add_brick", text="", icon="ADD")
|
||||
# row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
row.label(text="Bind New Namespace:")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="new_brick_namespace_alias", text="")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.alignment = "CENTER"
|
||||
col.scale_x = 1.1
|
||||
col.label(text=":")
|
||||
row.prop(data=self.props, property="new_brick_namespace_uri", text="")
|
||||
row.operator("bim.add_brick_namespace", text="", icon="ADD")
|
||||
|
||||
|
||||
class BIM_PT_brickschema_create_entity(Panel):
|
||||
bl_label = "Create Entity"
|
||||
bl_idname = "BIM_PT_brickschema_create_entity"
|
||||
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 BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
# TO DO: hide this if selected entity already has a reference, or something similar
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(data=self.props, property="brick_entity_create_type", text="Class")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "brick_entity_class", text="Type")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
active = tool.Ifc.get_entity(context.active_object)
|
||||
if active and context.selected_objects:
|
||||
row.label(text="Label:")
|
||||
row.label(text=active.Name if active.Name else "Unnamed")
|
||||
else:
|
||||
row.prop(data=self.props, property="new_brick_label", text="Label")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_brick", text="Create Entity")
|
||||
|
||||
|
||||
class BIM_PT_brickschema_viewport(Panel):
|
||||
bl_label = "Viewport"
|
||||
bl_idname = "BIM_PT_brickschema_viewport"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.column().alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.prop(data=self.props, property="split_screen_toggled", text="", icon="WINDOW")
|
||||
row.operator("bim.refresh_brick_viewer", text="", icon="FILE_REFRESH")
|
||||
|
||||
grid = self.layout.grid_flow(even_columns=True)
|
||||
grid1 = grid.column(align=True)
|
||||
row = grid1.row(align=True)
|
||||
grid_left = grid.column(align=True)
|
||||
row = grid_left.row(align=True)
|
||||
if len(self.props.brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = False
|
||||
row.prop(data=self.props, property="set_list_root_toggled", text="", icon="OUTLINER")
|
||||
row.label(text=self.props.active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid1.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = False
|
||||
row = grid_left.row(align=True)
|
||||
row.prop(data=self.props, property="brick_list_root", text="")
|
||||
|
||||
row = grid1.row()
|
||||
row = grid_left.row()
|
||||
BIM_UL_bricks.split_screen = False
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
if self.props.split_screen_toggled:
|
||||
grid2 = grid.column(align=True)
|
||||
row = grid2.row(align=True)
|
||||
grid_right = grid.column(align=True)
|
||||
row = grid_right.row(align=True)
|
||||
if len(self.props.split_screen_brick_breadcrumbs):
|
||||
op = row.operator("bim.rewind_brick_class", text="", icon="FRAME_PREV")
|
||||
op.split_screen = True
|
||||
row.label(text=self.props.split_screen_active_brick_class)
|
||||
|
||||
if self.props.set_list_root_toggled:
|
||||
row = grid2.row(align=True)
|
||||
op = row.operator("bim.set_brick_list_root", text="Set View")
|
||||
op.split_screen = True
|
||||
row = grid_right.row(align=True)
|
||||
row.prop(data=self.props, property="split_screen_brick_list_root", text="")
|
||||
|
||||
row = grid2.row()
|
||||
row = grid_right.row()
|
||||
BIM_UL_bricks.split_screen = True
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index")
|
||||
|
||||
if BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
row.column().alignment = "RIGHT"
|
||||
row.prop(data=self.props, property="brick_create_relations_toggled", text="", icon="PLUGIN")
|
||||
row.prop(data=self.props, property="brick_edit_relations_toggled", text="", icon="TOOL_SETTINGS")
|
||||
row.operator("bim.remove_brick", text="", icon="X")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Create Relation:")
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
if self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled and self.props.split_screen_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
if split_screen_selection.total_items:
|
||||
try:
|
||||
split_screen_selection = self.props.split_screen_bricks[self.props.split_screen_active_brick_index]
|
||||
except:
|
||||
split_screen_selection = None
|
||||
if not split_screen_selection or split_screen_selection.total_items:
|
||||
row.label(text="No selection", icon="INFO")
|
||||
else:
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.label(text=split_screen_selection.label if split_screen_selection.label else split_screen_selection.name)
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_namespace", text="")
|
||||
|
||||
elif self.props.brick_create_relations_toggled:
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "new_brick_relation_type", text="")
|
||||
row.prop(data=self.props, property="new_brick_relation_object", text="")
|
||||
row.operator("bim.add_brick_relation", text="", icon="ADD")
|
||||
|
||||
|
||||
if self.props.brick_create_relations_toggled and self.props.add_relation_failed:
|
||||
if self.props.brick_create_relations_toggled and self.props.add_brick_relation_failed:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Failed to find this entity!", icon="ERROR")
|
||||
|
||||
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
row = self.layout.row(align=True)
|
||||
split = self.layout.split(factor=0.85, align=True)
|
||||
row = split.row(align=True)
|
||||
row.label(text=relation["predicate_name"])
|
||||
row.separator()
|
||||
row.label(text=relation["object_name"])
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_name"] != "type":
|
||||
row = split.row(align=True)
|
||||
row.column().alignment = "RIGHT"
|
||||
if self.props.brick_edit_relations_toggled and relation["predicate_uri"] and relation["predicate_name"] != "type":
|
||||
op = row.operator("bim.remove_brick_relation", text="", icon="UNLINKED")
|
||||
op.predicate = relation["predicate"]
|
||||
op.object = relation["object"]
|
||||
if relation["is_uri"] and relation["predicate_name"] != "type":
|
||||
op.predicate = relation["predicate_uri"]
|
||||
op.object = relation["object_uri"]
|
||||
if relation["is_uri"] and relation["object_uri"].toPython().split("#")[-1] != self.props.active_brick_class:
|
||||
op = row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT")
|
||||
op.item = relation["object_uri"]
|
||||
if relation["is_globalid"]:
|
||||
@@ -188,6 +246,23 @@ class BIM_PT_brickschema(Panel):
|
||||
op.global_id = relation["object_name"]
|
||||
|
||||
|
||||
class BIM_UL_bricks(UIList):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
split = layout.split(factor=0.85, align=True)
|
||||
row = split.row()
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
op.split_screen = self.split_screen
|
||||
row.label(text=label)
|
||||
if item.total_items:
|
||||
row = split.row()
|
||||
row.label(text=str(item.total_items))
|
||||
|
||||
class BIM_PT_ifc_brickschema_references(Panel):
|
||||
bl_label = "Brickschema References"
|
||||
bl_idname = "BIM_PT_ifc_brickschema_references"
|
||||
@@ -211,15 +286,24 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
row.label(text="No Brickschema Project Loaded")
|
||||
return
|
||||
|
||||
if not BrickschemaReferencesData.data["libraries"]:
|
||||
if not BrickschemaReferencesData.data["libraries"] and BrickStore.path:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
return
|
||||
|
||||
elif not BrickschemaReferencesData.data["libraries"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="No IFC Libraries: save the Brick project to create a new library", icon="ERROR")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Libraries must have location pointing to a \".ttl\" file", icon="INFO")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "libraries")
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
if BrickStore.path:
|
||||
row.operator("bim.convert_brick_project", text="", icon="ADD")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.assign_brick_reference", icon="ADD")
|
||||
@@ -234,19 +318,4 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
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):
|
||||
split_screen = False
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
label = item.label if item.label else item.name
|
||||
if item.total_items:
|
||||
op = row.operator("bim.view_brick_class", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
|
||||
op.brick_class = item.name
|
||||
op.split_screen = self.split_screen
|
||||
label = label + " (" + str(item.total_items) + ")"
|
||||
row.label(text=label)
|
||||
row.operator("bim.view_brick_item", text="", icon="DISCLOSURE_TRI_RIGHT").item = reference["identification"]
|
||||
@@ -27,7 +27,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
bl_parent_id = "BIM_PT_tab_clash_detection"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@@ -102,9 +102,10 @@ class BIM_PT_clash_manager(Panel):
|
||||
bl_idname = "BIM_PT_clash_manager"
|
||||
bl_label = "Clash Manager"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "BlenderBIM"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_clash_detection"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
@@ -20,64 +20,65 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.AddCostSchedule,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.EditCostSchedule,
|
||||
operator.AddCostValue,
|
||||
operator.AddCurrency,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.AssignCostItemType,
|
||||
operator.AssignCostValue,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.ContractCostItem,
|
||||
operator.ContractCostItemRate,
|
||||
operator.ContractCostItems,
|
||||
operator.CopyCostItem,
|
||||
operator.CopyCostItemValues,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.EditCostItem,
|
||||
operator.EditCostItemQuantity,
|
||||
operator.EditCostItemValue,
|
||||
operator.EditCostItemValueFormula,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EditCostSchedule,
|
||||
operator.EnableEditingCostItem,
|
||||
operator.ExportCostSchedules,
|
||||
operator.ExpandCostItems,
|
||||
operator.EnableEditingCostItemQuantities,
|
||||
operator.EnableEditingCostItemQuantity,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostItems,
|
||||
operator.EnableEditingCostItemValue,
|
||||
operator.EnableEditingCostItemValueFormula,
|
||||
operator.DisableEditingCostItem,
|
||||
operator.DisableEditingCostSchedule,
|
||||
operator.DisableEditingCostItemQuantity,
|
||||
operator.DisableEditingCostItemValue,
|
||||
operator.AddCostColumn,
|
||||
operator.RemoveCostColumn,
|
||||
operator.AddCostItem,
|
||||
operator.AddSummaryCostItem,
|
||||
operator.EnableEditingCostItemValues,
|
||||
operator.EnableEditingCostSchedule,
|
||||
operator.ExpandCostItem,
|
||||
operator.ContractCostItem,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ExpandCostItems,
|
||||
operator.ExportCostSchedules,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.LoadProductCostItems,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.RemoveCostColumn,
|
||||
operator.RemoveCostItem,
|
||||
operator.AssignCostItemType,
|
||||
operator.UnassignCostItemType,
|
||||
operator.AssignCostItemQuantity,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.AddCostItemQuantity,
|
||||
operator.RemoveCostItemQuantity,
|
||||
operator.AddCostValue,
|
||||
operator.RemoveCostItemValue,
|
||||
operator.CopyCostItemValues,
|
||||
operator.RemoveCostSchedule,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectCostItemProducts,
|
||||
operator.SelectCostScheduleProducts,
|
||||
operator.ImportCostScheduleCsv,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
operator.AssignCostValue,
|
||||
operator.LoadScheduleOfRates,
|
||||
operator.ExpandCostItemRate,
|
||||
operator.ContractCostItemRate,
|
||||
operator.CalculateCostItemResourceValue,
|
||||
operator.ClearCostItemAssignments,
|
||||
operator.HighlightProductCostItem,
|
||||
operator.LoadProductCostItems,
|
||||
operator.ReorderCostItem,
|
||||
operator.SelectUnassignedProducts,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.ChangeParentCostItem,
|
||||
operator.CopyCostItem,
|
||||
operator.AddCurrency,
|
||||
operator.UnassignCostItemQuantity,
|
||||
operator.UnassignCostItemType,
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
|
||||
@@ -117,6 +117,7 @@ class CostSchedulesData:
|
||||
data["TotalAppliedValue"] = 0.0
|
||||
data["TotalCost"] = 0.0
|
||||
has_unit_basis = False
|
||||
is_sum = False
|
||||
if root_element.is_a("IfcCostItem"):
|
||||
values = root_element.CostValues
|
||||
elif root_element.is_a("IfcConstructionResource"):
|
||||
@@ -130,6 +131,11 @@ class CostSchedulesData:
|
||||
data["UnitBasisValueComponent"] = cost_value_data["UnitBasis"]["ValueComponent"]
|
||||
data["UnitBasisUnitSymbol"] = cost_value_data["UnitBasis"]["UnitSymbol"]
|
||||
has_unit_basis = True
|
||||
else:
|
||||
data["UnitBasisValueComponent"] = 1
|
||||
data["UnitBasisUnitSymbol"] = "U"
|
||||
if cost_value.Category == "*":
|
||||
is_sum = True
|
||||
if has_unit_basis:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] / data["UnitBasisValueComponent"]
|
||||
else:
|
||||
@@ -137,7 +143,8 @@ class CostSchedulesData:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * data["TotalCostQuantity"]
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"]
|
||||
data["TotalAppliedValue"] = None
|
||||
if is_sum:
|
||||
data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item, data):
|
||||
@@ -154,7 +161,7 @@ class CostSchedulesData:
|
||||
if unit:
|
||||
data["UnitSymbol"] = ifcopenshell.util.unit.get_unit_symbol(unit)
|
||||
else:
|
||||
data["UnitSymbol"] = None
|
||||
data["UnitSymbol"] = "U"
|
||||
|
||||
# same_unit_nested_cost_item = set()
|
||||
# data["DerivedTotalCostQuantity"] = None
|
||||
|
||||
@@ -33,9 +33,26 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Add Cost Schedule"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Add a new cost schedule"
|
||||
name: bpy.props.StringProperty()
|
||||
object_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_cost_schedule(tool.Ifc, predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types)
|
||||
core.add_cost_schedule(
|
||||
tool.Ifc,
|
||||
name=self.name,
|
||||
predefined_type=context.scene.BIMCostProperties.cost_schedule_predefined_types,
|
||||
object_type=self.object_type,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "name", text="Name")
|
||||
layout.prop(context.scene.BIMCostProperties, "cost_schedule_predefined_types", text="Type")
|
||||
if context.scene.BIMCostProperties.cost_schedule_predefined_types == "USERDEFINED":
|
||||
layout.prop(self, "object_type", text="Object type")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class EditCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -156,6 +173,17 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
|
||||
|
||||
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.contract_cost_items"
|
||||
bl_label = "Contract Cost Item"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Collapse cost item tree"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.contract_cost_items(tool.Cost)
|
||||
|
||||
|
||||
class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_cost_item"
|
||||
bl_label = "Remove Cost Item"
|
||||
@@ -635,7 +663,9 @@ class ExportCostSchedules(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
cost_schedule = tool.Ifc.get().by_id(self.cost_schedule) if self.cost_schedule else None
|
||||
r = core.export_cost_schedules(tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule)
|
||||
r = core.export_cost_schedules(
|
||||
tool.Cost, filepath=self.filepath, format=self.format, cost_schedule=cost_schedule
|
||||
)
|
||||
if isinstance(r, str):
|
||||
self.report({"ERROR"}, r)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -21,7 +21,6 @@ import blenderbim.bim.module.cost.prop as CostProp
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.cost.data import CostSchedulesData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_cost_schedules(Panel):
|
||||
@@ -30,30 +29,32 @@ class BIM_PT_cost_schedules(Panel):
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_cost"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
if not CostSchedulesData.is_loaded:
|
||||
CostSchedulesData.load()
|
||||
|
||||
self.props = context.scene.BIMCostProperties
|
||||
row = self.layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
if not self.props.active_cost_schedule_id:
|
||||
if CostSchedulesData.data["total_cost_schedules"]:
|
||||
row.label(text=f"{CostSchedulesData.data['total_cost_schedules']} Cost Schedules Found", icon="TEXT")
|
||||
row.operator("bim.export_cost_schedules", text="Export as spreadsheet", icon="EXPORT")
|
||||
else:
|
||||
row.label(text="No Cost Schedules found.", icon="COMMUNITY")
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "cost_schedule_predefined_types")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="Add")
|
||||
row.label(text="No Cost Schedules found.", icon="TEXT")
|
||||
row.operator("bim.add_cost_schedule", icon="ADD", text="")
|
||||
|
||||
for schedule in CostSchedulesData.data["schedules"]:
|
||||
self.draw_cost_schedule_ui(schedule)
|
||||
row2 = self.layout.row()
|
||||
row2.alignment = "RIGHT"
|
||||
row2.operator("bim.export_cost_schedules", text="Export all", icon="EXPORT")
|
||||
|
||||
def draw_cost_schedule_ui(self, cost_schedule):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -138,8 +139,8 @@ class BIM_PT_cost_schedules(Panel):
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.add_summary_cost_item", text="Add Summary Cost", icon="ADD")
|
||||
row.operator("bim.expand_all_tasks", text="Expand All")
|
||||
row.operator("bim.contract_all_tasks", text="Contract All")
|
||||
row.operator("bim.expand_cost_items", text="Expand All")
|
||||
row.operator("bim.contract_cost_items", text="Contract All")
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
if self.props.cost_items and self.props.active_cost_item_index < len(self.props.cost_items):
|
||||
@@ -619,18 +620,22 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_quantity_column(self, layout, cost_item):
|
||||
if CostSchedulesData.data["is_editing_rates"]:
|
||||
self.draw_uom_column(layout, cost_item)
|
||||
else:
|
||||
self.draw_total_quantity_column(layout, cost_item)
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"])
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
@@ -642,8 +647,11 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_uom_column(self, layout, cost_item):
|
||||
layout.label(text=cost_item["UnitBasisUnitSymbol"] or "-" if cost_item["UnitBasisValueComponent"] else "-")
|
||||
def draw_total_cost_column(self, layout, cost_item):
|
||||
format_numbers = "{0:,.2f}".format(cost_item["TotalCost"]).replace(",", " ")
|
||||
currency = CostSchedulesData.data["currency"]
|
||||
text = "{} {}".format(format_numbers, currency["name"]) if currency else format_numbers
|
||||
layout.label(text=text)
|
||||
|
||||
def draw_order_operator(self, row, ifc_definition_id, cost_item):
|
||||
if cost_item["NestingIndex"] is not None:
|
||||
@@ -656,20 +664,6 @@ class BIM_UL_cost_items_trait:
|
||||
op.cost_item = ifc_definition_id
|
||||
op.new_index = cost_item["NestingIndex"] - 1
|
||||
|
||||
def draw_total_quantity_column(self, layout, cost_item):
|
||||
if cost_item["TotalCostQuantity"]:
|
||||
label = "{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}"
|
||||
layout.label(text=label)
|
||||
else:
|
||||
layout.label(text="-")
|
||||
# if cost_item["DerivedTotalCostQuantity"] not in [None, 0]:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["DerivedTotalCostQuantity"]) + f" {cost_item['DerivedUnitSymbol'] or '-'}")
|
||||
# else:
|
||||
# if cost_item["TotalCostQuantity"] == 0:
|
||||
# layout.label(text="-")
|
||||
# else:
|
||||
# layout.label(text="{0:.2f}".format(cost_item["TotalCostQuantity"]) + f" {cost_item['UnitSymbol'] or '-'}")
|
||||
|
||||
|
||||
class BIM_UL_cost_items(BIM_UL_cost_items_trait, UIList):
|
||||
def __init__(self):
|
||||
|
||||
@@ -21,14 +21,15 @@ from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.AddCsvAttribute,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportIfcCsv,
|
||||
operator.EyedropIfcCsv,
|
||||
operator.SelectCsvIfcFile,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ExportCsvAttributes,
|
||||
operator.ExportIfcCsv,
|
||||
operator.ImportCsvAttributes,
|
||||
operator.ImportIfcCsv,
|
||||
operator.RemoveAllCsvAttributes,
|
||||
operator.RemoveCsvAttribute,
|
||||
operator.ReorderCsvAttribute,
|
||||
operator.SelectCsvIfcFile,
|
||||
prop.CsvAttribute,
|
||||
prop.CsvProperties,
|
||||
ui.BIM_PT_ifccsv,
|
||||
)
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
# 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 ifccsv
|
||||
import ifcopenshell
|
||||
import os
|
||||
import logging
|
||||
import bpy
|
||||
import json
|
||||
import webbrowser
|
||||
import ifccsv
|
||||
import logging
|
||||
import tempfile
|
||||
import webbrowser
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.handler import purge_module_data
|
||||
from blenderbim.bim.handler import refresh_ui_data
|
||||
|
||||
|
||||
class AddCsvAttribute(bpy.types.Operator):
|
||||
@@ -61,28 +62,46 @@ class RemoveAllCsvAttributes(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReorderCsvAttribute(bpy.types.Operator):
|
||||
bl_idname = "bim.reorder_csv_attribute"
|
||||
bl_label = "Reorder CSV Attribute"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
old_index: bpy.props.IntProperty()
|
||||
new_index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
old = context.scene.CsvProperties.csv_attributes[self.old_index]
|
||||
new = context.scene.CsvProperties.csv_attributes[self.new_index]
|
||||
props = ["name", "header", "sort", "group", "varies_value", "summary"]
|
||||
for prop in props:
|
||||
value = getattr(new, prop)
|
||||
setattr(new, prop, getattr(old, prop))
|
||||
setattr(old, prop, value)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.import_csv_attributes"
|
||||
bl_label = "Import CSV Template"
|
||||
bl_label = "Load CSV Settings"
|
||||
bl_description = "Import a json template for CSV export"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_props = context.scene.CsvProperties
|
||||
csv_json = json.load(open(self.filepath))
|
||||
|
||||
expression = csv_json.get("expression", "")
|
||||
if expression:
|
||||
csv_props.ifc_selector = expression
|
||||
|
||||
attributes = csv_json.get("attributes", [])
|
||||
if attributes:
|
||||
csv_props.csv_attributes.clear()
|
||||
for attribute in attributes:
|
||||
csv_props.csv_attributes.add().name = attribute
|
||||
props = context.scene.CsvProperties
|
||||
data = json.load(open(self.filepath))
|
||||
tool.Search.import_filter_query(data["query"], props.filter_groups)
|
||||
|
||||
props.csv_attributes.clear()
|
||||
for attribute in data["attributes"]:
|
||||
new = props.csv_attributes.add()
|
||||
new.name = attribute["name"]
|
||||
new.header = attribute["header"]
|
||||
new.sort = attribute["sort"]
|
||||
new.group = attribute["group"]
|
||||
new.summary = attribute["summary"]
|
||||
new.formatting = attribute["formatting"]
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -92,7 +111,7 @@ class ImportCsvAttributes(bpy.types.Operator):
|
||||
|
||||
class ExportCsvAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.export_csv_attributes"
|
||||
bl_label = "Export CSV Template"
|
||||
bl_label = "Save CSV Settings"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Save a json template for CSV export"
|
||||
filename_ext = ".json"
|
||||
@@ -100,22 +119,25 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
csv_props = context.scene.CsvProperties
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
csv_template = {}
|
||||
expression = csv_props.ifc_selector
|
||||
if expression:
|
||||
csv_template["expression"] = expression
|
||||
|
||||
csv_attributes = []
|
||||
for attribute in csv_props.csv_attributes:
|
||||
attribute_name = attribute.name
|
||||
csv_attributes.append(attribute_name)
|
||||
if csv_attributes:
|
||||
csv_template["attributes"] = csv_attributes
|
||||
data = {
|
||||
"query": tool.Search.export_filter_query(props.filter_groups),
|
||||
"attributes": [
|
||||
{
|
||||
"name": a.name,
|
||||
"header": a.header,
|
||||
"sort": a.sort,
|
||||
"group": a.group,
|
||||
"summary": a.summary,
|
||||
"formatting": a.formatting,
|
||||
}
|
||||
for a in props.csv_attributes
|
||||
],
|
||||
}
|
||||
|
||||
with open(self.filepath, "w") as outfile:
|
||||
json.dump(csv_template, outfile)
|
||||
json.dump(data, outfile)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -129,9 +151,8 @@ class ExportCsvAttributes(bpy.types.Operator):
|
||||
class ExportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.export_ifccsv"
|
||||
bl_label = "Export IFC"
|
||||
#filename_ext = ".csv"
|
||||
filename_ext = ".csv"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.CsvProperties
|
||||
@@ -149,19 +170,56 @@ class ExportIfcCsv(bpy.types.Operator):
|
||||
ifc_file = IfcStore.get_file()
|
||||
else:
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
selector = ifcopenshell.util.selector.Selector()
|
||||
results = selector.parse(ifc_file, props.ifc_selector)
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
ifc_file, tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
headers = [a.header for a in props.csv_attributes]
|
||||
|
||||
sort = []
|
||||
groups = []
|
||||
summaries = []
|
||||
formatting = []
|
||||
for attribute in props.csv_attributes:
|
||||
if attribute.sort != "NONE":
|
||||
sort.append({"name": attribute.name, "order": attribute.sort})
|
||||
if attribute.group != "NONE":
|
||||
groups.append({"name": attribute.name, "type": attribute.group, "varies_value": attribute.varies_value})
|
||||
if attribute.summary != "NONE":
|
||||
summaries.append({"name": attribute.name, "type": attribute.summary})
|
||||
|
||||
if attribute.formatting != "{{value}}" and "{{value}}" in attribute.formatting:
|
||||
formatting.append({"name": attribute.name, "format": attribute.formatting})
|
||||
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.export(ifc_file, results, attributes, output=self.filepath, format=props.format, delimiter=sep)
|
||||
ifc_csv.export(
|
||||
ifc_file,
|
||||
results,
|
||||
attributes,
|
||||
headers=headers,
|
||||
output=self.filepath,
|
||||
format=props.format,
|
||||
should_preserve_existing=props.should_preserve_existing,
|
||||
delimiter=sep,
|
||||
include_global_id=props.include_global_id,
|
||||
null=props.null_value,
|
||||
empty=props.empty_value,
|
||||
bool_true=props.true_value,
|
||||
bool_false=props.false_value,
|
||||
sort=sort,
|
||||
groups=groups,
|
||||
summaries=summaries,
|
||||
formatting=formatting,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.import_ifccsv"
|
||||
bl_label = "Import CSV to IFC"
|
||||
filename_ext = ".csv"
|
||||
bl_label = "Import to IFC"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv;*.ods;*.xlsx", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -180,25 +238,20 @@ class ImportIfcCsv(bpy.types.Operator):
|
||||
ifc_file = ifcopenshell.open(props.csv_ifc_file)
|
||||
ifc_csv = ifccsv.IfcCsv()
|
||||
sep = props.csv_custom_delimiter if props.csv_delimiter == "CUSTOM" else props.csv_delimiter
|
||||
ifc_csv.Import(ifc_file, self.filepath, delimiter=sep)
|
||||
attributes = [a.name for a in props.csv_attributes]
|
||||
ifc_csv.Import(
|
||||
ifc_file,
|
||||
self.filepath,
|
||||
attributes=attributes,
|
||||
delimiter=sep,
|
||||
null=props.null_value,
|
||||
empty=props.empty_value,
|
||||
bool_true=props.true_value,
|
||||
bool_false=props.false_value,
|
||||
)
|
||||
if not props.should_load_from_memory:
|
||||
ifc_file.write(props.csv_ifc_file)
|
||||
purge_module_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EyedropIfcCsv(bpy.types.Operator):
|
||||
bl_idname = "bim.eyedrop_ifccsv"
|
||||
bl_label = "Query Selected Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
global_ids = []
|
||||
self.file = IfcStore.get_file()
|
||||
for obj in context.selected_objects:
|
||||
if hasattr(obj, "BIMObjectProperties") and obj.BIMObjectProperties.ifc_definition_id:
|
||||
global_ids.append("#" + self.file.by_id(obj.BIMObjectProperties.ifc_definition_id).GlobalId)
|
||||
context.scene.CsvProperties.ifc_selector = "|".join(global_ids)
|
||||
refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from blenderbim.bim.module.search.prop import BIMFilterGroup
|
||||
from blenderbim.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -31,10 +32,46 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
class CsvAttribute(PropertyGroup):
|
||||
name: StringProperty(name="Query", default="class")
|
||||
header: StringProperty(name="Header Value", default="IFC Class")
|
||||
sort: EnumProperty(items=[("NONE", "None", ""), ("ASC", "Ascending", ""), ("DESC", "Descending", "")])
|
||||
group: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "Don't group any rows"),
|
||||
("GROUP", "Group", "All rows where this value is identical will be merged"),
|
||||
("CONCAT", "Concatenation", "Concatenate values if values vary within a group"),
|
||||
("VARIES", "Varies", "Show a custom value if values vary within a group"),
|
||||
("SUM", "Sum", "Sums the total value of rows in a group"),
|
||||
("AVERAGE", "Average", "Averages the total value of rows in a group"),
|
||||
("MIN", "Min", "Gets the minimum value of rows in a group"),
|
||||
("MAX", "Max", "Gets the maximum value of rows in a group"),
|
||||
]
|
||||
)
|
||||
varies_value: StringProperty(default="Varies", name="Varies Value")
|
||||
summary: EnumProperty(
|
||||
items=[
|
||||
("NONE", "None", "Don't provide a summary row"),
|
||||
("SUM", "Sum", "Sums the total value of all rows"),
|
||||
("AVERAGE", "Average", "Averages the total value of all rows"),
|
||||
("MIN", "Min", "Gets the minimum value of all rows"),
|
||||
("MAX", "Max", "Gets the maximum value of all rows"),
|
||||
]
|
||||
)
|
||||
formatting: StringProperty(default="{{value}}", name="Formatting")
|
||||
|
||||
|
||||
class CsvProperties(PropertyGroup):
|
||||
csv_ifc_file: StringProperty(default="", name="IFC File")
|
||||
ifc_selector: StringProperty(default="", name="IFC Selector")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=StrProperty)
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
csv_attributes: CollectionProperty(name="CSV Attributes", type=CsvAttribute)
|
||||
should_preserve_existing: BoolProperty(default=False, name="Preserve Existing")
|
||||
include_global_id: BoolProperty(default=True, name="Include GlobalId")
|
||||
null_value: StringProperty(default="N/A", name="Null Value")
|
||||
empty_value: StringProperty(default="-", name="Empty String Value")
|
||||
true_value: StringProperty(default="YES", name="True Value")
|
||||
false_value: StringProperty(default="NO", name="False Value")
|
||||
csv_delimiter: EnumProperty(
|
||||
items=[
|
||||
(";", ";", ""),
|
||||
@@ -55,4 +92,9 @@ class CsvProperties(PropertyGroup):
|
||||
default="csv",
|
||||
)
|
||||
csv_custom_delimiter: StringProperty(default="", name="Custom Delimiter")
|
||||
should_show_settings: BoolProperty(default=False, name="Show Settings")
|
||||
should_show_sort: BoolProperty(default=False, name="Show Sorting")
|
||||
should_show_group: BoolProperty(default=False, name="Show Grouping")
|
||||
should_show_summary: BoolProperty(default=False, name="Show Summary")
|
||||
should_show_formatting: BoolProperty(default=False, name="Show Formatting")
|
||||
should_load_from_memory: BoolProperty(default=False, name="Load from Memory")
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
# 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 blenderbim.bim.module.search.data import SearchData
|
||||
|
||||
|
||||
class BIM_PT_ifccsv(Panel):
|
||||
bl_label = "CSV Import/Export"
|
||||
bl_label = "Spreadsheet Import/Export"
|
||||
bl_idname = "BIM_PT_ifccsv"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
@@ -36,45 +38,84 @@ class BIM_PT_ifccsv(Panel):
|
||||
props = scene.CsvProperties
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = layout.row()
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "should_load_from_memory")
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT", text="")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT", text="")
|
||||
row.prop(props, "should_show_settings", icon="PREFERENCES", text="")
|
||||
|
||||
if not IfcStore.get_file() or not props.should_load_from_memory:
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_ifc_file")
|
||||
row.operator("bim.select_csv_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "ifc_selector")
|
||||
row.operator("bim.eyedrop_ifccsv", icon="EYEDROPPER", text="")
|
||||
layout.separator()
|
||||
if props.should_show_settings:
|
||||
layout.use_property_split = True
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.format == "csv":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_preserve_existing")
|
||||
row = layout.row()
|
||||
row.prop(props, "include_global_id")
|
||||
row = layout.row()
|
||||
row.prop(props, "null_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "empty_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "true_value")
|
||||
row = layout.row()
|
||||
row.prop(props, "false_value")
|
||||
layout.use_property_split = False
|
||||
|
||||
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "csv")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_csv_attribute", icon="ADD")
|
||||
row.operator("bim.remove_all_csv_attributes", icon="CANCEL")
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.import_csv_attributes", icon="IMPORT")
|
||||
row.operator("bim.export_csv_attributes", icon="EXPORT")
|
||||
row.prop(props, "should_show_sort", icon="SORTSIZE", text="")
|
||||
row.prop(props, "should_show_group", icon="OUTLINER_COLLECTION", text="")
|
||||
row.prop(props, "should_show_summary", icon="SYNTAX_ON", text="")
|
||||
row.prop(props, "should_show_formatting", icon="CON_TRANSLIKE", text="")
|
||||
|
||||
total = len(props.csv_attributes)
|
||||
for index, attribute in enumerate(props.csv_attributes):
|
||||
row = layout.row(align=True)
|
||||
row.prop(attribute, "name", text="")
|
||||
row.prop(attribute, "header", text="")
|
||||
if props.should_show_sort:
|
||||
row.prop(attribute, "sort", text="")
|
||||
if props.should_show_group:
|
||||
row.prop(attribute, "group", text="")
|
||||
if attribute.group == "VARIES":
|
||||
row.prop(attribute, "varies_value", text="")
|
||||
if props.should_show_summary:
|
||||
row.prop(attribute, "summary", text="")
|
||||
if props.should_show_formatting:
|
||||
row.prop(attribute, "formatting", text="")
|
||||
if total > 1:
|
||||
if index != 0:
|
||||
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_UP", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index - 1
|
||||
if index + 1 != total:
|
||||
op = row.operator(f"bim.reorder_csv_attribute", icon="TRIA_DOWN", text="")
|
||||
op.old_index = index
|
||||
op.new_index = index + 1
|
||||
row.operator("bim.remove_csv_attribute", icon="X", text="").index = index
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "format")
|
||||
|
||||
if props.format == 'csv':
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_delimiter")
|
||||
|
||||
if props.csv_delimiter == "CUSTOM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(props, "csv_custom_delimiter")
|
||||
|
||||
row = layout.row()
|
||||
split = row.split(factor=0.5)
|
||||
c = split.column()
|
||||
c.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
c = split.column()
|
||||
c.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
row.operator("bim.export_ifccsv", icon="EXPORT", text="Export IFC to " + props.format.upper())
|
||||
row.operator("bim.import_ifccsv", icon="IMPORT")
|
||||
|
||||
@@ -116,7 +116,7 @@ class PurgeIfcLinks(bpy.types.Operator):
|
||||
context.scene.BIMProperties.ifc_file = ""
|
||||
context.scene.BIMDebugProperties.attributes.clear()
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class ConvertToBlender(bpy.types.Operator):
|
||||
m.BIMMaterialProperties.ifc_style_id = False
|
||||
bpy.context.scene.BIMProperties.ifc_file = ""
|
||||
IfcStore.purge()
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ class ValidateIfcFile(bpy.types.Operator):
|
||||
logger = logging.getLogger("validate")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ifcopenshell.validate.validate(IfcStore.get_file(), logger, express_rules=True)
|
||||
|
||||
self.report({"INFO"}, "Check validation results in the system console.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
# 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 os
|
||||
import blenderbim.tool as tool
|
||||
from mathutils import Vector
|
||||
import bpy
|
||||
import math
|
||||
import bmesh
|
||||
import blenderbim.tool as tool
|
||||
import ifcopenshell.util.element
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
|
||||
class Annotator:
|
||||
@@ -90,6 +92,15 @@ class Annotator:
|
||||
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_vertex_to_annotation(obj):
|
||||
verts_world_space = Annotator.get_placeholder_coords()
|
||||
vert_local = obj.matrix_world.inverted() @ verts_world_space[0]
|
||||
bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
|
||||
bm.verts.new(vert_local)
|
||||
tool.Blender.apply_bmesh(obj.data, bm, obj)
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
def add_plane_to_annotation(obj, remove_face=False):
|
||||
# default order = bot left, top left, bot right, top right
|
||||
@@ -111,7 +122,7 @@ class Annotator:
|
||||
def get_annotation_obj(drawing, object_type, data_type):
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
co1, _, _, _ = Annotator.get_placeholder_coords(camera)
|
||||
matrix_world = camera.matrix_world.copy()
|
||||
matrix_world = tool.Drawing.get_camera_matrix(camera)
|
||||
matrix_world.translation = co1
|
||||
collection = camera.BIMObjectProperties.collection
|
||||
|
||||
@@ -142,6 +153,8 @@ class Annotator:
|
||||
data = bpy.data.curves.new(object_type, type="CURVE")
|
||||
data.dimensions = "3D"
|
||||
data.resolution_u = 2
|
||||
elif data_type == "empty":
|
||||
data = None
|
||||
|
||||
obj = bpy.data.objects.new(object_type, data)
|
||||
obj.matrix_world = matrix_world
|
||||
@@ -154,6 +167,13 @@ class Annotator:
|
||||
camera = bpy.context.scene.camera
|
||||
|
||||
z_offset = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
|
||||
if (
|
||||
ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(camera), "EPset_Drawing", "TargetView")
|
||||
== "REFLECTED_PLAN_VIEW"
|
||||
):
|
||||
z_offset *= -1
|
||||
|
||||
y = camera.data.ortho_scale / 4
|
||||
|
||||
res_x = bpy.context.scene.render.resolution_x
|
||||
@@ -166,6 +186,7 @@ class Annotator:
|
||||
|
||||
center = camera.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
center.z = 0
|
||||
center = camera.matrix_world @ center
|
||||
|
||||
return (
|
||||
center + z_offset,
|
||||
|
||||
@@ -64,11 +64,6 @@ class profile_consequential:
|
||||
cls.lines = []
|
||||
|
||||
|
||||
def ccw(A, B, C):
|
||||
"""whether a-b-c located in counter-clockwise order in 2d space"""
|
||||
return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x)
|
||||
|
||||
|
||||
def worldspace_to_winspace(verts, context):
|
||||
"""Convert world space verts to window space"""
|
||||
region = context.region
|
||||
@@ -248,8 +243,13 @@ class BaseDecorator:
|
||||
if check_mode and obj.data.is_editmode:
|
||||
return self.get_editmesh_geom(obj)
|
||||
|
||||
vertices = [obj.matrix_world @ v.co for v in obj.data.vertices]
|
||||
indices = [e.vertices for e in obj.data.edges]
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
vertices = [obj.matrix_world @ v.co for v in bm.verts]
|
||||
# In object mode, it's nicer to not show "internal edges". Most will be dissolved anyway.
|
||||
indices = [[v.index for v in e.verts] for e in bm.edges if len(e.link_faces) != 2]
|
||||
bm.free()
|
||||
|
||||
return vertices, indices
|
||||
|
||||
def get_editmesh_geom(self, obj):
|
||||
@@ -520,7 +520,10 @@ class BaseDecorator:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(obj.matrix_world)
|
||||
text_dir = (camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(camera)
|
||||
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
pos = location_3d_to_region_2d(region, region3d, text_world_position)
|
||||
props = obj.BIMTextProperties
|
||||
@@ -742,7 +745,7 @@ class AngleDecorator(BaseDecorator):
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
circle_angle = acos(cos_a)
|
||||
counter_clockwise = ccw(v2, v1, v0)
|
||||
counter_clockwise = tool.Cad.is_counter_clockwise_order(v2, v1, v0)
|
||||
angle_circle = get_angle_circle(circle_start, circle_angle, counter_clockwise)
|
||||
add_verts_sequence([v1 + v for v in angle_circle], start_i_arcs, **out_kwargs_arcs)
|
||||
|
||||
@@ -774,7 +777,7 @@ class AngleDecorator(BaseDecorator):
|
||||
region3d = context.region_data
|
||||
|
||||
viewportDrawingScale = self.get_viewport_drawing_scale(context)
|
||||
ANGLE_LABEL_OFFSET = 25 * viewportDrawingScale
|
||||
ANGLE_LABEL_OFFSET = 20 * viewportDrawingScale
|
||||
last_segment_i = len(indices) - 1
|
||||
|
||||
for edge_i, edge_vertices in enumerate(indices):
|
||||
@@ -792,24 +795,24 @@ class AngleDecorator(BaseDecorator):
|
||||
edge1_ws = v2 - v1
|
||||
try:
|
||||
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
|
||||
angle_rad = acos(cos_a)
|
||||
angle = angle_rad / pi * 180
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
circle_angle_rad = acos(cos_a)
|
||||
circle_angle = circle_angle_rad / pi * 180
|
||||
angle = 0
|
||||
|
||||
# calculate angle position
|
||||
p0 = location_3d_to_region_2d(region, region3d, v0)
|
||||
p1 = location_3d_to_region_2d(region, region3d, v1)
|
||||
p2 = location_3d_to_region_2d(region, region3d, v2)
|
||||
p0, p1, p2 = [location_3d_to_region_2d(region, region3d, p) for p in vertices[i0 : i1 + 2]]
|
||||
edge0 = p0 - p1
|
||||
edge1 = p2 - p1
|
||||
base_edge = edge0 if ccw(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-circle_angle_rad / 2, 2) @ base_edge).normalized() * ANGLE_LABEL_OFFSET
|
||||
radius = min(edge0.length_squared, edge1.length_squared) ** 0.5
|
||||
# TODO: can be helpful for svg positioning
|
||||
base_edge = edge0 if tool.Cad.is_counter_clockwise_order(p0, p1, p2) else edge1
|
||||
text_offset = (Matrix.Rotation(-angle_rad / 2, 2) @ base_edge).normalized() * (radius + ANGLE_LABEL_OFFSET)
|
||||
label_position = p1 + text_offset
|
||||
|
||||
text = f"{int(circle_angle)}d"
|
||||
text = f"{int(angle)}deg"
|
||||
label_dir = Vector((1, 0))
|
||||
self.draw_label(context, text, label_position, label_dir)
|
||||
self.draw_label(context, text, label_position, label_dir, box_alignment="center")
|
||||
|
||||
|
||||
class DiameterDecorator(DimensionDecorator):
|
||||
|
||||
@@ -70,15 +70,6 @@ class profile:
|
||||
print(self.task, timer() - self.start)
|
||||
|
||||
|
||||
def open_with_user_command(user_command, path):
|
||||
if user_command:
|
||||
commands = eval(user_command)
|
||||
for command in commands:
|
||||
subprocess.Popen(command)
|
||||
else:
|
||||
webbrowser.open("file://" + path)
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
@@ -269,7 +260,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
with profile("Combine SVG layers"):
|
||||
svg_path = self.combine_svgs(context, underlay_svg, linework_svg, annotation_svg)
|
||||
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg_path)
|
||||
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, camera_view_point=False)
|
||||
@@ -485,7 +476,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
|
||||
self.setup_serialiser(ifc)
|
||||
self.setup_serialiser(ifc, target_view)
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
tree = ifcopenshell.geom.tree()
|
||||
@@ -555,7 +546,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
if self.camera.data.BIMCameraProperties.calculate_shapely_surfaces:
|
||||
# shapely variant
|
||||
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
nm = group.attrib["{http://www.ifcopenshell.org/ns}name"]
|
||||
m4 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}plane"]))
|
||||
m3 = np.array(json.loads(group.attrib["{http://www.ifcopenshell.org/ns}matrix3"]))
|
||||
@@ -566,10 +557,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
m44[1][3] = m3[1][2]
|
||||
m44 = np.linalg.inv(m44)
|
||||
|
||||
projections = group.findall('.//{http://www.w3.org/2000/svg}g[@class="projection"]') or []
|
||||
elements_with_faces = set()
|
||||
for element in drawing_elements.copy():
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj and obj.type == "MESH" and len(obj.data.polygons):
|
||||
elements_with_faces.add(element.GlobalId)
|
||||
|
||||
projections = root.xpath(
|
||||
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
|
||||
)
|
||||
|
||||
boundary_lines = []
|
||||
for projection in projections:
|
||||
boundary_lines = []
|
||||
global_id = projection.attrib["{http://www.ifcopenshell.org/ns}guid"]
|
||||
if global_id not in elements_with_faces:
|
||||
continue
|
||||
for path in projection.findall("./{http://www.w3.org/2000/svg}path"):
|
||||
# Rounding is necessary to ensure coincident points are coincident
|
||||
start, end = [[round(float(o), 1) for o in co[1:].split(",")] for co in path.attrib["d"].split()]
|
||||
@@ -578,50 +580,55 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Extension by 0.5mm is necessary to ensure lines overlap with other diagonal lines
|
||||
start, end = tool.Drawing.extend_line(start, end, 0.5)
|
||||
boundary_lines.append(shapely.LineString([start, end]))
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
for polygon in closed_polygons.geoms:
|
||||
# Less than 1mm2 is not worth styling on sheet
|
||||
if polygon.area < 1:
|
||||
continue
|
||||
centroid = polygon.centroid
|
||||
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
||||
if internal_point:
|
||||
internal_point = [internal_point.x, internal_point.y]
|
||||
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
|
||||
m44, m4, internal_point, -100.0
|
||||
)
|
||||
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
|
||||
if not inside_elements:
|
||||
elements = [
|
||||
e
|
||||
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||
if not e.instance.is_a("IfcAnnotation")
|
||||
and tool.Cad.is_point_on_edge(
|
||||
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
|
||||
)
|
||||
]
|
||||
if elements:
|
||||
path = etree.Element("path")
|
||||
d = (
|
||||
"M"
|
||||
+ " L".join(
|
||||
[",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]]
|
||||
)
|
||||
unioned_boundaries = shapely.union_all(shapely.GeometryCollection(boundary_lines))
|
||||
closed_polygons = shapely.polygonize(unioned_boundaries.geoms)
|
||||
|
||||
for polygon in closed_polygons.geoms:
|
||||
# Less than 1mm2 is not worth styling on sheet
|
||||
if polygon.area < 1:
|
||||
continue
|
||||
centroid = polygon.centroid
|
||||
internal_point = centroid if polygon.contains(centroid) else polygon.representative_point()
|
||||
if internal_point:
|
||||
internal_point = [internal_point.x, internal_point.y]
|
||||
a, b = self.drawing_to_model_co(m44, m4, internal_point, 0.0), self.drawing_to_model_co(
|
||||
m44, m4, internal_point, -100.0
|
||||
)
|
||||
inside_elements = [e for e in tree.select(self.pythonize(a)) if not e.is_a("IfcAnnotation")]
|
||||
if not inside_elements:
|
||||
elements = [
|
||||
e
|
||||
for e in tree.select_ray(self.pythonize(a), self.pythonize(b - a))
|
||||
if not e.instance.is_a("IfcAnnotation")
|
||||
and tool.Cad.is_point_on_edge(
|
||||
Vector(list(e.position)), (Vector(self.pythonize(a)), Vector(self.pythonize(b)))
|
||||
)
|
||||
]
|
||||
if elements:
|
||||
path = etree.Element("path")
|
||||
d = (
|
||||
"M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in polygon.exterior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
for interior in polygon.interiors:
|
||||
d += (
|
||||
" M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
for interior in polygon.interiors:
|
||||
d += (
|
||||
" M"
|
||||
+ " L".join([",".join([str(o) for o in co]) for co in interior.coords[0:-1]])
|
||||
+ " Z"
|
||||
)
|
||||
path.attrib["d"] = d
|
||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||
classes.append("surface")
|
||||
path.set("class", " ".join(list(classes)))
|
||||
group.insert(0, path)
|
||||
path.attrib["d"] = d
|
||||
classes = self.get_svg_classes(ifc.by_id(elements[0].instance.id()))
|
||||
classes.append(f"intpoint-{internal_point}")
|
||||
classes.append(f"ab-{a}, {b}")
|
||||
for i, ray_result in enumerate(elements):
|
||||
classes.append(f"el{i}-{ray_result.instance.id()}")
|
||||
classes.append(f"el{i}-pos-{list(ray_result.position)}")
|
||||
classes.append(f"el{i}-dst-{ray_result.distance}")
|
||||
classes.append("surface")
|
||||
path.set("class", " ".join(list(classes)))
|
||||
group.insert(0, path)
|
||||
|
||||
if self.camera.data.BIMCameraProperties.calculate_svgfill_surfaces:
|
||||
results = etree.tostring(root).decode("utf8")
|
||||
@@ -636,7 +643,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
dom1 = parseString(svg_data_1)
|
||||
svg1 = dom1.childNodes[0]
|
||||
groups1 = [g for g in yield_groups(svg1) if g.getAttribute("class") == "projection"]
|
||||
groups1 = [g for g in yield_groups(svg1) if "projection" in g.getAttribute("class")]
|
||||
|
||||
ls_groups = ifcopenshell.ifcopenshell_wrapper.svg_to_line_segments(results, "projection")
|
||||
|
||||
@@ -829,7 +836,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
return svg_path
|
||||
|
||||
def setup_serialiser(self, ifc):
|
||||
def setup_serialiser(self, ifc, target_view):
|
||||
self.svg_settings = ifcopenshell.geom.settings(
|
||||
DISABLE_TRIANGULATION=True, STRICT_TOLERANCE=True, INCLUDE_CURVES=True
|
||||
)
|
||||
@@ -840,7 +847,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setPolygonal(True)
|
||||
self.serialiser.setUseHlrPoly(True)
|
||||
# Objects with more than these edges are rendered as wireframe instead of HLR for optimisation
|
||||
self.serialiser.setProfileThreshold(1000)
|
||||
self.serialiser.setProfileThreshold(10000)
|
||||
self.serialiser.setUseNamespace(True)
|
||||
self.serialiser.setAlwaysProject(True)
|
||||
self.serialiser.setAutoElevation(False)
|
||||
@@ -853,6 +860,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.serialiser.setScale(self.scale)
|
||||
self.serialiser.setSubtractionSettings(ifcopenshell.ifcopenshell_wrapper.ALWAYS)
|
||||
self.serialiser.setUsePrefiltering(True) # See #3359
|
||||
self.serialiser.setUnifyInputs(True)
|
||||
self.serialiser.setSegmentProjection(True)
|
||||
if target_view == "REFLECTED_PLAN_VIEW":
|
||||
self.serialiser.setMirrorY(True)
|
||||
# tree = ifcopenshell.geom.tree()
|
||||
# This instructs the tree to explode BReps into faces and return
|
||||
# the style of the face when running tree.select_ray()
|
||||
@@ -887,6 +898,21 @@ class CreateDrawing(bpy.types.Operator):
|
||||
)
|
||||
return classes
|
||||
|
||||
def is_manifold(self, obj):
|
||||
result = self.is_manifold_cache.get(obj.data.name, None)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
for edge in bm.edges:
|
||||
if not edge.is_manifold:
|
||||
bm.free()
|
||||
self.is_manifold_cache[obj.data.name] = False
|
||||
return False
|
||||
self.is_manifold_cache[obj.data.name] = True
|
||||
return True
|
||||
|
||||
def merge_linework_and_add_metadata(self, root):
|
||||
join_criteria = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinCriteria")
|
||||
if join_criteria:
|
||||
@@ -895,16 +921,27 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Drawing convention states that same objects classes with the same material are merged when cut.
|
||||
join_criteria = ["class", "material.Name", 'r"Pset.*Common"."Status"']
|
||||
|
||||
group = root.findall(".//{http://www.w3.org/2000/svg}g")[0]
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
joined_paths = {}
|
||||
self.is_manifold_cache = {}
|
||||
|
||||
ifc = tool.Ifc.get()
|
||||
for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"):
|
||||
element = ifc.by_guid(el.get("{http://www.ifcopenshell.org/ns}guid"))
|
||||
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
if "projection" in el.get("class", "").split():
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("projection")
|
||||
el.set("class", " ".join(classes))
|
||||
continue
|
||||
else:
|
||||
classes = self.get_svg_classes(element)
|
||||
classes.append("cut")
|
||||
el.set("class", " ".join(classes))
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if not self.is_manifold(obj):
|
||||
continue
|
||||
|
||||
# An element group will contain a bunch of paths representing the
|
||||
# cut of that element. However IfcOpenShell may not correctly
|
||||
@@ -1022,9 +1059,12 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# IfcConvert puts the projection afterwards which is not correct since
|
||||
# projection should be drawn underneath the cut.
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
projection = group.find("{http://www.w3.org/2000/svg}g[@class='projection']")
|
||||
projection.getparent().remove(projection)
|
||||
group.insert(0, projection)
|
||||
projections = root.xpath(
|
||||
".//svg:g[contains(@class, 'projection')]", namespaces={"svg": "http://www.w3.org/2000/svg"}
|
||||
)
|
||||
for projection in projections:
|
||||
projection.getparent().remove(projection)
|
||||
group.insert(0, projection)
|
||||
|
||||
def generate_annotation(self, context):
|
||||
if not ifcopenshell.util.element.get_pset(self.drawing, "EPset_Drawing", "HasAnnotation"):
|
||||
@@ -1035,7 +1075,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
|
||||
elements = tool.Drawing.get_group_elements(tool.Drawing.get_drawing_group(self.camera_element))
|
||||
filtered_drawing_elements = tool.Drawing.get_drawing_elements(self.camera_element)
|
||||
elements = [e for e in elements if e in filtered_drawing_elements]
|
||||
filtered_drawing_annotations = {e for e in filtered_drawing_elements if e.is_a("IfcAnnotation")}
|
||||
elements = {e for e in elements if e in filtered_drawing_elements}
|
||||
elements = list(elements | filtered_drawing_annotations)
|
||||
|
||||
annotations = sorted(
|
||||
elements, key=lambda a: (tool.Drawing.get_annotation_z_index(a), 1 if a.ObjectType == "TEXT" else 0)
|
||||
@@ -1236,11 +1278,15 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
|
||||
# These variables will be made available to the evaluated commands
|
||||
svg = references["SHEET"]
|
||||
basename = os.path.basename(svg)
|
||||
path = os.path.dirname(svg)
|
||||
pdf = os.path.splitext(svg)[0] + ".pdf"
|
||||
eps = os.path.splitext(svg)[0] + ".eps"
|
||||
dxf = os.path.splitext(svg)[0] + ".dxf"
|
||||
replacements = {
|
||||
"svg": svg,
|
||||
"basename": os.path.basename(svg),
|
||||
"path": os.path.dirname(svg),
|
||||
"pdf": pdf,
|
||||
"eps": os.path.splitext(svg)[0] + ".eps",
|
||||
"dxf": os.path.splitext(svg)[0] + ".dxf",
|
||||
}
|
||||
|
||||
has_sheet_reference = False
|
||||
for reference in tool.Drawing.get_document_references(sheet):
|
||||
@@ -1273,22 +1319,23 @@ class CreateSheets(bpy.types.Operator, Operator):
|
||||
|
||||
if svg2pdf_command:
|
||||
# With great power comes great responsibility. Example:
|
||||
# [['inkscape', svg, '-o', pdf]]
|
||||
commands = eval(svg2pdf_command)
|
||||
# [["inkscape", "svg", "-o", "pdf"]]
|
||||
commands = json.loads(svg2pdf_command)
|
||||
for command in commands:
|
||||
subprocess.run(command)
|
||||
subprocess.run([replacements.get(c, c) for c in command])
|
||||
|
||||
if svg2dxf_command:
|
||||
# With great power comes great responsibility. Example:
|
||||
# [['inkscape', svg, '-o', eps], ['pstoedit', '-dt', '-f', 'dxf:-polyaslines -mm', eps, dxf, '-psarg', '-dNOSAFER']]
|
||||
commands = eval(svg2dxf_command)
|
||||
# [["inkscape", "svg", "-o", "eps"], ["pstoedit", "-dt", "-f", "dxf:-polyaslines -mm", "eps", "dxf", "-psarg", "-dNOSAFER"]]
|
||||
commands = json.loads(svg2dxf_command)
|
||||
for command in commands:
|
||||
subprocess.run(command)
|
||||
command[0] = shutil.which(command[0]) or command[0]
|
||||
subprocess.run([replacements.get(c, c) for c in command])
|
||||
|
||||
if svg2pdf_command:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.pdf_command, pdf)
|
||||
else:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
|
||||
tool.Drawing.open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, svg)
|
||||
|
||||
|
||||
class SelectAllDrawings(bpy.types.Operator):
|
||||
@@ -1355,7 +1402,9 @@ class OpenDrawing(bpy.types.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
for drawing_uri in drawing_uris:
|
||||
open_with_user_command(context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri)
|
||||
tool.Drawing.open_with_user_command(
|
||||
context.preferences.addons["blenderbim"].preferences.svg_command, drawing_uri
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1391,6 +1440,7 @@ class ActivateModel(bpy.types.Operator):
|
||||
|
||||
if view3d_context:
|
||||
bpy.ops.object.hide_view_clear(view3d_context)
|
||||
bpy.ops.bim.activate_status_filters()
|
||||
|
||||
subcontext = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@@ -1512,11 +1562,17 @@ class RemoveDrawing(bpy.types.Operator, Operator):
|
||||
tool.Ifc.get().by_id(d.ifc_definition_id) for d in context.scene.DocProperties.drawings if d.is_selected
|
||||
]
|
||||
else:
|
||||
if not self.drawing:
|
||||
self.report({"ERROR"}, "No drawing selected")
|
||||
return {"CANCELLED"}
|
||||
drawings = [tool.Ifc.get().by_id(self.drawing)]
|
||||
|
||||
removed_drawings = [drawing.id() for drawing in drawings]
|
||||
|
||||
for drawing in drawings:
|
||||
sheet_references = tool.Drawing.get_sheet_references(drawing)
|
||||
for reference in sheet_references:
|
||||
bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id())
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
@@ -1862,7 +1918,7 @@ class RemoveSchedule(bpy.types.Operator, Operator):
|
||||
schedule: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", schedule=tool.Ifc.get().by_id(self.schedule))
|
||||
core.remove_document(tool.Ifc, tool.Drawing, "SCHEDULE", document=tool.Ifc.get().by_id(self.schedule))
|
||||
|
||||
|
||||
class OpenSchedule(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -502,6 +502,8 @@ ANNOTATION_TYPES_DATA = {
|
||||
"PLAN_LEVEL": ("Level (Plan)", "", "SORTBYEXT", "curve"),
|
||||
"SECTION_LEVEL": ("Level (Section)", "", "TRIA_DOWN", "curve"),
|
||||
"BREAKLINE": ("Breakline", "", "FCURVE", "mesh"),
|
||||
"SYMBOL": ("Symbol", "", "KEYFRAME", "empty"),
|
||||
"MULTI_SYMBOL": ("Multi-Symbol", "", "OUTLINER_DATA_POINTCLOUD", "mesh"),
|
||||
"LINEWORK": ("Line", "", "SNAP_MIDPOINT", "mesh"),
|
||||
"BATTING": ("Batting", "Add batting annotation.\nThickness could be changed through Thickness property of BBIM_Batting property set", "FORCE_FORCE", "mesh"),
|
||||
"REVISION_CLOUD":("Revision Cloud", "Add revision cloud", "VOLUME_DATA", "mesh"),
|
||||
@@ -538,7 +540,7 @@ class BIMAnnotationProperties(PropertyGroup):
|
||||
name="Create Representation For Type",
|
||||
default=False,
|
||||
description='Whether "Add type" should define a representation for the type \n'
|
||||
"or allow occurences to have their own",
|
||||
"or allow occurrences to have their own",
|
||||
)
|
||||
is_adding_type: bpy.props.BoolProperty(default=False)
|
||||
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
|
||||
|
||||
@@ -118,6 +118,25 @@ class Scheduler:
|
||||
if cell_style:
|
||||
related_styles.append((style_name, cell_style))
|
||||
|
||||
# sometimes there are no column styles (e.g. in ODS from IfcCSV)
|
||||
# and we just use some constant number for column widths
|
||||
if not column_widths:
|
||||
row_columns = []
|
||||
for tr in table.getElementsByType(TableRow):
|
||||
row_cols = 0
|
||||
for td in tr.getElementsByType(TableCell):
|
||||
column_span = td.getAttribute("numbercolumnsspanned")
|
||||
column_span = int(column_span) if column_span else 1
|
||||
|
||||
col_repeat = td.getAttribute("numbercolumnsrepeated")
|
||||
col_repeat = int(col_repeat) if col_repeat else 1
|
||||
row_cols += column_span * col_repeat
|
||||
row_columns.append(row_cols)
|
||||
|
||||
n_columns = max(row_columns)
|
||||
column_widths = [25] * n_columns # some constant width value 👀
|
||||
column_styles = [None] * n_columns
|
||||
|
||||
# collect rows height
|
||||
row_heights = []
|
||||
# TODO: never used yet because unsure about priority for row styles
|
||||
@@ -351,7 +370,7 @@ class Scheduler:
|
||||
wrap_text: if True, text will be wrapped to fit in cell
|
||||
cell_width: width of cell, used for wrapping text
|
||||
"""
|
||||
text_lines = [str(p).upper() for p in p_tags]
|
||||
text_lines = [str(p) for p in p_tags]
|
||||
box_alignment_params = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
text_params = {
|
||||
"font-size": font_size,
|
||||
|
||||
@@ -174,6 +174,8 @@ class SheetBuilder:
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
|
||||
layout_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
if not os.path.exists(layout_path):
|
||||
return
|
||||
layout_tree = ET.parse(layout_path)
|
||||
layout_root = layout_tree.getroot()
|
||||
|
||||
@@ -414,7 +416,7 @@ class SheetBuilder:
|
||||
view.remove(image)
|
||||
|
||||
def get_href(self, element):
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href"))
|
||||
return urllib.parse.unquote(element.attrib.get("{http://www.w3.org/1999/xlink}href")).replace('\\','/')
|
||||
|
||||
def parse_embedded_svg(self, image, data):
|
||||
group = ET.Element("g")
|
||||
|
||||
@@ -36,7 +36,7 @@ import blenderbim.bim.module.drawing.annotation as annotation
|
||||
from blenderbim.bim.module.drawing.data import DecoratorData
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
|
||||
from math import pi, ceil, atan, degrees
|
||||
from math import pi, ceil, atan, degrees, acos
|
||||
from mathutils import geometry, Vector
|
||||
from bpy_extras import view3d_utils
|
||||
|
||||
@@ -326,11 +326,12 @@ class SvgWriter:
|
||||
# We have to decide whether this should come from Blender or from IFC.
|
||||
# For the moment, for convenience of experimenting with ideas, it comes
|
||||
# from Blender. In the future, it should probably come from IFC.
|
||||
if not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
|
||||
classes = self.get_attribute_classes(obj)
|
||||
if len(obj.data.vertices) and not len(obj.data.edges):
|
||||
if obj.data is None:
|
||||
return self.draw_empty_annotation(obj, classes)
|
||||
elif not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
elif len(obj.data.vertices) and not len(obj.data.edges):
|
||||
return self.draw_point_annotation(obj, classes)
|
||||
elif len(obj.data.polygons) == 0:
|
||||
return self.draw_edge_annotation(obj, classes)
|
||||
@@ -774,6 +775,18 @@ class SvgWriter:
|
||||
"text-anchor": text_anchor,
|
||||
}
|
||||
|
||||
def add_fill_bg(self, element, copy=True):
|
||||
if copy:
|
||||
element = element.copy()
|
||||
if hasattr(element, "xml"):
|
||||
attrib = element.xml.attrib
|
||||
elif isinstance(element, ET.Element):
|
||||
attrib = element.attrib
|
||||
else: # assuming it's svgwrite.base.BaseElement
|
||||
attrib = element.attribs
|
||||
attrib["filter"] = "url(#fill-background)"
|
||||
return element
|
||||
|
||||
def draw_text_annotation(self, text_obj, position):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -791,20 +804,21 @@ class SvgWriter:
|
||||
return matrix.inverted()[i].to_3d().normalized()
|
||||
|
||||
text_dir_world_x_axis = get_basis_vector(text_obj.matrix_world)
|
||||
text_dir = (self.camera.matrix_world.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
|
||||
# RCP cameras may be scaled, so reset scales.
|
||||
camera_matrix = tool.Drawing.get_camera_matrix(self.camera)
|
||||
text_dir = (camera_matrix.inverted().to_quaternion() @ text_dir_world_x_axis).to_2d().normalized()
|
||||
angle = math.degrees(-text_dir.angle_signed(Vector((1, 0))))
|
||||
|
||||
classes = self.get_attribute_classes(text_obj)
|
||||
classes_str = " ".join(classes)
|
||||
fill_bg = "fill-bg" in classes
|
||||
|
||||
symbol = tool.Drawing.get_annotation_symbol(element)
|
||||
template_text_fields = []
|
||||
if not symbol:
|
||||
text_transform = f"translate({text_position_svg_str}) rotate({angle})"
|
||||
else:
|
||||
if symbol:
|
||||
# NOTE: for now we assume that scale is uniform
|
||||
symbol_transform = f"translate({text_position_svg_str}) rotate({angle}) scale({text_obj.scale.x})"
|
||||
text_transform = symbol_transform
|
||||
|
||||
symbol_svg = self.find_xml_symbol_by_id(symbol)
|
||||
if symbol_svg:
|
||||
@@ -815,10 +829,20 @@ class SvgWriter:
|
||||
if template_text_fields:
|
||||
symbol_xml.attrib["transform"] = symbol_transform
|
||||
symbol_xml.attrib.pop("id")
|
||||
# note: zip makes sure that we iterate over the shortest list
|
||||
# NOTE: zip makes sure that we iterate over the shortest list
|
||||
for field, text_literal in zip(template_text_fields, text_literals):
|
||||
field.text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
field.text = tool.Drawing.replace_text_literal_variables(
|
||||
text_literal.Literal, product or element
|
||||
)
|
||||
field.attrib["class"] = classes_str
|
||||
|
||||
if fill_bg:
|
||||
symbol_copied = symbol_svg.copy()
|
||||
for text_tag in symbol_copied.xml.findall("text"):
|
||||
self.add_fill_bg(text_tag, copy=False)
|
||||
# NOTE: in case we'll later need to add fill-bg for the entire symbol:
|
||||
# self.add_fill_bg(symbol_svg, copy=False)
|
||||
self.svg.add(symbol_copied)
|
||||
self.svg.add(symbol_svg)
|
||||
return None
|
||||
|
||||
@@ -827,20 +851,40 @@ class SvgWriter:
|
||||
|
||||
line_number = 0
|
||||
for text_literal in text_literals:
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product)
|
||||
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
|
||||
text_tags = self.create_text_tag(
|
||||
text,
|
||||
text_position_svg,
|
||||
angle,
|
||||
text_literal.BoxAlignment,
|
||||
classes_str,
|
||||
fill_bg="fill-bg" in classes,
|
||||
fill_bg=fill_bg,
|
||||
line_number_start=line_number,
|
||||
)
|
||||
for tag in text_tags:
|
||||
self.svg.add(tag)
|
||||
line_number += len(tag.elements)
|
||||
|
||||
def draw_empty_annotation(self, obj, classes):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
|
||||
point = self.project_point_onto_camera(obj.matrix_world.translation)
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
return
|
||||
|
||||
point = Vector(((x_offset + point.x), (y_offset - point.y)))
|
||||
symbol_position_svg = point * self.svg_scale
|
||||
self.svg.add(self.svg.use(f"#{svg_id}", insert=symbol_position_svg))
|
||||
|
||||
def draw_point_annotation(self, obj, classes):
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
@@ -849,12 +893,14 @@ class SvgWriter:
|
||||
projected_points = [self.project_point_onto_camera(matrix_world @ v.co) for v in obj.data.vertices]
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
point_type = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if point_type:
|
||||
svg_id += f"-{point_type}"
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Symbol")
|
||||
if not svg_id:
|
||||
# EPset_AnnotationSurveyArea is not standard! See bSI-4.3 proposal #660.
|
||||
svg_id = ifcopenshell.util.element.get_pset(element, "EPset_AnnotationSurveyArea", "PointType")
|
||||
if not svg_id:
|
||||
svg_id = str(ifcopenshell.util.element.get_predefined_type(element))
|
||||
if not svg_id:
|
||||
return
|
||||
|
||||
for symbol_position in projected_points:
|
||||
symbol_position = Vector(((x_offset + symbol_position.x), (y_offset - symbol_position.y)))
|
||||
@@ -936,54 +982,54 @@ class SvgWriter:
|
||||
|
||||
def draw_angle_annotations(self, obj):
|
||||
points = obj.data.splines[0].points
|
||||
region = bpy.context.region
|
||||
area = tool.Blender.get_viewport_context()["area"]
|
||||
region_3d = area.spaces.active.region_3d
|
||||
# [1, 2, 3, 4, 5] -> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
|
||||
points_chunked = [points[i : i + 3] for i in range(len(points) - 2)]
|
||||
|
||||
for points_chunk in points_chunked:
|
||||
points_2d = [view3d_utils.location_3d_to_region_2d(region, region_3d, p.co.xyz) for p in points_chunk]
|
||||
points_chunk = [obj.matrix_world @ p.co.xyz for p in points_chunk]
|
||||
self.draw_svg_3point_arc(obj, points_chunk)
|
||||
|
||||
edge0 = points_2d[0] - points_2d[1]
|
||||
edge1 = points_2d[2] - points_2d[1]
|
||||
angle_radius = min(edge0.length, edge1.length)
|
||||
dir0 = edge0.normalized()
|
||||
dir1 = edge1.normalized()
|
||||
dir2 = ((dir0 + dir1) / 2).normalized()
|
||||
|
||||
# calculate p3 which is the center of the arc
|
||||
# to use draw_svg_3point_arc()
|
||||
p3 = points_2d[1] + dir2 * angle_radius
|
||||
|
||||
# make all edges the same radius
|
||||
p0 = points_2d[1] + dir0 * angle_radius
|
||||
p2 = points_2d[1] + dir1 * angle_radius
|
||||
points_chunk = [view3d_utils.region_2d_to_origin_3d(region, region_3d, p) for p in [p0, p3, p2]]
|
||||
# points = [p.co.xyz for p in bpy.context.active_object.data.splines[0].points[:3]]
|
||||
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
new_verts = [bm.verts.new(p) for p in points_chunk]
|
||||
new_edges = [bm.edges.new((new_verts[e[0]], new_verts[e[1]])) for e in ((0, 1), (1, 2))]
|
||||
self.draw_svg_3point_arc(obj, bm)
|
||||
|
||||
def draw_svg_3point_arc(self, obj, bm):
|
||||
def draw_svg_3point_arc(self, obj, angle_points):
|
||||
"""`angle_points` are expected to be already in world space"""
|
||||
# This implementation uses an SVG arc, which means that it can only draw
|
||||
# arcs that are orthogonal to the view (e.g. not arcs in 3D).
|
||||
# Gosh this is bad code :(
|
||||
|
||||
points = [v.co for v in bm.verts][:3]
|
||||
center = tool.Cad.get_center_of_arc(points, obj)
|
||||
classes = self.get_attribute_classes(obj)
|
||||
matrix_world = obj.matrix_world
|
||||
x_offset = self.raw_width / 2
|
||||
y_offset = self.raw_height / 2
|
||||
bm.verts.ensure_lookup_table()
|
||||
arc_end_verts = [v for v in bm.verts if len(v.link_edges) == 1]
|
||||
arc_end_pts = [matrix_world @ v.co for v in arc_end_verts]
|
||||
def position_on_svg(p):
|
||||
p = self.project_point_onto_camera(p)
|
||||
offset = Vector([self.raw_width / 2, self.raw_height / 2])
|
||||
return (offset + p.xy * Vector((1, -1))) * self.svg_scale
|
||||
|
||||
# Probably need this when rewriting to use an SVG polyline instead of an arc
|
||||
def get_angle_value():
|
||||
"""points should be in world space"""
|
||||
# calculate arc angle, need to make sure we do it in world space
|
||||
v0, v1, v2 = angle_points
|
||||
edge0_ws = v0 - v1
|
||||
edge1_ws = v2 - v1
|
||||
try:
|
||||
cos_a = edge0_ws.dot(edge1_ws) / (edge0_ws.length * edge1_ws.length)
|
||||
angle_rad = acos(cos_a)
|
||||
angle = angle_rad / pi * 180
|
||||
except ZeroDivisionError:
|
||||
angle = 0
|
||||
return angle
|
||||
|
||||
angle = get_angle_value()
|
||||
angle_points = [position_on_svg(p) for p in angle_points]
|
||||
|
||||
# creating arc and making sure radius is consistent across the arc
|
||||
edge0 = angle_points[0] - angle_points[1]
|
||||
edge1 = angle_points[2] - angle_points[1]
|
||||
angle_radius = min(edge0.length, edge1.length)
|
||||
dir0 = edge0.normalized()
|
||||
dir1 = edge1.normalized()
|
||||
arc_mid_dir = ((dir0 + dir1) / 2).normalized()
|
||||
arc_points = [angle_points[1] + direction * angle_radius for direction in [dir0, arc_mid_dir, dir1]]
|
||||
|
||||
arc_end_pts = [arc_points[0], arc_points[2]]
|
||||
arc_mid_point = arc_points[1]
|
||||
|
||||
# The commented code below can be useful when we start using SVG polyline instead of an arc
|
||||
# arc_path = [arc_end_verts[0]]
|
||||
# while True:
|
||||
# last_point = arc_end_verts[0]
|
||||
@@ -996,79 +1042,44 @@ class SvgWriter:
|
||||
# if not found_another_point:
|
||||
# break
|
||||
|
||||
distance_between_end_verts = (arc_end_verts[0].co - arc_end_verts[1].co).length
|
||||
arc_mid_vert = arc_end_verts[0].link_edges[0].other_vert(arc_end_verts[0])
|
||||
is_reflex = 0 if (arc_mid_vert.co - arc_end_verts[1].co).length < distance_between_end_verts else 1
|
||||
|
||||
bm.free()
|
||||
|
||||
# Calculate the angle
|
||||
# This is the true normal in 3D, whereas the camera projection we use is the drawing direction.
|
||||
# This assumes (because we use SVG arcs) that the radius is always orthogonal to our view.
|
||||
# When rewriting to use polylines, we should use this normal instead.
|
||||
# center = tool.Cad.get_center_of_arc([p.to_3d() for p in arc_points], None).to_2d()
|
||||
# normal = mathutils.geometry.normal([arc_end_pts[0], arc_end_pts[1], center])
|
||||
normal = Vector(self.camera_projection)
|
||||
dir1 = (arc_end_pts[0] - center).normalized()
|
||||
dir2 = (arc_end_pts[1] - center).normalized()
|
||||
# normal = Vector(self.camera_projection)
|
||||
# dir1 = (arc_end_pts[0] - center).normalized()
|
||||
# arc_mid_dir = (arc_end_pts[1] - center).normalized()
|
||||
|
||||
# Let's get the matrix that represents the coordinate system of the arc.
|
||||
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
|
||||
z = normal
|
||||
x = (arc_end_pts[0] - center).normalized()
|
||||
y = z.cross(x)
|
||||
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
# z = normal
|
||||
# x = (arc_end_pts[0] - center).normalized()
|
||||
# y = z.cross(x)
|
||||
# arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||
|
||||
dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
angle = -dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
# if is_reflex:
|
||||
# angle = angle % (math.pi * 2)
|
||||
|
||||
# Center of gravity of all vertices, used to help position the text
|
||||
cog = Vector((0, 0, 0))
|
||||
for point in points:
|
||||
cog += point
|
||||
cog = matrix_world @ (cog / len(points))
|
||||
|
||||
radius = ((matrix_world @ points[0]) - center).length
|
||||
|
||||
arc_midpoint = center + ((cog - center).normalized() * radius)
|
||||
|
||||
text_position = self.project_point_onto_camera(arc_midpoint)
|
||||
text_position = Vector(
|
||||
((x_offset + text_position.x) * self.svg_scale, (y_offset - text_position.y) * self.svg_scale)
|
||||
)
|
||||
|
||||
center_projected = self.project_point_onto_camera(center)
|
||||
center_position = Vector(
|
||||
((x_offset + center_projected.x) * self.svg_scale, (y_offset - center_projected.y) * self.svg_scale)
|
||||
)
|
||||
text_offset = (text_position - center_position).xy.normalized() * 5
|
||||
text_position += text_offset
|
||||
# dir1 = ((arc_matrix.inverted() @ arc_end_pts[0]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
# dir2 = ((arc_matrix.inverted() @ arc_end_pts[1]) - (arc_matrix.inverted() @ center)).normalized()
|
||||
# angle = -dir1.xy.angle_signed(dir2.xy)
|
||||
|
||||
# calculating text parameters and adding text
|
||||
text_position = arc_mid_point + arc_mid_dir * 5
|
||||
text_style = SvgWriter.get_box_alignment_parameters("center")
|
||||
angle_text = abs(round(math.degrees(angle), 3))
|
||||
if is_reflex:
|
||||
angle_text = 360 - angle_text
|
||||
self.svg.add(self.svg.text(f"{angle_text}deg", insert=tuple(text_position), class_="ANGLE", **text_style))
|
||||
angle_text = f"{int(angle)}deg"
|
||||
self.svg.add(self.svg.text(angle_text, insert=text_position, class_="ANGLE", **text_style))
|
||||
|
||||
# Draw SVG arc, see for details: http://xahlee.info/js/svg_circle_arc.html
|
||||
arc_proj_end_pts = [self.project_point_onto_camera(v) for v in arc_end_pts]
|
||||
p1 = Vector(
|
||||
((x_offset + arc_proj_end_pts[0].x) * self.svg_scale, (y_offset - arc_proj_end_pts[0].y) * self.svg_scale)
|
||||
)
|
||||
p2 = Vector(
|
||||
((x_offset + arc_proj_end_pts[1].x) * self.svg_scale, (y_offset - arc_proj_end_pts[1].y) * self.svg_scale)
|
||||
)
|
||||
r = radius * self.svg_scale
|
||||
# reflex = 1 if angle > math.pi else 0
|
||||
reflex = is_reflex
|
||||
if reflex:
|
||||
sense = 0 if angle > 0 else 1
|
||||
else:
|
||||
sense = 1 if angle > 0 else 0
|
||||
p1, p2 = arc_end_pts
|
||||
r = angle_radius
|
||||
# reflex: 0 => arc < 180 degrees
|
||||
# 1 => arc > 180 degrees
|
||||
reflex = int(angle > 180)
|
||||
# sense: 0 => moving at negative angles
|
||||
# 1 => moving at positive angles
|
||||
sense = int(tool.Cad.is_counter_clockwise_order(*arc_points))
|
||||
d = f"M {p1.x} {p1.y} A {r} {r} 0 {reflex} {sense} {p2.x} {p2.y}"
|
||||
classes = self.get_attribute_classes(obj)
|
||||
path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes)))
|
||||
|
||||
def draw_radius_annotations(self, obj):
|
||||
@@ -1344,18 +1355,10 @@ class SvgWriter:
|
||||
multiline_to_bottom=True,
|
||||
fill_bg=False,
|
||||
line_number_start=0,
|
||||
_draw_fill_bg=False,
|
||||
):
|
||||
"""returns list of created text tags"""
|
||||
text_tags = []
|
||||
if fill_bg:
|
||||
method_kwargs = locals() | {"_draw_fill_bg": True, "fill_bg": False}
|
||||
del method_kwargs["self"]
|
||||
del method_kwargs["text_tags"]
|
||||
text_tags += self.create_text_tag(**method_kwargs)
|
||||
|
||||
base_text_attrs = SvgWriter.get_box_alignment_parameters(box_alignment)
|
||||
base_text_attrs = base_text_attrs | ({"filter": "url(#fill-background)"} if _draw_fill_bg else {})
|
||||
|
||||
if not multiline:
|
||||
transform_kwargs = {"transform": "rotate({} {} {})".format(angle, text_position.x, text_position.y)}
|
||||
@@ -1392,6 +1395,11 @@ class SvgWriter:
|
||||
# doing it here and not in tspan constructor because constructor adds unnecessary spaces
|
||||
tspan.update({"dy": f"{line_number if multiline_to_bottom else -line_number}em"})
|
||||
text_tag.add(tspan)
|
||||
|
||||
if fill_bg:
|
||||
fill_bg_tags = [self.add_fill_bg(text_tag) for text_tag in text_tags]
|
||||
text_tags = fill_bg_tags + text_tags
|
||||
|
||||
return text_tags
|
||||
|
||||
def project_point_onto_camera(self, point):
|
||||
|
||||
@@ -314,7 +314,7 @@ class BIM_PT_references(Panel):
|
||||
|
||||
if not self.props.is_editing_references:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="LONGDISPLAY")
|
||||
row.label(text=f"{DocumentsData.data['total_references']} References Found", icon="OBJECT_HIDDEN")
|
||||
row.operator("bim.load_references", text="", icon="IMPORT")
|
||||
return
|
||||
|
||||
|
||||
@@ -26,17 +26,19 @@ classes = (
|
||||
operator.GetRepresentationIfcParameters,
|
||||
operator.OverrideDelete,
|
||||
operator.OverrideDuplicateMove,
|
||||
operator.OverrideDuplicateMoveAggregate,
|
||||
operator.OverrideDuplicateMoveAggregateMacro,
|
||||
operator.OverrideDuplicateMoveLinked,
|
||||
operator.OverrideDuplicateMoveLinkedMacro,
|
||||
operator.OverrideDuplicateMoveMacro,
|
||||
operator.OverrideDuplicateMoveAggregate,
|
||||
operator.OverrideDuplicateMoveAggregateMacro,
|
||||
operator.RefreshAggregate,
|
||||
operator.OverrideJoin,
|
||||
operator.OverrideModeSetEdit,
|
||||
operator.OverrideModeSetObject,
|
||||
operator.OverrideOriginSet,
|
||||
operator.OverrideOutlinerDelete,
|
||||
operator.OverridePasteBuffer,
|
||||
operator.PurgeUnusedRepresentations,
|
||||
operator.RefreshAggregate,
|
||||
operator.RemoveConnection,
|
||||
operator.RemoveRepresentation,
|
||||
operator.SelectConnection,
|
||||
@@ -50,6 +52,7 @@ classes = (
|
||||
ui.BIM_PT_connections,
|
||||
ui.BIM_PT_mesh,
|
||||
ui.BIM_PT_workarounds,
|
||||
ui.BIM_MT_object_set_origin,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ class ConnectionsData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"connections": cls.connections()}
|
||||
cls.data = {"connections": cls.connections(), "is_connection_realization": cls.is_connection_realization()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -114,7 +114,13 @@ class ConnectionsData:
|
||||
else:
|
||||
related_element = rel.RelatedElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
realizing_elements = []
|
||||
realizing_elements_connection_type = ""
|
||||
if rel.is_a("IfcRelConnectsWithRealizingElements"):
|
||||
realizing_elements.extend(rel.RealizingElements)
|
||||
realizing_elements_connection_type = rel.ConnectionType
|
||||
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
related_element_connection_type = rel.RelatedConnectionType
|
||||
else:
|
||||
related_element_connection_type = ""
|
||||
@@ -125,6 +131,8 @@ class ConnectionsData:
|
||||
"is_relating": True,
|
||||
"Name": related_element.Name or "Unnamed",
|
||||
"ConnectionType": related_element_connection_type,
|
||||
"realizing_elements": realizing_elements,
|
||||
"realizing_elements_connection_type": realizing_elements_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -134,7 +142,13 @@ class ConnectionsData:
|
||||
else:
|
||||
relating_element = rel.RelatingElement
|
||||
|
||||
if element.is_a("IfcRelConnectsPathElements"):
|
||||
realizing_elements = []
|
||||
realizing_elements_connection_type = ""
|
||||
if rel.is_a("IfcRelConnectsWithRealizingElements"):
|
||||
realizing_elements.extend(rel.RealizingElements)
|
||||
realizing_elements_connection_type = rel.ConnectionType
|
||||
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
relating_element_connection_type = rel.RelatingConnectionType
|
||||
else:
|
||||
relating_element_connection_type = ""
|
||||
@@ -145,11 +159,30 @@ class ConnectionsData:
|
||||
"is_relating": False,
|
||||
"Name": relating_element.Name or "Unnamed",
|
||||
"ConnectionType": relating_element_connection_type,
|
||||
"realizing_elements": realizing_elements,
|
||||
"realizing_elements_connection_type": realizing_elements_connection_type,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def is_connection_realization(cls):
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
connections = element.IsConnectionRealization
|
||||
if not connections:
|
||||
return
|
||||
|
||||
results = []
|
||||
for rel in connections:
|
||||
data = {
|
||||
"realizing_elements_connection_type": rel.ConnectionType,
|
||||
"connected_from": rel.RelatingElement,
|
||||
"connected_to": rel.RelatedElement,
|
||||
}
|
||||
results.append(data)
|
||||
return results
|
||||
|
||||
|
||||
class DerivedPlacementsData:
|
||||
data = {}
|
||||
|
||||
@@ -57,6 +57,31 @@ class EditObjectPlacement(bpy.types.Operator, Operator):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
|
||||
class OverrideOriginSet(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.override_origin_set"
|
||||
bl_label = "IFC Origin Set"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
origin_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
objs = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if tool.Ifc.is_moved(obj):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
representation = tool.Geometry.get_active_representation(obj)
|
||||
if not representation:
|
||||
continue
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
if not tool.Geometry.is_meshlike(representation):
|
||||
continue
|
||||
bpy.ops.object.origin_set(type=self.origin_type)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
|
||||
class AddRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.add_representation"
|
||||
bl_label = "Add Representation"
|
||||
@@ -185,6 +210,15 @@ class RemoveRepresentation(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
|
||||
class PurgeUnusedRepresentations(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.purge_unused_representations"
|
||||
bl_label = "Purge Unused Representations"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.purge_unused_representations(tool.Ifc, tool.Geometry)
|
||||
|
||||
|
||||
class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.update_representation"
|
||||
bl_label = "Update Representation"
|
||||
@@ -214,6 +248,10 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
material = ifcopenshell.util.element.get_material(product, should_skip_usage=True)
|
||||
|
||||
if getattr(product, "HasOpenings", False) and obj.data.BIMMeshProperties.has_openings_applied:
|
||||
# Meshlike things with openings can only be updated without openings applied.
|
||||
return
|
||||
|
||||
if not product.is_a("IfcGridAxis"):
|
||||
tool.Geometry.clear_cache(product)
|
||||
|
||||
@@ -231,8 +269,15 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
if self.ifc_representation_class == "IfcTessellatedFaceSet":
|
||||
# We are explicitly casting to a tessellation, so remove all parametric materials.
|
||||
element_type = ifcopenshell.util.element.get_type(product)
|
||||
if element_type: # Some invalid IFCs use material sets without a type.
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
|
||||
else:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
@@ -285,7 +330,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
|
||||
"style.assign_representation_styles",
|
||||
self.file,
|
||||
shape_representation=new_representation,
|
||||
styles=tool.Geometry.get_styles(obj),
|
||||
styles=tool.Geometry.get_styles(obj, only_assigned_to_faces=True),
|
||||
should_use_presentation_style_assignment=context.scene.BIMGeometryProperties.should_use_presentation_style_assignment,
|
||||
)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
@@ -353,6 +398,8 @@ class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
|
||||
parameters = context.active_object.data.BIMMeshProperties.ifc_parameters
|
||||
self.report({"INFO"}, f"{len(parameters)} parameters found.")
|
||||
|
||||
|
||||
class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
@@ -637,9 +684,17 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING":
|
||||
continue # For now, don't copy drawings until we stabilise a bit more. It's tricky.
|
||||
|
||||
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
new_obj = obj.copy()
|
||||
temp_data = None
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
# assure root.copy_class won't replace the previous mesh globally
|
||||
temp_data = obj.data.copy()
|
||||
new_obj.data = temp_data
|
||||
if obj == context.active_object:
|
||||
self.new_active_obj = new_obj
|
||||
for collection in obj.users_collection:
|
||||
@@ -648,8 +703,13 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
new_obj.select_set(True)
|
||||
# clear object's collection so it will be able to have it's own
|
||||
new_obj.BIMObjectProperties.collection = None
|
||||
# Copy the actual class
|
||||
# copy the actual class
|
||||
new = blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj)
|
||||
|
||||
# clean up the orphaned mesh with ifc id of the original object to avoid confusion
|
||||
if new and temp_data:
|
||||
tool.Blender.remove_data_block(temp_data)
|
||||
|
||||
if new:
|
||||
array_pset = ifcopenshell.util.element.get_pset(new, "BBIM_Array")
|
||||
if array_pset:
|
||||
@@ -660,7 +720,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
tool.Boundary.decorate_boundary(new_obj)
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
|
||||
class OverrideDuplicateMoveLinkedMacro(bpy.types.Macro):
|
||||
@@ -705,6 +765,10 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
|
||||
old_to_new = {}
|
||||
for obj in context.selected_objects:
|
||||
# Prior to duplicating, sync the object placement to make decomposition recreation more stable.
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
new_obj = obj.copy()
|
||||
if obj.data:
|
||||
new_obj.data = obj.data.copy()
|
||||
@@ -724,7 +788,7 @@ class OverrideDuplicateMoveLinked(bpy.types.Operator):
|
||||
old_to_new[tool.Ifc.get_entity(obj)] = new
|
||||
# Recreate decompositions
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1012,7 +1076,7 @@ class OverrideDuplicateMoveAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1159,7 +1223,7 @@ class RefreshAggregate(bpy.types.Operator):
|
||||
|
||||
tool.Root.recreate_decompositions(relationships, old_to_new)
|
||||
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1317,8 +1381,13 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
is_profile = True
|
||||
representation_class = tool.Geometry.get_ifc_representation_class(element, representation)
|
||||
if usage_type == "PROFILE":
|
||||
operator = lambda: bpy.ops.bim.hotkey(hotkey="A_E")
|
||||
elif representation_class and "IfcCircleProfileDef" in representation_class:
|
||||
self.report({"INFO"}, "Can't edit Circle Profile Extrusion")
|
||||
obj.select_set(False)
|
||||
continue
|
||||
elif (
|
||||
tool.Geometry.is_profile_based(obj.data)
|
||||
or usage_type == "LAYER3"
|
||||
@@ -1347,6 +1416,7 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
continue
|
||||
|
||||
if tool.Geometry.is_meshlike(representation):
|
||||
tool.Ifc.edit(obj)
|
||||
if getattr(element, "HasOpenings", None):
|
||||
# Mesh elements with openings must disable openings
|
||||
# so that you can edit the original topology.
|
||||
@@ -1365,8 +1435,9 @@ class OverrideModeSetEdit(bpy.types.Operator):
|
||||
else:
|
||||
obj.select_set(False)
|
||||
continue
|
||||
|
||||
if not context.selected_objects or len(context.selected_objects) != len(selected_objs):
|
||||
if len(selected_objs) > 1 and (
|
||||
not context.selected_objects or len(context.selected_objects) != len(selected_objs)
|
||||
):
|
||||
# We are trying to edit at least one non-mesh-like object : Display a hint to the user
|
||||
self.report({"INFO"}, "Only mesh-compatible representations may be edited concurrently in edit mode.")
|
||||
|
||||
@@ -1408,9 +1479,11 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
self.reload_representation(obj)
|
||||
else:
|
||||
self.reload_representation(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
for obj in self.unchanged_objs_with_openings:
|
||||
self.reload_representation(obj)
|
||||
tool.Ifc.finish_edit(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def reload_representation(self, obj):
|
||||
@@ -1490,6 +1563,8 @@ class OverrideModeSetObject(bpy.types.Operator):
|
||||
self.edited_objs.append(obj)
|
||||
elif getattr(element, "HasOpenings", None):
|
||||
self.unchanged_objs_with_openings.append(obj)
|
||||
else:
|
||||
tool.Ifc.finish_edit(obj)
|
||||
|
||||
if self.edited_objs:
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import blenderbim.tool as tool
|
||||
from bpy.types import Panel
|
||||
from bpy.types import Panel, Menu
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.helper import prop_with_search
|
||||
from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData
|
||||
@@ -29,6 +29,29 @@ def object_menu(self, context):
|
||||
self.layout.operator("bim.override_object_duplicate_move", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_object_delete", icon="PLUGIN")
|
||||
self.layout.operator("bim.override_paste_buffer", icon="PLUGIN")
|
||||
self.layout.menu("BIM_MT_object_set_origin", icon="PLUGIN")
|
||||
|
||||
|
||||
class BIM_MT_object_set_origin(Menu):
|
||||
bl_idname = "BIM_MT_object_set_origin"
|
||||
bl_label = "IFC Set Origin"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Geometry to Origin"
|
||||
).origin_type = "GEOMETRY_ORIGIN"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Geometry"
|
||||
).origin_type = "ORIGIN_GEOMETRY"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to 3D Cursor"
|
||||
).origin_type = "ORIGIN_CURSOR"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Surface)"
|
||||
).origin_type = "ORIGIN_CENTER_OF_MASS"
|
||||
self.layout.operator(
|
||||
"bim.override_origin_set", icon="PLUGIN", text="IFC Origin to Center of Mass (Volume)"
|
||||
).origin_type = "ORIGIN_CENTER_OF_VOLUME"
|
||||
|
||||
|
||||
def outliner_menu(self, context):
|
||||
@@ -111,7 +134,7 @@ class BIM_PT_connections(Panel):
|
||||
layout = self.layout
|
||||
props = context.active_object.BIMObjectProperties
|
||||
|
||||
if not ConnectionsData.data["connections"]:
|
||||
if not ConnectionsData.data["connections"] and not ConnectionsData.data["is_connection_realization"]:
|
||||
layout.label(text="No connections found")
|
||||
|
||||
for connection in ConnectionsData.data["connections"]:
|
||||
@@ -123,6 +146,44 @@ class BIM_PT_connections(Panel):
|
||||
op = row.operator("bim.remove_connection", icon="X", text="")
|
||||
op.connection = connection["id"]
|
||||
|
||||
if connection["realizing_elements"]:
|
||||
row = self.layout.row(align=True)
|
||||
connection_type = connection["realizing_elements_connection_type"]
|
||||
connection_type = f" ({connection_type})" if connection_type else ""
|
||||
row.label(text=f"Realizing elements{connection_type}:")
|
||||
|
||||
for element in connection["realizing_elements"]:
|
||||
row = self.layout.row(align=True)
|
||||
obj = tool.Ifc.get_object(element)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = element.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
# display connections where element is connection realization
|
||||
connections = ConnectionsData.data["is_connection_realization"]
|
||||
if not connections:
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Element is connections realization:")
|
||||
for connection in connections:
|
||||
# NOTE: not displayed yet
|
||||
connection_type = connection["realizing_elements_connection_type"]
|
||||
connection_type = f" ({connection_type})" if connection_type else ""
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
connected_from = connection["connected_from"]
|
||||
obj = tool.Ifc.get_object(connected_from)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_from.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
row.label(text="", icon="FORWARD")
|
||||
|
||||
connected_to = connection["connected_to"]
|
||||
obj = tool.Ifc.get_object(connected_to)
|
||||
row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF").ifc_id = connected_to.id()
|
||||
row.label(text=obj.name)
|
||||
|
||||
|
||||
class BIM_PT_mesh(Panel):
|
||||
bl_label = "Representation Utilities"
|
||||
|
||||
@@ -34,6 +34,7 @@ classes = (
|
||||
operator.GetCursorLocation,
|
||||
operator.SetCursorLocation,
|
||||
operator.ConvertAngleToCoordinates,
|
||||
operator.ImportPlot,
|
||||
prop.BIMGeoreferenceProperties,
|
||||
ui.BIM_PT_gis,
|
||||
ui.BIM_PT_gis_utilities,
|
||||
|
||||
@@ -76,7 +76,7 @@ class SetIfcGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_grid_north"
|
||||
bl_label = "Set IFC Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC grid north"
|
||||
bl_description = "Set IFC grid north based on current Blender North Offset"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_grid_north(tool.Georeference)
|
||||
@@ -86,7 +86,7 @@ class SetBlenderGridNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_grid_north"
|
||||
bl_label = "Set Blender Grid North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender grid north"
|
||||
bl_description = "Set Blender North Offset based on current IFC grid north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_grid_north(tool.Georeference)
|
||||
@@ -110,7 +110,7 @@ class SetCursorLocation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_cursor_location"
|
||||
bl_label = "Set Cursor Location"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Move curson location to the specified coordinates"
|
||||
bl_description = "Move cursor location to the specified coordinates"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -125,7 +125,7 @@ class SetIfcTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_ifc_true_north"
|
||||
bl_label = "Set IFC True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set IFC True north"
|
||||
bl_description = "Set IFC true north based on current Blender North Offset"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_ifc_true_north(tool.Georeference)
|
||||
@@ -135,7 +135,7 @@ class SetBlenderTrueNorth(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.set_blender_true_north"
|
||||
bl_label = "Set Blender True North"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Set Blender true north"
|
||||
bl_description = "Set Blender North Offset based on current IFC true north"
|
||||
|
||||
def _execute(self, context):
|
||||
core.set_blender_true_north(tool.Georeference)
|
||||
@@ -187,3 +187,20 @@ class ConvertAngleToCoordinates(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.convert_angle_to_coord(tool.Georeference, type=self.type)
|
||||
|
||||
|
||||
class ImportPlot(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.import_plot"
|
||||
bl_label = "Import Plot"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Import plot"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
core.import_plot(tool.Georeference, filepath=self.filepath)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
@@ -58,6 +58,8 @@ class BIM_PT_materials(Panel):
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
if self.props.material_type == "IfcMaterial":
|
||||
if not self.props.active_material_id:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
material = self.props.materials[self.props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
@@ -66,13 +68,10 @@ class BIM_PT_materials(Panel):
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL").material = material.ifc_definition_id
|
||||
self.draw_editable_material_attributes_ui()
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.material = material.ifc_definition_id
|
||||
row.operator("bim.enable_editing_material", text="", icon="GREASEPENCIL").material = material.ifc_definition_id
|
||||
row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id
|
||||
else:
|
||||
row.operator("bim.add_material", text="", icon="ADD")
|
||||
else:
|
||||
row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type
|
||||
if self.props.materials and self.props.active_material_index < len(self.props.materials):
|
||||
|
||||
@@ -50,8 +50,9 @@ class BIM_PT_misc_utilities(bpy.types.Panel):
|
||||
row.operator("bim.clean_wireframes")
|
||||
row = layout.row()
|
||||
row.operator("bim.patch_non_parametric_mep_segment")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.enable_editing_sketch_extrusion_profile", text="Start Sketching")
|
||||
row.operator("bim.edit_sketch_extrusion_profile", text="", icon="FILE_REFRESH")
|
||||
row.operator("bim.disable_editing_sketch_extrusion_profile", text="", icon="CANCEL")
|
||||
row = layout.row()
|
||||
row.operator("bim.import_plot", text="Import Plot Coordinates", icon="FILE_FOLDER")
|
||||
|
||||
@@ -78,6 +78,7 @@ classes = (
|
||||
opening.AddPotentialHalfSpaceSolid,
|
||||
opening.AddPotentialOpening,
|
||||
opening.EditOpenings,
|
||||
opening.CloneOpening,
|
||||
opening.FlipFill,
|
||||
opening.HideBooleans,
|
||||
opening.HideOpenings,
|
||||
@@ -178,6 +179,7 @@ classes = (
|
||||
roof.SetGableRoofEdgeAngle,
|
||||
mep.MEPAddObstruction,
|
||||
mep.MEPAddTransition,
|
||||
mep.MEPAddBend,
|
||||
)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
@@ -130,11 +130,11 @@ def update_door_modifier_representation(context, obj):
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.OperationType = props.door_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
# occurrences attributes
|
||||
occurrences = tool.Ifc.get_all_element_occurrences(element)
|
||||
for occurrence in occurrences:
|
||||
occurrence.OverallWidth = props.overall_width / si_conversion
|
||||
occurrence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
@@ -29,16 +29,18 @@ import ifcopenshell.util.system
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import mathutils.geometry
|
||||
import numpy as np
|
||||
import blenderbim.bim.handler
|
||||
import blenderbim.core.type
|
||||
import blenderbim.core.root
|
||||
import blenderbim.core.geometry
|
||||
import blenderbim.tool as tool
|
||||
from math import pi, degrees, radians
|
||||
from math import pi, degrees, radians, sin, cos, asin, tan
|
||||
from copy import copy
|
||||
from mathutils import Vector, Matrix
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
from blenderbim.bim.module.model.profile import DumbProfileJoiner
|
||||
from blenderbim.tool.cad import VTX_PRECISION
|
||||
|
||||
V = lambda *x: Vector([float(i) for i in x])
|
||||
|
||||
@@ -46,7 +48,8 @@ V = lambda *x: Vector([float(i) for i in x])
|
||||
class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.regenerate_distribution_element"
|
||||
bl_description = (
|
||||
"Regenerates the positions and segment lengths of a distribution element and all connected elements."
|
||||
"Regenerates the positions and segment lengths of a distribution element and all connected elements.\n"
|
||||
"Will try to adjust as less elements as possible, never rotate them. Segments will also try to change their length to fit"
|
||||
)
|
||||
bl_label = "Regenerate Distribution Element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -73,29 +76,68 @@ class RegenerateDistributionElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
[e for e in ifcopenshell.util.system.get_connected_from(element) if e not in processed_elements]
|
||||
)
|
||||
|
||||
if len(connected) == 1:
|
||||
extend_branch(list(connected)[0], branch, element)
|
||||
else:
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
for connected_element in connected:
|
||||
branch_element["children"].append(extend_branch(connected_element, [], element))
|
||||
|
||||
return branch
|
||||
|
||||
queue = extend_branch(current_element, [])[0]["children"]
|
||||
extended_branch = extend_branch(current_element, [])
|
||||
queue = extended_branch[0]["children"]
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint(queue)
|
||||
|
||||
def get_connected_ports_between(element1, element2):
|
||||
ports1 = tool.System.get_ports(element1)
|
||||
ports2 = tool.System.get_ports(element2)
|
||||
|
||||
for p in ports1:
|
||||
connected_port = tool.System.get_connected_port(p)
|
||||
# in IFC2X3 there is no PredefinedType
|
||||
if getattr(p, "PredefinedType", None) == "WIRELESS":
|
||||
continue
|
||||
if connected_port in ports2:
|
||||
return p, connected_port
|
||||
|
||||
return None, None
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
|
||||
def process_branch(branch):
|
||||
for branch_element in branch:
|
||||
element = branch_element["element"]
|
||||
print("processing", element)
|
||||
predecessor = branch_element["predecessor"]
|
||||
if False: # If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
# Perform the extend, translate, rotate, etc the element as necessary based on the predecessor.
|
||||
# For segments, prioritise extensions instead of translations.
|
||||
# For everything else, only translate. No rotation.
|
||||
# For everything besides segments, only translate. No rotation.
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
obj_pred = tool.Ifc.get_object(predecessor)
|
||||
if tool.Ifc.is_moved(obj):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
if tool.Ifc.is_moved(obj_pred):
|
||||
blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj_pred)
|
||||
|
||||
port, port_pred = get_connected_ports_between(element, predecessor)
|
||||
port_matrix_pred = tool.Model.get_element_matrix(port_pred)
|
||||
|
||||
# Only segments can be extended
|
||||
# extension for them takes priority over translation
|
||||
if element.is_a("IfcFlowSegment"):
|
||||
DumbProfileJoiner().join_E(obj, port_matrix_pred.translation * si_conversion)
|
||||
context.view_layer.update() # update since extrusion might involve changing object's location
|
||||
|
||||
port_martix = tool.Model.get_element_matrix(port)
|
||||
port_location = port_martix.translation
|
||||
port_location_pred = port_matrix_pred.translation
|
||||
if not tool.Cad.are_vectors_equal(port_location, port_location_pred):
|
||||
obj.location += (port_location_pred - port_location) * si_conversion
|
||||
context.view_layer.update() # otherwise tool.Ifc.is_moved won't get triggered
|
||||
else:
|
||||
# If the element does not need to be transformed, return early.
|
||||
return
|
||||
|
||||
for child_branch in branch_element["children"]:
|
||||
process_branch(child_branch)
|
||||
|
||||
@@ -161,6 +203,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
is_on_axis2 = tool.Cad.is_point_on_edge(intersect2, axis2)
|
||||
if not is_on_axis1 and not is_on_axis2:
|
||||
fitting_type = "BEND"
|
||||
bpy.ops.bim.mep_add_bend()
|
||||
elif is_on_axis1 and is_on_axis2:
|
||||
fitting_type = "CROSS"
|
||||
else:
|
||||
@@ -168,6 +211,7 @@ class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
bpy.ops.bim.mep_add_transition()
|
||||
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
@@ -229,6 +273,10 @@ class MEPGenerator:
|
||||
if port_position == "end_port":
|
||||
tool.Model.edit_element_placement(port, end_port_matrix)
|
||||
|
||||
continue
|
||||
|
||||
# NOTE: currently this functionality is moved to bim.regenerate_distribution_element
|
||||
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
if not connected_port:
|
||||
continue
|
||||
@@ -289,7 +337,7 @@ class MEPGenerator:
|
||||
class_name = "".join(split_camel_case(element.is_a())[:-1] + [mep_class_type])
|
||||
return class_name
|
||||
|
||||
def get_compatible_fitting_type(self, segment_or_segments, port_or_ports, predefined_type):
|
||||
def get_compatible_fitting_type(self, segment_or_segments, port_or_ports, predefined_type, bbim_data=None):
|
||||
"""
|
||||
returns a dict of compatible fitting_type and start_port_match flag to correctly place the fitting.
|
||||
|
||||
@@ -302,7 +350,12 @@ class MEPGenerator:
|
||||
|
||||
There lies the problem that it won't be
|
||||
able to identify the fittings that were not yet connected to any segments yet.
|
||||
|
||||
|
||||
`bbim_data` is used to find compatible fitting build with BBIM parametrically (BBIM_Fitting pset).
|
||||
All data in `bbim_data` supposed to be in project units.
|
||||
"""
|
||||
|
||||
if not isinstance(segment_or_segments, collections.abc.Iterable):
|
||||
segments = [segment_or_segments]
|
||||
ports = [port_or_ports]
|
||||
@@ -310,14 +363,40 @@ class MEPGenerator:
|
||||
segments = segment_or_segments
|
||||
ports = port_or_ports
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
precision = VTX_PRECISION / si_conversion
|
||||
|
||||
segments_data = []
|
||||
for segment, port in zip(segments, ports, strict=True):
|
||||
segment_type = ifcopenshell.util.element.get_type(segment)
|
||||
# if segment doesn't have type we cannot check compatibility by available occurences
|
||||
# if segment doesn't have type we cannot check compatibility by available occurrences
|
||||
if segment_type is None:
|
||||
return
|
||||
segments_data.append((segment_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
# TODO: test it with flipped transition where start length != end length
|
||||
def compatible_with_bbim_data(fitting_type):
|
||||
if not bbim_data:
|
||||
return True
|
||||
fitting_type_obj = tool.Ifc.get_object(fitting_type)
|
||||
fitting_bbim_data = tool.Model.get_modeling_bbim_pset_data(fitting_type_obj, "BBIM_Fitting")
|
||||
if not fitting_bbim_data:
|
||||
return False
|
||||
|
||||
fitting_bbim_data = fitting_bbim_data["data_dict"]
|
||||
for key in bbim_data:
|
||||
requested_value = bbim_data[key]
|
||||
fitting_value = fitting_bbim_data[key]
|
||||
if isinstance(requested_value, float):
|
||||
compare_precision = None if key == "angle" else precision
|
||||
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
|
||||
elif isinstance(fitting_value, list):
|
||||
compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision)
|
||||
if not compare:
|
||||
return False
|
||||
return True
|
||||
|
||||
def are_connected_elements_compatible(segments_data, fitting_data):
|
||||
# prevent arguments mutation, not using deepcopy because of the errors with ifc elements
|
||||
segments_data = [copy(i) for i in segments_data]
|
||||
@@ -344,7 +423,7 @@ class MEPGenerator:
|
||||
|
||||
# NOTE: I have a feeling that there are cases where order
|
||||
# in which we're checking the segments is important
|
||||
# but I couldn't pin it down exact cases
|
||||
# but I couldn't pin it down to exact cases
|
||||
for test_segment_data in fitting_data[:]:
|
||||
for base_segment_data in segments_data:
|
||||
if not are_segments_compatible(test_segment_data, base_segment_data):
|
||||
@@ -355,42 +434,62 @@ class MEPGenerator:
|
||||
return len(segments_data) == 0
|
||||
|
||||
def pack_return_data(fitting_type, ports, segments_data):
|
||||
packed_data = {"fitting_type": fitting_type}
|
||||
|
||||
if predefined_type == "OBSTRUCTION":
|
||||
return packed_data
|
||||
|
||||
for port in ports:
|
||||
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
if tool.Cad.is_x(port_local_position.length, 0.0):
|
||||
start_port = port
|
||||
break
|
||||
|
||||
connected_port = tool.System.get_connected_port(start_port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
return {"fitting_type": fitting_type, "start_port_match": element_type == segments_data[0][0]}
|
||||
packed_data["start_port_match"] = element_type == segments_data[0][0]
|
||||
|
||||
return packed_data
|
||||
|
||||
fitting_types = tool.Ifc.get().by_type(self.get_mep_element_class_name(segments[0], "FittingType"))
|
||||
for fitting_type in fitting_types:
|
||||
if fitting_type.PredefinedType != predefined_type:
|
||||
continue
|
||||
fittings = tool.Ifc.get_all_element_occurences(fitting_type)
|
||||
fittings = tool.Ifc.get_all_element_occurrences(fitting_type)
|
||||
if not fittings:
|
||||
continue
|
||||
fitting = fittings[0]
|
||||
|
||||
ports = ifcopenshell.util.system.get_ports(fitting)
|
||||
fitting_data = []
|
||||
fitting_connected_to_none_type = False
|
||||
for port in ports:
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
if element_type is None:
|
||||
fitting_connected_to_none_type = True
|
||||
for fitting in fittings:
|
||||
ports = ifcopenshell.util.system.get_ports(fitting)
|
||||
fitting_data = []
|
||||
skipped_the_occurrence = False
|
||||
for port in ports:
|
||||
connected_port = tool.System.get_connected_port(port)
|
||||
|
||||
# fitting port is not connected to anything
|
||||
if not connected_port:
|
||||
skipped_the_occurrence = True
|
||||
break
|
||||
|
||||
connected_element = tool.System.get_port_relating_element(connected_port)
|
||||
element_type = ifcopenshell.util.element.get_type(connected_element)
|
||||
|
||||
# fitting is connected to none type
|
||||
if element_type is None:
|
||||
skipped_the_occurrence = True
|
||||
break
|
||||
|
||||
fitting_data.append((element_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
# if we skipped the occurrence we still need to check other occurrences
|
||||
# otherwise checking 1 occurrence is enough
|
||||
if not skipped_the_occurrence:
|
||||
if compatible_with_bbim_data(fitting_type) and are_connected_elements_compatible(
|
||||
segments_data, fitting_data
|
||||
):
|
||||
return pack_return_data(fitting_type, ports, segments_data)
|
||||
break
|
||||
fitting_data.append((element_type, port.PredefinedType, port.SystemType))
|
||||
|
||||
if fitting_connected_to_none_type:
|
||||
continue
|
||||
|
||||
if are_connected_elements_compatible(segments_data, fitting_data):
|
||||
return pack_return_data(fitting_type, ports, segments_data)
|
||||
|
||||
def create_obstruction_type(self, segment):
|
||||
# code is very similar to "bim.add_type"
|
||||
@@ -453,7 +552,7 @@ class MEPGenerator:
|
||||
obstruction_type = self.create_obstruction_type(segment)
|
||||
|
||||
profile_joiner = DumbProfileJoiner()
|
||||
# create obstruction occurence and setup it's length and port
|
||||
# create obstruction occurrence and setup it's length and port
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=obstruction_type.id())
|
||||
obstruction_obj = bpy.context.active_object
|
||||
@@ -530,6 +629,9 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
end_length: bpy.props.FloatProperty(
|
||||
name="End Length", description="Transition end length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
angle: bpy.props.FloatProperty(
|
||||
name="Transition Angle", description="Transition angle in degrees", default=pi / 6, subtype="ANGLE"
|
||||
)
|
||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||
|
||||
@@ -570,57 +672,143 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
start_axis = tool.Model.get_flow_segment_axis(start_object)
|
||||
end_axis = tool.Model.get_flow_segment_axis(end_object)
|
||||
start_object_rotation = start_object.matrix_world.to_quaternion()
|
||||
start_object_z_basis = start_object_rotation.to_matrix().col[2] # z basis vector
|
||||
keep_only_z_axis = lambda p_ws: p_ws.dot(start_object_z_basis) * start_object_z_basis
|
||||
|
||||
# TODO: support cases when segments are partially or completely overlapping each other
|
||||
if not tool.Cad.are_edges_collinear(start_axis, end_axis):
|
||||
self.report({"ERROR"}, f"Failed to add transition - non collinear segments are not yet supported.")
|
||||
if not tool.Cad.are_edges_parallel(start_axis, end_axis):
|
||||
self.report({"ERROR"}, f"Failed to add transition - segments are not parallel.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# TODO: support different profiles rotation by local Z
|
||||
# check rotation difference
|
||||
end_object_rotation = end_object.matrix_world.to_quaternion()
|
||||
rotation_difference_z = (
|
||||
start_object.matrix_world.to_quaternion().rotation_difference(end_object_rotation).to_euler().z
|
||||
)
|
||||
|
||||
def is_multiple_of_pi(value):
|
||||
n = round(value / pi)
|
||||
return tool.Cad.is_x(abs(value - n * pi), 0)
|
||||
|
||||
if not is_multiple_of_pi(rotation_difference_z):
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"There is some rotation difference between profiles by local Z axis: "
|
||||
f"{round(degrees(rotation_difference_z))} deg, this kind of transition is not yet supported.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# setup start / end points
|
||||
start_segment_data = MEPGenerator().get_segment_data(start_element)
|
||||
end_segment_data = MEPGenerator().get_segment_data(end_element)
|
||||
end_port = end_segment_data["start_port"]
|
||||
start_port = start_segment_data["end_port"]
|
||||
|
||||
points_ports_map = {
|
||||
start_segment_data["start_point"]: start_segment_data["start_port"],
|
||||
start_segment_data["end_point"]: start_segment_data["end_port"],
|
||||
end_segment_data["start_point"]: end_segment_data["start_port"],
|
||||
end_segment_data["end_point"]: end_segment_data["end_port"],
|
||||
}
|
||||
|
||||
start_point, end_point = tool.Cad.closest_points(
|
||||
# transition points
|
||||
(start_point, end_point), (first_segment_start, second_segment_end) = tool.Cad.closest_points(
|
||||
(start_segment_data["start_point"], start_segment_data["end_point"]),
|
||||
(end_segment_data["start_point"], end_segment_data["end_point"]),
|
||||
)
|
||||
transition_dir = (end_point - start_point).normalized()
|
||||
start_port = points_ports_map[start_point]
|
||||
end_port = points_ports_map[end_point]
|
||||
|
||||
# figure profile offset
|
||||
base_transition_dir = keep_only_z_axis(end_point - start_point).normalized()
|
||||
flip_profile_offset = base_transition_dir.dot(start_object_z_basis) < 0
|
||||
|
||||
if tool.Cad.are_edges_collinear(start_axis, end_axis):
|
||||
profile_offset = V(0, 0)
|
||||
else:
|
||||
to_start_object_space = start_object_rotation.inverted()
|
||||
profile_offset = (
|
||||
(to_start_object_space @ end_object.location) - (to_start_object_space @ start_object.location)
|
||||
).xy
|
||||
profile_offset = profile_offset / si_conversion
|
||||
if flip_profile_offset:
|
||||
profile_offset *= V(1, -1)
|
||||
|
||||
# world space profile offset
|
||||
profile_offset_ws = start_object_rotation @ (profile_offset * si_conversion).to_3d()
|
||||
|
||||
def get_segments_length():
|
||||
start_dir = (start_point - first_segment_start).normalized()
|
||||
segments_vector = second_segment_end - first_segment_start
|
||||
return segments_vector.dot(start_dir)
|
||||
|
||||
entire_length = get_segments_length()
|
||||
|
||||
# can't rely on (end_point-start_point) here because
|
||||
# transition might change the segments length and therefore direction will be changed
|
||||
segments_dir = (start_point - first_segment_start).normalized()
|
||||
|
||||
# add transition representation
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
rep, transition_data = builder.mep_transition_shape(
|
||||
start_element, end_element, self.start_length / si_conversion, self.end_length / si_conversion
|
||||
start_element,
|
||||
end_element,
|
||||
self.start_length / si_conversion,
|
||||
self.end_length / si_conversion,
|
||||
angle=degrees(self.angle),
|
||||
profile_offset=profile_offset,
|
||||
)
|
||||
|
||||
if not rep:
|
||||
self.report({"ERROR"}, f"Failed to add transition - this kind of profiles is not yet supported.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
middle_point = (start_point + end_point) / 2
|
||||
full_transition_length = transition_data["full_transition_length"] * si_conversion
|
||||
start_segment_extend_point = middle_point - transition_dir * full_transition_length / 2
|
||||
end_segment_extend_point = middle_point + transition_dir * full_transition_length / 2
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point)
|
||||
if full_transition_length >= entire_length:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Failed to add transition - transition length is larger the segments and the distance between them.\n"
|
||||
+ f"Transition length: {full_transition_length:.2f}m, segments length: {entire_length:.2f}m",
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# calculate bunch of points to for adjustments
|
||||
middle_point = keep_only_z_axis((start_point + end_point) / 2 - start_point) + start_point
|
||||
start_segment_extend_point = middle_point - segments_dir * full_transition_length / 2
|
||||
end_segment_extend_point = middle_point + segments_dir * full_transition_length / 2 + profile_offset_ws
|
||||
transition_dir = keep_only_z_axis(end_segment_extend_point - start_segment_extend_point).normalized()
|
||||
|
||||
# adjust the segments
|
||||
end_object_rotation = end_object.matrix_world.to_quaternion()
|
||||
end_object_z_basis = end_object_rotation.to_matrix().col[2] # z basis vector
|
||||
|
||||
# TODO: do it beforehand, as with bends
|
||||
if tool.Cad.is_x(start_object_z_basis.dot(transition_dir), 1):
|
||||
start_connection = "ATEND"
|
||||
else:
|
||||
start_connection = "ATSTART"
|
||||
if tool.Cad.is_x(end_object_z_basis.dot(transition_dir), 1):
|
||||
end_connection = "ATSTART"
|
||||
else:
|
||||
end_connection = "ATEND"
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection)
|
||||
|
||||
parametric_data = {
|
||||
"start_length": self.start_length / si_conversion,
|
||||
"end_length": self.end_length / si_conversion,
|
||||
"profile_offset": profile_offset,
|
||||
"angle": degrees(self.angle),
|
||||
}
|
||||
|
||||
# find the compatible fitting type
|
||||
fitting_data = MEPGenerator().get_compatible_fitting_type(
|
||||
[start_element, end_element], [start_port, end_port], "TRANSITION"
|
||||
[start_element, end_element], [start_port, end_port], "TRANSITION", bbim_data=parametric_data
|
||||
)
|
||||
|
||||
transition_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||
|
||||
if not transition_type:
|
||||
if transition_type:
|
||||
# TODO: handle the case without creating a representation in the first place?
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
else: # create new fitting type if nothing is compatible
|
||||
mesh = bpy.data.meshes.new("Transition")
|
||||
obj = bpy.data.objects.new("Transition", mesh)
|
||||
transition_type = blenderbim.core.root.assign_class(
|
||||
@@ -643,25 +831,353 @@ class MEPAddTransition(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
# create transition element
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=transition_type.id())
|
||||
transition_obj = bpy.context.active_object
|
||||
|
||||
# adjust transition segment rotation and location
|
||||
# required since we'll base our `transition_obj_dir` on this
|
||||
transition_obj.matrix_world = start_object.matrix_world
|
||||
context.view_layer.update()
|
||||
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
|
||||
direction_match = tool.Cad.are_vectors_equal(transition_obj_dir, transition_dir)
|
||||
|
||||
# depending on transition direction we may need to flip it or attach it's origin to end segment
|
||||
# direction can be different depending on:
|
||||
# - order of the current segments
|
||||
# - order of the segments that were used with the same transition type before
|
||||
transition_obj_dir = tool.Cad.get_edge_direction(tool.Model.get_flow_segment_axis(transition_obj))
|
||||
direction_match = tool.Cad.are_vectors_equal(transition_dir, transition_obj_dir)
|
||||
# if there are no mismatches or everything matches up we don't need to flip the transition
|
||||
if start_port_match != direction_match:
|
||||
transition_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X")
|
||||
transition_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
|
||||
|
||||
# add ports and connect them
|
||||
ports = tool.System.add_ports(transition_obj)
|
||||
ports = tool.System.add_ports(transition_obj, offset_end_port=profile_offset_ws)
|
||||
if not start_port_match:
|
||||
start_port, end_port = end_port, start_port
|
||||
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
|
||||
tool.Ifc.run("system.connect_port", port1=ports[1], port2=end_port, direction="NOTDEFINED")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class MEPAddBend(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.mep_add_bend"
|
||||
bl_label = "Add Bend"
|
||||
bl_description = "Adds a bend between two MEP elements. Elements are either provided by ID or selected in Blender"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
start_length: bpy.props.FloatProperty(
|
||||
name="Start Length", description="Bend start length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
end_length: bpy.props.FloatProperty(
|
||||
name="End Length", description="Bend end length in SI units", default=0.1, subtype="DISTANCE"
|
||||
)
|
||||
start_segment_id: bpy.props.IntProperty(name="Start Segment Element ID", default=0)
|
||||
end_segment_id: bpy.props.IntProperty(name="End Segment Element ID", default=0)
|
||||
radius: bpy.props.FloatProperty(
|
||||
"Bend Inner Radius", description="Bend inner radius in SI units", default=0.2, subtype="DISTANCE"
|
||||
)
|
||||
|
||||
def _execute(self, context):
|
||||
start_element, end_element = None, None
|
||||
ifc_file = tool.Ifc.get()
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
|
||||
self.start_length, self.end_length = 0, 0
|
||||
|
||||
if not (self.start_length == 0 and self.end_length == 0):
|
||||
self.report({"ERROR"}, f"Only zero lengths are now supported.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if self.start_segment_id and self.end_segment_id:
|
||||
start_element = ifc_file.by_id(self.start_segment_id)
|
||||
end_element = ifc_file.by_id(self.end_segment_id)
|
||||
start_object = tool.Ifc.get_object(start_element)
|
||||
end_object = tool.Ifc.get_object(end_element)
|
||||
|
||||
elif len(context.selected_objects) == 2:
|
||||
start_object = context.active_object
|
||||
end_object = next(o for o in context.selected_objects if o != context.active_object)
|
||||
start_element = tool.Ifc.get_entity(start_object)
|
||||
end_element = tool.Ifc.get_entity(end_object)
|
||||
if not start_element or not end_element:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be selected for the bend.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
else:
|
||||
self.report({"ERROR"}, f"Two IFC elements should be provided for the bend.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# check rotation difference
|
||||
def rotation_difference_check():
|
||||
end_object_rotation = end_object.matrix_world.to_quaternion()
|
||||
rotation_difference = (
|
||||
start_object.matrix_world.to_quaternion().rotation_difference(end_object_rotation).to_euler()
|
||||
)
|
||||
|
||||
def is_multiple_of_pi(value):
|
||||
n = round(value / pi)
|
||||
return tool.Cad.is_x(abs(value - n * pi), 0)
|
||||
|
||||
if not is_multiple_of_pi(rotation_difference.z):
|
||||
error_msg = (
|
||||
"There is some rotation difference between profiles by local Z axis: "
|
||||
f"{round(degrees(rotation_difference.z))} deg, adding a bend is not possible."
|
||||
)
|
||||
return error_msg
|
||||
|
||||
if error_msg := rotation_difference_check():
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# check segments types
|
||||
def types_check():
|
||||
start_type = ifcopenshell.util.element.get_type(start_element)
|
||||
end_type = ifcopenshell.util.element.get_type(end_element)
|
||||
if not start_type or not end_type:
|
||||
return False
|
||||
return start_type == end_type
|
||||
|
||||
if not types_check():
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"Segments types do not match " "or one of the segments doesn't have type which is required for a bend.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# TODO: support circular profiles
|
||||
profile = tool.Model.get_flow_segment_profile(start_element)
|
||||
if not profile.is_a("IfcRectangleProfileDef"):
|
||||
self.report(
|
||||
{
|
||||
"ERROR",
|
||||
"For now Only IfcRectangleProfileDef profiles supported for a bend, "
|
||||
f"the segments are {profile.is_a()}",
|
||||
}
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
def get_dim(profile):
|
||||
if profile.is_a("IfcRectangleProfileDef"):
|
||||
return V(profile.XDim / 2, profile.YDim / 2)
|
||||
elif profile.is_a("IfcCircleProfileDef"):
|
||||
return V(profile.Radius, profile.Radius)
|
||||
return None
|
||||
|
||||
# setup start / end points
|
||||
start_object_rotation = start_object.matrix_world.to_quaternion().to_matrix()
|
||||
start_segment_data = MEPGenerator().get_segment_data(start_element)
|
||||
end_segment_data = MEPGenerator().get_segment_data(end_element)
|
||||
points_ports_map = {
|
||||
start_segment_data["start_point"]: start_segment_data["start_port"],
|
||||
start_segment_data["end_point"]: start_segment_data["end_port"],
|
||||
end_segment_data["start_point"]: end_segment_data["start_port"],
|
||||
end_segment_data["end_point"]: end_segment_data["end_port"],
|
||||
}
|
||||
|
||||
get_z_basis = lambda o: o.matrix_world.col[2].normalized().to_3d()
|
||||
segments_intersection_ws = tool.Cad.intersect_edges(
|
||||
(start_object.location, start_object.location + get_z_basis(start_object)),
|
||||
(end_object.location, end_object.location + get_z_basis(end_object)),
|
||||
)[0]
|
||||
|
||||
start_point, first_segment_start = tool.Cad.closest_and_furthest_vectors(
|
||||
segments_intersection_ws, (start_segment_data["start_point"], start_segment_data["end_point"])
|
||||
)
|
||||
end_point, second_segment_end = tool.Cad.closest_and_furthest_vectors(
|
||||
segments_intersection_ws, (end_segment_data["start_point"], end_segment_data["end_point"])
|
||||
)
|
||||
|
||||
start_port = points_ports_map[start_point]
|
||||
end_port = points_ports_map[end_point]
|
||||
start_point_on_origin = start_point == start_segment_data["start_point"]
|
||||
start_connection = "ATSTART" if start_point_on_origin else "ATEND"
|
||||
start_segment_sign = -1 if start_point_on_origin else 1
|
||||
|
||||
end_point_on_origin = end_point == end_segment_data["start_point"]
|
||||
end_connection = "ATSTART" if end_point_on_origin else "ATEND"
|
||||
end_segment_sign = -1 if end_point_on_origin else 1
|
||||
|
||||
profile_dim = get_dim(profile) * si_conversion
|
||||
|
||||
# TODO: profile offset may need to be flipped (check transition code)
|
||||
to_start_object_space = start_object_rotation.inverted()
|
||||
profile_offset = (to_start_object_space @ end_point) - (to_start_object_space @ start_point)
|
||||
|
||||
def check_for_double_bends():
|
||||
# The theory is To avoid double bends, the profile offset should occur along only two axes:
|
||||
# 1) The local Z-axis of the start segment
|
||||
# 2) One of the lateral axes (either X or Y)
|
||||
#
|
||||
# Double bend required when:
|
||||
# - there are 2 or 0 lateral axes involved
|
||||
# - offset appear by the non-lateral axis
|
||||
#
|
||||
# NOTE: some double bends are only possible for square profiles:
|
||||
# https://i.imgur.com/ZhdGbEp.png
|
||||
|
||||
z_axis_end_object = end_object.matrix_world.col[2].normalized().to_3d()
|
||||
z_axis_end_object_local = to_start_object_space @ z_axis_end_object
|
||||
lateral_axes = [i for i in range(2) if not tool.Cad.is_x(z_axis_end_object_local[i], 0)]
|
||||
|
||||
if len(lateral_axes) != 1:
|
||||
return (
|
||||
None,
|
||||
f"For now only one lateral axis is supported for a bend (double bends not supported). Found lateral axes: {len(lateral_axes)}.",
|
||||
)
|
||||
|
||||
non_lateral_axis = 0 if lateral_axes[0] == 1 else 1
|
||||
non_lateral_axis_offset = profile_offset[non_lateral_axis]
|
||||
if not tool.Cad.is_x(non_lateral_axis_offset, 0):
|
||||
return (
|
||||
None,
|
||||
"For now offset by non-lateral axis is not supported for a bend (double bends not supported).\n"
|
||||
f"Detected an offset of {round(non_lateral_axis_offset, 5)} along the local axis {'XY'[non_lateral_axis]} when lateral axis is {'XY'[lateral_axes[0]]}.",
|
||||
)
|
||||
|
||||
return lateral_axes[0], None
|
||||
|
||||
lateral_axis, error_msg = check_for_double_bends()
|
||||
if error_msg:
|
||||
self.report({"ERROR"}, error_msg)
|
||||
return {"CANCELLED"}
|
||||
|
||||
O = V(0, 0, 0)
|
||||
angle = tool.Cad.angle_edges((get_z_basis(start_object), O), (get_z_basis(end_object), O))
|
||||
|
||||
lateral_sign = tool.Cad.sign(profile_offset[lateral_axis])
|
||||
radial_offset = V(0, 0, 0)
|
||||
ref_point_radius = self.radius + profile_dim[lateral_axis]
|
||||
radial_offset[lateral_axis] = ref_point_radius * (1 - cos(angle)) * lateral_sign
|
||||
radial_offset.z = ref_point_radius * sin(angle)
|
||||
|
||||
def get_segments_extend():
|
||||
segments_intersection = segments_intersection_ws - start_point
|
||||
segments_intersection = to_start_object_space @ segments_intersection
|
||||
|
||||
# since tangent segments are equal
|
||||
# if drawn for the circle from the same point
|
||||
required_offset = ref_point_radius * tan(angle / 2)
|
||||
|
||||
current_start_offset = segments_intersection.length
|
||||
current_end_offset = (segments_intersection - profile_offset).length
|
||||
|
||||
start_extend = current_start_offset - required_offset
|
||||
end_extend = current_end_offset - required_offset
|
||||
|
||||
return start_extend, end_extend
|
||||
|
||||
def check_new_segment_length(start_point, end_point, extend_point):
|
||||
"""Check if segment is placed too near to the bend point.
|
||||
|
||||
The idea is that we can either extend segment toward the bend
|
||||
but we can shrink it only until it's start.
|
||||
|
||||
If the segment is too near it will return offset to fix the problem,
|
||||
otherwise returns `None`.
|
||||
|
||||
"""
|
||||
base_edge = end_point - start_point
|
||||
new_edge = extend_point - start_point
|
||||
projection = new_edge.dot(base_edge.normalized())
|
||||
if projection < 0 or tool.Cad.is_x(projection, 0):
|
||||
return projection
|
||||
return None
|
||||
|
||||
# adjust segments to fit the radius and angle
|
||||
start_segment_extend, end_segment_extend = get_segments_extend()
|
||||
start_segment_extend_point = start_point + start_segment_sign * start_segment_extend * get_z_basis(start_object)
|
||||
projection = check_new_segment_length(first_segment_start, start_point, start_segment_extend_point)
|
||||
if projection is not None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"Start segment starts too near to the bend, need to offset it atleast by {round(projection, 3)} m.",
|
||||
)
|
||||
return {"ERROR"}
|
||||
|
||||
end_segment_extend_point = end_point + end_segment_sign * end_segment_extend * get_z_basis(end_object)
|
||||
projection = check_new_segment_length(second_segment_end, end_point, end_segment_extend_point)
|
||||
if projection is not None:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"End segment starts too near to the bend, need to offset it atleast by {round(projection, 3)} m.",
|
||||
)
|
||||
return {"ERROR"}
|
||||
|
||||
DumbProfileJoiner().join_E(start_object, start_segment_extend_point, start_connection)
|
||||
DumbProfileJoiner().join_E(end_object, end_segment_extend_point, end_connection)
|
||||
|
||||
context.view_layer.update() # update matrices
|
||||
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
rep, bend_data = builder.mep_bend_shape(
|
||||
start_element,
|
||||
self.start_length / si_conversion,
|
||||
self.end_length / si_conversion,
|
||||
angle,
|
||||
self.radius / si_conversion,
|
||||
profile_offset / si_conversion,
|
||||
flip_z_axis=start_segment_sign == -1,
|
||||
)
|
||||
|
||||
bpy.ops.bim.create_shape_from_step_id(step_id=rep.id(), should_include_curves=True)
|
||||
|
||||
# find the compatible fitting type
|
||||
fitting_data = MEPGenerator().get_compatible_fitting_type(
|
||||
[start_element, end_element], [start_port, end_port], "BEND"
|
||||
)
|
||||
bend_type = fitting_data["fitting_type"] if fitting_data else None
|
||||
start_port_match = fitting_data["start_port_match"] if fitting_data else True
|
||||
if bend_type:
|
||||
# TODO: handle the case without creating a representation in the first place?
|
||||
ifcopenshell.api.run("geometry.remove_representation", ifc_file, representation=rep)
|
||||
else: # create new fitting type if nothing is compatible
|
||||
mesh = bpy.data.meshes.new("Bend")
|
||||
obj = bpy.data.objects.new("Bend", mesh)
|
||||
bend_type = blenderbim.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=MEPGenerator().get_mep_element_class_name(start_element, "FittingType"),
|
||||
predefined_type="BEND",
|
||||
should_add_representation=False,
|
||||
)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
tool.Model.replace_object_ifc_representation(body, obj, rep)
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=bend_type, name="BBIM_Fitting")
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Data": json.dumps(bend_data, default=list)},
|
||||
)
|
||||
|
||||
# NOTE: at this point we loose current blender objects selection
|
||||
# create transition element
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=bend_type.id())
|
||||
fitting_obj = bpy.context.active_object
|
||||
|
||||
# adjust fitting object rotation and location
|
||||
# required since we'll base our `fitting_obj_dir` on this
|
||||
fitting_obj.matrix_world = start_object.matrix_world
|
||||
context.view_layer.update()
|
||||
|
||||
# depending on fitting direction we may need to flip it or attach it's origin to end segment
|
||||
# direction can be different depending on:
|
||||
# - order of the current segments
|
||||
# - order of the segments that were used with the same fitting type before
|
||||
direction_match = tool.Cad.are_vectors_equal(get_z_basis(start_object), get_z_basis(fitting_obj))
|
||||
# if there are no mismatches or everything matches up we don't need to flip the transition
|
||||
if start_port_match != direction_match:
|
||||
fitting_obj.matrix_world = start_object.matrix_world @ Matrix.Rotation(radians(180), 4, "X")
|
||||
fitting_obj.location = start_segment_extend_point if start_port_match else end_segment_extend_point
|
||||
|
||||
# add ports and connect them
|
||||
ports = tool.System.add_ports(fitting_obj, offset_end_port=start_object_rotation @ (radial_offset * V(1, 1, 0)))
|
||||
if not start_port_match:
|
||||
start_port, end_port = end_port, start_port
|
||||
tool.Ifc.run("system.connect_port", port1=ports[0], port2=start_port, direction="NOTDEFINED")
|
||||
tool.Ifc.run("system.connect_port", port1=ports[1], port2=end_port, direction="NOTDEFINED")
|
||||
|
||||
self.report({"INFO"}, f"Success!.. kind of. The angle was {round(bend_data['angle'])}")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -70,7 +70,10 @@ class FilledOpeningGenerator:
|
||||
)
|
||||
|
||||
if target is None:
|
||||
target = bpy.context.scene.cursor.location
|
||||
should_set_z_level = True
|
||||
target = bpy.context.scene.cursor.location.copy()
|
||||
else:
|
||||
should_set_z_level = False
|
||||
|
||||
# Sometimes, the voided_obj may be an aggregate, which won't have any representation.
|
||||
if voided_obj.data:
|
||||
@@ -86,12 +89,17 @@ class FilledOpeningGenerator:
|
||||
axis = tool.Model.get_wall_axis(voided_obj, layers=layers)["base"]
|
||||
|
||||
new_matrix = voided_obj.matrix_world.copy()
|
||||
new_matrix.translation = tool.Cad.point_on_edge(target, axis)
|
||||
point_on_axis = tool.Cad.point_on_edge(target, axis)
|
||||
new_matrix.translation.x = point_on_axis.x
|
||||
new_matrix.translation.y = point_on_axis.y
|
||||
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
if should_set_z_level:
|
||||
if filling.is_a("IfcDoor"):
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
else:
|
||||
new_matrix.translation.z = voided_obj.matrix_world.translation.z + props.rl2
|
||||
new_matrix.translation.z = filling_obj.matrix_world.copy().translation.z
|
||||
|
||||
filling_obj.matrix_world = new_matrix
|
||||
bpy.context.view_layer.update()
|
||||
@@ -498,10 +506,14 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boolean"
|
||||
bl_label = "Add Boolean"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Applies a boolean to the selected IFC object using the other selected blender object as a void"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) == 2
|
||||
if not len(context.selected_objects) == 2:
|
||||
cls.poll_message_set("Exactly 2 objects need to be selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMModelProperties
|
||||
@@ -646,6 +658,7 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
upstream_obj = None
|
||||
for obj in context.selected_objects:
|
||||
if (
|
||||
not obj.data
|
||||
@@ -673,6 +686,8 @@ class RemoveBooleans(Operator, tool.Ifc.Operator, AddObjectHelper):
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
tool.Blender.set_active_object(upstream_obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -735,12 +750,19 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
building_objs = set()
|
||||
model = tool.Ifc.get()
|
||||
all_openings = model.by_type("IfcOpeningElement")
|
||||
similar_openings = []
|
||||
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
openings = [r.RelatedOpeningElement for r in element.HasOpenings]
|
||||
for opening in openings:
|
||||
for all_opening in all_openings:
|
||||
if all_opening.ObjectPlacement == opening.ObjectPlacement:
|
||||
similar_openings.append(all_opening)
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
if opening_obj:
|
||||
if tool.Ifc.is_edited(opening_obj):
|
||||
@@ -749,6 +771,8 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
blenderbim.core.geometry.edit_object_placement(
|
||||
tool.Ifc, tool.Geometry, tool.Surveyor, obj=opening_obj
|
||||
)
|
||||
for similar_opening in similar_openings:
|
||||
similar_opening.ObjectPlacement = opening.ObjectPlacement
|
||||
building_objs.add(obj)
|
||||
building_objs.update(self.get_all_building_objects_of_similar_openings(opening))
|
||||
tool.Ifc.unlink(element=opening, obj=opening_obj)
|
||||
@@ -773,6 +797,34 @@ class EditOpenings(Operator, tool.Ifc.Operator):
|
||||
results.add(obj)
|
||||
return results
|
||||
|
||||
class CloneOpening(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.clone_opening"
|
||||
bl_label = "Clone Opening"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create and assign to the selected element the selected opening and assign to it the same representation and object placement"
|
||||
|
||||
def _execute(self, context):
|
||||
objects = bpy.context.selected_objects
|
||||
|
||||
for obj in objects:
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
if entity.is_a() == "IfcWall":
|
||||
wall = entity
|
||||
continue
|
||||
if entity.is_a() == "IfcOpeningElement":
|
||||
opening = entity
|
||||
continue
|
||||
|
||||
opening_placement = opening.ObjectPlacement
|
||||
opening_representations = opening.Representation.Representations
|
||||
|
||||
new_opening = ifcopenshell.api.run("root.create_entity", tool.Ifc.get(), ifc_class="IfcOpeningElement")
|
||||
for representation in opening_representations:
|
||||
ifcopenshell.api.run("geometry.assign_representation", tool.Ifc.get(), product = new_opening, representation = representation)
|
||||
|
||||
ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening = new_opening, element = wall)
|
||||
new_opening.ObjectPlacement = opening_placement
|
||||
return {"FINISHED"}
|
||||
|
||||
# TODO: merge with ProfileDecorator?
|
||||
class DecorationsHandler:
|
||||
|
||||
@@ -213,7 +213,7 @@ class AddConstrTypeInstance(bpy.types.Operator):
|
||||
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=parent_obj, element_obj=obj
|
||||
)
|
||||
|
||||
# set occurences properties for the types defined with modifiers
|
||||
# set occurrences properties for the types defined with modifiers
|
||||
if instance_class in ["IfcWindow", "IfcDoor"]:
|
||||
pset_name = f"BBIM_{instance_class[3:]}"
|
||||
bbim_pset = ifcopenshell.util.element.get_psets(element).get(pset_name, None)
|
||||
|
||||
@@ -272,13 +272,21 @@ class DumbProfileJoiner:
|
||||
body = copy.deepcopy(axis1)
|
||||
self.recreate_profile(element1, profile1, axis, body)
|
||||
|
||||
def join_E(self, profile1, target):
|
||||
def join_E(self, profile1, target, connection=None):
|
||||
"""`connection` = `ATEND` / `ATSTART` to explicitly define the reference point for the join.
|
||||
|
||||
For example if profile 1m long and `target` is at (0, 0, 0.1) and `connection` = `None`
|
||||
it will implicitly use `connection` = `ATSTART` resulting in profile object 0.9m long and moved to (0, 0, 0.1).
|
||||
|
||||
But with `connection` = `ATEND` it will result in the profile object 0.1m long, locaiton unchanged.
|
||||
"""
|
||||
element1 = tool.Ifc.get_entity(profile1)
|
||||
if not element1:
|
||||
return
|
||||
axis1 = self.get_profile_axis(profile1)
|
||||
intersect, connection = mathutils.geometry.intersect_point_line(target, *axis1)
|
||||
connection = "ATEND" if connection > 0.5 else "ATSTART"
|
||||
intersect, connection_value = mathutils.geometry.intersect_point_line(target, *axis1)
|
||||
if connection is None:
|
||||
connection = "ATEND" if connection_value > 0.5 else "ATSTART"
|
||||
|
||||
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element1, connection_type=connection)
|
||||
|
||||
|
||||
@@ -59,10 +59,10 @@ def update_railing_modifier_ifc_data(context):
|
||||
|
||||
# type attributes
|
||||
element.PredefinedType = "USERDEFINED"
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.ObjectType = props.railing_type
|
||||
# occurrences attributes
|
||||
occurrences = tool.Ifc.get_all_element_occurrences(element)
|
||||
for occurrence in occurrences:
|
||||
occurrence.ObjectType = props.railing_type
|
||||
|
||||
# update pset
|
||||
pset_common = tool.Pset.get_element_pset(element, "Pset_RailingCommon")
|
||||
|
||||
@@ -40,10 +40,6 @@ from pprint import pprint
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm
|
||||
|
||||
|
||||
def float_is_zero(f):
|
||||
return 0.0001 >= f >= -0.0001
|
||||
|
||||
|
||||
def bm_mesh_clean_up(bm):
|
||||
# remove internal edges and faces
|
||||
# adding missing faces so we could rely on `e.is_boundary` later
|
||||
@@ -99,7 +95,7 @@ def is_valid_roof_footprint(bm):
|
||||
# should be bmesh to support edit mode
|
||||
bm.verts.ensure_lookup_table()
|
||||
base_z = bm.verts[0].co.z
|
||||
all_verts_same_level = all([float_is_zero(v.co.z - base_z) for v in bm.verts[1:]])
|
||||
all_verts_same_level = all([tool.Cad.is_x(v.co.z - base_z, 0) for v in bm.verts[1:]])
|
||||
if not all_verts_same_level:
|
||||
return (
|
||||
{"ERROR"},
|
||||
@@ -202,7 +198,7 @@ def generate_hiped_roof_bmesh(
|
||||
|
||||
def find_identical_new_vert(co):
|
||||
for v in bm.verts:
|
||||
if float_is_zero((co - v.co).length):
|
||||
if tool.Cad.is_x((co - v.co).length, 0):
|
||||
return v
|
||||
|
||||
def find_other_polygon_verts(edge):
|
||||
@@ -234,7 +230,7 @@ def generate_hiped_roof_bmesh(
|
||||
bottom_chords_to_remove = []
|
||||
|
||||
def is_footprint_vert(v):
|
||||
return float_is_zero(v.co.z - footprint_z)
|
||||
return tool.Cad.is_x(v.co.z - footprint_z, 0)
|
||||
|
||||
def is_footprint_edge(edge):
|
||||
return all(is_footprint_vert(v) for v in edge.verts)
|
||||
@@ -326,7 +322,7 @@ def generate_hiped_roof_bmesh(
|
||||
default_offset_dir = Vector([0, 0, 1]) * roof_thickness
|
||||
footprint_verts = set()
|
||||
|
||||
if not float_is_zero(rafter_edge_angle):
|
||||
if not tool.Cad.is_x(rafter_edge_angle, 0):
|
||||
footprint_edges = []
|
||||
for edge in extruded_edges:
|
||||
if is_footprint_edge(edge):
|
||||
@@ -398,7 +394,7 @@ def update_roof_modifier_ifc_data(context):
|
||||
if not angle_layer:
|
||||
return False
|
||||
for edge_angle in angle_layer:
|
||||
if float_is_zero(edge_angle - pi / 2):
|
||||
if tool.Cad.is_x(edge_angle - pi / 2, 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -406,8 +402,8 @@ def update_roof_modifier_ifc_data(context):
|
||||
if props.roof_type == "HIP/GABLE ROOF":
|
||||
element.PredefinedType = "GABLE_ROOF" if roof_is_gabled() else "HIP_ROOF"
|
||||
|
||||
# occurences attributes
|
||||
# occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
# occurrences attributes
|
||||
# occurrences = tool.Ifc.get_all_element_occurrences(element)
|
||||
|
||||
tool.Ifc.edit(obj)
|
||||
|
||||
|
||||
@@ -95,19 +95,19 @@ class GenerateSpace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
gross_settings.set(gross_settings.DISABLE_OPENING_SUBTRACTIONS, True)
|
||||
|
||||
for obj in bpy.context.visible_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
visible_element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if (
|
||||
not element
|
||||
not visible_element
|
||||
or obj.type != "MESH"
|
||||
or not self.is_bounding_class(element)
|
||||
or not self.is_bounding_class(visible_element)
|
||||
or not tool.Drawing.is_intersecting_plane(obj, self.cut_point, self.cut_normal)
|
||||
):
|
||||
continue
|
||||
|
||||
old_mesh = None
|
||||
if element.HasOpenings:
|
||||
new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, element))
|
||||
if visible_element.HasOpenings:
|
||||
new_mesh = self.create_mesh(ifcopenshell.geom.create_shape(gross_settings, visible_element))
|
||||
old_mesh = obj.data
|
||||
obj.data = new_mesh
|
||||
|
||||
|
||||
@@ -156,15 +156,14 @@ class BIM_PT_authoring(Panel):
|
||||
class BIM_PT_Grids(Panel):
|
||||
bl_label = "Grids"
|
||||
bl_idname = "BIM_PT_Grids"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_category = "BlenderBIM"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_project_setup"
|
||||
|
||||
def draw(self, context):
|
||||
self.animation_props = context.scene.BIMAnimationProperties
|
||||
row = self.layout.row()
|
||||
row.operator("mesh.add_grid", icon="ADD", text="Add Grids")
|
||||
self.layout.row().operator("mesh.add_grid", icon="ADD", text="Add Grids")
|
||||
|
||||
|
||||
class BIM_PT_array(bpy.types.Panel):
|
||||
|
||||
@@ -1119,8 +1119,13 @@ class DumbWallJoiner:
|
||||
other = tool.Ifc.get_object(rel.RelatedElement)
|
||||
if connection not in ["ATPATH", "NOTDEFINED"]:
|
||||
self.join(
|
||||
obj, other, connection, rel.RelatedConnectionType, is_relating=True, description=rel.Description
|
||||
)
|
||||
obj,
|
||||
other,
|
||||
connection,
|
||||
rel.RelatedConnectionType,
|
||||
is_relating=True,
|
||||
description=rel.Description
|
||||
)
|
||||
for rel in element.ConnectedFrom:
|
||||
if rel.is_a("IfcRelConnectsPathElements"):
|
||||
connection = rel.RelatedConnectionType
|
||||
@@ -1299,7 +1304,7 @@ class DumbWallJoiner:
|
||||
# The user has moved the wall into an invalid position that cannot connect at the desired end
|
||||
return False
|
||||
|
||||
self.axis[1 if connection1 == "ATEND" else 0] = intersect
|
||||
self.axis = proposed_axis
|
||||
|
||||
# Work out body
|
||||
|
||||
@@ -1309,55 +1314,79 @@ class DumbWallJoiner:
|
||||
tp1 = wall1.matrix_world @ Vector(wall1.bound_box[1])
|
||||
|
||||
# Axis lines on bottom, for reference, base, and side axes
|
||||
bra1 = (Vector((*axis1["reference"][0], bp1[2])), Vector((*axis1["reference"][1], bp1[2])))
|
||||
bba1 = (Vector((*axis1["base"][0], bp1[2])), Vector((*axis1["base"][1], bp1[2])))
|
||||
bsa1 = (Vector((*axis1["side"][0], bp1[2])), Vector((*axis1["side"][1], bp1[2])))
|
||||
bba2 = (Vector((*axis2["base"][0], bp2[2])), Vector((*axis2["base"][1], bp2[2])))
|
||||
bsa2 = (Vector((*axis2["side"][0], bp2[2])), Vector((*axis2["side"][1], bp2[2])))
|
||||
def to_3d_axis(axis, z):
|
||||
return (Vector((*axis[0], z)), Vector((*axis[1], z)))
|
||||
|
||||
bra1 = to_3d_axis(axis1["reference"], bp1.z)
|
||||
bba1 = to_3d_axis(axis1["base"], bp1.z)
|
||||
tba1 = to_3d_axis(axis1["base"], tp1.z)
|
||||
bsa1 = to_3d_axis(axis1["side"], bp1.z)
|
||||
bba2 = to_3d_axis(axis2["base"], bp2.z)
|
||||
bsa2 = to_3d_axis(axis2["side"], bp2.z)
|
||||
|
||||
# Intersecting the walls sides defined by planes gives 4 lines of intersection
|
||||
# Line point, and line direction
|
||||
lp1, ld1 = mathutils.geometry.intersect_plane_plane(bba1[0], normal1, bba2[0], normal2)
|
||||
lp2, ld2 = mathutils.geometry.intersect_plane_plane(bba1[0], normal1, bsa2[0], normal2)
|
||||
lp3, ld3 = mathutils.geometry.intersect_plane_plane(bsa1[0], normal1, bba2[0], normal2)
|
||||
lp4, ld4 = mathutils.geometry.intersect_plane_plane(bsa1[0], normal1, bsa2[0], normal2)
|
||||
lpb1, ldb1 = mathutils.geometry.intersect_plane_plane(bba1[0], normal1, bba2[0], normal2)
|
||||
lpb2, ldb2 = mathutils.geometry.intersect_plane_plane(bba1[0], normal1, bsa2[0], normal2)
|
||||
lps1, lds1 = mathutils.geometry.intersect_plane_plane(bsa1[0], normal1, bba2[0], normal2)
|
||||
lps2, lds2 = mathutils.geometry.intersect_plane_plane(bsa1[0], normal1, bsa2[0], normal2)
|
||||
|
||||
# Intersecting the 4 lines gives the 8 possible verts of intersection
|
||||
# 4 on bottom, and 4 on top. 4 on our base line, 4 on our side line.
|
||||
bb1 = mathutils.geometry.intersect_line_plane(lp1, lp1 + ld1, bp1, Vector((0, 0, 1)))
|
||||
bb2 = mathutils.geometry.intersect_line_plane(lp2, lp2 + ld2, bp1, Vector((0, 0, 1)))
|
||||
bs1 = mathutils.geometry.intersect_line_plane(lp3, lp3 + ld3, bp1, Vector((0, 0, 1)))
|
||||
bs2 = mathutils.geometry.intersect_line_plane(lp4, lp4 + ld4, bp1, Vector((0, 0, 1)))
|
||||
tb1 = mathutils.geometry.intersect_line_plane(lp1, lp1 + ld1, tp1, Vector((0, 0, 1)))
|
||||
tb2 = mathutils.geometry.intersect_line_plane(lp2, lp2 + ld2, tp1, Vector((0, 0, 1)))
|
||||
ts1 = mathutils.geometry.intersect_line_plane(lp3, lp3 + ld3, tp1, Vector((0, 0, 1)))
|
||||
ts2 = mathutils.geometry.intersect_line_plane(lp4, lp4 + ld4, tp1, Vector((0, 0, 1)))
|
||||
# Diagram: https://i.imgur.com/jwWx2Ox.png
|
||||
# NOTE: bb/bs always equal lpb/lps?
|
||||
bb1 = mathutils.geometry.intersect_line_plane(lpb1, lpb1 + ldb1, bp1, Vector((0, 0, 1)))
|
||||
bb2 = mathutils.geometry.intersect_line_plane(lpb2, lpb2 + ldb2, bp1, Vector((0, 0, 1)))
|
||||
bs1 = mathutils.geometry.intersect_line_plane(lps1, lps1 + lds1, bp1, Vector((0, 0, 1)))
|
||||
bs2 = mathutils.geometry.intersect_line_plane(lps2, lps2 + lds2, bp1, Vector((0, 0, 1)))
|
||||
|
||||
# similar to bb/bs but also have local z offset
|
||||
tb1 = mathutils.geometry.intersect_line_plane(lpb1, lpb1 + ldb1, tp1, Vector((0, 0, 1)))
|
||||
tb2 = mathutils.geometry.intersect_line_plane(lpb2, lpb2 + ldb2, tp1, Vector((0, 0, 1)))
|
||||
ts1 = mathutils.geometry.intersect_line_plane(lps1, lps1 + lds1, tp1, Vector((0, 0, 1)))
|
||||
ts2 = mathutils.geometry.intersect_line_plane(lps2, lps2 + lds2, tp1, Vector((0, 0, 1)))
|
||||
|
||||
# Let's distinguish the 8 points by whether they are nearer or further away from the other end
|
||||
# These 8 points will be used to find the final body position and clippings.
|
||||
i = 0 if connection1 == "ATEND" else 1
|
||||
j = 1 if connection1 == "ATEND" else 0
|
||||
bbn = tool.Cad.closest_vector(axis1["base"][i].to_3d(), (bb1, bb2))
|
||||
bbf = bb2 if bbn == bb1 else bb1
|
||||
bsn = tool.Cad.closest_vector(axis1["side"][i].to_3d(), (bs1, bs2))
|
||||
bsf = bs2 if bsn == bs1 else bs1
|
||||
tbn = tool.Cad.closest_vector(axis1["base"][i].to_3d(), (tb1, tb2))
|
||||
tbf = tb2 if tbn == tb1 else tb1
|
||||
tsn = tool.Cad.closest_vector(axis1["side"][i].to_3d(), (ts1, ts2))
|
||||
tsf = ts2 if tsn == ts1 else ts1
|
||||
connected_at_end = connection1 == "ATEND"
|
||||
i = 0 if connected_at_end else 1
|
||||
|
||||
def get_closest_and_furthest_vectors(ref_point_2d, vectors, clamp_axis=None):
|
||||
def clamp_point_by_direction(point, edge):
|
||||
percent = tool.Cad.edge_percent(point, edge)
|
||||
if percent < 0:
|
||||
return edge[0]
|
||||
return point
|
||||
|
||||
# When there is a small angle between walls, intersection points can occur outside the wall's axis.
|
||||
# Which can lead to inaccuracies - therefore we bottom clamp them to stay within the axis
|
||||
if clamp_axis:
|
||||
# if wall connected at the start then reference point will be at the end
|
||||
# therefore we reverse the axis
|
||||
if not connected_at_end:
|
||||
clamp_axis = clamp_axis[::-1]
|
||||
vectors = tuple([clamp_point_by_direction(v, clamp_axis) for v in vectors])
|
||||
|
||||
return tool.Cad.closest_and_furthest_vectors(ref_point_2d.to_3d(), vectors)
|
||||
|
||||
bbn, bbf = get_closest_and_furthest_vectors(axis1["base"][i], (bb1, bb2), bba1)
|
||||
bsn, bsf = get_closest_and_furthest_vectors(axis1["side"][i], (bs1, bs2))
|
||||
tbn, tbf = get_closest_and_furthest_vectors(axis1["base"][i], (tb1, tb2), tba1)
|
||||
tsn, tsf = get_closest_and_furthest_vectors(axis1["side"][i], (ts1, ts2))
|
||||
|
||||
j = 1 if connected_at_end else 0
|
||||
if description == "MITRE":
|
||||
# Mitre joints are an unofficial convention
|
||||
bsf_ = tool.Cad.point_on_edge(bsf, bba1)
|
||||
tbf_ = tool.Cad.point_on_edge(tbf, bba1)
|
||||
tsf_ = tool.Cad.point_on_edge(tsf, bba1)
|
||||
new_body = tool.Cad.furthest_vector(bba1[i], (bbf, bsf_)).copy()
|
||||
new_body = tool.Cad.furthest_vector(bba1[i], (new_body, tbf_)).copy()
|
||||
new_body = tool.Cad.furthest_vector(bba1[i], (bbf, bsf_))
|
||||
new_body = tool.Cad.furthest_vector(bba1[i], (new_body, tbf_))
|
||||
new_body = tool.Cad.furthest_vector(bba1[i], (new_body, tsf_)).copy()
|
||||
self.body[j] = tool.Cad.point_on_edge(new_body, bra1).to_2d()
|
||||
|
||||
if connection1 == connection2:
|
||||
if (connection1 == "ATEND" and angle > 0) or (connection1 != "ATEND" and angle < 0):
|
||||
if (connected_at_end and angle > 0) or (not connected_at_end and angle < 0):
|
||||
pt = bbf.to_2d().to_3d()
|
||||
x_axis = bsn - bbf
|
||||
y_axis = tbf - bbf
|
||||
@@ -1366,7 +1395,7 @@ class DumbWallJoiner:
|
||||
x_axis = bsf - bbn
|
||||
y_axis = tbn - bbn
|
||||
else:
|
||||
if (connection1 == "ATEND" and angle < 0) or (connection1 != "ATEND" and angle > 0):
|
||||
if (connected_at_end and angle < 0) or (not connected_at_end and angle > 0):
|
||||
pt = bbf.to_2d().to_3d()
|
||||
x_axis = bsn - bbf
|
||||
y_axis = tbf - bbf
|
||||
@@ -1377,6 +1406,9 @@ class DumbWallJoiner:
|
||||
|
||||
if connection1 != "ATEND":
|
||||
y_axis *= -1
|
||||
|
||||
x_axis.normalize()
|
||||
y_axis.normalize()
|
||||
z_axis = x_axis.cross(y_axis)
|
||||
y_axis = z_axis.cross(x_axis)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from mathutils import Vector
|
||||
# TODO: move to some utils helpers/tool module
|
||||
def update_simple_openings(element, opening_width, opening_height):
|
||||
ifc_file = tool.Ifc.get()
|
||||
fillings = tool.Ifc.get_all_element_occurences(element)
|
||||
fillings = tool.Ifc.get_all_element_occurrences(element)
|
||||
|
||||
voided_objs = set()
|
||||
has_replaced_opening_representation = False
|
||||
@@ -158,11 +158,11 @@ def update_window_modifier_representation(context, obj):
|
||||
if tool.Ifc.get_schema() != "IFC2X3":
|
||||
element.PartitioningType = props.window_type
|
||||
|
||||
# occurences attributes
|
||||
occurences = tool.Ifc.get_all_element_occurences(element)
|
||||
for occurence in occurences:
|
||||
occurence.OverallWidth = props.overall_width / si_conversion
|
||||
occurence.OverallHeight = props.overall_height / si_conversion
|
||||
# occurrences attributes
|
||||
occurrences = tool.Ifc.get_all_element_occurrences(element)
|
||||
for occurrence in occurrences:
|
||||
occurrence.OverallWidth = props.overall_width / si_conversion
|
||||
occurrence.OverallHeight = props.overall_height / si_conversion
|
||||
|
||||
update_simple_openings(element, props.overall_width / si_conversion, props.overall_height / si_conversion)
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class BimTool(WorkSpaceTool):
|
||||
("bim.hotkey", {"type": "K", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_K")]}),
|
||||
("bim.hotkey", {"type": "M", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_M")]}),
|
||||
("bim.hotkey", {"type": "O", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_O")]}),
|
||||
("bim.hotkey", {"type": "L", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_L")]}),
|
||||
("bim.hotkey", {"type": "Q", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Q")]}),
|
||||
("bim.hotkey", {"type": "R", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_R")]}),
|
||||
("bim.hotkey", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_T")]}),
|
||||
@@ -77,9 +78,11 @@ class WallTool(BimTool):
|
||||
bl_description = "Create and edit walls"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.wall")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcWallType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcWallType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class SlabTool(BimTool):
|
||||
@@ -90,9 +93,11 @@ class SlabTool(BimTool):
|
||||
bl_description = "Create and edit slabs"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.slab")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcSlabType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcSlabType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class DoorTool(BimTool):
|
||||
@@ -103,9 +108,11 @@ class DoorTool(BimTool):
|
||||
bl_description = "Create and edit doors"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.door")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcDoorType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcDoorType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class WindowTool(BimTool):
|
||||
@@ -116,9 +123,11 @@ class WindowTool(BimTool):
|
||||
bl_description = "Create and edit windows"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.window")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcWindowType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcWindowType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class ColumnTool(BimTool):
|
||||
@@ -129,9 +138,11 @@ class ColumnTool(BimTool):
|
||||
bl_description = "Create and edit columns"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.column")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcColumnType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcColumnType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class BeamTool(BimTool):
|
||||
@@ -142,9 +153,11 @@ class BeamTool(BimTool):
|
||||
bl_description = "Create and edit beams"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.beam")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcBeamType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcBeamType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class DuctTool(BimTool):
|
||||
@@ -155,9 +168,11 @@ class DuctTool(BimTool):
|
||||
bl_description = "Create and edit ducks" # No, not a typo.
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.duct")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcDuctSegmentType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcDuctSegmentType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
class PipeTool(BimTool):
|
||||
@@ -168,9 +183,11 @@ class PipeTool(BimTool):
|
||||
bl_description = "Create and edit pipes"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.pipe")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcPipeSegmentType"
|
||||
|
||||
def draw_settings(context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type="IfcPipeSegmentType")
|
||||
@classmethod
|
||||
def draw_settings(cls, context, layout, ws_tool):
|
||||
BimToolUI.draw(context, layout, ifc_element_type=cls.ifc_element_type)
|
||||
|
||||
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description):
|
||||
@@ -283,7 +300,12 @@ class BimToolUI:
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(
|
||||
cls.layout,
|
||||
"Mitre",
|
||||
"S_Y",
|
||||
"Join two intersecting walls using a mitre joint.\nOther selected wall is connected to the active",
|
||||
)
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Merge", "S_M", bpy.ops.bim.merge_wall.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_wall.__doc__)
|
||||
@@ -393,6 +415,13 @@ class BimToolUI:
|
||||
else:
|
||||
row.operator("bim.show_openings", icon="HIDE_OFF", text="")
|
||||
|
||||
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
||||
if len(context.selected_objects) == 2:
|
||||
row = cls.layout.row(align=True)
|
||||
row.label(text="", icon="EVENT_SHIFT")
|
||||
row.label(text="", icon="EVENT_L")
|
||||
row.operator("bim.clone_opening", text="Clone Opening")
|
||||
|
||||
cls.layout.row(align=True).label(text="Align")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Exterior", "S_X", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Align Centerline", "S_C", "")
|
||||
@@ -544,7 +573,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
||||
if bpy.ops.bim.align_wall.poll():
|
||||
bpy.ops.bim.align_wall(align_type="CENTERLINE")
|
||||
else:
|
||||
bpy.ops.bim.align_product(align_type="CENTERLINE")
|
||||
|
||||
@@ -726,6 +756,11 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.props.y = self.y
|
||||
self.props.z = self.z
|
||||
|
||||
def hotkey_S_L(self):
|
||||
if AuthoringData.data["active_class"] in ("IfcOpeningElement",):
|
||||
if len(bpy.context.selected_objects) == 2:
|
||||
bpy.ops.bim.clone_opening()
|
||||
|
||||
def hotkey_A_D(self):
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
@@ -744,3 +779,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.edit_openings()
|
||||
else:
|
||||
bpy.ops.bim.show_openings()
|
||||
|
||||
|
||||
LIST_OF_TOOLS = [cls.bl_idname for cls in (BimTool.__subclasses__() + [BimTool])]
|
||||
TOOLS_TO_CLASSES_MAP = {cls.bl_idname: cls.ifc_element_type for cls in BimTool.__subclasses__()}
|
||||
|
||||
@@ -29,6 +29,7 @@ classes = (
|
||||
operator.EnableEditingArbitraryProfile,
|
||||
operator.EnableEditingProfile,
|
||||
operator.LoadProfiles,
|
||||
operator.PurgeUnusedProfiles,
|
||||
operator.RemoveProfileDef,
|
||||
prop.Profile,
|
||||
prop.BIMProfileProperties,
|
||||
|
||||
@@ -36,6 +36,7 @@ class ProfileData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"total_profiles": cls.total_profiles(),
|
||||
"active_profile_users": cls.active_profile_users(),
|
||||
"profile_classes": cls.profile_classes(),
|
||||
"is_arbitrary_profile": cls.is_arbitrary_profile(),
|
||||
"is_editing_arbitrary_profile": cls.is_editing_arbitrary_profile(),
|
||||
@@ -46,6 +47,15 @@ class ProfileData:
|
||||
def total_profiles(cls):
|
||||
return len([p for p in tool.Ifc.get().by_type("IfcProfileDef") if p.ProfileName])
|
||||
|
||||
@classmethod
|
||||
def active_profile_users(cls):
|
||||
profiles_props = bpy.context.scene.BIMProfileProperties
|
||||
if profiles_props.active_profile_index >= len(profiles_props.profiles):
|
||||
return 0
|
||||
profile_prop = profiles_props.profiles[profiles_props.active_profile_index]
|
||||
profile_ifc = tool.Ifc.get().by_id(profile_prop.ifc_definition_id)
|
||||
return tool.Ifc.get().get_total_inverses(profile_ifc)
|
||||
|
||||
@classmethod
|
||||
def profile_classes(cls):
|
||||
classes = ["IfcArbitraryClosedProfileDef"]
|
||||
@@ -72,4 +82,9 @@ class ProfileData:
|
||||
@classmethod
|
||||
def is_editing_arbitrary_profile(cls):
|
||||
obj = bpy.context.active_object
|
||||
return obj and obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
return (
|
||||
obj
|
||||
and obj.data
|
||||
and hasattr(obj.data, "BIMMeshProperties")
|
||||
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell.api
|
||||
import blenderbim.bim.helper
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.model.profile as model_profile
|
||||
import blenderbim.core.profile as core
|
||||
from blenderbim.bim.module.model.decorator import ProfileDecorator
|
||||
from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile
|
||||
from blenderbim.bim.module.profile.data import refresh
|
||||
@@ -213,3 +214,16 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.active_arbitrary_profile_id = 0
|
||||
|
||||
model_profile.DumbProfileRegenerator().regenerate_from_profile_def(profile)
|
||||
|
||||
|
||||
class PurgeUnusedProfiles(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.purge_unused_profiles"
|
||||
bl_label = "Purge Unused Profiles"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMProfileProperties
|
||||
core.purge_unused_profiles(tool.Ifc, tool.Profile)
|
||||
if props.is_editing:
|
||||
refresh()
|
||||
bpy.ops.bim.load_profiles()
|
||||
|
||||
@@ -49,10 +49,14 @@ class Profile(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
def update_active_profile_index(self, context):
|
||||
ProfileData.data["active_profile_users"] = ProfileData.active_profile_users()
|
||||
|
||||
|
||||
class BIMProfileProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
profiles: CollectionProperty(name="Profiles", type=Profile)
|
||||
active_profile_index: IntProperty(name="Active Profile Index")
|
||||
active_profile_index: IntProperty(name="Active Profile Index", update=update_active_profile_index)
|
||||
active_profile_id: IntProperty(name="Active Profile Id")
|
||||
active_arbitrary_profile_id: IntProperty(name="Active Arbitrary Profile Id")
|
||||
profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute)
|
||||
|
||||
@@ -79,17 +79,23 @@ class BIM_PT_profiles(Panel):
|
||||
self.props,
|
||||
"active_profile_index",
|
||||
)
|
||||
if active_profile and active_profile.ifc_class in (
|
||||
"IfcArbitraryClosedProfileDef",
|
||||
"IfcArbitraryProfileDefWithVoids",
|
||||
):
|
||||
if self.props.active_arbitrary_profile_id:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_arbitrary_profile", text="Save Arbitrary Profile", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_arbitrary_profile", text="Edit Arbitrary Profile", icon="GREASEPENCIL")
|
||||
if active_profile:
|
||||
if active_profile.ifc_class in (
|
||||
"IfcArbitraryClosedProfileDef",
|
||||
"IfcArbitraryProfileDefWithVoids",
|
||||
):
|
||||
if self.props.active_arbitrary_profile_id:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_arbitrary_profile", text="Save Arbitrary Profile", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_arbitrary_profile", text="", icon="CANCEL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator(
|
||||
"bim.enable_editing_arbitrary_profile", text="Edit Arbitrary Profile", icon="GREASEPENCIL"
|
||||
)
|
||||
|
||||
users_of_profile = ProfileData.data["active_profile_users"]
|
||||
self.layout.label(icon="INFO", text=f"Profile has {users_of_profile} inverse relationship(s) in project")
|
||||
|
||||
if self.props.active_profile_id:
|
||||
self.draw_editable_ui(context)
|
||||
|
||||
@@ -41,11 +41,13 @@ classes = (
|
||||
operator.SaveLibraryFile,
|
||||
operator.SelectLibraryFile,
|
||||
operator.ToggleFilterCategories,
|
||||
operator.ToggleLinkSelectability,
|
||||
operator.ToggleLinkVisibility,
|
||||
operator.UnassignLibraryDeclaration,
|
||||
operator.UnlinkIfc,
|
||||
operator.UnloadLink,
|
||||
operator.UnloadProject,
|
||||
operator.ReloadLink,
|
||||
prop.LibraryElement,
|
||||
prop.FilterCategory,
|
||||
prop.Link,
|
||||
@@ -55,6 +57,7 @@ classes = (
|
||||
ui.BIM_PT_project,
|
||||
ui.BIM_PT_project_library,
|
||||
ui.BIM_PT_links,
|
||||
ui.BIM_PT_purge,
|
||||
ui.BIM_UL_library,
|
||||
ui.BIM_UL_filter_categories,
|
||||
ui.BIM_UL_links,
|
||||
|
||||
@@ -429,7 +429,7 @@ class AppendLibraryElement(bpy.types.Operator):
|
||||
except:
|
||||
# TODO Remove this terrible code when I refactor this into the core
|
||||
pass
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
|
||||
def import_material_from_ifc(self, element, context):
|
||||
@@ -850,7 +850,7 @@ class UnloadLink(bpy.types.Operator):
|
||||
for scene in bpy.data.scenes:
|
||||
if scene.library and scene.library.filepath == filepath:
|
||||
bpy.data.scenes.remove(scene)
|
||||
link = context.scene.BIMProjectProperties.links.get(filepath)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -866,7 +866,7 @@ class LoadLink(bpy.types.Operator):
|
||||
filepath = self.filepath
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), filepath))
|
||||
with bpy.data.libraries.load(self.filepath, link=True) as (data_from, data_to):
|
||||
with bpy.data.libraries.load(filepath, link=True) as (data_from, data_to):
|
||||
data_to.scenes = data_from.scenes
|
||||
for scene in bpy.data.scenes:
|
||||
if not scene.library or scene.library.filepath != filepath:
|
||||
@@ -875,11 +875,55 @@ class LoadLink(bpy.types.Operator):
|
||||
if "IfcProject" not in child.name:
|
||||
continue
|
||||
bpy.data.scenes[0].collection.children.link(child)
|
||||
link = context.scene.BIMProjectProperties.links.get(filepath)
|
||||
link = context.scene.BIMProjectProperties.links.get(self.filepath)
|
||||
link.is_loaded = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ReloadLink(bpy.types.Operator):
|
||||
bl_idname = "bim.reload_link"
|
||||
bl_label = "Reload Link"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Reload the selected file"
|
||||
filepath: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
def get_linked_ifcs():
|
||||
selected_filename = os.path.basename(self.filepath)
|
||||
return [
|
||||
c.library
|
||||
for c in bpy.data.collections
|
||||
if "IfcProject" in c.name and c.library and os.path.basename(c.library.filepath) == selected_filename
|
||||
]
|
||||
for library in get_linked_ifcs() or []:
|
||||
library.reload()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleLinkSelectability(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_link_selectability"
|
||||
bl_label = "Toggle Link Selectability"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Toggle selectability"
|
||||
link: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
link = props.links.get(self.link)
|
||||
self.filepath = self.link
|
||||
if not os.path.isabs(self.filepath):
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
|
||||
for collection in self.get_linked_collections():
|
||||
collection.hide_select = not collection.hide_select
|
||||
link.is_selectable = not collection.hide_select
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_linked_collections(self):
|
||||
return [
|
||||
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
|
||||
]
|
||||
|
||||
|
||||
class ToggleLinkVisibility(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_link_visibility"
|
||||
bl_label = "Toggle Link Visibility"
|
||||
@@ -891,6 +935,9 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMProjectProperties
|
||||
link = props.links.get(self.link)
|
||||
self.filepath = self.link
|
||||
if not os.path.isabs(self.filepath):
|
||||
self.filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), self.filepath))
|
||||
if self.mode == "WIREFRAME":
|
||||
self.toggle_wireframe(link)
|
||||
elif self.mode == "VISIBLE":
|
||||
@@ -927,7 +974,7 @@ class ToggleLinkVisibility(bpy.types.Operator):
|
||||
|
||||
def get_linked_collections(self):
|
||||
return [
|
||||
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.link
|
||||
c for c in bpy.data.collections if "IfcProject" in c.name and c.library and c.library.filepath == self.filepath
|
||||
]
|
||||
|
||||
|
||||
@@ -1016,6 +1063,7 @@ class ExportIFC(bpy.types.Operator):
|
||||
settings.json_compact = self.json_compact
|
||||
|
||||
ifc_exporter = export_ifc.IfcExporter(settings)
|
||||
print("Starting export")
|
||||
settings.logger.info("Starting export")
|
||||
ifc_exporter.export()
|
||||
settings.logger.info("Export finished in {:.2f} seconds".format(time.time() - start))
|
||||
@@ -1031,7 +1079,7 @@ class ExportIFC(bpy.types.Operator):
|
||||
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
|
||||
if save_blend_file:
|
||||
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'IFC Project "{os.path.basename(output_file)}" {"" if not save_blend_file else "And Current Blend File Are"} Saved',
|
||||
|
||||
@@ -93,6 +93,7 @@ class FilterCategory(PropertyGroup):
|
||||
class Link(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_loaded: BoolProperty(name="Is Loaded", default=False)
|
||||
is_selectable: BoolProperty(name="Is Selectable", default=True)
|
||||
is_wireframe: BoolProperty(name="Is Wireframe", default=False)
|
||||
is_hidden: BoolProperty(name="Is Hidden", default=False)
|
||||
|
||||
|
||||
@@ -350,6 +350,13 @@ class BIM_UL_links(UIList):
|
||||
row = layout.row(align=True)
|
||||
if item.is_loaded:
|
||||
row.label(text=item.name)
|
||||
op = row.operator(
|
||||
"bim.toggle_link_selectability",
|
||||
text="",
|
||||
icon="RESTRICT_SELECT_OFF" if item.is_selectable else "RESTRICT_SELECT_ON",
|
||||
emboss=False,
|
||||
)
|
||||
op.link = item.name
|
||||
op = row.operator(
|
||||
"bim.toggle_link_visibility",
|
||||
text="",
|
||||
@@ -368,9 +375,26 @@ class BIM_UL_links(UIList):
|
||||
op.mode = "VISIBLE"
|
||||
op = row.operator("bim.unload_link", text="", icon="UNLINKED")
|
||||
op.filepath = item.name
|
||||
op = row.operator("bim.reload_link", text="", icon="FILE_REFRESH")
|
||||
op.filepath = item.name
|
||||
else:
|
||||
row.prop(item, "name", text="")
|
||||
op = row.operator("bim.load_link", text="", icon="LINKED")
|
||||
op.filepath = item.name
|
||||
op = row.operator("bim.unlink_ifc", text="", icon="X")
|
||||
op.filepath = item.name
|
||||
|
||||
|
||||
class BIM_PT_purge(Panel):
|
||||
bl_label = "Purge Data"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator("bim.purge_unused_profiles")
|
||||
layout.operator("bim.purge_unused_types")
|
||||
layout.operator("bim.purge_unused_representations")
|
||||
|
||||
@@ -91,7 +91,7 @@ class ObjectPsetsData(Data):
|
||||
return []
|
||||
psets = blenderbim.bim.schema.ifc.psetqto.get_applicable(element.is_a(), pset_only=True)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True).keys()
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True, should_inherit=False).keys()
|
||||
return [p for p in psetnames if p[0] not in assigned_names]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -398,6 +398,7 @@ class CalculateQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.calculate_quantity"
|
||||
bl_label = "Calculate Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Calculates the quantity with a defined formula for this exact entity and quantity"
|
||||
prop: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -424,6 +425,10 @@ class GuessQuantity(bpy.types.Operator):
|
||||
bl_idname = "bim.guess_quantity"
|
||||
bl_label = "Guess Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Calculate the quantity by guessing the formula from the quantity name. "
|
||||
"Less reliable than Calculate Quantity"
|
||||
)
|
||||
prop: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
@@ -450,7 +455,13 @@ class CopyPropertyToSelection(bpy.types.Operator, Operator):
|
||||
else:
|
||||
is_pset = context.active_object.PsetProperties.active_pset_type == "PSET"
|
||||
pset_name = context.active_object.PsetProperties.active_pset_name
|
||||
prop_value = context.active_object.PsetProperties.properties.get(self.name).metadata.get_value()
|
||||
prop = context.active_object.PsetProperties.properties.get(self.name)
|
||||
if prop.value_type == "IfcPropertySingleValue":
|
||||
prop_value = prop.metadata.get_value()
|
||||
elif prop.value_type == "IfcPropertyEnumeratedValue":
|
||||
value_name = prop.metadata.get_value_name()
|
||||
prop_value = [e[value_name] for e in prop.enumerated_value.enumerated_values if e.is_selected]
|
||||
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
core.copy_property_to_selection(
|
||||
tool.Ifc,
|
||||
|
||||
@@ -71,6 +71,9 @@ def draw_enumerated_property(prop, layout, copy_operator=None):
|
||||
grid = layout.column_flow(columns=3)
|
||||
for e in prop.enumerated_value.enumerated_values:
|
||||
grid.prop(e, "is_selected", text=str(e[value_name]))
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = prop.metadata.name
|
||||
|
||||
|
||||
def get_active_pset_obj_name(context, obj_type):
|
||||
@@ -370,6 +373,7 @@ class BIM_PT_task_qtos(Panel):
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_work_schedules"
|
||||
bl_order = 2
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -510,6 +514,7 @@ class BIM_PT_work_schedule_psets(Panel):
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_work_schedules"
|
||||
bl_order = 3
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
||||
@@ -22,7 +22,7 @@ from . import ui, prop, operator
|
||||
classes = (
|
||||
operator.AddPropEnum,
|
||||
operator.AddPropTemplate,
|
||||
operator.AddPsetFile,
|
||||
operator.AddPsetTemplateFile,
|
||||
operator.AddPsetTemplate,
|
||||
operator.DeletePropEnum,
|
||||
operator.DisableEditingPropTemplate,
|
||||
@@ -33,7 +33,7 @@ classes = (
|
||||
operator.EnableEditingPsetTemplate,
|
||||
operator.RemovePropTemplate,
|
||||
operator.RemovePsetTemplate,
|
||||
operator.SavePsetTemplateFile,
|
||||
operator.RemovePsetTemplateFile,
|
||||
prop.PsetTemplate,
|
||||
prop.EnumerationValues,
|
||||
prop.PropTemplate,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import pathlib
|
||||
import ifcopenshell
|
||||
import blenderbim.tool as tool
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -51,17 +52,23 @@ class PsetTemplatesData:
|
||||
|
||||
@classmethod
|
||||
def pset_template_files(cls):
|
||||
files = os.listdir(os.path.join(bpy.context.scene.BIMProperties.data_dir, "pset"))
|
||||
return [(f.replace(".ifc", ""), f.replace(".ifc", ""), "") for f in files]
|
||||
results = []
|
||||
pset_dir = os.path.join(bpy.context.scene.BIMProperties.data_dir, "pset")
|
||||
files = os.listdir(pset_dir)
|
||||
for f in files:
|
||||
results.append((os.path.join(pset_dir, f), os.path.splitext(os.path.basename(f))[0], "Global Pset Template"))
|
||||
|
||||
pset_dir = tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir)
|
||||
if os.path.isdir(pset_dir):
|
||||
for path in pathlib.Path(pset_dir).glob("*.ifc"):
|
||||
results.append((str(path), os.path.splitext(os.path.basename(str(path)))[0], "Project Pset Template"))
|
||||
|
||||
return sorted(results, key=lambda x: x[1])
|
||||
|
||||
@classmethod
|
||||
def pset_templates(cls):
|
||||
if not IfcStore.pset_template_file:
|
||||
IfcStore.pset_template_path = os.path.join(
|
||||
bpy.context.scene.BIMProperties.data_dir,
|
||||
"pset",
|
||||
bpy.context.scene.BIMPsetTemplateProperties.pset_template_files + ".ifc",
|
||||
)
|
||||
IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files
|
||||
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
|
||||
return [(str(t.id()), t.Name, "") for t in IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")]
|
||||
|
||||
|
||||
@@ -44,9 +44,9 @@ class Operator:
|
||||
IfcStore.pset_template_file.redo()
|
||||
|
||||
|
||||
class AddPsetFile(bpy.types.Operator):
|
||||
bl_idname = "bim.add_pset_file"
|
||||
bl_label = "Add Pset File"
|
||||
class AddPsetTemplateFile(bpy.types.Operator):
|
||||
bl_idname = "bim.add_pset_template_file"
|
||||
bl_label = "Add Pset Template File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -58,24 +58,15 @@ class AddPsetFile(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
template = ifcopenshell.file()
|
||||
filepath = os.path.join(
|
||||
context.scene.BIMProperties.data_dir,
|
||||
"pset",
|
||||
self.props.new_template_filename + ".ifc",
|
||||
)
|
||||
filepath = os.path.join(context.scene.BIMProperties.data_dir, "pset", self.props.new_template_filename + ".ifc")
|
||||
|
||||
template.create_entity(
|
||||
"IFCPROPERTYSETTEMPLATE",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"TemplateType": "PSET_TYPEDRIVENONLY",
|
||||
"ApplicableEntity": "IfcTypeObject",
|
||||
}
|
||||
)
|
||||
pset_template = ifcopenshell.api.run("pset_template.add_pset_template", template)
|
||||
ifcopenshell.api.run("pset_template.add_prop_template", template, pset_template=pset_template)
|
||||
template.write(filepath)
|
||||
self.props.new_template_filename = ""
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
context.scene.BIMPsetTemplateProperties.pset_template_files = filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -86,7 +77,10 @@ class AddPsetTemplate(bpy.types.Operator, Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
template = ifcopenshell.api.run("pset_template.add_pset_template", IfcStore.pset_template_file)
|
||||
ifcopenshell.api.run("pset_template.add_prop_template", IfcStore.pset_template_file, pset_template=template)
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
context.scene.BIMPsetTemplateProperties.pset_templates = str(template.id())
|
||||
|
||||
|
||||
@@ -104,6 +98,9 @@ class RemovePsetTemplate(bpy.types.Operator, Operator):
|
||||
IfcStore.pset_template_file,
|
||||
**{"pset_template": IfcStore.pset_template_file.by_id(int(props.pset_templates))}
|
||||
)
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
|
||||
class EnableEditingPsetTemplate(bpy.types.Operator):
|
||||
@@ -215,6 +212,9 @@ class EditPsetTemplate(bpy.types.Operator, Operator):
|
||||
}
|
||||
)
|
||||
bpy.ops.bim.disable_editing_pset_template()
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
|
||||
class SavePsetTemplateFile(bpy.types.Operator):
|
||||
@@ -223,7 +223,21 @@ class SavePsetTemplateFile(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RemovePsetTemplateFile(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_pset_template_file"
|
||||
bl_label = "Remove Pset Template File"
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
os.remove(IfcStore.pset_template_path)
|
||||
except:
|
||||
pass
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -239,9 +253,12 @@ class AddPropTemplate(bpy.types.Operator, Operator):
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.add_prop_template",
|
||||
IfcStore.pset_template_file,
|
||||
**{"pset_template": IfcStore.pset_template_file.by_id(pset_template_id)}
|
||||
pset_template=IfcStore.pset_template_file.by_id(pset_template_id),
|
||||
)
|
||||
bpy.ops.bim.disable_editing_prop_template()
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
|
||||
class RemovePropTemplate(bpy.types.Operator, Operator):
|
||||
@@ -257,6 +274,9 @@ class RemovePropTemplate(bpy.types.Operator, Operator):
|
||||
IfcStore.pset_template_file,
|
||||
**{"prop_template": IfcStore.pset_template_file.by_id(self.prop_template)}
|
||||
)
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
|
||||
class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
@@ -285,6 +305,9 @@ class EditPropTemplate(bpy.types.Operator, Operator):
|
||||
}
|
||||
)
|
||||
bpy.ops.bim.disable_editing_prop_template()
|
||||
IfcStore.pset_template_file.write(IfcStore.pset_template_path)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
blenderbim.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
# TODO -This will need to go into the
|
||||
# api code at some point - vulevukusej
|
||||
|
||||
@@ -38,6 +38,7 @@ from bpy.props import (
|
||||
|
||||
def updatePsetTemplateFiles(self, context):
|
||||
IfcStore.pset_template_file = None
|
||||
PsetTemplatesData.is_loaded = False
|
||||
PsetTemplatesData.data["pset_template_files"] = PsetTemplatesData.pset_template_files()
|
||||
PsetTemplatesData.data["pset_templates"] = PsetTemplatesData.pset_templates()
|
||||
PsetTemplatesData.data["prop_templates"] = PsetTemplatesData.prop_templates()
|
||||
@@ -179,7 +180,7 @@ class BIMPsetTemplateProperties(PropertyGroup):
|
||||
pset_template_files: EnumProperty(
|
||||
items=getPsetTemplateFiles, name="Pset Template Files", update=updatePsetTemplateFiles
|
||||
)
|
||||
pset_templates: EnumProperty(items=getPsetTemplates, name="Pset Template Files", update=updatePsetTemplates)
|
||||
pset_templates: EnumProperty(items=getPsetTemplates, name="Pset Templates", update=updatePsetTemplates)
|
||||
active_pset_template_id: IntProperty(name="Active Pset Template Id")
|
||||
active_prop_template_id: IntProperty(name="Active Prop Template Id")
|
||||
active_pset_template: PointerProperty(type=PsetTemplate)
|
||||
|
||||
@@ -38,9 +38,9 @@ class BIM_PT_pset_template(Panel):
|
||||
self.props = context.scene.BIMPsetTemplateProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
prop_with_search(row, self.props, "pset_template_files", text="")
|
||||
row.operator("bim.save_pset_template_file", text="", icon="EXPORT")
|
||||
row.operator("bim.add_pset_file", icon="ADD", text="")
|
||||
prop_with_search(row, self.props, "pset_template_files", text="", icon="FILE")
|
||||
row.operator("bim.add_pset_template_file", icon="ADD", text="")
|
||||
row.operator("bim.remove_pset_template_file", icon="X", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
|
||||
@@ -20,36 +20,41 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.DisableResourceEditingUI,
|
||||
operator.DisableEditingResource,
|
||||
operator.EnableEditingResource,
|
||||
operator.LoadResources,
|
||||
operator.AddResource,
|
||||
operator.AddResourceQuantity,
|
||||
operator.EditResource,
|
||||
operator.RemoveResource,
|
||||
operator.RemoveResourceQuantity,
|
||||
operator.LoadResourceProperties,
|
||||
operator.ExpandResource,
|
||||
operator.ContractResource,
|
||||
operator.AddProductivityData,
|
||||
operator.AssignResource,
|
||||
operator.UnassignResource,
|
||||
operator.EnableEditingResourceTime,
|
||||
operator.EnableEditingResourceQuantity,
|
||||
operator.EnableEditingResourceBaseQuantity,
|
||||
operator.EnableEditingResourceCosts,
|
||||
operator.EnableEditingResourceCostValueFormula,
|
||||
operator.EnableEditingResourceCostValue,
|
||||
operator.EditResourceTime,
|
||||
operator.EditResourceQuantity,
|
||||
operator.CalculateResourceWork,
|
||||
operator.ConstrainResourceWork,
|
||||
operator.ContractResource,
|
||||
operator.DisableEditingResource,
|
||||
operator.DisableEditingResourceCostValue,
|
||||
operator.DisableEditingResourceQuantity,
|
||||
operator.DisableEditingResourceTime,
|
||||
operator.DisableResourceEditingUI,
|
||||
operator.EditProductivityData,
|
||||
operator.EditResource,
|
||||
operator.EditResourceCostValue,
|
||||
operator.EditResourceCostValueFormula,
|
||||
operator.DisableEditingResourceTime,
|
||||
operator.DisableEditingResourceQuantity,
|
||||
operator.DisableEditingResourceCostValue,
|
||||
operator.CalculateResourceWork,
|
||||
operator.EditResourceQuantity,
|
||||
operator.EditResourceTime,
|
||||
operator.EnableEditingResource,
|
||||
operator.EnableEditingResourceBaseQuantity,
|
||||
operator.EnableEditingResourceCosts,
|
||||
operator.EnableEditingResourceCostValue,
|
||||
operator.CalculateResourceUsage,
|
||||
operator.EnableEditingResourceCostValueFormula,
|
||||
operator.EnableEditingResourceQuantity,
|
||||
operator.EnableEditingResourceTime,
|
||||
operator.ExpandResource,
|
||||
operator.GoToResource,
|
||||
operator.ImportResources,
|
||||
operator.EditProductivityData,
|
||||
operator.LoadResourceProperties,
|
||||
operator.LoadResources,
|
||||
operator.RemoveResource,
|
||||
operator.RemoveResourceQuantity,
|
||||
operator.RemoveUsageConstraint,
|
||||
operator.UnassignResource,
|
||||
prop.Resource,
|
||||
prop.BIMResourceProperties,
|
||||
prop.BIMResourceTreeProperties,
|
||||
|
||||
@@ -56,21 +56,75 @@ class ResourceData:
|
||||
"type": resource.is_a(),
|
||||
"BaseQuantity": base_quantity,
|
||||
}
|
||||
results[resource.id()]["Benchmarks"] = cls.get_resource_benchmarks(resource)
|
||||
if resource.is_a() in ["IfcLaborResource", "IfcConstructionEquipmentResource"]:
|
||||
results[resource.id()]["Productivity"] = {}
|
||||
results[resource.id()]["InheritedProductivity"] = {}
|
||||
productivity = cls.get_productivity(resource)
|
||||
if productivity:
|
||||
results[resource.id()]["Productivity"] = {
|
||||
"id": productivity.get("id"),
|
||||
"QuantityProduced": ifcopenshell.util.resource.get_quantity_produced(productivity),
|
||||
"TimeConsumed": ifcopenshell.util.resource.get_unit_consumed(productivity),
|
||||
"QuantityProducedName": ifcopenshell.util.resource.get_quantity_produced_name(productivity),
|
||||
}
|
||||
inherited_productivity = cls.get_parent_productivity(resource)
|
||||
if inherited_productivity:
|
||||
results[resource.id()]["InheritedProductivity"] = {
|
||||
"QuantityProduced": ifcopenshell.util.resource.get_quantity_produced(inherited_productivity),
|
||||
"TimeConsumed": ifcopenshell.util.resource.get_unit_consumed(inherited_productivity),
|
||||
"QuantityProducedName": ifcopenshell.util.resource.get_quantity_produced_name(
|
||||
inherited_productivity
|
||||
),
|
||||
}
|
||||
if resource.Usage:
|
||||
results[resource.id()]["ScheduleWork"] = (
|
||||
ifcopenshell.util.date.readable_ifc_duration(resource.Usage.ScheduleWork)
|
||||
if resource.Usage.ScheduleWork
|
||||
else None
|
||||
)
|
||||
results[resource.id()]["ScheduleUsage"] = (
|
||||
resource.Usage.ScheduleUsage if resource.Usage.ScheduleUsage else None
|
||||
)
|
||||
if resource.IsNestedBy:
|
||||
results[resource.id()]["DerivedScheduleWork"] = cls.sum_person_hours(resource)
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def sum_person_hours(cls, resource):
|
||||
sum = 0
|
||||
nested_resources = ifcopenshell.util.resource.get_nested_resources(resource)
|
||||
for nested_resource in nested_resources or []:
|
||||
if not nested_resource.Usage or not nested_resource.Usage.ScheduleWork:
|
||||
continue
|
||||
duration = ifcopenshell.util.date.ifc2datetime(nested_resource.Usage.ScheduleWork)
|
||||
sum += duration.total_seconds() / 3600
|
||||
return round(float(sum), 2) if sum else 0
|
||||
|
||||
@classmethod
|
||||
def get_resource_benchmarks(cls, resource):
|
||||
constraints = []
|
||||
for constraint in ifcopenshell.util.constraint.get_constraints(resource) or []:
|
||||
metrics = []
|
||||
for metric in ifcopenshell.util.constraint.get_metrics(constraint) or []:
|
||||
metrics.append(
|
||||
{
|
||||
"reference": ifcopenshell.util.constraint.get_metric_reference(metric),
|
||||
"Benchmark": metric.Benchmark,
|
||||
"ConstraintGrade": metric.ConstraintGrade,
|
||||
}
|
||||
)
|
||||
constraints.append({"ObjectiveQualifier": constraint.ObjectiveQualifier, "metrics": metrics})
|
||||
return constraints
|
||||
|
||||
@classmethod
|
||||
def get_productivity(cls, resource):
|
||||
return ifcopenshell.util.resource.get_productivity(resource, should_inherit=False)
|
||||
|
||||
@classmethod
|
||||
def get_parent_productivity(cls, resource):
|
||||
return ifcopenshell.util.resource.get_parent_productivity(resource)
|
||||
|
||||
@classmethod
|
||||
def cost_values(cls):
|
||||
results = []
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from blenderbim.bim.module.resource.ui import draw_productivity_ui
|
||||
import blenderbim.core.resource as core
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -198,6 +199,13 @@ class CalculateResourceWork(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_resource = tool.Resource.get_highlighted_resource()
|
||||
if active_resource:
|
||||
if tool.Resource.get_productivity(active_resource, should_inherit=True):
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
core.calculate_resource_work(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
|
||||
|
||||
@@ -334,7 +342,7 @@ class EditResourceQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "import_resources.bim"
|
||||
bl_label = "Import P6"
|
||||
bl_label = "Import Resources"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
@@ -348,6 +356,16 @@ class ImportResources(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
core.import_resources(tool.Resource, file_path=self.filepath)
|
||||
|
||||
|
||||
class AddProductivityData(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_productivity_data"
|
||||
bl_description = "Apply"
|
||||
bl_label = "Add Productivity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
tool.Ifc.run("pset.add_pset", product=tool.Resource.get_highlighted_resource(), name="EPset_Productivity")
|
||||
|
||||
|
||||
class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_productivity_data"
|
||||
bl_description = "Apply"
|
||||
@@ -356,3 +374,67 @@ class EditProductivityData(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.edit_productivity_pset(tool.Ifc, tool.Resource)
|
||||
|
||||
def draw(self, context):
|
||||
draw_productivity_ui(self, context)
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self, width=600)
|
||||
|
||||
|
||||
class ConstrainResourceWork(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_usage_constraint"
|
||||
bl_label = "Constrain Resource Work"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
attribute: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.add_usage_constraint(
|
||||
tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute
|
||||
)
|
||||
|
||||
|
||||
class RemoveUsageConstraint(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_usage_constraint"
|
||||
bl_label = "Remove Usage Constraint"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
attribute: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_usage_constraint(
|
||||
tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(self.resource), reference_path=self.attribute
|
||||
)
|
||||
|
||||
|
||||
class GoToResource(bpy.types.Operator):
|
||||
bl_idname = "bim.go_to_resource"
|
||||
bl_label = "Go To Resource"
|
||||
bl_description = "Selects the resource in the Resource Panel"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resource: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
core.go_to_resource(tool.Resource, resource=tool.Ifc.get().by_id(self.resource))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CalculateResourceUsage(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.calculate_resource_usage"
|
||||
bl_label = "Calculate Resource Usage"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_resource = tool.Resource.get_highlighted_resource()
|
||||
if active_resource:
|
||||
if active_resource.Usage and active_resource.Usage.ScheduleWork:
|
||||
task = tool.Resource.get_task_assignments(active_resource)
|
||||
if task and tool.Sequence.has_duration(task):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _execute(self, context):
|
||||
core.calculate_resource_usage(tool.Ifc, tool.Resource, resource=tool.Ifc.get().by_id(tool.Resource.get_highlighted_resource()))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
# Copyright (C) 2021, 2022, 2023 Dion Moult, Yassine Oualid <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of BlenderBIM Add-on.
|
||||
#
|
||||
@@ -22,6 +22,8 @@ import ifcopenshell.util.resource
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
import blenderbim.tool as tool
|
||||
import blenderbim.bim.module.pset.data
|
||||
import blenderbim.bim.module.resource.data
|
||||
import blenderbim.bim.module.sequence.data
|
||||
from blenderbim.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -56,6 +58,8 @@ def updateResourceName(self, context):
|
||||
if props.active_resource_id == self.ifc_definition_id:
|
||||
attribute = props.resource_attributes.get("Name")
|
||||
attribute.string_value = self.name
|
||||
blenderbim.bim.module.resource.data.refresh()
|
||||
tool.Sequence.refresh_task_resources()
|
||||
|
||||
|
||||
def get_quantity_types(self, context):
|
||||
@@ -72,7 +76,7 @@ def get_quantity_types(self, context):
|
||||
|
||||
def update_active_resource_index(self, context):
|
||||
blenderbim.bim.module.pset.data.refresh()
|
||||
if self.should_show_productivity:
|
||||
if self.should_show_resource_tools:
|
||||
tool.Resource.load_productivity_data()
|
||||
|
||||
|
||||
@@ -80,18 +84,20 @@ def updateResourceUsage(self, context):
|
||||
props = context.scene.BIMResourceProperties
|
||||
if not props.is_resource_update_enabled:
|
||||
return
|
||||
|
||||
if self.schedule_usage == "":
|
||||
if not self.schedule_usage:
|
||||
return
|
||||
resource = tool.Ifc.get().by_id(self.ifc_definition_id)
|
||||
if not resource.Usage:
|
||||
tool.Ifc.run(
|
||||
"resource.add_resource_time",
|
||||
resource=resource,
|
||||
)
|
||||
resource.Usage.ScheduleUsage = self.schedule_usage
|
||||
blenderbim.bim.module.pset.data.refresh()
|
||||
if resource.Usage and resource.Usage.ScheduleUsage == self.schedule_usage:
|
||||
return
|
||||
tool.Resource.run_edit_resource_time(resource, attributes={
|
||||
"ScheduleUsage": self.schedule_usage
|
||||
})
|
||||
tool.Sequence.load_task_properties()
|
||||
tool.Resource.load_resource_properties()
|
||||
tool.Sequence.refresh_task_resources()
|
||||
blenderbim.bim.module.resource.data.refresh()
|
||||
blenderbim.bim.module.sequence.data.refresh()
|
||||
blenderbim.bim.module.pset.data.refresh()
|
||||
|
||||
|
||||
class ISODuration(PropertyGroup):
|
||||
@@ -144,7 +150,7 @@ class BIMResourceProperties(PropertyGroup):
|
||||
quantity_types: EnumProperty(items=get_quantity_types, name="Quantity Types")
|
||||
is_editing_quantity: BoolProperty(name="Is Editing Quantity")
|
||||
quantity_attributes: CollectionProperty(name="Quantity Attributes", type=Attribute)
|
||||
should_show_productivity: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
|
||||
should_show_resource_tools: BoolProperty(name="Edit Productivity", update=update_active_resource_index)
|
||||
|
||||
|
||||
class BIMResourceProductivity(PropertyGroup):
|
||||
|
||||
@@ -20,20 +20,21 @@ import blenderbim.bim.helper
|
||||
from bpy.types import Panel, UIList
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.resource.data import ResourceData
|
||||
import blenderbim.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_resources(Panel):
|
||||
bl_label = "Resources"
|
||||
bl_idname = "BIM_PT_resources"
|
||||
bl_options = {"DEFAULT_CLOSED"}
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "scene"
|
||||
bl_parent_id = "BIM_PT_tab_resources"
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Blender.is_tab(context, "SCHEDULING") and tool.Ifc.get() and tool.Ifc.get().schema != "IFC2X3"
|
||||
file = IfcStore.get_file()
|
||||
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMResourceProperties
|
||||
@@ -64,58 +65,124 @@ class BIM_PT_resources(Panel):
|
||||
self.props,
|
||||
"active_resource_index",
|
||||
)
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.props, "should_show_productivity", icon="RECOVER_LAST")
|
||||
if self.props.should_show_productivity:
|
||||
self.draw_productivity_ui(context)
|
||||
if self.props.active_resource_id and self.props.editing_resource_type == "ATTRIBUTES":
|
||||
self.draw_editable_resource_attributes_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY":
|
||||
self.draw_editable_resource_quantity_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
|
||||
self.draw_editable_resource_costs_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
|
||||
self.draw_editable_resource_time_attributes_ui()
|
||||
|
||||
if self.props.active_resource_id:
|
||||
if self.props.editing_resource_type == "ATTRIBUTES":
|
||||
self.draw_editable_resource_attributes_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "QUANTITY":
|
||||
self.draw_editable_resource_quantity_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "COSTS":
|
||||
self.draw_editable_resource_costs_ui()
|
||||
elif self.props.active_resource_id and self.props.editing_resource_type == "USAGE":
|
||||
self.draw_editable_resource_time_attributes_ui()
|
||||
self.draw_productivity_ui(context)
|
||||
|
||||
def draw_productivity_ui(self, context):
|
||||
total_resources = len(self.tprops.resources)
|
||||
if not total_resources or self.props.active_resource_index >= total_resources:
|
||||
return
|
||||
|
||||
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
|
||||
resource = ResourceData.data["resources"][ifc_definition_id]
|
||||
|
||||
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Resource type cannot have productivity data", icon="ERROR")
|
||||
return
|
||||
|
||||
self.productivity_props = context.scene.BIMResourceProductivity
|
||||
|
||||
if resource["Productivity"]:
|
||||
produtivitiy_rate_message = "Current Rate: {}/{}".format(
|
||||
resource["Productivity"]["QuantityProduced"], resource["Productivity"]["TimeConsumed"]
|
||||
)
|
||||
row = self.layout.row()
|
||||
row.alignment = "LEFT"
|
||||
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "LEFT"
|
||||
produtivitiy_rate_message = "No productivity data found"
|
||||
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
row.prop(self.productivity_props, "quantity_produced", text="Quantity Produced")
|
||||
row.prop(self.productivity_props, "quantity_produced_name", text="Quantity Name")
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
self.draw_duration_property(self.productivity_props.quantity_consumed, row)
|
||||
row = self.layout.row()
|
||||
row.alignment = "RIGHT"
|
||||
row.operator("bim.edit_productivity_data", text="Apply", icon="CHECKMARK")
|
||||
row.prop(self.props, "should_show_resource_tools", text="Resource Tools", icon="RECOVER_LAST")
|
||||
if self.props.should_show_resource_tools:
|
||||
total_resources = len(self.tprops.resources)
|
||||
if not total_resources or self.props.active_resource_index >= total_resources:
|
||||
return
|
||||
|
||||
ifc_definition_id = self.tprops.resources[self.props.active_resource_index].ifc_definition_id
|
||||
resource = ResourceData.data["resources"][ifc_definition_id]
|
||||
|
||||
if not resource["type"] in ["IfcConstructionEquipmentResource", "IfcLaborResource"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Resource type cannot have productivity data", icon="ERROR")
|
||||
else:
|
||||
is_usage_locked = False
|
||||
is_work_locked = False
|
||||
for constraint in resource["Benchmarks"] or []:
|
||||
for metric in constraint["metrics"] or []:
|
||||
if (
|
||||
metric["ConstraintGrade"] == "HARD"
|
||||
and metric["reference"]
|
||||
and metric["reference"] == "Usage.ScheduleUsage"
|
||||
):
|
||||
is_usage_locked = True
|
||||
elif (
|
||||
metric["ConstraintGrade"] == "HARD"
|
||||
and metric["reference"]
|
||||
and metric["reference"] == "Usage.ScheduleWork"
|
||||
):
|
||||
is_work_locked = True
|
||||
grid = self.layout.grid_flow(columns=3, even_columns=False, even_rows=False, align=False)
|
||||
|
||||
col1 = grid.column(align=True)
|
||||
col2 = grid.column(align=False)
|
||||
col3 = grid.column(align=True)
|
||||
col1.ui_units_x = 1
|
||||
col2.ui_units_x = 1
|
||||
col3.ui_units_x = 2
|
||||
|
||||
row1_col1 = col1.row()
|
||||
row1_col1.label(text="Schedule Work")
|
||||
row1col2 = col2.row()
|
||||
schedule_work = resource.get("ScheduleWork", None)
|
||||
derived_schedule_work = resource.get("DerivedScheduleWork", None)
|
||||
row1col2.label(
|
||||
text="{}".format(schedule_work) if schedule_work else "{} h*".format(derived_schedule_work),
|
||||
icon="TIME",
|
||||
)
|
||||
|
||||
row1col3 = col3.row()
|
||||
row1col3.operator("bim.calculate_resource_work", text="", icon="TEMP").resource = ifc_definition_id
|
||||
op = row1col3.operator(
|
||||
"bim.add_usage_constraint" if not is_work_locked else "bim.remove_usage_constraint",
|
||||
text="",
|
||||
icon="LOCKED" if is_work_locked else "UNLOCKED",
|
||||
)
|
||||
op.resource = ifc_definition_id
|
||||
op.attribute = "Usage.ScheduleWork"
|
||||
row2_col1 = col1.row()
|
||||
row2_col1.label(text="Schedule Usage")
|
||||
row2col2 = col2.row()
|
||||
row2col2.prop(self.tprops.resources[self.props.active_resource_index], "schedule_usage", text="")
|
||||
row2col3 = col3.row()
|
||||
row2col3.operator("bim.calculate_resource_usage", text="", icon="TEMP")
|
||||
op = row2col3.operator(
|
||||
"bim.add_usage_constraint" if not is_usage_locked else "bim.remove_usage_constraint",
|
||||
text="",
|
||||
icon="LOCKED" if is_usage_locked else "UNLOCKED",
|
||||
)
|
||||
op.resource = ifc_definition_id
|
||||
op.attribute = "Usage.ScheduleUsage"
|
||||
|
||||
productivity = resource["Productivity"]
|
||||
parent_productivity = resource["InheritedProductivity"]
|
||||
row = self.layout.row()
|
||||
if productivity:
|
||||
produtivitiy_rate_message = "Current Productivity Rate: {} {} / {}".format(
|
||||
productivity["QuantityProduced"],
|
||||
productivity["QuantityProducedName"],
|
||||
productivity["TimeConsumed"],
|
||||
)
|
||||
row.alignment = "LEFT"
|
||||
row.label(text=produtivitiy_rate_message, icon="ARMATURE_DATA")
|
||||
row.operator("bim.edit_productivity_data", text="", icon="GREASEPENCIL")
|
||||
op = row.operator("bim.remove_pset", text="", icon="X")
|
||||
op.pset_id = productivity["id"]
|
||||
op.obj_type = "Resource"
|
||||
op.obj = ""
|
||||
|
||||
elif parent_productivity:
|
||||
produtivitiy_rate_message = "Inherited Productivity Rate: {} {} / {}*".format(
|
||||
parent_productivity["QuantityProduced"],
|
||||
parent_productivity["QuantityProducedName"],
|
||||
parent_productivity["TimeConsumed"],
|
||||
)
|
||||
row.alignment = "LEFT"
|
||||
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
|
||||
row.operator("bim.add_productivity_data", text="", icon="ADD")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "LEFT"
|
||||
produtivitiy_rate_message = "No productivity data found"
|
||||
row.label(text="{}".format(produtivitiy_rate_message), icon="ARMATURE_DATA")
|
||||
row.operator("bim.add_productivity_data", text="", icon="ADD")
|
||||
|
||||
def draw_resource_operators(self):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -153,9 +220,6 @@ class BIM_PT_resources(Panel):
|
||||
|
||||
if not self.props.active_resource_id:
|
||||
if resource["type"] in ["IfcLaborResource", "IfcConstructionEquipmentResource"]:
|
||||
if resource["Productivity"]:
|
||||
op = row.operator("bim.calculate_resource_work", text="", icon="TEMP")
|
||||
op.resource = ifc_definition_id
|
||||
row.operator("bim.enable_editing_resource_time", text="", icon="TIME").resource = ifc_definition_id
|
||||
op = row.operator("bim.enable_editing_resource_base_quantity", text="", icon="PROPERTIES")
|
||||
op.resource = ifc_definition_id
|
||||
@@ -163,6 +227,12 @@ class BIM_PT_resources(Panel):
|
||||
op.resource = ifc_definition_id
|
||||
row.operator("bim.enable_editing_resource", text="", icon="GREASEPENCIL").resource = ifc_definition_id
|
||||
row.operator("bim.remove_resource", text="", icon="X").resource = ifc_definition_id
|
||||
else:
|
||||
if self.props.editing_resource_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_resource", text="", icon="CHECKMARK")
|
||||
elif self.props.editing_resource_type == "USAGE":
|
||||
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
|
||||
|
||||
def draw_editable_resource_attributes_ui(self):
|
||||
blenderbim.bim.helper.draw_attributes(self.props.resource_attributes, self.layout)
|
||||
@@ -250,7 +320,6 @@ class BIM_PT_resources(Panel):
|
||||
def draw_duration_property(self, duration_props, layout):
|
||||
for duration_prop in duration_props:
|
||||
if duration_prop.name == "BaseQuantityConsumed":
|
||||
layout.label(text=duration_prop.name)
|
||||
layout.prop(duration_prop, "years", text="Y")
|
||||
layout.prop(duration_prop, "months", text="M")
|
||||
layout.prop(duration_prop, "days", text="D")
|
||||
@@ -287,7 +356,7 @@ class BIM_UL_resources(UIList):
|
||||
else:
|
||||
row.label(text="", icon="DOT")
|
||||
row.prop(item, "name", emboss=False, text="", icon=icon_map[resource["type"]])
|
||||
row.prop(item, "schedule_usage", text="", emboss=False)
|
||||
row.prop(item, "schedule_usage", text="", emboss=False) if item.schedule_usage else None
|
||||
if context.active_object and not props.active_resource_id:
|
||||
row = layout.row(align=True)
|
||||
if item.ifc_definition_id in ResourceData.data["active_resource_ids"]:
|
||||
@@ -303,3 +372,29 @@ class BIM_UL_resources(UIList):
|
||||
elif props.editing_resource_type == "USAGE":
|
||||
row.operator("bim.edit_resource_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_resource", text="", icon="CANCEL")
|
||||
|
||||
|
||||
def draw_productivity_ui(self, context):
|
||||
def draw_duration_property(duration_props, layout):
|
||||
for duration_prop in duration_props:
|
||||
if duration_prop.name == "BaseQuantityConsumed":
|
||||
layout.prop(duration_prop, "years", text="Y")
|
||||
layout.prop(duration_prop, "months", text="M")
|
||||
layout.prop(duration_prop, "days", text="D")
|
||||
layout.prop(duration_prop, "hours", text="H")
|
||||
layout.prop(duration_prop, "minutes", text="Min")
|
||||
layout.prop(duration_prop, "seconds", text="S")
|
||||
|
||||
productivity_props = context.scene.BIMResourceProductivity
|
||||
grid = self.layout.grid_flow(columns=2, even_columns=False, even_rows=False, align=False)
|
||||
col1 = grid.column(align=False)
|
||||
col2 = grid.column(align=False)
|
||||
row1_col1 = col1.row()
|
||||
row1_col1.label(text="Quantity")
|
||||
row1_col2 = col2.row()
|
||||
row1_col2.prop(productivity_props, "quantity_produced", text="")
|
||||
row1_col2.prop(productivity_props, "quantity_produced_name", text="")
|
||||
row2_col1 = col1.row()
|
||||
row2_col1.label(text="Time")
|
||||
row2_col2 = col2.row()
|
||||
draw_duration_property(productivity_props.quantity_consumed, row2_col2)
|
||||
|
||||
@@ -49,8 +49,8 @@ class IfcClassData:
|
||||
@classmethod
|
||||
def ifc_products(cls):
|
||||
products = [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcElement",
|
||||
"IfcSpatialElement",
|
||||
"IfcSpatialElementType",
|
||||
"IfcGroup",
|
||||
@@ -62,8 +62,8 @@ class IfcClassData:
|
||||
version = tool.Ifc.get_schema()
|
||||
if version == "IFC2X3":
|
||||
products = [
|
||||
"IfcElement",
|
||||
"IfcElementType",
|
||||
"IfcElement",
|
||||
"IfcSpatialStructureElement",
|
||||
"IfcGroup",
|
||||
"IfcStructuralItem",
|
||||
|
||||
@@ -212,4 +212,4 @@ class CopyClass(bpy.types.Operator, Operator):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
for obj in objects:
|
||||
core.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
|
||||
@@ -33,10 +33,6 @@ from bpy.props import (
|
||||
)
|
||||
|
||||
|
||||
def purge():
|
||||
pass
|
||||
|
||||
|
||||
def get_ifc_predefined_types(self, context):
|
||||
if not IfcClassData.is_loaded:
|
||||
IfcClassData.load()
|
||||
|
||||
@@ -26,6 +26,7 @@ classes = (
|
||||
operator.AddFilterGroup,
|
||||
operator.AddToIfcGroup,
|
||||
operator.ColourByProperty,
|
||||
operator.EditFilterQuery,
|
||||
operator.FilterModelElements,
|
||||
operator.IfcSelector,
|
||||
operator.LoadColourscheme,
|
||||
@@ -39,6 +40,7 @@ classes = (
|
||||
operator.SaveSearch,
|
||||
operator.SaveSelectorQuery,
|
||||
operator.Search,
|
||||
operator.SelectFilterElements,
|
||||
operator.SelectGlobalId,
|
||||
operator.SelectIfcClass,
|
||||
operator.SelectSimilar,
|
||||
|
||||
@@ -40,12 +40,14 @@ class SearchData:
|
||||
|
||||
@classmethod
|
||||
def saved_searches(cls):
|
||||
if not tool.Ifc.get():
|
||||
return []
|
||||
groups = tool.Ifc.get().by_type("IfcGroup")
|
||||
results = []
|
||||
for group in groups:
|
||||
try:
|
||||
data = json.loads(group.Description)
|
||||
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search":
|
||||
if isinstance(data, dict) and data.get("type", None) == "BBIM_Search" and data.get("query", None):
|
||||
results.append(group)
|
||||
except:
|
||||
pass
|
||||
@@ -102,7 +104,7 @@ class SelectSimilarData:
|
||||
psets = ifcopenshell.util.element.get_psets(element, psets_only=True)
|
||||
for pset, properties in psets.items():
|
||||
if pset.endswith("Common"):
|
||||
keys.extend([f'r".*Common"."{name}"' for name in properties.keys() if name != "id"])
|
||||
keys.extend([f'/.*Common/."{name}"' for name in properties.keys() if name != "id"])
|
||||
else:
|
||||
keys.extend([f"{pset}.{name}" for name in properties.keys() if name != "id"])
|
||||
return [(k, k, "") for k in keys]
|
||||
|
||||
@@ -61,9 +61,14 @@ colour_list = [
|
||||
class AddFilterGroup(Operator):
|
||||
bl_idname = "bim.add_filter_group"
|
||||
bl_label = "Add Filter Group"
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMSearchProperties.filter_groups.add()
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
props.filter_groups.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -71,9 +76,13 @@ class RemoveFilterGroup(Operator):
|
||||
bl_idname = "bim.remove_filter_group"
|
||||
bl_label = "Remove Filter Group"
|
||||
index: IntProperty()
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
props.filter_groups.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -83,9 +92,13 @@ class RemoveFilter(Operator):
|
||||
bl_label = "Remove Filter Group"
|
||||
group_index: IntProperty()
|
||||
index: IntProperty()
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
props.filter_groups[self.group_index].filters.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -95,13 +108,87 @@ class AddFilter(Operator):
|
||||
bl_label = "Add Filter"
|
||||
index: IntProperty()
|
||||
type: StringProperty()
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
new = context.scene.BIMSearchProperties.filter_groups[self.index].filters.add()
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
new = props.filter_groups[self.index].filters.add()
|
||||
new.type = self.type
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectFilterElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_filter_elements"
|
||||
bl_label = "Select Filter Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
group_index: IntProperty()
|
||||
index: IntProperty()
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
global_ids = []
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
global_id = getattr(element, "GlobalId", None)
|
||||
if global_id:
|
||||
global_ids.append(global_id)
|
||||
if len(global_ids) > 50:
|
||||
# Too much to store in a string property
|
||||
name = "globalid-filter-" + ifcopenshell.guid.new()
|
||||
text_data = bpy.data.texts.new(name)
|
||||
text_data.from_string(",".join(global_ids))
|
||||
props.filter_groups[self.group_index].filters[self.index].value = f"bpy.data.texts['{name}']"
|
||||
else:
|
||||
props.filter_groups[self.group_index].filters[self.index].value = ",".join(global_ids)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditFilterQuery(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_filter_query"
|
||||
bl_label = "Edit Filter Query"
|
||||
bl_description = "Edit the underlying filter query for advanced users"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
query: StringProperty(name="Query")
|
||||
module: StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
if self.query == self.old_query:
|
||||
return
|
||||
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
try:
|
||||
tool.Search.import_filter_query(self.query, props.filter_groups)
|
||||
except:
|
||||
return
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "query", text="")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
self.query = tool.Search.export_filter_query(props.filter_groups)
|
||||
self.old_query = self.query
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class Search(Operator):
|
||||
bl_idname = "bim.search"
|
||||
bl_label = "Search"
|
||||
@@ -127,21 +214,36 @@ class SaveSearch(Operator, tool.Ifc.Operator):
|
||||
bl_description = "Save search filter to an IFC group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: StringProperty(name="Name")
|
||||
module: StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
if not self.name:
|
||||
return
|
||||
|
||||
query = tool.Search.export_filter_query(context.scene.BIMSearchProperties.filter_groups)
|
||||
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
|
||||
try:
|
||||
query = tool.Search.export_filter_query(props.filter_groups)
|
||||
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
|
||||
except:
|
||||
return
|
||||
|
||||
description = json.dumps({"type": "BBIM_Search", "query": query})
|
||||
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
|
||||
if group:
|
||||
group = group[0]
|
||||
group.Description = description
|
||||
else:
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=results, group=group)
|
||||
if results:
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=list(results), group=group)
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "name")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
@@ -152,11 +254,15 @@ class LoadSearch(Operator, tool.Ifc.Operator):
|
||||
bl_label = "Load Search"
|
||||
bl_description = "Load search filter from an IFC group"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
module: StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
group = tool.Ifc.get().by_id(int(props.saved_searches))
|
||||
query = tool.Search.import_filter_query(group, context.scene.BIMSearchProperties.filter_groups)
|
||||
if self.module == "search":
|
||||
props = context.scene.BIMSearchProperties
|
||||
elif self.module == "csv":
|
||||
props = context.scene.CsvProperties
|
||||
group = tool.Ifc.get().by_id(int(context.scene.BIMSearchProperties.saved_searches))
|
||||
query = tool.Search.import_filter_query(tool.Search.get_group_query(group), props.filter_groups)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
@@ -242,14 +348,15 @@ class SaveColourscheme(Operator, tool.Ifc.Operator):
|
||||
query = props.colourscheme_query
|
||||
|
||||
group = [g for g in tool.Ifc.get().by_type("IfcGroup") if g.Name == self.name]
|
||||
coulour_scheme = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
|
||||
if group:
|
||||
group = group[0]
|
||||
description = json.loads(group.Description)
|
||||
description["colourscheme"] = {cs.name: cs.colour[0:3] for cs in props.colourscheme}
|
||||
description["colourscheme"] = coulour_scheme
|
||||
description["colourscheme_query"] = query
|
||||
group.Description = json.dumps(description)
|
||||
else:
|
||||
description = json.dumps({"type": "BBIM_Search", "colourscheme": query})
|
||||
description = json.dumps({"type": "BBIM_Search", "colourscheme": coulour_scheme,"colourscheme_query": query})
|
||||
group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.name, Description=description)
|
||||
|
||||
def invoke(self, context, event):
|
||||
@@ -264,7 +371,7 @@ class LoadColourscheme(Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMSearchProperties
|
||||
group = tool.Ifc.get().by_id(int(props.saved_searches))
|
||||
group = tool.Ifc.get().by_id(int(props.saved_colourschemes))
|
||||
description = json.loads(group.Description)
|
||||
props.colourscheme_query = description.get("colourscheme_query")
|
||||
props.colourscheme.clear()
|
||||
|
||||
@@ -128,7 +128,8 @@ class BIMSearchProperties(PropertyGroup):
|
||||
("classification", "Classification", "", "OUTLINER", 4),
|
||||
("location", "Location", "", "PACKAGE", 5),
|
||||
("type", "Type", "", "FILE_VOLUME", 6),
|
||||
("instance", "GlobalId", "", "GRIP", 7),
|
||||
("query", "Query", "", "POINTCLOUD_DATA", 7),
|
||||
("instance", "GlobalId", "", "GRIP", 8),
|
||||
],
|
||||
)
|
||||
saved_searches: EnumProperty(items=get_saved_searches, name="Saved Searches")
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import blenderbim.bim.helper
|
||||
from bpy.types import Panel
|
||||
from blenderbim.bim.module.search.data import SearchData, ColourByPropertyData, SelectSimilarData
|
||||
|
||||
@@ -35,59 +36,7 @@ class BIM_PT_search(Panel):
|
||||
|
||||
props = context.scene.BIMSearchProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{len(SearchData.data['saved_searches'])} Saved Searches")
|
||||
|
||||
if SearchData.data["saved_searches"]:
|
||||
row.operator("bim.load_search", text="", icon="IMPORT")
|
||||
row.operator("bim.save_search", text="", icon="EXPORT")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD")
|
||||
|
||||
for i, filter_group in enumerate(props.filter_groups):
|
||||
box = self.layout.box()
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(props, "facet", text="")
|
||||
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
|
||||
op.type = props.facet
|
||||
op.index = i
|
||||
|
||||
for j, ifc_filter in enumerate(filter_group.filters):
|
||||
if ifc_filter.type == "entity":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_3D")
|
||||
elif ifc_filter.type == "attribute":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="COPY_ID")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "type":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME")
|
||||
elif ifc_filter.type == "material":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="MATERIAL")
|
||||
elif ifc_filter.type == "property":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "pset", text="", icon="PROPERTIES")
|
||||
row.prop(ifc_filter, "name", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "classification":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
|
||||
elif ifc_filter.type == "location":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "name", text="", icon="PACKAGE")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
row.prop(ifc_filter, "value", text="", icon="GRIP")
|
||||
op = row.operator("bim.remove_filter", text="", icon="X")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
|
||||
row = box.row()
|
||||
row.operator("bim.remove_filter_group", icon="X").index = i
|
||||
blenderbim.bim.helper.draw_filter(self.layout, props, SearchData, "search")
|
||||
|
||||
if len(props.filter_groups):
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
@@ -20,12 +20,11 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.ExpandAllTasks,
|
||||
operator.ContractAllTasks,
|
||||
operator.AddAnimationCamera,
|
||||
operator.AddSummaryTask,
|
||||
operator.AddTask,
|
||||
operator.AddTaskColumn,
|
||||
operator.AddTaskBars,
|
||||
operator.AddTaskColumn,
|
||||
operator.AddTimePeriod,
|
||||
operator.AddWorkCalendar,
|
||||
operator.AddWorkPlan,
|
||||
@@ -42,9 +41,12 @@ classes = (
|
||||
operator.BlenderBIM_DatePickerSetDate,
|
||||
operator.BlenderBIM_RedrawDatePicker,
|
||||
operator.CalculateTaskDuration,
|
||||
operator.ClearPreviousAnimation,
|
||||
operator.ContractAllTasks,
|
||||
operator.ContractTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.CopyTask,
|
||||
operator.CopyTaskAttribute,
|
||||
operator.CreateBaseline,
|
||||
operator.DisableEditingSequence,
|
||||
operator.DisableEditingTask,
|
||||
operator.DisableEditingTaskTime,
|
||||
@@ -67,27 +69,29 @@ classes = (
|
||||
operator.EnableEditingTaskCalendar,
|
||||
operator.EnableEditingTaskSequence,
|
||||
operator.EnableEditingTaskTime,
|
||||
operator.EnableEditingWorkScheduleTasks,
|
||||
operator.EnableEditingWorkCalendar,
|
||||
operator.EnableEditingWorkCalendarTimes,
|
||||
operator.EnableEditingWorkPlan,
|
||||
operator.EnableEditingWorkPlanSchedules,
|
||||
operator.EnableEditingWorkSchedule,
|
||||
operator.EnableEditingWorkScheduleTasks,
|
||||
operator.EnableEditingWorkTime,
|
||||
operator.ExpandAllTasks,
|
||||
operator.ExpandTask,
|
||||
operator.ExportMSP,
|
||||
operator.ExportP6,
|
||||
operator.GenerateGanttChart,
|
||||
operator.GuessDateRange,
|
||||
operator.ImportMSP,
|
||||
operator.GoToTask,
|
||||
operator.ImportCSV,
|
||||
operator.ImportMSP,
|
||||
operator.ImportP6,
|
||||
operator.ImportP6XER,
|
||||
operator.ImportPP,
|
||||
operator.LoadTaskInputs,
|
||||
operator.LoadTaskOutputs,
|
||||
operator.LoadAnimationColorScheme,
|
||||
operator.LoadDefaultAnimationColors,
|
||||
operator.LoadProductTasks,
|
||||
operator.LoadTaskProperties,
|
||||
operator.LoadTaskResources,
|
||||
operator.RecalculateSchedule,
|
||||
operator.RemoveTask,
|
||||
operator.RemoveTaskCalendar,
|
||||
@@ -98,10 +102,11 @@ classes = (
|
||||
operator.RemoveWorkSchedule,
|
||||
operator.RemoveWorkTime,
|
||||
operator.ReorderTask,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SaveAnimationColorScheme,
|
||||
operator.SelectTaskRelatedInputs,
|
||||
operator.SelectWorkScheduleProducts,
|
||||
operator.SelectTaskRelatedProducts,
|
||||
operator.SelectUnassignedWorkScheduleProducts,
|
||||
operator.SelectWorkScheduleProducts,
|
||||
operator.SetTaskSortColumn,
|
||||
operator.SetupDefaultTaskColumns,
|
||||
operator.UnassignLagTime,
|
||||
@@ -113,17 +118,18 @@ classes = (
|
||||
operator.UnassignWorkSchedule,
|
||||
operator.VisualiseWorkScheduleDate,
|
||||
operator.VisualiseWorkScheduleDateRange,
|
||||
operator.LoadTaskAnimationColors,
|
||||
operator.DisableEditingTaskAnimationColors,
|
||||
operator.LoadProductTasks,
|
||||
operator.HighlightTask,
|
||||
operator.CreateBaseline,
|
||||
operator.EnableStatusFilters,
|
||||
operator.DisableStatusFilters,
|
||||
operator.ActivateStatusFilters,
|
||||
operator.SelectStatusFilter,
|
||||
prop.WorkPlan,
|
||||
prop.BIMWorkPlanProperties,
|
||||
prop.Task,
|
||||
prop.TaskResource,
|
||||
prop.TaskProduct,
|
||||
prop.ISODuration,
|
||||
prop.IFCStatus,
|
||||
prop.BIMStatusProperties,
|
||||
prop.BIMWorkScheduleProperties,
|
||||
prop.BIMTaskTreeProperties,
|
||||
prop.BIMTaskTypeColor,
|
||||
@@ -133,17 +139,19 @@ classes = (
|
||||
prop.BIMWorkCalendarProperties,
|
||||
prop.DatePickerProperties,
|
||||
prop.BIMDateTextProperties,
|
||||
ui.BIM_PT_status,
|
||||
ui.BIM_PT_work_plans,
|
||||
ui.BIM_PT_work_schedules,
|
||||
ui.BIM_PT_work_calendars,
|
||||
ui.BIM_PT_animation_tools,
|
||||
ui.BIM_PT_task_icom,
|
||||
ui.BIM_PT_animation_Color_Scheme,
|
||||
ui.BIM_UL_task_columns,
|
||||
ui.BIM_UL_task_inputs,
|
||||
ui.BIM_UL_task_resources,
|
||||
ui.BIM_UL_task_outputs,
|
||||
ui.BIM_UL_tasks,
|
||||
ui.BIM_PT_4D_Tools,
|
||||
ui.BIM_PT_Task_Bar_Creator,
|
||||
ui.BIM_UL_animation_colors,
|
||||
ui.BIM_UL_product_input_tasks,
|
||||
ui.BIM_UL_product_output_tasks,
|
||||
@@ -164,6 +172,7 @@ def menu_func_import(self, context):
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMStatusProperties = bpy.props.PointerProperty(type=prop.BIMStatusProperties)
|
||||
bpy.types.Scene.BIMWorkPlanProperties = bpy.props.PointerProperty(type=prop.BIMWorkPlanProperties)
|
||||
bpy.types.Scene.BIMWorkScheduleProperties = bpy.props.PointerProperty(type=prop.BIMWorkScheduleProperties)
|
||||
bpy.types.Scene.BIMTaskTreeProperties = bpy.props.PointerProperty(type=prop.BIMTaskTreeProperties)
|
||||
@@ -176,6 +185,7 @@ def register():
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMStatusProperties
|
||||
del bpy.types.Scene.BIMWorkPlanProperties
|
||||
del bpy.types.Scene.BIMWorkScheduleProperties
|
||||
del bpy.types.Scene.BIMTaskTreeProperties
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user