mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Compare commits
256 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91a54be2c9 | |||
| 02532fd021 | |||
| 21f5363c74 | |||
| 71d469a271 | |||
| 96bdda305e | |||
| 1063238479 | |||
| d1337cff02 | |||
| f26f2780e9 | |||
| 2e3c733c0d | |||
| ff09cd0a56 | |||
| df91f475c1 | |||
| a733f36576 | |||
| 9282ccabb3 | |||
| 404c39873d | |||
| 34a1bc6f04 | |||
| 15112211c1 | |||
| e3bbec6394 | |||
| bd1871bd44 | |||
| a75f6a5804 | |||
| a12bb343ca | |||
| b486b32c8d | |||
| 5e7893dbb3 | |||
| c8d74a560c | |||
| 9b8357c5ec | |||
| 67b539aba1 | |||
| 176c5cc0fb | |||
| 152c94a7d6 | |||
| 4b92d7d076 | |||
| d10c176bb6 | |||
| 3503d41b3f | |||
| 34676a567e | |||
| f5a46ffb7c | |||
| 94452b4a7a | |||
| 2c0b1691ae | |||
| 60c71c145b | |||
| d0f4dd00c7 | |||
| eee94f53a4 | |||
| 6bb028696d | |||
| 3499c50d7d | |||
| cd952d7e54 | |||
| 2741acf4fe | |||
| 69b6c41791 | |||
| d1306d7a27 | |||
| 2bb373c1b2 | |||
| b4bc7ec371 | |||
| 1fecd77121 | |||
| c9b41a1499 | |||
| b1ae213349 | |||
| 5d632eae3d | |||
| 1608677d40 | |||
| a37d46ca3d | |||
| 862119834f | |||
| 4d7453f9a4 | |||
| a4f06f69cc | |||
| 24f693e0bb | |||
| 32088fb95a | |||
| 812baa1c04 | |||
| 6862523c75 | |||
| 8b4683aef1 | |||
| 02d359d0e6 | |||
| 7d4176d105 | |||
| 6eb45917fe | |||
| 3f4656600e | |||
| 7034c0b18a | |||
| 97ece7c20b | |||
| 5ba82d7764 | |||
| 8e8b44a744 | |||
| 8b9927b77b | |||
| 09f6991565 | |||
| 14e8fad841 | |||
| c76785e02c | |||
| 7808dd535d | |||
| 72cf77690e | |||
| bf4a3f074f | |||
| a374a7e3ff | |||
| b6267e4ad1 | |||
| f21b34ffc1 | |||
| 6de16bf658 | |||
| 9f17b9fd86 | |||
| 781df9e3d8 | |||
| 8c9ccfdaae | |||
| 3892ac56a4 | |||
| ba6674299c | |||
| fc0624692f | |||
| d6b5c73d4a | |||
| f622d2f961 | |||
| 3a66a1c754 | |||
| d0741cd495 | |||
| 0d39f8df28 | |||
| 87265d052b | |||
| 5af7c9a32d | |||
| 1ce9a4c68e | |||
| 930d58cf69 | |||
| 926785c5ec | |||
| 274e19dcb9 | |||
| 7f0f93f726 | |||
| b07b0e4bea | |||
| a0e4bdcbef | |||
| 2da183bfa6 | |||
| adf8a1f4e0 | |||
| e1ec422bb1 | |||
| c51e36dbca | |||
| b393c120f1 | |||
| 3c7e938d90 | |||
| a614fac71a | |||
| 5a211c67eb | |||
| 6ea53d54ba | |||
| b9b5aefd5e | |||
| e1c1b828ad | |||
| 3180004a28 | |||
| 2ea2dc303c | |||
| 0aae57620f | |||
| 5c681d729c | |||
| 802ab8c296 | |||
| 2ebe7046d3 | |||
| 3562d89f01 | |||
| 1155b52375 | |||
| 2d67440085 | |||
| cc3f2aa28d | |||
| 390349aa7a | |||
| eade620581 | |||
| 88ac0a0f6c | |||
| a531bd8c1f | |||
| f44769c138 | |||
| a6e3cc667c | |||
| 59b17fc593 | |||
| fb1b5bea51 | |||
| e8aa508405 | |||
| 363083104b | |||
| 2798bf3b68 | |||
| e41c4bf7ae | |||
| 3bdce4fbc3 | |||
| ad7473ff1f | |||
| 3d77fc7c7d | |||
| 92a222cda7 | |||
| cc60794293 | |||
| c7b4033f6f | |||
| 1ab288de0b | |||
| 48f62c0b5b | |||
| 5fc549453c | |||
| bd5f5a30b3 | |||
| baf3edca13 | |||
| c23e973575 | |||
| a5801e439f | |||
| 4e65c33bae | |||
| c3652a56ee | |||
| e1b3b68a8b | |||
| dbce4c5d98 | |||
| 0663c5130a | |||
| ea7ced738a | |||
| 7cde9629f8 | |||
| 5f5ba72919 | |||
| feebc701fa | |||
| 5ab14f1e4e | |||
| f289b55766 | |||
| 3976b77ee1 | |||
| 7259dd625f | |||
| 03f5ee653b | |||
| 62a0c258b2 | |||
| c838139fdc | |||
| a42a95adcb | |||
| 3b9d6ed145 | |||
| 3c4105cbc5 | |||
| 302423bfdb | |||
| 6dbf86382f | |||
| 3b50993b21 | |||
| a8634a561a | |||
| a16b8fa376 | |||
| a25698734a | |||
| 1c1bf56893 | |||
| ceeb7ca355 | |||
| 6b77147d45 | |||
| a929ab0c4f | |||
| e9eb5f892a | |||
| 72ef055946 | |||
| 32e4a06fe4 | |||
| 02af46c99f | |||
| 085fc8ca1c | |||
| 93d36501a3 | |||
| a74f1c6e08 | |||
| 8766cad37e | |||
| 320a4cf4fa | |||
| 77ed64a900 | |||
| b17c6dde85 | |||
| b0b7bbf95f | |||
| 873fe6a87f | |||
| 31dc3fe002 | |||
| 697b034c22 | |||
| 810e8df6fe | |||
| d6cc1ff9e9 | |||
| 242d70c311 | |||
| 327899ca62 | |||
| 482512085c | |||
| 12774607f3 | |||
| 242d0ffb3f | |||
| 2bef2a0219 | |||
| 8cb871a6a2 | |||
| fb5358e54e | |||
| deae4e659b | |||
| 29bd89324a | |||
| 087608a5f5 | |||
| bc5a291321 | |||
| 88befea174 | |||
| 441e2ddbd6 | |||
| 09e07c80c5 | |||
| e3ddccdb11 | |||
| 621f6e7a31 | |||
| 0052570fdb | |||
| 55e2171cac | |||
| 1f8967d5b8 | |||
| 0410cec21b | |||
| 08ab48887a | |||
| 97bb587772 | |||
| b452348748 | |||
| 60c18628c5 | |||
| 5bb769448d | |||
| ca890d2f9b | |||
| 99782a2813 | |||
| d48406006f | |||
| 0405b5c61f | |||
| c9eea76e57 | |||
| cc73143ac1 | |||
| 84b0abfb60 | |||
| 13ea2ba8d2 | |||
| d148ab4f31 | |||
| c7a030cc2d | |||
| 718254fe5e | |||
| 41227b89c8 | |||
| 2547ee9ab5 | |||
| d22f1bcd71 | |||
| 0c2158089d | |||
| ccafc203b5 | |||
| 0efc2d25f1 | |||
| ac50f002f0 | |||
| 66f4c957ea | |||
| 67016d6f90 | |||
| 439f9c14ce | |||
| 404bf13a18 | |||
| 3a37bcdcaa | |||
| 992ad8e24a | |||
| 1de2515fcd | |||
| 4bc88eac2f | |||
| 22f10e5549 | |||
| 8e34c1d7df | |||
| 4369baa226 | |||
| 7941d71feb | |||
| 68d967c2e5 | |||
| 4ea9a598c7 | |||
| 06d1c81954 | |||
| aa5f08880b | |||
| 9e6320d545 | |||
| f39d3d4b9c | |||
| 5c72cf35bd | |||
| b9a435e1c3 | |||
| 92f2559ce0 | |||
| acf9713943 |
@@ -44,7 +44,11 @@ jobs:
|
||||
brew update
|
||||
brew install git bison autoconf automake libffi cmake findutils
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
|
||||
|
||||
- name: Install aws cli
|
||||
run: |
|
||||
python -m pip install awscli
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
@@ -117,20 +121,6 @@ jobs:
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Overwrite aws cli
|
||||
run: |
|
||||
# Error: The `brew link` step did not complete successfully
|
||||
# The formula built, but is not symlinked into /usr/local
|
||||
# Could not symlink bin/idle3
|
||||
# Target /usr/local/bin/idle3
|
||||
# already exists. You may want to remove it:
|
||||
# rm '/usr/local/bin/idle3'
|
||||
#
|
||||
# To force the link and overwrite all conflicting files:
|
||||
# brew link --overwrite python@3.13
|
||||
# https://github.com/rust-lang/rustup/pull/3989/files
|
||||
brew install --overwrite awscli | true
|
||||
|
||||
- name: Upload .zip archives to S3
|
||||
run: |
|
||||
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
repository: pyodide/pyodide
|
||||
ref: '0.26.4'
|
||||
ref: '0.28.0a3'
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
path: pyodide
|
||||
|
||||
@@ -29,18 +29,18 @@ jobs:
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/meta.yaml
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/setup.py
|
||||
echo '#!/usr/bin/bash' > script.sh
|
||||
echo 'cd pyodide' > script.sh
|
||||
echo 'make && pip install ./pyodide-build' >> script.sh
|
||||
echo 'cd pyodide' >> script.sh
|
||||
echo 'make' >> script.sh
|
||||
echo 'cd ..' >> script.sh
|
||||
echo 'mkdir -p packages/ifcopenshell' >> script.sh
|
||||
echo 'cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell' >> script.sh
|
||||
echo 'PYODIDE_ROOT=/src/pyodide \' >> script.sh
|
||||
echo 'PATH=/src/pyodide/emsdk/emsdk:/src/pyodide/emsdk/emsdk/node/20.18.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> script.sh
|
||||
echo 'PATH=/src/pyodide/emsdk/emsdk:/src/pyodide/emsdk/emsdk/node/22.16.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> script.sh
|
||||
echo 'pyodide build-recipes ifcopenshell --install' >> script.sh
|
||||
chmod +x script.sh
|
||||
sed -i s/--tty// pyodide/run_docker
|
||||
pyodide/run_docker ./script.sh
|
||||
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp312-cp312-emscripten_3_1_58_wasm32.whl
|
||||
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp313-cp313-emscripten_4_0_9_wasm32.whl
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: _deps-vs2019-x64-installed
|
||||
path: _deps-vs2022-x64-installed
|
||||
ref: windows-${{ matrix.arch }}
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
cd _deps-vs2022-x64-installed
|
||||
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
|
||||
7z x $_.FullName
|
||||
}
|
||||
@@ -53,17 +53,17 @@ jobs:
|
||||
for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do (
|
||||
set PY_VER_MAJOR_MINOR=%%a%%b
|
||||
)
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
SET IFCOS_INSTALL_PYTHON=FALSE
|
||||
cd win
|
||||
echo y | call build-deps.cmd vs2019-x64 Release
|
||||
echo y | call build-deps.cmd vs2022-x64 Release
|
||||
SET PYTHONHOME=C:\Python\${{ matrix.python }}
|
||||
call run-cmake.bat vs2019-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
|
||||
call install-ifcopenshell.bat vs2019-x64 Release
|
||||
call run-cmake.bat vs2022-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
|
||||
call install-ifcopenshell.bat vs2022-x64 Release
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
cd _deps-vs2022-x64-installed
|
||||
Get-ChildItem -Path . -Directory | ForEach-Object {
|
||||
$cacheFile = "cache-$($_.Name).zip"
|
||||
echo $cacheFile
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
- name: Commit and Push Changes to Build Repository
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
cd _deps-vs2022-x64-installed
|
||||
git config user.name "IfcOpenBot"
|
||||
git config user.email "ifcopenbot@ifcopenshell.org"
|
||||
git add *.zip
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
|
||||
if ("${{ matrix.python }}" -eq "3.9.11") {
|
||||
# only for the first python version the executables are assembled for upload
|
||||
cd _installed-vs2019-x64/bin
|
||||
cd _installed-vs2022-x64/bin
|
||||
Get-ChildItem -Path . | ForEach-Object {
|
||||
echo $_
|
||||
$exe = $_.Name
|
||||
@@ -124,4 +124,7 @@ jobs:
|
||||
AWS_MAX_ATTEMPTS: 3
|
||||
run: |
|
||||
dir "$env:USERPROFILE\output"
|
||||
aws s3 cp "$env:USERPROFILE\output" s3://ifcopenshell-builds/ --recursive --debug
|
||||
foreach ($zip in Get-ChildItem -Path "$env:USERPROFILE\output" -Filter *.zip) {
|
||||
aws s3 cp "$($zip.FullName)" s3://ifcopenshell-builds/ --debug
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -17,27 +14,39 @@ jobs:
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install 'black>=24.10.0'
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
uv tool install ruff
|
||||
uv tool install black
|
||||
|
||||
# black doesn't catch all syntax errors, so we check them explicitly.
|
||||
- name: Check syntax errors
|
||||
id: syntax-errors
|
||||
run: |
|
||||
ERROR=0
|
||||
python3 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3 -W error -m compileall -q src/bonsai || ERROR=1
|
||||
python3.9 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
|
||||
exit $ERROR
|
||||
continue-on-error: true
|
||||
|
||||
- name: Black formatter
|
||||
id: linting
|
||||
id: black
|
||||
run: |
|
||||
python3 -m black --diff --check .
|
||||
uvx black --diff --check .
|
||||
continue-on-error: true
|
||||
|
||||
- name: Ruff check
|
||||
id: ruff
|
||||
run: |
|
||||
uvx ruff check
|
||||
continue-on-error: true
|
||||
|
||||
- name: Final check
|
||||
@@ -46,7 +55,10 @@ jobs:
|
||||
if [ "${{ steps.syntax-errors.outcome }}" != "success" ]; then
|
||||
echo "::error::Syntax errors check failed, see 'syntax-errors' step for the details." && ERROR=1
|
||||
fi
|
||||
if [ "${{ steps.linting.outcome }}" != "success" ]; then
|
||||
echo "::error::Black formatting check failed, see 'linting' step for the details." && ERROR=1
|
||||
if [ "${{ steps.black.outcome }}" != "success" ]; then
|
||||
echo "::error::Black formatting check failed, see 'black' step for the details." && ERROR=1
|
||||
fi
|
||||
if [ "${{ steps.ruff.outcome }}" != "success" ]; then
|
||||
echo "::error::Ruff check failed, see 'ruff' step for the details." && ERROR=1
|
||||
fi
|
||||
exit $ERROR
|
||||
|
||||
@@ -44,6 +44,7 @@ jobs:
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
|
||||
@@ -44,6 +44,8 @@ jobs:
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
|
||||
@@ -39,6 +39,8 @@ jobs:
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v2 # https://github.com/actions/setup-python
|
||||
with:
|
||||
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
|
||||
|
||||
@@ -82,6 +82,7 @@ jobs:
|
||||
cmake \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
@@ -127,12 +128,21 @@ jobs:
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
pip install -e ../ifcpatch --no-deps # Needed for sql.py tests.
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
ERROR=0
|
||||
make test || ERROR=1
|
||||
cd ../bcf && make test || ERROR=1
|
||||
pip install requests
|
||||
cd ../bsdd && make test
|
||||
cd ../bsdd && make test || ERROR=1
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifcdiff && make test || ERROR=1
|
||||
cd ../ifcpatch && make test || ERROR=1
|
||||
pip install -e ../ifctester --no-deps
|
||||
cd ../ifctester && make test
|
||||
cd ../ifctester && make test || ERROR=1
|
||||
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
|
||||
cd ../ifcopenshell-python
|
||||
pip install mathutils
|
||||
make test-mathutils || ERROR=1
|
||||
if [ $ERROR -ne 0 ]; then
|
||||
echo "One or more tests failed";
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
@@ -46,6 +46,7 @@ jobs:
|
||||
mkdir build && cd build
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$PWD/install/ \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_PREFIX_PATH=/usr \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=/usr \
|
||||
|
||||
+2
-1
@@ -86,6 +86,7 @@ src/bonsai/bonsai/bim/data/build/
|
||||
src/bonsai/bonsai/bim/data/gantt/index.html
|
||||
src/bonsai/bonsai/bim/data/gantt/jsgantt.js
|
||||
src/bonsai/bonsai/bim/data/gantt/jsgantt.css
|
||||
src/bonsai/bonsai/bim/data/webui/static/js/jquery.min.js
|
||||
|
||||
src/bonsai/drawings
|
||||
src/bonsai/layouts
|
||||
@@ -106,4 +107,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
bonsaiDecoratorForLoads.code-workspace
|
||||
dev_environment.bat
|
||||
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
if (NOT DEFINED CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
if (CMAKE_CXX_STANDARD LESS 17)
|
||||
message(FATAL_ERROR "C++17 or newer is required.")
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -1289,6 +1294,8 @@ endif()
|
||||
list(APPEND CPACK_SOURCE_IGNORE_FILES
|
||||
"/\\\\.git"
|
||||
"/build/"
|
||||
"/.pytest_cache/"
|
||||
"/__pycache__/"
|
||||
)
|
||||
set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_SOURCE_DIR}/..;/")
|
||||
set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${EXTRA_VERSION}")
|
||||
|
||||
@@ -9,6 +9,7 @@ set LIBXML2="%LIBRARY_PREFIX%/lib/libxml2.lib"
|
||||
cmake -G "Ninja" ^
|
||||
-D SCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" ^
|
||||
-D CMAKE_BUILD_TYPE:STRING=Release ^
|
||||
-D CMAKE_CXX_STANDARD=17 ^
|
||||
-D CMAKE_INSTALL_PREFIX:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
-D CMAKE_SYSTEM_PREFIX_PATH:FILEPATH="%LIBRARY_PREFIX%" ^
|
||||
|
||||
@@ -16,6 +16,7 @@ cmake ${CMAKE_ARGS} -G Ninja \
|
||||
-DSCHEMA_VERSIONS="2x3;4;4x1;4x3_add2" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
${CMAKE_PLATFORM_FLAGS[@]} \
|
||||
-DCMAKE_PREFIX_PATH=$PREFIX \
|
||||
-DCMAKE_SYSTEM_PREFIX_PATH=$PREFIX \
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
python:
|
||||
- 3.12
|
||||
|
||||
occt:
|
||||
- 7.8.1
|
||||
|
||||
@@ -28,7 +31,7 @@ hdf5:
|
||||
libboost_devel:
|
||||
- '1.86'
|
||||
libxml2:
|
||||
- '2'
|
||||
- 2.13
|
||||
mpfr:
|
||||
- '4'
|
||||
gmp:
|
||||
@@ -52,4 +55,4 @@ MACOSX_DEPLOYMENT_TARGET: # [osx]
|
||||
- 10.13 # [osx and x86_64]
|
||||
|
||||
CONDA_BUILD_SYSROOT: # [osx]
|
||||
- "/Users/runner/work/MacOSX10.13.sdk" # [osx and x86_64]
|
||||
- "/Users/runner/work/MacOSX10.13.sdk" # [osx and x86_64]
|
||||
|
||||
+40
-13
@@ -69,6 +69,7 @@ import sysconfig
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
import time
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
@@ -88,14 +89,14 @@ OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.86.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
LIBXML2_VERSION = "2.13.8"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
OPENCOLLADA_VERSION = "v1.6.68"
|
||||
HDF5_VERSION = "1.12.1"
|
||||
|
||||
GMP_VERSION = "6.2.1"
|
||||
MPFR_VERSION = "3.1.6" # latest is 4.1.0
|
||||
CGAL_VERSION = "5.3"
|
||||
CGAL_VERSION = "5.6.x-branch"
|
||||
USD_VERSION = "23.05"
|
||||
TBB_VERSION = "2021.9.0"
|
||||
|
||||
@@ -323,7 +324,7 @@ def run_autoconf(arg1, configure_args, cwd):
|
||||
if "wasm" in flags:
|
||||
wasm.append("emconfigure")
|
||||
|
||||
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
run([*wasm, "/bin/sh", "../configure", *(["--host=wasm32"] if "wasm" in flags and not any(s.startswith('--host') for s in configure_args) else []), *configure_args, f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
@@ -336,7 +337,7 @@ def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
if "wasm" in flags:
|
||||
wasm.append("emcmake")
|
||||
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}"], cwd=cwd)
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}", f"-DCMAKE_CXX_FLAGS='{os.environ['CXXFLAGS']}'", f"-DCMAKE_C_FLAGS='{os.environ['CFLAGS']}'", f"-DCMAKE_SHARED_LINKER_FLAGS={os.environ['LDFLAGS']}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
@@ -362,7 +363,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
run([git, "checkout", revision], cwd=target_dir)
|
||||
|
||||
|
||||
def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, additional_files={}, no_append_name=False, **kwargs):
|
||||
def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, shell=None, pre_compile_subs=[], additional_files={}, no_append_name=False, **kwargs):
|
||||
"""Handles building of dependencies with different tools (which are
|
||||
distinguished with the `mode` argument. `build_tool_args` is expected to be
|
||||
a list which is necessary in order to not mess up quoting of compiler and
|
||||
@@ -385,7 +386,14 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
|
||||
download_path = os.path.join(build_dir, download_name)
|
||||
if not os.path.exists(download_path):
|
||||
urlretrieve(url, os.path.join(build_dir, download_path))
|
||||
for _ in range(3):
|
||||
try:
|
||||
urlretrieve(url, os.path.join(build_dir, download_path))
|
||||
break
|
||||
except ConnectionError as e:
|
||||
print(e, "... retrying...")
|
||||
time.sleep(30.)
|
||||
continue
|
||||
else:
|
||||
logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
|
||||
elif download_tool == download_tool_git:
|
||||
@@ -404,6 +412,8 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
compr = "gz"
|
||||
elif download_name.endswith(".tar.bz2"):
|
||||
compr = "bz2"
|
||||
elif download_name.endswith(".tar.xz"):
|
||||
compr = "xz"
|
||||
else:
|
||||
raise RuntimeError("fix source for new download type")
|
||||
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
|
||||
@@ -430,6 +440,9 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
except Exception as e:
|
||||
# Assert that the patch has already been applied
|
||||
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
|
||||
|
||||
if shell is not None:
|
||||
sp.run(shell, shell=True, check=True, cwd=extract_dir)
|
||||
|
||||
if mode == "ctest":
|
||||
run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
|
||||
@@ -451,6 +464,12 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
run_cmake(name, build_tool_args, cwd=extract_build_dir)
|
||||
else:
|
||||
raise ValueError()
|
||||
for fn, before, after in pre_compile_subs:
|
||||
with open(os.path.join(extract_dir, fn), 'r') as f:
|
||||
s = f.read()
|
||||
s = s.replace(before, after)
|
||||
with open(os.path.join(extract_dir, fn), 'w') as f:
|
||||
f.write(s)
|
||||
logger.info(f"\rBuilding {name}... ")
|
||||
run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
|
||||
logger.info(f"\rInstalling {name}... ")
|
||||
@@ -478,9 +497,6 @@ ADDITIONAL_ARGS = []
|
||||
if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
|
||||
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fwasm-exceptions"))
|
||||
|
||||
# If the linker supports GC sections, set it up to reduce binary file size
|
||||
# -fPIC is required for the shared libraries to work
|
||||
|
||||
@@ -491,7 +507,11 @@ CFLAGS = os.environ.get("CFLAGS", "")
|
||||
LDFLAGS = os.environ.get("LDFLAGS", "")
|
||||
|
||||
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
|
||||
if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
|
||||
if "wasm" in flags:
|
||||
CFLAGS_MINIMAL = CXXFLAGS_MINIMAL = CFLAGS = CXXFLAGS = os.environ['SIDE_MODULE_CFLAGS']
|
||||
LDFLAGS = os.environ['SIDE_MODULE_LDFLAGS']
|
||||
elif sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
|
||||
CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
|
||||
if BUILD_STATIC:
|
||||
@@ -517,6 +537,7 @@ if "lto" in flags:
|
||||
locals()[f] += f" -flto={IFCOS_NUM_BUILD_PROCS}"
|
||||
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS
|
||||
os.environ["CPPFLAGS"] = CXXFLAGS
|
||||
os.environ["CFLAGS"] = CFLAGS
|
||||
os.environ["LDFLAGS"] = LDFLAGS
|
||||
|
||||
@@ -653,8 +674,8 @@ if "libxml2" in targets:
|
||||
"--without-iconv",
|
||||
"--without-lzma"
|
||||
],
|
||||
download_url="ftp://xmlsoft.org/libxml2/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
|
||||
download_url=f"https://download.gnome.org/sources/libxml2/{'.'.join(LIBXML2_VERSION.split('.')[0:2])}/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.xz"
|
||||
)
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
@@ -687,9 +708,11 @@ if "OpenCOLLADA" in targets:
|
||||
|
||||
if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
|
||||
# Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
|
||||
OLD_CPP_FLAGS = os.environ["CPPFLAGS"]
|
||||
OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
|
||||
OLD_C_FLAGS = os.environ["CFLAGS"]
|
||||
os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
|
||||
os.environ["CPPFLAGS"] = CXXFLAGS_MINIMAL
|
||||
os.environ["CFLAGS"] = CFLAGS_MINIMAL
|
||||
|
||||
# On OSX a dynamic python library is built or it would not be compatible
|
||||
@@ -719,6 +742,7 @@ if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flag
|
||||
):
|
||||
raise e
|
||||
|
||||
os.environ["CPPFLAGS"] = OLD_CPP_FLAGS
|
||||
os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
|
||||
os.environ["CFLAGS"] = OLD_C_FLAGS
|
||||
|
||||
@@ -764,6 +788,7 @@ if "cgal" in targets:
|
||||
name=f"gmp-{GMP_VERSION}",
|
||||
mode="autoconf",
|
||||
build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
|
||||
pre_compile_subs=([("build/config.h", "HAVE_OBSTACK_VPRINTF 1", "HAVE_OBSTACK_VPRINTF 0")] if "wasm" in flags else []),
|
||||
download_url="https://ftp.gnu.org/gnu/gmp/",
|
||||
download_name=f"gmp-{GMP_VERSION}.tar.bz2"
|
||||
)
|
||||
@@ -792,7 +817,7 @@ if "cgal" in targets:
|
||||
download_url="https://github.com/CGAL/cgal.git",
|
||||
download_name="cgal",
|
||||
download_tool=download_tool_git,
|
||||
revision=f"v{CGAL_VERSION}"
|
||||
revision=CGAL_VERSION
|
||||
)
|
||||
|
||||
if "usd" in targets:
|
||||
@@ -842,6 +867,7 @@ os.makedirs(executables_dir, exist_ok=True)
|
||||
|
||||
|
||||
cmake_args = [
|
||||
"-DCMAKE_CXX_STANDARD=" "17",
|
||||
"-DUSE_MMAP=" "OFF",
|
||||
"-DBUILD_EXAMPLES=" "OFF",
|
||||
"-DBUILD_SHARED_LIBS=" +OFF_ON[not BUILD_STATIC],
|
||||
@@ -947,6 +973,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"
|
||||
|
||||
os.environ["CPPFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
|
||||
os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ source:
|
||||
|
||||
build:
|
||||
script: |
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py312 IfcOpenShell-Python
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py313 IfcOpenShell-Python
|
||||
mv package/ifcopenshell .
|
||||
cp pyodide/setup.py .
|
||||
|
||||
|
||||
+49
-24
@@ -1,38 +1,63 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|bonsai
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|ifc4d
|
||||
|ifc5d
|
||||
|ifcbimtester
|
||||
|ifcblender
|
||||
|ifccityjson
|
||||
|ifcclash
|
||||
|ifccsv
|
||||
|ifcdiff
|
||||
|ifcfm
|
||||
|ifcpatch
|
||||
|ifctester
|
||||
|ifcopenshell-python
|
||||
|ifcsverchok
|
||||
|opencdeserver
|
||||
)/.*.py$
|
||||
'''
|
||||
include = "src/.*.pyi?$"
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifcopenshell-python/ifcopenshell/simple_spf/*
|
||||
|src/ifc2ca/templates/*
|
||||
|src/ifcconvert/cityjson/*
|
||||
|src/svgfill
|
||||
|src/exterior-shell-extractor
|
||||
|src/pyodide
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
reportInvalidTypeForm = false
|
||||
disableBytesTypePromotions = true
|
||||
reportUnnecessaryTypeIgnoreComment = true
|
||||
# Just to add a quick insert of `# pyright: ignore[xxx]` comments in Pylance.
|
||||
enableTypeIgnoreComments = false
|
||||
|
||||
# Define here general ruff settings,
|
||||
# then they will be inherited projects .toml files.
|
||||
# This allows using assuming different Python version for different projects.
|
||||
[tool.ruff]
|
||||
exclude = [
|
||||
# Submodules.
|
||||
"src/ifcconvert/cityjson",
|
||||
"src/ifcopenshell-python/ifcopenshell/express",
|
||||
"src/ifcopenshell-python/ifcopenshell/mvd",
|
||||
"src/ifcopenshell-python/ifcopenshell/simple_spf",
|
||||
"src/svgfill",
|
||||
#
|
||||
# Unformatted.
|
||||
"src/exterior-shell-extractor",
|
||||
# Incompatible with linter.
|
||||
"src/ifc2ca/templates",
|
||||
]
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# Default Ruff rules.
|
||||
# "E4", # imports
|
||||
# "E7", # statements
|
||||
"E9", # io errors
|
||||
# "F", # pyflakes
|
||||
#
|
||||
"FA", # future annotations
|
||||
"UP", # pyupgrade
|
||||
"RUF015", # next() > list_comprehension[0]
|
||||
]
|
||||
ignore = [
|
||||
"FA100", # Conflicts with Blender using annotations for props definitions.
|
||||
# Maybe will enable later:
|
||||
"UP007", # Optional to X | Y
|
||||
"UP015", # Unnecessary mode argument
|
||||
"UP028", # yield for -> yield from
|
||||
"UP030", # implicit references for positional format fields
|
||||
"UP031", # Replace % with .format
|
||||
"UP032", # Replace .format with f-string
|
||||
|
||||
# Deprecated Ruff rules.
|
||||
"UP038", # Use X | Y in isinstance
|
||||
]
|
||||
|
||||
@@ -20,7 +20,7 @@ def get_extensions_attributes(extensions: Extensions) -> dict[str, AttributeData
|
||||
- subattribute name"""
|
||||
possible_attributes = {}
|
||||
for field in fields(type(extensions)):
|
||||
field_type = field.type.__args__[0] # type: ignore [reportAttributeAccessIssue]
|
||||
field_type = field.type.__args__[0] # pyright: ignore [reportAttributeAccessIssue]
|
||||
subfield = next(iter(fields(field_type)))
|
||||
xsd_name = subfield.metadata["name"]
|
||||
possible_attributes[field.name] = AttributeData(field_type, subfield.name, xsd_name)
|
||||
|
||||
@@ -6,6 +6,7 @@ Copyright (c) 2017-2020 Anthon van der Neut, Ruamel bvba
|
||||
original idea from https://stackoverflow.com/a/19722365/1307905
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from os import PathLike
|
||||
@@ -47,7 +48,7 @@ class InMemoryZipFile:
|
||||
def data(self) -> bytes:
|
||||
return self.in_memory_data.getvalue()
|
||||
|
||||
def __enter__(self) -> "InMemoryZipFile":
|
||||
def __enter__(self) -> InMemoryZipFile:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BCF XML V2 handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
@@ -31,7 +32,7 @@ class BcfXml:
|
||||
self._extension_schema: Optional[bytes] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
def __enter__(self) -> "BcfXml":
|
||||
def __enter__(self) -> BcfXml:
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: Any) -> None:
|
||||
@@ -99,7 +100,7 @@ class BcfXml:
|
||||
extensions = mdl_extensions.Extensions()
|
||||
|
||||
xs = "{http://www.w3.org/2001/XMLSchema}"
|
||||
root = etree.parse(io.BytesIO((self.extension_schema)))
|
||||
root = etree.parse(io.BytesIO(self.extension_schema))
|
||||
|
||||
attrs = bcf.agnostic.extensions.get_extensions_attributes(extensions)
|
||||
xsd_to_attrs = {v.subattr_xsd_name: k for k, v in attrs.items()}
|
||||
@@ -148,7 +149,7 @@ class BcfXml:
|
||||
return topics
|
||||
|
||||
@classmethod
|
||||
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional["BcfXml"]:
|
||||
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
|
||||
"""
|
||||
Create a BcfXml object from a file.
|
||||
|
||||
@@ -172,7 +173,7 @@ class BcfXml:
|
||||
cls,
|
||||
project_name: Optional[str] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "BcfXml":
|
||||
) -> BcfXml:
|
||||
"""
|
||||
Create a new BcfXml object.
|
||||
|
||||
@@ -261,7 +262,7 @@ class BcfXml:
|
||||
)
|
||||
|
||||
# region Deprecated methods
|
||||
def new_project(self) -> "BcfXml":
|
||||
def new_project(self) -> BcfXml:
|
||||
"""Deprecated method."""
|
||||
warnings.warn("new_project is deprecated, use create_new instead.", DeprecationWarning)
|
||||
return self.create_new()
|
||||
|
||||
@@ -14,16 +14,20 @@
|
||||
# Currently extensions support for v2 is only read-only.
|
||||
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import List, NamedTuple, Optional
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: List[str] = field(
|
||||
priority: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
@@ -35,12 +39,12 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: List[str] = field(
|
||||
snippet_type: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
@@ -52,12 +56,12 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: List[str] = field(
|
||||
stage: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
@@ -69,12 +73,12 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: List[str] = field(
|
||||
topic_label: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
@@ -86,12 +90,12 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: List[str] = field(
|
||||
topic_status: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
@@ -103,12 +107,12 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: List[str] = field(
|
||||
topic_type: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
@@ -120,12 +124,12 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: List[str] = field(
|
||||
user: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "UserIdType",
|
||||
@@ -137,7 +141,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -38,7 +41,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -53,7 +56,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class HeaderFile:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -109,7 +112,7 @@ class HeaderFile:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicDocumentReference:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -147,7 +150,7 @@ class TopicDocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -162,7 +165,7 @@ class TopicRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -198,7 +201,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -258,9 +261,9 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Header:
|
||||
file: List[HeaderFile] = field(
|
||||
file: list[HeaderFile] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
@@ -271,9 +274,9 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Topic:
|
||||
reference_link: List[str] = field(
|
||||
reference_link: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
@@ -305,7 +308,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
labels: List[str] = field(
|
||||
labels: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Labels",
|
||||
@@ -385,7 +388,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
document_reference: List[TopicDocumentReference] = field(
|
||||
document_reference: list[TopicDocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
@@ -393,7 +396,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
related_topic: List[TopicRelatedTopic] = field(
|
||||
related_topic: list[TopicRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
@@ -425,7 +428,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
@@ -443,7 +446,7 @@ class Markup:
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
comment: List[Comment] = field(
|
||||
comment: list[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
@@ -451,7 +454,7 @@ class Markup:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
viewpoints: List[ViewPoint] = field(
|
||||
viewpoints: list[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Viewpoints",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -21,7 +24,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ProjectExtension:
|
||||
project: Optional[Project] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Version:
|
||||
detailed_version: Optional[str] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
@@ -8,7 +11,7 @@ class BitmapFormat(Enum):
|
||||
JPG = "JPG"
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -35,7 +38,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -60,7 +63,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -85,7 +88,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
@@ -110,7 +113,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -128,12 +131,12 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -151,9 +154,9 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentSelection:
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -163,12 +166,12 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -178,7 +181,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -196,7 +199,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -236,7 +239,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -281,7 +284,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoBitmap:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -330,9 +333,9 @@ class VisualizationInfoBitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentColoring:
|
||||
color: List[ComponentColoringColor] = field(
|
||||
color: list[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
@@ -342,7 +345,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentVisibility:
|
||||
exceptions: Optional[ComponentVisibilityExceptions] = field(
|
||||
default=None,
|
||||
@@ -360,12 +363,12 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
clipping_plane: list[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
@@ -374,12 +377,12 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: List[Line] = field(
|
||||
line: list[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
@@ -389,7 +392,7 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Components:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -421,7 +424,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
@@ -462,7 +465,7 @@ class VisualizationInfo:
|
||||
"type": "Element",
|
||||
},
|
||||
)
|
||||
bitmap: List[VisualizationInfoBitmap] = field(
|
||||
bitmap: list[VisualizationInfoBitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BCF XML V2 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import datetime
|
||||
import tempfile
|
||||
import uuid
|
||||
@@ -152,7 +153,7 @@ class TopicHandler:
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "TopicHandler":
|
||||
) -> TopicHandler:
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
+21
-21
@@ -21,11 +21,11 @@ import http.server
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import urllib
|
||||
import urllib.parse
|
||||
import uuid
|
||||
import webbrowser
|
||||
from re import A
|
||||
from typing import Any, Optional, Tuple
|
||||
from typing import Any, Optional
|
||||
|
||||
import requests
|
||||
|
||||
@@ -40,7 +40,7 @@ class OAuthReceiver(http.server.BaseHTTPRequestHandler):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/plain")
|
||||
self.end_headers()
|
||||
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
|
||||
self.wfile.write(b"You have now authenticated :) You may now close this browser window.")
|
||||
|
||||
|
||||
class FoundationClient:
|
||||
@@ -174,7 +174,7 @@ class BcfClient:
|
||||
except requests.exceptions.HTTPError as e:
|
||||
print(f"message: {response.reason}' '{response.status_code}' '{ e }")
|
||||
|
||||
def post(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
|
||||
def post(self, endpoint: str, data: Any = None, params: Any = None) -> tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
@@ -192,7 +192,7 @@ class BcfClient:
|
||||
print(f"message: {response.reason}' '{response.status_code}, {errh}")
|
||||
return response.status_code, response.reason
|
||||
|
||||
def put(self, endpoint: str, data: Any = None, params: Any = None) -> Tuple[int, str]:
|
||||
def put(self, endpoint: str, data: Any = None, params: Any = None) -> tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
@@ -210,7 +210,7 @@ class BcfClient:
|
||||
print(f"message: {response.reason}' '{response.status_code}, {errh}")
|
||||
return response.status_code, response.reason
|
||||
|
||||
def delete(self, endpoint: str, params: Any = None) -> Tuple[int, str]:
|
||||
def delete(self, endpoint: str, params: Any = None) -> tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/json",
|
||||
@@ -237,7 +237,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def update_project(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
def update_project(self, project_id: str = "", data: Any = None) -> tuple[int, str]:
|
||||
url = f"{self.baseurl}/projects/{project_id}"
|
||||
headers = {"Authorization": f"Bearer {self.foundation_client.get_access_token()}"}
|
||||
resp = requests.put(url, headers=headers, data=data)
|
||||
@@ -276,16 +276,16 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def create_topic(self, project_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
def create_topic(self, project_id: str = "", data: Any = None) -> tuple[int, str]:
|
||||
return self.post(f"/projects/{project_id}/topics", data=data)
|
||||
|
||||
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
def update_topic(self, project_id: str = "", topic_id: str = "", data: Any = None) -> tuple[int, str]:
|
||||
return self.put(f"/projects/{project_id}/topics/{topic_id}", data=data)
|
||||
|
||||
def delete_topic(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
|
||||
def delete_topic(self, project_id: str = "", topic_id: str = "") -> tuple[int, str]:
|
||||
return self.delete(f"/projects/{project_id}/topics/{topic_id}")
|
||||
|
||||
def get_snippet(self, project_id: str = "", topic_id: str = "") -> Tuple[int, str]:
|
||||
def get_snippet(self, project_id: str = "", topic_id: str = "") -> tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
@@ -332,7 +332,7 @@ class BcfClient:
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
params: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/files",
|
||||
data=data,
|
||||
@@ -347,7 +347,7 @@ class BcfClient:
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
params: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments",
|
||||
data=data,
|
||||
@@ -363,7 +363,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> Tuple[int, str]:
|
||||
def delete_comment(self, project_id: str = "", topic_id: str = "", comment_id: str = "") -> tuple[int, str]:
|
||||
return self.delete(f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}")
|
||||
|
||||
def update_comment(
|
||||
@@ -372,7 +372,7 @@ class BcfClient:
|
||||
topic_id: str = "",
|
||||
comment_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
data=data,
|
||||
@@ -387,7 +387,7 @@ class BcfClient:
|
||||
},
|
||||
)
|
||||
|
||||
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> Tuple[int, str]:
|
||||
def create_viewpoints(self, project_id: str = "", topic_id: str = "", data: Any = None) -> tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints",
|
||||
data=data,
|
||||
@@ -408,7 +408,7 @@ class BcfClient:
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
viewpoint_id: str = "",
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.delete(
|
||||
f"/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
)
|
||||
@@ -478,7 +478,7 @@ class BcfClient:
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/related_topics",
|
||||
data=data,
|
||||
@@ -498,7 +498,7 @@ class BcfClient:
|
||||
project_id: str = "",
|
||||
topic_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.post(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
data=data,
|
||||
@@ -510,7 +510,7 @@ class BcfClient:
|
||||
topic_id: str = "",
|
||||
document_reference_id: str = "",
|
||||
data: Any = None,
|
||||
) -> Tuple[int, str]:
|
||||
) -> tuple[int, str]:
|
||||
return self.put(
|
||||
f"/projects/{project_id}/topics/{topic_id}/document_references/{document_reference_id}",
|
||||
data=data,
|
||||
@@ -543,7 +543,7 @@ class BcfClient:
|
||||
|
||||
return response.status_code
|
||||
|
||||
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> Tuple[int, str]:
|
||||
def get_document(self, project_id: str = "", topic_id: str = "", document_id: str = "") -> tuple[int, str]:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.foundation_client.get_access_token()}",
|
||||
"Content-type": "application/octet-stream",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BCF XML V3 handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
@@ -30,7 +31,7 @@ class BcfXml:
|
||||
self._documents: Optional[DocumentsHandler] = None
|
||||
self._zip_file = self._load_zip_file()
|
||||
|
||||
def __enter__(self) -> "BcfXml":
|
||||
def __enter__(self) -> BcfXml:
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: Any) -> None:
|
||||
@@ -116,7 +117,7 @@ class BcfXml:
|
||||
return self._documents
|
||||
|
||||
@classmethod
|
||||
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional["BcfXml"]:
|
||||
def load(cls, filename: Path, xml_handler: Optional[AbstractXmlParserSerializer] = None) -> Optional[BcfXml]:
|
||||
"""
|
||||
Create a BcfXml object from a file.
|
||||
|
||||
@@ -141,7 +142,7 @@ class BcfXml:
|
||||
project_name: Optional[str] = None,
|
||||
extensions: Optional[mdl.Extensions] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "BcfXml":
|
||||
) -> BcfXml:
|
||||
"""
|
||||
Create a new BcfXml object.
|
||||
|
||||
@@ -239,7 +240,7 @@ class BcfXml:
|
||||
)
|
||||
|
||||
# region Deprecated methods
|
||||
def new_project(self) -> "BcfXml":
|
||||
def new_project(self) -> BcfXml:
|
||||
"""Deprecated method."""
|
||||
warnings.warn("new_project is deprecated, use create_new instead.", DeprecationWarning)
|
||||
return self.create_new()
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Document:
|
||||
filename: str = field(
|
||||
metadata={
|
||||
@@ -34,12 +37,12 @@ class Document:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class DocumentInfoDocuments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document: List[Document] = field(
|
||||
document: list[Document] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Document",
|
||||
@@ -49,7 +52,7 @@ class DocumentInfoDocuments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class DocumentInfo:
|
||||
documents: Optional[DocumentInfoDocuments] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: List[str] = field(
|
||||
priority: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
@@ -19,12 +22,12 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: List[str] = field(
|
||||
snippet_type: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
@@ -36,12 +39,12 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: List[str] = field(
|
||||
stage: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
@@ -53,12 +56,12 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: List[str] = field(
|
||||
topic_label: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
@@ -70,12 +73,12 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: List[str] = field(
|
||||
topic_status: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
@@ -87,12 +90,12 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: List[str] = field(
|
||||
topic_type: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
@@ -104,12 +107,12 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: List[str] = field(
|
||||
user: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "User",
|
||||
@@ -121,7 +124,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -44,7 +47,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -59,7 +62,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class DocumentReference:
|
||||
document_guid: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -100,7 +103,7 @@ class DocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class File:
|
||||
filename: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -157,12 +160,12 @@ class File:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
label: List[str] = field(
|
||||
label: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Label",
|
||||
@@ -174,12 +177,12 @@ class TopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicReferenceLinks:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
reference_link: List[str] = field(
|
||||
reference_link: list[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
@@ -191,7 +194,7 @@ class TopicReferenceLinks:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicRelatedTopicsRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -206,7 +209,7 @@ class TopicRelatedTopicsRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -246,7 +249,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -312,12 +315,12 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class HeaderFiles:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
file: List[File] = field(
|
||||
file: list[File] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
@@ -327,12 +330,12 @@ class HeaderFiles:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicDocumentReferences:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document_reference: List[DocumentReference] = field(
|
||||
document_reference: list[DocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
@@ -342,12 +345,12 @@ class TopicDocumentReferences:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicRelatedTopics:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
|
||||
related_topic: list[TopicRelatedTopicsRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
@@ -357,12 +360,12 @@ class TopicRelatedTopics:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicViewpoints:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
view_point: List[ViewPoint] = field(
|
||||
view_point: list[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ViewPoint",
|
||||
@@ -372,7 +375,7 @@ class TopicViewpoints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Header:
|
||||
files: Optional[HeaderFiles] = field(
|
||||
default=None,
|
||||
@@ -384,12 +387,12 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class TopicComments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
comment: List[Comment] = field(
|
||||
comment: list[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
@@ -399,7 +402,7 @@ class TopicComments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Topic:
|
||||
reference_links: Optional[TopicReferenceLinks] = field(
|
||||
default=None,
|
||||
@@ -596,7 +599,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -25,7 +28,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ProjectInfo:
|
||||
project: Project = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Version:
|
||||
version_id: str = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
@@ -8,7 +12,7 @@ class BitmapFormat(Enum):
|
||||
JPG = "jpg"
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -39,7 +43,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -64,7 +68,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -89,7 +93,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: bool = field(
|
||||
default=False,
|
||||
@@ -114,7 +118,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Bitmap:
|
||||
format: BitmapFormat = field(
|
||||
metadata={
|
||||
@@ -162,7 +166,7 @@ class Bitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -180,12 +184,12 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentColoringColorComponents:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -195,9 +199,9 @@ class ComponentColoringColorComponents:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentSelection:
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -206,12 +210,12 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: List[Component] = field(
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -220,7 +224,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -238,7 +242,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -288,7 +292,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -344,7 +348,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -366,7 +370,7 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentVisibility:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -391,12 +395,12 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoBitmaps:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
bitmap: List[Bitmap] = field(
|
||||
bitmap: list[Bitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
@@ -405,12 +409,12 @@ class VisualizationInfoBitmaps:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
clipping_plane: list[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
@@ -419,12 +423,12 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: List[Line] = field(
|
||||
line: list[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
@@ -433,9 +437,9 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class ComponentColoring:
|
||||
color: List[ComponentColoringColor] = field(
|
||||
color: list[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
@@ -444,7 +448,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class Components:
|
||||
selection: Optional[ComponentSelection] = field(
|
||||
default=None,
|
||||
@@ -469,7 +473,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""BCF XML V3 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
@@ -91,12 +92,12 @@ class TopicHandler:
|
||||
self._bim_snippet = value
|
||||
|
||||
@property
|
||||
def viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
def viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self._viewpoints is None:
|
||||
self._viewpoints = self._load_viewpoints()
|
||||
return self._viewpoints
|
||||
|
||||
def _load_viewpoints(self) -> dict[str, "VisualizationInfoHandler"]:
|
||||
def _load_viewpoints(self) -> dict[str, VisualizationInfoHandler]:
|
||||
if self._topic_dir and self.topic.viewpoints and (viewpoints := self.topic.viewpoints.view_point):
|
||||
return VisualizationInfoHandler.from_topic_viewpoints(self._topic_dir, viewpoints)
|
||||
return {}
|
||||
@@ -139,7 +140,7 @@ class TopicHandler:
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> "TopicHandler":
|
||||
) -> TopicHandler:
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""XML Parser and Serializer factories."""
|
||||
|
||||
from typing import Optional, Protocol, Type, TypeVar
|
||||
from typing import Optional, Protocol, TypeVar
|
||||
|
||||
from xsdata.formats.dataclass.context import XmlContext
|
||||
from xsdata.formats.dataclass.parsers import XmlParser
|
||||
@@ -29,7 +29,7 @@ T = TypeVar("T")
|
||||
class AbstractXmlParserSerializer(Protocol):
|
||||
"""XML Parser and serializer wrapper."""
|
||||
|
||||
def parse(self, xml: bytes, clazz: Type[T]) -> T:
|
||||
def parse(self, xml: bytes, clazz: type[T]) -> T:
|
||||
"""
|
||||
Parse an XML file to an object.
|
||||
|
||||
@@ -61,7 +61,7 @@ class XmlParserSerializer:
|
||||
self.parser = build_xml_parser(self.context)
|
||||
self.serializer = build_serializer(self.context)
|
||||
|
||||
def parse(self, xml: bytes, clazz: Type[T]) -> T:
|
||||
def parse(self, xml: bytes, clazz: type[T]) -> T:
|
||||
"""
|
||||
Parse an XML file to an object.
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ dependencies = [
|
||||
"xsdata>=24.4",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
"requests",
|
||||
]
|
||||
version = "0.0.0"
|
||||
classifiers = [
|
||||
|
||||
+6
-4
@@ -87,7 +87,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=c49ca69
|
||||
OLD:=ec13294
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -138,7 +138,7 @@ endif
|
||||
# odfpy doesn't come with its own wheel, so whee'l (get it?) create it ourselves!
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) wheel odfpy --wheel-dir=./wheels
|
||||
# Required by IFCCityJSON
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download cjio --dest=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download "cjio >=0.8, <0.10" --dest=./wheels
|
||||
# Required in general for sorting all sorts of stuff in a nice way
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download natsort --dest=./wheels
|
||||
# Provides express rule validation for ifcopenshell.validate
|
||||
@@ -193,13 +193,15 @@ else
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pyradiance $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
endif
|
||||
|
||||
# jQuery is required for web UI functionality
|
||||
cd build/bonsai/bim/data/webui/static/js/ && wget https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
|
||||
|
||||
# Provides jsgantt-improved supports for web-based construction sequencing gantt charts
|
||||
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.js
|
||||
cd build/bonsai/bim/data/gantt/ && wget https://raw.githubusercontent.com/jsGanttImproved/jsgantt-improved/master/dist/jsgantt.css
|
||||
|
||||
# Provides IFCJSON functionality
|
||||
# TODO: replace with main repo if https://github.com/IFCJSON-Team/IFC2JSON_python/pull/3 is merged.
|
||||
cd build && wget -O ifc2json.zip https://github.com/Moult/IFC2JSON_python/archive/refs/heads/feature-ios-v0.8.0.zip
|
||||
cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip
|
||||
cd build && unzip ifc2json.zip && rm ifc2json.zip
|
||||
# IFCJSON doesn't have pyproject.toml, so we use python command.
|
||||
cd build && . env/$(VENV_ACTIVATE) && cd IFC2JSON_python-*/file_converters && \
|
||||
|
||||
@@ -40,7 +40,8 @@ import uuid
|
||||
import shutil
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import Union, Any, Generator
|
||||
from typing import Union, Any
|
||||
from collections.abc import Generator
|
||||
|
||||
|
||||
last_commit_hash = "8888888"
|
||||
|
||||
@@ -22,7 +22,8 @@ import bpy.utils.previews
|
||||
import importlib
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from . import handler, ui, prop, operator
|
||||
from typing import Callable, Union
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
|
||||
try:
|
||||
from bonsai.translations import translations_dict
|
||||
@@ -152,10 +153,10 @@ classes = [
|
||||
ui.BIM_PT_tab_new_project_wizard,
|
||||
ui.BIM_PT_tab_project_info,
|
||||
ui.BIM_PT_tab_spatial,
|
||||
ui.BIM_PT_tab_grouping_and_filtering,
|
||||
ui.BIM_PT_tab_project_setup,
|
||||
ui.BIM_PT_tab_geometry,
|
||||
ui.BIM_PT_tab_stakeholders,
|
||||
ui.BIM_PT_tab_grouping_and_filtering,
|
||||
# Object information
|
||||
ui.BIM_PT_tab_object_metadata,
|
||||
ui.BIM_PT_tab_misc,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
src="/static/js/jquery.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
src="/static/js/jquery.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
src="/static/js/jquery.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
src="/static/js/jquery.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
src="/static/js/jquery.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -76,11 +76,7 @@ class IfcExporter:
|
||||
def set_header(self):
|
||||
self.file.wrapped_data.header.file_name.name = os.path.basename(self.ifc_export_settings.output_file)
|
||||
self.file.wrapped_data.header.file_name.time_stamp = (
|
||||
datetime.datetime.utcnow()
|
||||
.replace(tzinfo=datetime.timezone.utc)
|
||||
.astimezone()
|
||||
.replace(microsecond=0)
|
||||
.isoformat()
|
||||
datetime.datetime.utcnow().replace(tzinfo=datetime.UTC).astimezone().replace(microsecond=0).isoformat()
|
||||
)
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "{} {}".format(
|
||||
|
||||
@@ -24,6 +24,7 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.api.owner.settings
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
import weakref
|
||||
from bpy.app.handlers import persistent
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
@@ -33,7 +34,8 @@ from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDe
|
||||
from bonsai.bim.module.nest.decorator import NestDecorator
|
||||
from mathutils import Vector
|
||||
from math import cos
|
||||
from typing import Union, Callable
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -256,31 +258,70 @@ def redo_post(scene: bpy.types.Scene) -> None:
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
# Cache is important as those entities will be retrieved very often,
|
||||
# for every IfcOwnerHistory creation or update.
|
||||
class SettingsCache:
|
||||
APPLICATION_ID: Union[int, None] = None
|
||||
USER_ID: Union[int, None] = None
|
||||
|
||||
_file: Union[weakref.ReferenceType[ifcopenshell.file], None] = None
|
||||
|
||||
@classmethod
|
||||
def get_file(cls) -> Union[ifcopenshell.file, None]:
|
||||
if cls._file is None:
|
||||
return None
|
||||
return cls._file()
|
||||
|
||||
@classmethod
|
||||
def set_file(cls, file: ifcopenshell.file) -> None:
|
||||
cls._file = weakref.ref(file)
|
||||
|
||||
|
||||
def get_application(ifc: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
version = tool.Blender.get_bonsai_version()
|
||||
if SettingsCache.get_file() is ifc and SettingsCache.APPLICATION_ID is not None:
|
||||
try:
|
||||
app = ifc.by_id(SettingsCache.APPLICATION_ID)
|
||||
return app
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Use only main part from the version to avoid flooding advanced users projects with IfcApplications.
|
||||
version = tool.Blender.get_bonsai_version().split("-")[0]
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "Bonsai" and element.Version == version:
|
||||
return element
|
||||
return ifcopenshell.api.run(
|
||||
"owner.add_application",
|
||||
application = ifcopenshell.api.owner.add_application(
|
||||
ifc,
|
||||
version=version,
|
||||
application_full_name="Bonsai",
|
||||
application_identifier="Bonsai",
|
||||
)
|
||||
SettingsCache.APPLICATION_ID = application.id()
|
||||
SettingsCache.set_file(ifc)
|
||||
return application
|
||||
|
||||
|
||||
def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None]:
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
if SettingsCache.get_file() is ifc and SettingsCache.USER_ID is not None:
|
||||
try:
|
||||
user = ifc.by_id(SettingsCache.USER_ID)
|
||||
return user
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
if pao := next(iter(ifc.by_type("IfcPersonAndOrganization")), None):
|
||||
SettingsCache.USER_ID = pao.id()
|
||||
SettingsCache.set_file(ifc)
|
||||
return pao
|
||||
elif ifc.schema == "IFC2X3":
|
||||
if (person := next(iter(ifc.by_type("IfcPerson")), None)) is None:
|
||||
person = tool.Ifc.run("owner.add_person")
|
||||
person = ifcopenshell.api.owner.add_person(ifc)
|
||||
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
|
||||
organization = tool.Ifc.run("owner.add_organisation")
|
||||
pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
|
||||
organization = ifcopenshell.api.owner.add_organisation(ifc)
|
||||
pao = ifcopenshell.api.owner.add_person_and_organisation(ifc, person=person, organisation=organization)
|
||||
SettingsCache.USER_ID = pao.id()
|
||||
SettingsCache.set_file(ifc)
|
||||
return pao
|
||||
|
||||
|
||||
@@ -350,6 +391,11 @@ def load_post(scene):
|
||||
aggregate_props = tool.Aggregate.get_aggregate_props()
|
||||
nest_props = tool.Nest.get_nest_props()
|
||||
model_props = tool.Model.get_model_props()
|
||||
GeoreferenceDecorator.uninstall()
|
||||
AggregateDecorator.uninstall()
|
||||
NestDecorator.uninstall()
|
||||
WallAxisDecorator.uninstall()
|
||||
SlabDirectionDecorator.uninstall()
|
||||
if georeference_props.should_visualise:
|
||||
GeoreferenceDecorator.install(bpy.context)
|
||||
if aggregate_props.aggregate_decorator:
|
||||
|
||||
@@ -27,7 +27,8 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
import bonsai.tool as tool
|
||||
from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING
|
||||
from collections.abc import Callable, Iterable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bonsai.bim.prop
|
||||
@@ -120,7 +121,8 @@ def import_attributes(
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
schema = tool.Ifc.schema()
|
||||
for attribute in schema.declaration_by_name(ifc_class).all_attributes():
|
||||
assert (entity := schema.declaration_by_name(ifc_class).as_entity())
|
||||
for attribute in entity.all_attributes():
|
||||
import_attribute(attribute, props, data, callback=callback)
|
||||
|
||||
|
||||
@@ -131,11 +133,13 @@ def import_attributes2(
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
|
||||
assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
info = {a.name(): None for a in attributes}
|
||||
info["type"] = element
|
||||
else:
|
||||
attributes = element.wrapped_data.declaration().as_entity().all_attributes()
|
||||
assert (entity := element.wrapped_data.declaration().as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
info = element.get_info()
|
||||
for attribute in attributes:
|
||||
import_attribute(attribute, props, info, callback=callback)
|
||||
@@ -351,8 +355,23 @@ def get_enum_items(
|
||||
return items
|
||||
|
||||
|
||||
def draw_expandable_panel(layout, context, label: str, ui_func, default_closed: bool = True):
|
||||
header, panel = layout.panel(label, default_closed=default_closed)
|
||||
def draw_expandable_panel(
|
||||
layout: bpy.types.UILayout,
|
||||
context: bpy.types.Context,
|
||||
label: str,
|
||||
ui_func: Callable[[bpy.types.UILayout, bpy.types.Context], None],
|
||||
default_closed: bool = True,
|
||||
*,
|
||||
panel_id: str = "",
|
||||
) -> None:
|
||||
"""
|
||||
:param panel_id: Optional unique identifier for the panel.
|
||||
By default is matching ``label``, but if more than one panel with the same name is used,
|
||||
then ``panel_id`` can be provided explicitly to ensure panels can be expanded/collapsed separately.
|
||||
"""
|
||||
if not panel_id:
|
||||
panel_id = label
|
||||
header, panel = layout.panel(panel_id, default_closed=default_closed)
|
||||
header.label(text=label)
|
||||
if panel:
|
||||
ui_func(panel, context)
|
||||
@@ -396,7 +415,12 @@ def draw_filter(
|
||||
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
|
||||
|
||||
if module != "search":
|
||||
if module == "drawing_include":
|
||||
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "INCLUDE"
|
||||
if module == "drawing_exclude":
|
||||
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "EXCLUDE"
|
||||
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
|
||||
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
|
||||
|
||||
@@ -34,7 +34,8 @@ import bonsai.tool as tool
|
||||
from ifcopenshell.file import UndoSystemError
|
||||
from pathlib import Path
|
||||
from bonsai.tool.brick import BrickStore
|
||||
from typing import Set, Union, Optional, TypedDict, Callable, NotRequired, Literal
|
||||
from typing import Union, Optional, TypedDict, NotRequired, Literal
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
@@ -69,7 +70,7 @@ class IfcStore:
|
||||
cache_path: Optional[str] = None
|
||||
id_map: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
guid_map: dict[str, IFC_CONNECTED_TYPE] = {}
|
||||
edited_objs: Set[bpy.types.Object] = set()
|
||||
edited_objs: set[bpy.types.Object] = set()
|
||||
pset_template_path: str = ""
|
||||
pset_template_file: Optional[ifcopenshell.file] = None
|
||||
classification_path: str = ""
|
||||
|
||||
@@ -38,7 +38,8 @@ import ifcopenshell.util.shape
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from bonsai.tool.loader import OBJECT_DATA_TYPE
|
||||
from typing import Dict, Union, Optional, Any, Literal, Iterable
|
||||
from typing import Union, Optional, Any, Literal
|
||||
from collections.abc import Iterable
|
||||
from ifcopenshell.util.shape import MatrixType
|
||||
|
||||
|
||||
@@ -47,7 +48,7 @@ class MaterialCreator:
|
||||
obj: bpy.types.Object
|
||||
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter):
|
||||
self.styles: Dict[int, bpy.types.Material] = {}
|
||||
self.styles: dict[int, bpy.types.Material] = {}
|
||||
self.parsed_meshes: set[str] = set()
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
self.ifc_importer = ifc_importer
|
||||
@@ -300,7 +301,6 @@ class IfcImporter:
|
||||
tool.Spatial.run_spatial_import_spatial_decomposition()
|
||||
if default_container := tool.Spatial.guess_default_container():
|
||||
tool.Spatial.set_default_container(default_container)
|
||||
tool.Loader.setup_active_bsdd_classification()
|
||||
self.update_progress(100)
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
@@ -857,7 +857,7 @@ class IfcImporter:
|
||||
return obj
|
||||
|
||||
def load_existing_meshes(self) -> None:
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes})
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes if m.library is None})
|
||||
|
||||
def merge_materials_by_colour(self):
|
||||
cleaned_materials = {}
|
||||
@@ -1034,7 +1034,7 @@ class IfcImporter:
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
) -> bpy.types.Curve:
|
||||
if hasattr(shape, "geometry"):
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
@@ -1067,7 +1067,7 @@ class IfcImporter:
|
||||
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
|
||||
) -> Union[bpy.types.Mesh, None]:
|
||||
try:
|
||||
if hasattr(shape, "geometry"):
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
# shape is ShapeElementType
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
@@ -1076,7 +1076,7 @@ class IfcImporter:
|
||||
# Mesh may already exists (e.g. during representation reimport)
|
||||
# and we assign some suffix to it to prevent Blender from adding '.001' suffix to the new mesh.
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(geometry)
|
||||
if old_mesh := bpy.data.meshes.get(mesh_name):
|
||||
if old_mesh := bpy.data.meshes.get((mesh_name, None)):
|
||||
old_mesh.name = mesh_name + ".old"
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
|
||||
@@ -164,8 +164,6 @@ class AggregateDecorator:
|
||||
|
||||
def draw_aggregate(self, context):
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
if props.in_aggregate_mode:
|
||||
return
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
decorator_color_special = self.addon_prefs.decorator_color_special
|
||||
decorator_color_selected = self.addon_prefs.decorator_color_selected
|
||||
@@ -194,7 +192,13 @@ class AggregateDecorator:
|
||||
aggregates.append(obj)
|
||||
continue
|
||||
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
aggregates_list = tool.Aggregate.get_aggregates_recursively(element)
|
||||
if props.in_aggregate_mode and props.editing_aggregate:
|
||||
index = aggregates_list.index(tool.Ifc.get_entity(props.editing_aggregate))
|
||||
if index > 0:
|
||||
aggregate = aggregates_list[index - 1]
|
||||
else:
|
||||
aggregate = aggregates_list[-1]
|
||||
if aggregate:
|
||||
aggregates.append(tool.Ifc.get_object(aggregate))
|
||||
|
||||
@@ -212,9 +216,7 @@ class AggregateDecorator:
|
||||
self.draw_batch("LINES", line_y, color, [(0, 1)])
|
||||
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
|
||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||
if props.in_aggregate_mode:
|
||||
return
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate))
|
||||
parts = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate))
|
||||
parts_objs = [tool.Ifc.get_object(p) for p in parts]
|
||||
|
||||
indices, edges = create_bounding_box(parts_objs)
|
||||
@@ -333,7 +335,7 @@ class AggregateModeDecorator:
|
||||
self.draw_batch("LINES", line_y, color, [(0, 1)])
|
||||
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
|
||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate_obj))
|
||||
parts = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate_obj))
|
||||
|
||||
color = self.addon_prefs.decorator_color_selected
|
||||
parts_objs = [tool.Ifc.get_object(p) for p in parts]
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.aggregate as core
|
||||
@@ -112,7 +113,7 @@ class BIM_OT_aggregate_unassign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
|
||||
@@ -312,7 +313,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
for part in parts:
|
||||
pset = ifcopenshell.util.element.get_pset(part, "BBIM_Linked_Aggregate")
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=part, pset=pset)
|
||||
ifcopenshell.api.pset.remove_pset(tool.Ifc.get(), product=part, pset=pset)
|
||||
|
||||
linked_aggregate_group = next(
|
||||
r.RelatingGroup
|
||||
@@ -385,7 +386,7 @@ class BIM_OT_disable_aggregate_mode(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bonsai.core.aggregate.exit_aggregate_mode(tool.Aggregate)
|
||||
core.exit_aggregate_mode(tool.Aggregate)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ import ifcopenshell.util.selector
|
||||
from datetime import datetime
|
||||
from dateutil import parser, relativedelta
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from typing import get_args, TYPE_CHECKING
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
|
||||
@@ -397,7 +397,7 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props_are_filled = all(
|
||||
(getattr(props, attr) for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type"))
|
||||
getattr(props, attr) for attr in ("bim_snippet_reference", "bim_snippet_schema", "bim_snippet_type")
|
||||
)
|
||||
if not props_are_filled:
|
||||
cls.poll_message_set("Some BIM snippet fields are empty.")
|
||||
|
||||
@@ -31,9 +31,6 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from functools import partial
|
||||
from typing import Literal
|
||||
from typing_extensions import assert_never
|
||||
from bcf.agnostic.extensions import get_extensions_attributes
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
@@ -404,12 +404,13 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
boundary = tool.Ifc.get_entity(obj)
|
||||
assert boundary
|
||||
attributes = dict()
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
attributes[blender_property] = entity
|
||||
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
|
||||
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
|
||||
bpy.ops.bim.disable_editing_boundary()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -605,7 +606,12 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
parent_boundaries: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
objs = tool.Blender.get_selected_objects()
|
||||
ifc_objects = {element: obj for obj in objs if (element := tool.Ifc.get_entity(obj))}
|
||||
ifc_objects = {
|
||||
element: obj
|
||||
for obj in objs
|
||||
if (element := tool.Ifc.get_entity(obj)) and isinstance(obj.data, bpy.types.Mesh) and obj.data.vertices
|
||||
}
|
||||
|
||||
if len(ifc_objects) not in (1, 2):
|
||||
self.report({"ERROR"}, "Please select one or two IFC elements.")
|
||||
return {"CANCELLED"}
|
||||
@@ -647,46 +653,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"FINISHED"}
|
||||
parent_boundaries.extend(res)
|
||||
|
||||
elif len(objs) == 1 and False:
|
||||
# Optionally the user may select just the space, and the building element shall be auto-detected
|
||||
# TODO : refactor to be able to generate all boundaries for selected space automatically or with an option
|
||||
|
||||
relating_space = None
|
||||
related_building_element = None
|
||||
relating_space_obj = None
|
||||
related_building_element_obj = None
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="Please set an active container to detect space boundaries from.")
|
||||
|
||||
element = tool.Ifc.get_entity(objs[0])
|
||||
if element.is_a("IfcSpace"):
|
||||
relating_space = element
|
||||
relating_space_obj = objs[0]
|
||||
|
||||
if not (container := tool.Root.get_default_container()):
|
||||
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||
return
|
||||
|
||||
for subelement in ifcopenshell.util.element.get_decomposition(container):
|
||||
if not (
|
||||
subelement.is_a("IfcWall") or subelement.is_a("IfcSlab") or subelement.is_a("IfcVirtualElement")
|
||||
):
|
||||
continue
|
||||
obj = tool.Ifc.get_object(subelement)
|
||||
if obj:
|
||||
related_building_element = subelement
|
||||
related_building_element_obj = obj
|
||||
parent_boundary = self.create_element_boundary(
|
||||
context,
|
||||
relating_space,
|
||||
relating_space_obj,
|
||||
related_building_element,
|
||||
related_building_element_obj,
|
||||
)
|
||||
if parent_boundary:
|
||||
parent_boundaries.append(parent_boundary)
|
||||
|
||||
bpy.ops.bim.show_boundaries()
|
||||
for parent_boundary in parent_boundaries:
|
||||
obj = tool.Ifc.get_object(parent_boundary)
|
||||
@@ -711,6 +677,14 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
+ tool.Ifc.get().by_type("IfcVirtualElement")
|
||||
)
|
||||
|
||||
for building_element in building_elements:
|
||||
if obj := tool.Ifc.get_object(building_element):
|
||||
if tool.Ifc.is_moved(obj):
|
||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
if tool.Ifc.is_moved(space_obj):
|
||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=space_obj)
|
||||
|
||||
# Don't generate boundaries of building elements that we've already got bounaries for.
|
||||
for boundary in space.BoundedBy:
|
||||
if boundary.RelatedBuildingElement in building_elements:
|
||||
@@ -727,7 +701,10 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
while True:
|
||||
tree.add_element(iterator.get_native())
|
||||
shape = iterator.get()
|
||||
shapes[shape.id] = shape
|
||||
shapes[shape.id] = {
|
||||
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
|
||||
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
|
||||
}
|
||||
if not iterator.next():
|
||||
break
|
||||
|
||||
@@ -748,13 +725,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bm = bmesh.new()
|
||||
shape = shapes[building_element.id()]
|
||||
|
||||
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
|
||||
for vert in verts:
|
||||
for vert in shape["verts"]:
|
||||
bm.verts.new(Vector(vert))
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
|
||||
for face in faces:
|
||||
for face in shape["faces"]:
|
||||
bm.faces.new([bm.verts[i] for i in face])
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
@@ -20,16 +20,20 @@ import bpy
|
||||
from . import ui, prop, operator
|
||||
|
||||
classes = (
|
||||
operator.GetBSDDClassificationProperties,
|
||||
operator.LoadBSDDDomains,
|
||||
operator.SearchBSDDClass,
|
||||
operator.SetActiveBSDDDictionary,
|
||||
prop.BSDDDomain,
|
||||
operator.AddBSDDProperties,
|
||||
operator.ImportBSDDClasses,
|
||||
operator.LoadBSDDDictionaries,
|
||||
operator.SearchBSDDClassifications,
|
||||
operator.SearchBSDDProperties,
|
||||
prop.BSDDDictionary,
|
||||
prop.BSDDClassification,
|
||||
prop.BSDDProperty,
|
||||
prop.BSDDPset,
|
||||
prop.BIMBSDDProperties,
|
||||
ui.BIM_UL_bsdd_domains,
|
||||
ui.BIM_UL_bsdd_classifications,
|
||||
ui.BIM_UL_bsdd_dictionaries,
|
||||
ui.BIM_UL_bsdd_classes,
|
||||
ui.BIM_UL_bsdd_properties,
|
||||
ui.BIM_PT_bsdd,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.classification
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def refresh():
|
||||
BSDDData.is_loaded = False
|
||||
|
||||
|
||||
class BSDDData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.is_loaded = True
|
||||
cls.data["active_dictionary"] = cls.active_dictionary()
|
||||
|
||||
@classmethod
|
||||
def active_dictionary(cls):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
results = [("ALL", "All Dictionaries", "All active dictionaries")]
|
||||
results.extend([(d.uri, d.name, f"{d.status} - {d.version}") for d in props.dictionaries if d.is_active])
|
||||
return results
|
||||
@@ -19,61 +19,88 @@
|
||||
import bpy
|
||||
import bsdd
|
||||
import bonsai.tool as tool
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
from bonsai.core import bsdd as core
|
||||
|
||||
|
||||
class LoadBSDDDomains(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bsdd_domains"
|
||||
class LoadBSDDDictionaries(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bsdd_dictionaries"
|
||||
bl_label = "Load bSDD Dictionaries"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
core.load_bsdd(bsdd.Client(), tool.Bsdd)
|
||||
core.load_bsdd(tool.Bsdd)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SetActiveBSDDDictionary(bpy.types.Operator):
|
||||
bl_idname = "bim.set_active_bsdd_domain"
|
||||
bl_label = "Set bSDD Dictionary as active"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
uri: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
core.set_active_bsdd_dictionary(self.name, self.uri, tool.Bsdd)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SearchBSDDClass(bpy.types.Operator):
|
||||
class SearchBSDDClassifications(bpy.types.Operator):
|
||||
bl_idname = "bim.search_bsdd_classifications"
|
||||
bl_label = "Search bSDD Class"
|
||||
bl_description = "Search for bSDD classes by the provided keyword"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# Requirement by buildingSMART bSDD api.
|
||||
if len(context.scene.BIMBSDDProperties.keyword) < 3:
|
||||
cls.poll_message_set("Search query has to be at least 3 characters long.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
keyword = context.scene.BIMBSDDProperties.keyword
|
||||
classes_found = core.search_class(keyword, bsdd.Client(), tool.Bsdd)
|
||||
self.report({"INFO"}, f"{classes_found} bSDD classes found for '{keyword}'.")
|
||||
total = core.search_bsdd_class(tool.Bsdd, context.scene.BIMBSDDProperties.keyword)
|
||||
self.report({"INFO"}, f"{total} bSDD classes found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetBSDDClassificationProperties(bpy.types.Operator):
|
||||
bl_idname = "bim.get_bsdd_classification_properties"
|
||||
bl_label = "Search bSDD Class Properties"
|
||||
bl_description = "Search for bSDD class properties for the currently selected class"
|
||||
class ImportBSDDClasses(bpy.types.Operator):
|
||||
bl_idname = "bim.import_bsdd_classes"
|
||||
bl_label = "Import bSDD Classes"
|
||||
bl_description = "Load bSDD classes that apply to the current element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
pset_data = core.get_class_properties(bsdd.Client(), tool.Bsdd)
|
||||
psets_found = len(pset_data)
|
||||
props_found = sum(len(pset) for pset in pset_data.values())
|
||||
self.report({"INFO"}, f"{psets_found} psets found ({props_found} props) for the active classification.")
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
core.import_bsdd_classes(tool.Bsdd, self.obj, self.obj_type)
|
||||
self.report({"INFO"}, f"{len(props.classes)} bSDD classes found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SearchBSDDProperties(bpy.types.Operator):
|
||||
bl_idname = "bim.search_bsdd_properties"
|
||||
bl_label = "Search bSDD Properties"
|
||||
bl_description = "Search for bSDD properties that apply to the current element"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bsdd.get_bsdd_props()
|
||||
core.search_bsdd_properties(tool.Bsdd, context.scene.BIMBSDDProperties.keyword, self.obj, self.obj_type)
|
||||
self.report({"INFO"}, f"{len(props.properties)} bSDD properties found.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddBSDDProperties(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_bsdd_properties"
|
||||
bl_label = "Add bSDD Properties"
|
||||
bl_description = "Add selected bSDD properties"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
|
||||
psets = {}
|
||||
for selected_property in bprops.selected_properties:
|
||||
psets.setdefault(selected_property.metadata, {})[selected_property.name] = selected_property.get_value()
|
||||
|
||||
props = tool.Pset.get_pset_props(self.obj, self.obj_type)
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(self.obj, self.obj_type, context)
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
properties = {}
|
||||
|
||||
current_psets = ifcopenshell.util.element.get_psets(element, verbose=True)
|
||||
for pset_name, properties in psets.items():
|
||||
if pset := current_psets.get(pset_name, None):
|
||||
pset = self.file.by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, product=element, name=pset_name)
|
||||
ifcopenshell.api.pset.edit_pset(self.file, pset=pset, properties=properties)
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import PropertyGroup
|
||||
from bonsai.bim.module.bsdd.data import BSDDData
|
||||
from bonsai.bim.prop import Attribute, StrProperty
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -29,23 +31,56 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import Union
|
||||
|
||||
|
||||
class BSDDDomain(PropertyGroup):
|
||||
def get_active_dictionary(self, context):
|
||||
if not BSDDData.is_loaded:
|
||||
BSDDData.load()
|
||||
return BSDDData.data["active_dictionary"]
|
||||
|
||||
|
||||
def update_is_active(self: "BSDDDictionary", context: bpy.types.Context) -> None:
|
||||
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
|
||||
|
||||
|
||||
def update_is_selected(self: "BSDDProperty", context: bpy.types.Context) -> None:
|
||||
tool.Bsdd.import_selected_properties()
|
||||
|
||||
|
||||
def update_active_class_index(self: "BIMBSDDProperties", context: bpy.types.Context) -> None:
|
||||
tool.Bsdd.import_class_properties()
|
||||
BSDDData.data["active_dictionary"] = BSDDData.active_dictionary()
|
||||
|
||||
|
||||
class BSDDDictionary(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
uri: StringProperty(name="URI")
|
||||
default_language_code: StringProperty(name="Language")
|
||||
organization_name_owner: StringProperty(name="Organization")
|
||||
status: StringProperty(name="Status")
|
||||
version: StringProperty(name="Version")
|
||||
is_active: BoolProperty(
|
||||
name="Is Active", description="Enable to search with this dictionary", default=False, update=update_is_active
|
||||
)
|
||||
|
||||
|
||||
class BSDDClassification(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
reference_code: StringProperty(name="Reference Code")
|
||||
uri: StringProperty(name="Namespace URI")
|
||||
domain_name: StringProperty(name="Domain Name")
|
||||
domain_namespace_uri: StringProperty(name="Domain Namespace URI")
|
||||
uri: StringProperty(name="URI")
|
||||
dictionary_name: StringProperty(name="Dictionary Name")
|
||||
dictionary_namespace_uri: StringProperty(name="Dictionary Namespace URI")
|
||||
|
||||
|
||||
class BSDDProperty(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
code: StringProperty(name="Code")
|
||||
uri: StringProperty(name="URI")
|
||||
pset: StringProperty(name="Pset")
|
||||
is_selected: BoolProperty(
|
||||
name="Is Selected", description="Select to add or edit this property", default=False, update=update_is_selected
|
||||
)
|
||||
|
||||
|
||||
class BSDDPset(PropertyGroup):
|
||||
@@ -54,12 +89,26 @@ class BSDDPset(PropertyGroup):
|
||||
|
||||
|
||||
class BIMBSDDProperties(PropertyGroup):
|
||||
active_domain: StringProperty(name="Active Domain")
|
||||
active_dictionary: StringProperty(name="Active Dictionary")
|
||||
active_dictionary: EnumProperty(items=get_active_dictionary, name="Active Dictionary")
|
||||
active_uri: StringProperty(name="Active URI")
|
||||
domains: CollectionProperty(name="Domains", type=BSDDDomain)
|
||||
active_domain_index: IntProperty(name="Active Domain Index")
|
||||
dictionaries: CollectionProperty(name="Dictionaries", type=BSDDDictionary)
|
||||
active_dictionary_index: IntProperty(name="Active Dictionary Index")
|
||||
classifications: CollectionProperty(name="Classifications", type=BSDDClassification)
|
||||
active_classification_index: IntProperty(name="Active Classification Index")
|
||||
property_filter_mode: EnumProperty(
|
||||
name="Property Filter Mode",
|
||||
items=[
|
||||
("CLASS", "By Class", "Browse properties by class or group"),
|
||||
("KEYWORD", "By Keyword", "Search properties directly using a keyword"),
|
||||
],
|
||||
default="CLASS",
|
||||
)
|
||||
classes: CollectionProperty(name="Classes", type=BSDDClassification)
|
||||
active_class_index: IntProperty(name="Active Class Index", update=update_active_class_index)
|
||||
properties: CollectionProperty(name="Properties", type=BSDDProperty)
|
||||
active_property_index: IntProperty(name="Active Property Index")
|
||||
selected_properties: CollectionProperty(name="Selected Properties", type=Attribute)
|
||||
keyword: StringProperty(name="Keyword", description="Query for bsdd classes search, case and accent insensitive")
|
||||
should_filter_ifc_class: BoolProperty(
|
||||
name="Filter Active IFC Class",
|
||||
@@ -69,9 +118,19 @@ class BIMBSDDProperties(PropertyGroup):
|
||||
use_only_ifc_properties: BoolProperty(
|
||||
name="Only IFC Properties",
|
||||
description="Whether to display and assign only properties from IFC dictionary",
|
||||
default=True,
|
||||
default=False,
|
||||
)
|
||||
load_preview_domains: BoolProperty(
|
||||
name="Load Preview Domains", description="Whether it should load preview and inactive domains", default=False
|
||||
load_preview_dictionaries: BoolProperty(
|
||||
name="Load Preview Dictionaries", description="Load dictionaries marked as Preview status", default=False
|
||||
)
|
||||
load_inactive_dictionaries: BoolProperty(
|
||||
name="Load Inactive Dictionaries", description="Load dictionaries marked as Inactive status", default=False
|
||||
)
|
||||
load_test_dictionaries: BoolProperty(
|
||||
name="Load Test Dictionaries", description="Load dictionaries that are for testing only", default=False
|
||||
)
|
||||
classification_psets: CollectionProperty(name="Classification Psets", type=BSDDPset)
|
||||
|
||||
@property
|
||||
def active_class(self) -> Union[BSDDClassification, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.classes, self.active_class_index)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.bsdd.data import BSDDData
|
||||
from bpy.types import Panel, UIList
|
||||
|
||||
|
||||
@@ -30,65 +31,58 @@ class BIM_PT_bsdd(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_project_setup"
|
||||
|
||||
def draw(self, context):
|
||||
if not BSDDData.is_loaded:
|
||||
BSDDData.load()
|
||||
props = context.scene.BIMBSDDProperties
|
||||
layout = self.layout
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(props, "load_preview_domains")
|
||||
if len(props.domains):
|
||||
row.operator("bim.load_bsdd_domains", text="", icon="FILE_REFRESH")
|
||||
|
||||
if props.active_domain:
|
||||
if len(props.dictionaries):
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + props.active_domain, icon="URL")
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
row.operator("bim.load_bsdd_dictionaries", icon="FILE_REFRESH")
|
||||
|
||||
if len(props.domains):
|
||||
if len(props.dictionaries):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_bsdd_domains",
|
||||
"BIM_UL_bsdd_dictionaries",
|
||||
"",
|
||||
props,
|
||||
"domains",
|
||||
"dictionaries",
|
||||
props,
|
||||
"active_domain_index",
|
||||
"active_dictionary_index",
|
||||
)
|
||||
if 0 <= props.active_domain_index < len(props.domains):
|
||||
selected_domain = props.domains[props.active_domain_index]
|
||||
if 0 <= props.active_dictionary_index < len(props.dictionaries):
|
||||
selected_dictionary = props.dictionaries[props.active_dictionary_index]
|
||||
else:
|
||||
selected_domain = None
|
||||
selected_dictionary = None
|
||||
|
||||
if selected_domain:
|
||||
layout.label(text="Selected domain:")
|
||||
if selected_dictionary:
|
||||
layout.label(text="Selected Dictionary:")
|
||||
box = layout.box()
|
||||
row = box.row(align=True)
|
||||
row.label(text="Language")
|
||||
row.label(text=selected_domain.default_language_code)
|
||||
row.label(text=selected_dictionary.default_language_code)
|
||||
row = box.row(align=True)
|
||||
row.label(text="Version")
|
||||
row.label(text=selected_domain.version)
|
||||
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_domain.uri
|
||||
row.label(text=selected_dictionary.version)
|
||||
box.operator("bim.open_uri", text="Open bSDD In Browser", icon="URL").uri = selected_dictionary.uri
|
||||
|
||||
else:
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_bsdd_domains")
|
||||
row.operator("bim.load_bsdd_dictionaries")
|
||||
|
||||
|
||||
class BIM_UL_bsdd_domains(UIList):
|
||||
class BIM_UL_bsdd_dictionaries(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
props = context.scene.BIMBSDDProperties
|
||||
row = layout.row(align=True)
|
||||
if item.status != "Active":
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner}) - {item.status}", icon="ERROR")
|
||||
row.label(
|
||||
text=f"{item.name} ({item.organization_name_owner}) v{item.version} - {item.status}", icon="ERROR"
|
||||
)
|
||||
else:
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner})")
|
||||
if item.uri == props.active_uri:
|
||||
row.label(text="", icon="URL")
|
||||
else:
|
||||
op = row.operator("bim.set_active_bsdd_domain", text="", icon="RESTRICT_SELECT_OFF")
|
||||
op.name = item.name
|
||||
op.uri = item.uri
|
||||
row.label(text=f"{item.name} ({item.organization_name_owner}) v{item.version}")
|
||||
row.prop(
|
||||
item, "is_active", icon="CHECKBOX_HLT" if item.is_active else "CHECKBOX_DEHLT", text="", emboss=False
|
||||
)
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classifications(UIList):
|
||||
@@ -98,3 +92,31 @@ class BIM_UL_bsdd_classifications(UIList):
|
||||
row.label(text=item.reference_code)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
|
||||
|
||||
|
||||
class BIM_UL_bsdd_classes(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
|
||||
|
||||
|
||||
class BIM_UL_bsdd_properties(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
name = item.name
|
||||
if name != item.code:
|
||||
name += f" ({item.code})"
|
||||
row.label(text=name)
|
||||
if item.pset:
|
||||
row.label(text=item.pset)
|
||||
row.operator("bim.open_uri", text="", icon="URL").uri = item.uri
|
||||
row.prop(
|
||||
item,
|
||||
"is_selected",
|
||||
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
|
||||
text="",
|
||||
emboss=False,
|
||||
)
|
||||
|
||||
@@ -177,7 +177,7 @@ class CadFillet(bpy.types.Operator):
|
||||
selected_edges.append(cut_edges[1])
|
||||
|
||||
# Calculate the distance to slide each edge to make space for the fillet arc
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
shared_vert = next(iter(set(selected_edges[0].verts) & set(selected_edges[1].verts)))
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
dir1 = (v1.co - shared_vert.co).normalized()
|
||||
@@ -205,7 +205,7 @@ class CadFillet(bpy.types.Operator):
|
||||
# If faces are involved, then the chamfer edge protects existing faces from the newly created fillet.
|
||||
# If no faces are involved, we delete the chamfer edge.
|
||||
if is_wire:
|
||||
chamfer_edge = [e for e in bm.edges if shared_vert in e.verts and v2 in e.verts][0]
|
||||
chamfer_edge = next(e for e in bm.edges if shared_vert in e.verts and v2 in e.verts)
|
||||
bm.edges.remove(chamfer_edge)
|
||||
|
||||
bm.verts.index_update()
|
||||
@@ -675,7 +675,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
|
||||
mid = next(iter(set(edges[0].verts) & set(edges[1].verts)))
|
||||
v1 = edges[0].other_vert(mid)
|
||||
v2 = edges[1].other_vert(mid)
|
||||
assert v1 and v2
|
||||
@@ -738,7 +738,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
bmesh.ops.remove_doubles(bm, verts=all_verts, dist=1e-4)
|
||||
|
||||
# Calculate the distance to slide each edge to make space for the fillet arc
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
shared_vert = next(iter(set(selected_edges[0].verts) & set(selected_edges[1].verts)))
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
assert v1 and v2
|
||||
|
||||
@@ -19,16 +19,19 @@
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import tempfile
|
||||
import bmesh
|
||||
import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import bonsai.tool as tool
|
||||
from pathlib import Path
|
||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||
from math import radians
|
||||
from mathutils import Matrix, Vector
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.clash.decorator import ClashDecorator
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class ExportClashSets(bpy.types.Operator, ExportHelper):
|
||||
@@ -127,7 +130,8 @@ class AddClashSource(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
source = getattr(clash_set, self.group).add()
|
||||
assert clash_set
|
||||
clash_set.get_clash_sources_group(self.group).add()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -142,7 +146,8 @@ class RemoveClashSource(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
getattr(clash_set, self.group).remove(self.index)
|
||||
assert clash_set
|
||||
clash_set.get_clash_sources_group(self.group).remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -152,14 +157,16 @@ class SelectClashSource(bpy.types.Operator, ImportHelper):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC file to add as a clash source"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
index: bpy.props.IntProperty(options={"HIDDEN"})
|
||||
group: bpy.props.StringProperty(options={"HIDDEN"})
|
||||
filename_ext = ".ifc"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
getattr(clash_set, self.group)[self.index].name = self.filepath
|
||||
assert clash_set
|
||||
clash_source = clash_set.get_clash_sources_group(self.group)[self.index]
|
||||
clash_source.name = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -190,16 +197,39 @@ class SelectSmartGroupedClashesPath(bpy.types.Operator, ImportHelper):
|
||||
class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
bl_description = "Execute clash detection and save the information to a .bcf or .json file"
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
bl_description = (
|
||||
"Execute clash detection and save the information to a .bcf or .json file.\n\n"
|
||||
"ALT+click to run a quick clash without selecting a file to save."
|
||||
)
|
||||
|
||||
filter_glob: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
default="*.bcf;*.json", options={"HIDDEN"}
|
||||
)
|
||||
format: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Format", items=[(i, i, "") for i in ("bcf", "json")]
|
||||
)
|
||||
filepath: bpy.props.StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
subtype="FILE_PATH", options={"SKIP_SAVE"}
|
||||
)
|
||||
quick_clash: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_glob: str
|
||||
format: str
|
||||
filepath: str
|
||||
quick_clash: bool
|
||||
|
||||
@property
|
||||
def filename_ext(self) -> str:
|
||||
return f".{self.format.lower()}"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.alt:
|
||||
self.quick_clash = True
|
||||
return self.execute(context)
|
||||
|
||||
if self.filepath:
|
||||
return self.execute(context)
|
||||
return ExportHelper.invoke(self, context, event)
|
||||
@@ -209,14 +239,32 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
|
||||
self.props = tool.Clash.get_clash_props()
|
||||
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".bcf":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
# TODO Temporarily until BCF support comes back
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
for clash_set in self.props.clash_sets:
|
||||
for clash_sources in clash_set.get_clash_sources().values():
|
||||
for clash_source in clash_sources:
|
||||
if not Path(clash_source.name).is_file():
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"One of the provided clash source filepaths do not exist: '{clash_source.name}'.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
temp_file = None
|
||||
if self.quick_clash:
|
||||
temp_file = tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w")
|
||||
temp_file.close()
|
||||
extension = ".json"
|
||||
self.filepath = temp_file.name
|
||||
else:
|
||||
extension = Path(self.filepath).suffix.lower()
|
||||
if extension != ".bcf":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
# TODO Temporarily until BCF support comes back
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
assert extension in (".bcf", ".json")
|
||||
self.props.export_path = self.filepath
|
||||
|
||||
self.props.export_path = self.filepath
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
@@ -225,11 +273,14 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
|
||||
if self.props.should_create_clash_snapshots:
|
||||
|
||||
def get_viewpoint_snapshot(viewpoint):
|
||||
def get_viewpoint_snapshot(viewpoint) -> tuple[str, bytes]:
|
||||
assert context.scene
|
||||
|
||||
camera = bpy.data.objects.get("IFC Clash Camera")
|
||||
if not camera:
|
||||
camera = bpy.data.objects.new("IFC Clash Camera", bpy.data.cameras.new("IFC Clash Camera"))
|
||||
context.scene.collection.objects.link(camera)
|
||||
assert isinstance(camera.data, bpy.types.Camera)
|
||||
|
||||
bcf_camera = viewpoint.visualization_info.perspective_camera
|
||||
p = bcf_camera.camera_view_point
|
||||
@@ -238,6 +289,7 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
y = bcf_camera.camera_up_vector
|
||||
y = Vector([y.x, y.y, y.z])
|
||||
x = y.cross(z)
|
||||
assert isinstance(x, Vector)
|
||||
|
||||
mat = Matrix(
|
||||
[
|
||||
@@ -251,9 +303,9 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
camera.matrix_world = mat
|
||||
context.scene.camera = camera
|
||||
camera.data.angle = radians(60)
|
||||
area = next(area for area in context.screen.areas if area.type == "VIEW_3D")
|
||||
area.spaces[0].region_3d.view_perspective = "CAMERA"
|
||||
area.spaces[0].shading.show_xray = True
|
||||
assert (space := tool.Blender.get_view3d_space()) and space.region_3d
|
||||
space.region_3d.view_perspective = "CAMERA"
|
||||
space.shading.show_xray = True
|
||||
context.scene.render.resolution_x = 480
|
||||
context.scene.render.resolution_y = 270
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
@@ -268,10 +320,26 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
clasher.clash()
|
||||
clasher.export()
|
||||
|
||||
if extension == ".json":
|
||||
# Load clash results to UI.
|
||||
if extension == ".bcf":
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
|
||||
try:
|
||||
tmp.close()
|
||||
settings.output = tmp.name
|
||||
clasher.export()
|
||||
tool.Clash.load_clash_sets(tmp.name)
|
||||
finally:
|
||||
Path(tmp.name).unlink()
|
||||
else:
|
||||
tool.Clash.load_clash_sets(self.filepath)
|
||||
tool.Clash.import_active_clashes()
|
||||
self.report({"INFO"}, "Finished IFC clash.")
|
||||
tool.Clash.import_active_clashes()
|
||||
|
||||
if self.quick_clash:
|
||||
assert temp_file is not None
|
||||
Path(temp_file.name).unlink()
|
||||
self.report({"INFO"}, "IFC Clash completed and results are loaded.")
|
||||
else:
|
||||
self.report({"INFO"}, f"IFC Clash results are saved to '{Path(self.filepath).name}'.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -348,14 +416,16 @@ class SelectClash(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
self.props = tool.Clash.get_clash_props()
|
||||
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
|
||||
active_clash = self.props.active_clash
|
||||
assert (active_clash := self.props.active_clash)
|
||||
assert (active_clash_set := self.props.active_clash_set)
|
||||
clash_set = tool.Clash.get_clash_set(active_clash_set.name)
|
||||
assert clash_set
|
||||
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
|
||||
|
||||
if not clash:
|
||||
return {"FINISHED"}
|
||||
|
||||
products = []
|
||||
products: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
for global_id in (clash["a_global_id"], clash["b_global_id"]):
|
||||
try:
|
||||
|
||||
@@ -30,14 +30,18 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from ifcopenshell.geom.main import ClashType, CLASH_TYPE_ITEMS
|
||||
from mathutils import Vector
|
||||
from typing import TYPE_CHECKING, Literal, Union
|
||||
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
name: StringProperty(name="File")
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups")
|
||||
mode: EnumProperty(
|
||||
name: StringProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="File",
|
||||
description="Absolute filepath to existing .ifc file to use as a clash source.",
|
||||
)
|
||||
filter_groups: CollectionProperty(type=BIMFilterGroup, name="Filter Groups") # pyright: ignore[reportRedeclaration]
|
||||
mode: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
items=[
|
||||
("a", "All Elements", "All elements will be used for clashing"),
|
||||
("i", "Include", "Only the selected elements are included for clashing"),
|
||||
@@ -47,6 +51,7 @@ class ClashSource(PropertyGroup):
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
|
||||
mode: Literal["a", "i", "e"]
|
||||
|
||||
@@ -56,16 +61,31 @@ class Clash(PropertyGroup):
|
||||
b_global_id: StringProperty(name="B")
|
||||
a_name: StringProperty(name="A Name")
|
||||
b_name: StringProperty(name="B Name")
|
||||
status: BoolProperty(name="Status", default=False)
|
||||
clash_type: EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Clash Type",
|
||||
items=tuple((i, i, "") for i in CLASH_TYPE_ITEMS),
|
||||
)
|
||||
status: BoolProperty(
|
||||
name="Status",
|
||||
description="Clash status, not stored anywhere - currently just displayed in UI for convenience.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
a_global_id: str
|
||||
b_global_id: str
|
||||
a_name: str
|
||||
b_name: str
|
||||
clash_type: ClashType
|
||||
status: bool
|
||||
|
||||
|
||||
def clashes_loaded_update(self: "ClashSet", context: bpy.types.Context) -> None:
|
||||
if self.clashes_loaded:
|
||||
return
|
||||
tool.Clash.clear_active_clash_set_results()
|
||||
|
||||
|
||||
class ClashSet(PropertyGroup):
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
@@ -81,13 +101,18 @@ class ClashSet(PropertyGroup):
|
||||
],
|
||||
name="Mode",
|
||||
)
|
||||
tolerance: FloatProperty(name="Tolerance", default=0.002)
|
||||
clearance: FloatProperty(name="Clearance", default=0.01)
|
||||
tolerance: FloatProperty(name="Tolerance", default=0.002, subtype="DISTANCE")
|
||||
clearance: FloatProperty(name="Clearance", default=0.01, subtype="DISTANCE")
|
||||
allow_touching: BoolProperty(name="Allow Touching", default=False)
|
||||
check_all: BoolProperty(name="Check All", default=False)
|
||||
a: CollectionProperty(name="Group A", type=ClashSource)
|
||||
b: CollectionProperty(name="Group B", type=ClashSource)
|
||||
clashes: CollectionProperty(name="Clashes", type=Clash)
|
||||
clashes_loaded: BoolProperty(
|
||||
name="Clash Results Are Loaded",
|
||||
description="Click to unload clash results for the clash set.",
|
||||
update=clashes_loaded_update,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
mode: Literal["intersection", "collision", "clearance"]
|
||||
@@ -98,6 +123,17 @@ class ClashSet(PropertyGroup):
|
||||
a: bpy.types.bpy_prop_collection_idprop[ClashSource]
|
||||
b: bpy.types.bpy_prop_collection_idprop[ClashSource]
|
||||
clashes: bpy.types.bpy_prop_collection_idprop[Clash]
|
||||
clashes_loaded: bool
|
||||
|
||||
def get_clash_sources_group(
|
||||
self, group: tool.Clash.ClashSourceGroup
|
||||
) -> "bpy.types.bpy_prop_collection_idprop[ClashSource]":
|
||||
return getattr(self, group)
|
||||
|
||||
def get_clash_sources(
|
||||
self,
|
||||
) -> "dict[tool.Clash.ClashSourceGroup, bpy.types.bpy_prop_collection_idprop[ClashSource]]":
|
||||
return {g: self.get_clash_sources_group(g) for g in tool.Clash.CLASH_SOURCE_GROUP_LITERALS}
|
||||
|
||||
|
||||
class SmartClashGroup(PropertyGroup):
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bpy.types import Panel
|
||||
from bonsai.bim.module.clash.data import ClashData
|
||||
from typing import TYPE_CHECKING, assert_never
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.clash.prop import BIMClashProperties, ClashSet, SmartClashGroup, Clash
|
||||
|
||||
|
||||
class BIM_PT_ifcclash(Panel):
|
||||
@@ -36,6 +41,7 @@ class BIM_PT_ifcclash(Panel):
|
||||
if not ClashData.is_loaded:
|
||||
ClashData.load()
|
||||
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
|
||||
@@ -74,46 +80,62 @@ class BIM_PT_ifcclash(Panel):
|
||||
row.prop(clash_set, "clearance")
|
||||
row = layout.row()
|
||||
row.prop(clash_set, "check_all")
|
||||
else:
|
||||
assert_never(clash_set.mode)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Group A:", icon="OUTLINER_OB_POINTCLOUD")
|
||||
row.operator("bim.add_clash_source", icon="ADD", text="").group = "a"
|
||||
|
||||
for index, source in enumerate(clash_set.a):
|
||||
def draw_clash_set_group(group: tool.Clash.ClashSourceGroup) -> None:
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
row.prop(source, "mode", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "a"
|
||||
row.label(text=f"Group {group.upper()}:", icon="OUTLINER_OB_POINTCLOUD")
|
||||
row.operator("bim.add_clash_source", icon="ADD", text="").group = group
|
||||
|
||||
if source.mode != "a":
|
||||
bonsai.bim.helper.draw_filter(
|
||||
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_a_{index}"
|
||||
)
|
||||
sources = clash_set.get_clash_sources_group(group)
|
||||
if not sources:
|
||||
return
|
||||
layout_ = layout.box()
|
||||
for index, source in enumerate(sources):
|
||||
row = layout_.row(align=True)
|
||||
row.column().label(text="", icon="POINTCLOUD_POINT")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Group B:", icon="OUTLINER_OB_POINTCLOUD")
|
||||
row.operator("bim.add_clash_source", icon="ADD", text="").group = "b"
|
||||
# Draw user attention to empty filepaths.
|
||||
col = row.column()
|
||||
col.alert = not bool(source.name)
|
||||
col.prop(source, "name", text="", placeholder="source.ifc")
|
||||
|
||||
for index, source in enumerate(clash_set.b):
|
||||
row = layout.row(align=True)
|
||||
row.prop(source, "name", text="")
|
||||
row.prop(source, "mode", text="")
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = "b"
|
||||
op = row.operator("bim.select_clash_source", icon="FILE_FOLDER", text="")
|
||||
op.index = index
|
||||
op.group = group
|
||||
# Make sure file selection sticks to "name" field.
|
||||
row.label(text="", icon="BLANK1")
|
||||
|
||||
if source.mode != "a":
|
||||
bonsai.bim.helper.draw_filter(
|
||||
layout, source.filter_groups, ClashData, f"clash_{props.active_clash_set_index}_b_{index}"
|
||||
)
|
||||
row.prop(source, "mode", text="")
|
||||
op = row.operator("bim.remove_clash_source", icon="X", text="")
|
||||
op.index = index
|
||||
op.group = group
|
||||
|
||||
if source.mode != "a":
|
||||
|
||||
def draw_filter(layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
|
||||
bonsai.bim.helper.draw_filter(
|
||||
layout,
|
||||
source.filter_groups,
|
||||
ClashData,
|
||||
f"clash_{props.active_clash_set_index}_{group}_{index}",
|
||||
)
|
||||
|
||||
bonsai.bim.helper.draw_expandable_panel(
|
||||
layout_,
|
||||
context,
|
||||
"Filter",
|
||||
draw_filter,
|
||||
default_closed=True,
|
||||
panel_id=f"filter_{group}_{index}",
|
||||
)
|
||||
|
||||
layout.separator()
|
||||
draw_clash_set_group("a")
|
||||
layout.separator()
|
||||
draw_clash_set_group("b")
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_create_clash_snapshots")
|
||||
@@ -125,11 +147,26 @@ class BIM_PT_ifcclash(Panel):
|
||||
op.filepath = props.export_path
|
||||
|
||||
row = layout.row()
|
||||
row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
|
||||
if clash_set.clashes_loaded:
|
||||
row.column().label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
|
||||
|
||||
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
|
||||
row = layout.row()
|
||||
row.operator("bim.select_clash")
|
||||
col = row.column()
|
||||
col.alignment = "RIGHT"
|
||||
col.prop(clash_set, "clashes_loaded", text="", icon="TRASH", invert_checkbox=True)
|
||||
|
||||
split = layout.split(factor=0.07, align=True)
|
||||
split.label(text="#")
|
||||
|
||||
row = split.row(align=True)
|
||||
row.label(text="Group A Element")
|
||||
row.label(text="Group B Element")
|
||||
row.label(text="Type")
|
||||
|
||||
layout.template_list("BIM_UL_clashes", "", props.active_clash_set, "clashes", props, "active_clash_index")
|
||||
row = layout.row()
|
||||
row.operator("bim.select_clash")
|
||||
else:
|
||||
row.label(text="Clashes Are Not Loaded", icon="PIVOT_CURSOR")
|
||||
|
||||
|
||||
class BIM_PT_clash_manager(Panel):
|
||||
@@ -155,6 +192,7 @@ class BIM_PT_smart_clash_manager(Panel):
|
||||
bl_parent_id = "BIM_PT_clash_manager"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
|
||||
@@ -188,8 +226,16 @@ class BIM_PT_smart_clash_manager(Panel):
|
||||
|
||||
|
||||
class BIM_UL_clash_sets(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: ClashSet,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.prop(item, "name", text="", emboss=False)
|
||||
else:
|
||||
@@ -197,8 +243,16 @@ class BIM_UL_clash_sets(bpy.types.UIList):
|
||||
|
||||
|
||||
class BIM_UL_smart_groups(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
ob = data
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: SmartClashGroup,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
) -> None:
|
||||
if item:
|
||||
layout.label(text=str(item.number), translate=False, icon="NONE", icon_value=0)
|
||||
else:
|
||||
@@ -206,11 +260,30 @@ class BIM_UL_smart_groups(bpy.types.UIList):
|
||||
|
||||
|
||||
class BIM_UL_clashes(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMClashProperties,
|
||||
item: Clash,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
index,
|
||||
fit_flag,
|
||||
) -> None:
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
split = layout.split(factor=0.05, align=True)
|
||||
split.label(text=str(index + 1))
|
||||
|
||||
row = split.row(align=False)
|
||||
row.label(text=str(item.a_name), translate=False, icon="NONE", icon_value=0)
|
||||
row.label(text=str(item.b_name), translate=False, icon="NONE", icon_value=0)
|
||||
|
||||
col = row.column()
|
||||
col.enabled = False
|
||||
col.prop(item, "clash_type", text="")
|
||||
|
||||
row.prop(item, "status", text="")
|
||||
else:
|
||||
layout.label(text="", translate=False)
|
||||
|
||||
@@ -21,6 +21,7 @@ import ifcopenshell
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.classification
|
||||
import bonsai.tool as tool
|
||||
from typing import Any
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
@@ -41,6 +42,7 @@ class ClassificationsData:
|
||||
cls.data["has_classification_file"] = cls.has_classification_file()
|
||||
cls.data["classifications"] = cls.classifications()
|
||||
cls.data["available_classifications"] = cls.available_classifications()
|
||||
cls.data["classification_source"] = cls.classification_source()
|
||||
|
||||
@classmethod
|
||||
def has_classification_file(cls):
|
||||
@@ -62,6 +64,19 @@ class ClassificationsData:
|
||||
return []
|
||||
return [(str(e.id()), e.Name, "") for e in IfcStore.classification_file.by_type("IfcClassification")]
|
||||
|
||||
@classmethod
|
||||
def classification_source(cls):
|
||||
items = [
|
||||
("FILE", "IFC File", ""),
|
||||
("MANUAL", "Manual Entry", ""),
|
||||
]
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
dictionaries = [(d.uri, f"bSDD: {d.name}", "") for d in bprops.dictionaries if d.is_active]
|
||||
if dictionaries:
|
||||
items.append(("BSDD", "All Active bSDDs", ""))
|
||||
items.extend(dictionaries)
|
||||
return items
|
||||
|
||||
|
||||
class ReferencesData:
|
||||
@classmethod
|
||||
@@ -143,10 +158,13 @@ class CostClassificationsData(ReferencesData):
|
||||
cls.data["object_type"] = "Cost"
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
def references(cls) -> list[dict[str, Any]]:
|
||||
results: list[dict[str, Any]] = []
|
||||
props = tool.Cost.get_cost_props()
|
||||
element = tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id)
|
||||
active_cost_item = props.active_cost_item
|
||||
if not active_cost_item:
|
||||
return results
|
||||
element = tool.Ifc.get().by_id(active_cost_item.ifc_definition_id)
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.classification
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
@@ -47,8 +48,7 @@ class AddClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
ifcopenshell.api.run(
|
||||
"classification.add_classification",
|
||||
ifcopenshell.api.classification.add_classification(
|
||||
tool.Ifc.get(),
|
||||
classification=IfcStore.classification_file.by_id(int(props.available_classifications)),
|
||||
)
|
||||
@@ -62,11 +62,9 @@ class AddManualClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes)
|
||||
classification = ifcopenshell.api.run(
|
||||
"classification.add_classification", tool.Ifc.get(), classification="Unnamed"
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_classification", tool.Ifc.get(), classification=classification, attributes=attributes
|
||||
classification = ifcopenshell.api.classification.add_classification(tool.Ifc.get(), classification="Unnamed")
|
||||
ifcopenshell.api.classification.edit_classification(
|
||||
tool.Ifc.get(), classification=classification, attributes=attributes
|
||||
)
|
||||
props.is_adding = False
|
||||
|
||||
@@ -95,17 +93,14 @@ class AddManualClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
]
|
||||
if products:
|
||||
classification = tool.Ifc.get().by_id(int(props.classifications))
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
reference = ifcopenshell.api.classification.add_reference(
|
||||
tool.Ifc.get(),
|
||||
products=products,
|
||||
classification=classification,
|
||||
identification="X",
|
||||
name="Unnamed",
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_reference", tool.Ifc.get(), reference=reference, attributes=attributes
|
||||
)
|
||||
ifcopenshell.api.classification.edit_reference(tool.Ifc.get(), reference=reference, attributes=attributes)
|
||||
props.is_adding = False
|
||||
|
||||
|
||||
@@ -115,29 +110,30 @@ class AddClassificationFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBSDDProperties
|
||||
domain = next((d for d in props.domains if d.name == props.active_domain and d.uri == props.active_uri), None)
|
||||
|
||||
# Maybe user loaded preview domains, set it as active
|
||||
# and then reloaded them without preview domains.
|
||||
if not domain:
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"Couldn't find domain '{props.active_domain}' ({props.active_uri}). Try to reload bSDD dictionaries.",
|
||||
cprops = context.scene.BIMClassificationProperties
|
||||
bprops = tool.Bsdd.get_bsdd_props()
|
||||
if cprops.classification_source == "BSDD":
|
||||
dictionaries = [d.uri for d in bprops.dictionaries if d.is_active]
|
||||
else:
|
||||
dictionaries = [cprops.classification_source]
|
||||
for uri in dictionaries:
|
||||
if not (dictionary := tool.Bsdd.get_dictionary(uri)):
|
||||
continue
|
||||
has_classification = False
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
if element.Name == dictionary["name"] or (
|
||||
tool.Classification.get_location(element) == dictionary["uri"]
|
||||
):
|
||||
self.report({"INFO"}, f"Classification '{dictionary['name']}' is already added to the project.")
|
||||
has_classification = True
|
||||
if has_classification:
|
||||
continue
|
||||
classification = ifcopenshell.api.classification.add_classification(
|
||||
tool.Ifc.get(), classification=dictionary["name"]
|
||||
)
|
||||
return
|
||||
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
if element.Name == props.active_domain or (tool.Classification.get_location(element) == domain.uri):
|
||||
self.report({"INFO"}, f"Classification '{props.active_domain}' is already added to the project.")
|
||||
return
|
||||
|
||||
classification = ifcopenshell.api.run(
|
||||
"classification.add_classification", tool.Ifc.get(), classification=props.active_domain
|
||||
)
|
||||
classification.Source = domain.organization_name_owner
|
||||
classification.Edition = domain.version
|
||||
tool.Classification.set_location(classification, domain.uri)
|
||||
classification.Source = dictionary["organizationNameOwner"]
|
||||
classification.Edition = dictionary["version"]
|
||||
tool.Classification.set_location(classification, dictionary["uri"])
|
||||
|
||||
|
||||
class EnableAddingManualClassification(bpy.types.Operator):
|
||||
@@ -238,8 +234,7 @@ class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_classification",
|
||||
ifcopenshell.api.classification.remove_classification(
|
||||
tool.Ifc.get(),
|
||||
classification=self.file.by_id(self.classification),
|
||||
)
|
||||
@@ -328,8 +323,7 @@ class RemoveClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if elements_by_references:
|
||||
for reference, products in elements_by_references.items():
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_reference",
|
||||
ifcopenshell.api.classification.remove_reference(
|
||||
tool.Ifc.get(),
|
||||
reference=reference,
|
||||
products=products,
|
||||
@@ -384,8 +378,7 @@ class AddClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if (ifc_definition_id := tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context))
|
||||
]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
ifcopenshell.api.classification.add_reference(
|
||||
tool.Ifc.get(),
|
||||
reference=IfcStore.classification_file.by_id(self.reference),
|
||||
products=products,
|
||||
@@ -420,25 +413,24 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
classification = None
|
||||
for element in tool.Ifc.get().by_type("IfcClassification"):
|
||||
if element.Name == bsdd_classification.domain_name or (
|
||||
tool.Classification.get_location(element) == bsdd_classification.domain_namespace_uri
|
||||
if element.Name == bsdd_classification.dictionary_name or (
|
||||
tool.Classification.get_location(element) == bsdd_classification.dictionary_namespace_uri
|
||||
):
|
||||
classification = element
|
||||
break
|
||||
|
||||
if not classification:
|
||||
classification = ifcopenshell.api.run(
|
||||
"classification.add_classification", tool.Ifc.get(), classification=bsdd_classification.domain_name
|
||||
classification = ifcopenshell.api.classification.add_classification(
|
||||
tool.Ifc.get(), classification=bsdd_classification.dictionary_name
|
||||
)
|
||||
tool.Classification.set_location(classification, bsdd_classification.domain_namespace_uri)
|
||||
tool.Classification.set_location(classification, bsdd_classification.dictionary_namespace_uri)
|
||||
|
||||
for obj in objects:
|
||||
ifc_definition_id = tool.Blender.get_obj_ifc_definition_id(obj, self.obj_type, context)
|
||||
if not ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get().by_id(ifc_definition_id)
|
||||
reference = ifcopenshell.api.run(
|
||||
"classification.add_reference",
|
||||
reference = ifcopenshell.api.classification.add_reference(
|
||||
tool.Ifc.get(),
|
||||
products=[element],
|
||||
classification=classification,
|
||||
@@ -476,18 +468,16 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
elif is_pset:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=element, name=classification_pset.name
|
||||
pset = ifcopenshell.api.pset.add_pset(
|
||||
tool.Ifc.get(), product=element, name=classification_pset.name
|
||||
)
|
||||
else:
|
||||
pset = ifcopenshell.api.run(
|
||||
"pset.add_qto", tool.Ifc.get(), product=element, name=classification_pset.name
|
||||
)
|
||||
pset = ifcopenshell.api.pset.add_qto(tool.Ifc.get(), product=element, name=classification_pset.name)
|
||||
|
||||
if is_pset:
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties=properties)
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties=properties)
|
||||
else:
|
||||
ifcopenshell.api.run("pset.edit_qto", tool.Ifc.get(), qto=pset, properties=properties)
|
||||
ifcopenshell.api.pset.edit_qto(tool.Ifc.get(), qto=pset, properties=properties)
|
||||
|
||||
|
||||
class ChangeClassificationLevel(bpy.types.Operator):
|
||||
|
||||
@@ -44,6 +44,12 @@ def get_classifications(self, context):
|
||||
return ClassificationReferencesData.data["classifications"]
|
||||
|
||||
|
||||
def get_classification_source(self, context):
|
||||
if not ClassificationsData.is_loaded:
|
||||
ClassificationsData.load()
|
||||
return ClassificationsData.data["classification_source"]
|
||||
|
||||
|
||||
class ClassificationReference(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
identification: StringProperty(name="Identification")
|
||||
@@ -54,15 +60,7 @@ class ClassificationReference(PropertyGroup):
|
||||
|
||||
class BIMClassificationProperties(PropertyGroup):
|
||||
is_adding: BoolProperty(name="Is Adding", default=False)
|
||||
classification_source: EnumProperty(
|
||||
items=[
|
||||
("FILE", "IFC File", ""),
|
||||
("BSDD", "buildingSMART Data Dictionary", ""),
|
||||
("MANUAL", "Manual Entry", ""),
|
||||
],
|
||||
name="Classification Source",
|
||||
default="FILE",
|
||||
)
|
||||
classification_source: EnumProperty(items=get_classification_source, name="Classification Source")
|
||||
available_classifications: EnumProperty(items=get_available_classifications, name="Available Classifications")
|
||||
classification_attributes: CollectionProperty(name="Classification Attributes", type=Attribute)
|
||||
active_classification_id: IntProperty(name="Active Classification Id")
|
||||
|
||||
@@ -54,10 +54,10 @@ class BIM_PT_classifications(Panel):
|
||||
|
||||
if self.props.classification_source == "FILE":
|
||||
self.draw_add_file_ui(context)
|
||||
elif self.props.classification_source == "BSDD":
|
||||
self.draw_add_bsdd_ui(context)
|
||||
elif self.props.classification_source == "MANUAL":
|
||||
self.draw_add_manual_ui(context)
|
||||
else:
|
||||
self.draw_add_bsdd_ui(context)
|
||||
|
||||
for classification in ClassificationsData.data["classifications"]:
|
||||
if self.props.active_classification_id == classification["id"]:
|
||||
@@ -76,16 +76,6 @@ class BIM_PT_classifications(Panel):
|
||||
row.operator("bim.enable_adding_manual_classification", text="Add Classification", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
self.bprops = context.scene.BIMBSDDProperties
|
||||
|
||||
if not self.bprops.active_domain:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.add_classification_from_bsdd", icon="ADD")
|
||||
|
||||
@@ -144,10 +134,10 @@ class ReferenceUI:
|
||||
|
||||
if self.sprops.classification_source == "FILE":
|
||||
self.draw_add_file_ui(context)
|
||||
elif self.sprops.classification_source == "BSDD":
|
||||
self.draw_add_bsdd_ui(context)
|
||||
elif self.sprops.classification_source == "MANUAL":
|
||||
self.draw_add_manual_ui(context)
|
||||
else:
|
||||
self.draw_add_bsdd_ui(context)
|
||||
|
||||
def draw_add_manual_ui(self, context):
|
||||
row = self.layout.row()
|
||||
@@ -163,22 +153,11 @@ class ReferenceUI:
|
||||
row.operator("bim.enable_adding_manual_classification_reference", text="Add Reference", icon="ADD")
|
||||
|
||||
def draw_add_bsdd_ui(self, context):
|
||||
if not self.bprops.active_domain:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Active bSDD Domain", icon="ERROR")
|
||||
return
|
||||
|
||||
row = self.layout.row()
|
||||
row.label(text="Active: " + self.bprops.active_domain, icon="URL")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.bprops, "keyword", text="")
|
||||
row.prop(self.bprops, "should_filter_ifc_class", text="", icon="FILTER")
|
||||
row.operator("bim.search_bsdd_classifications", text="", icon="VIEWZOOM")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(self.bprops, "should_filter_ifc_class")
|
||||
row.prop(self.bprops, "use_only_ifc_properties")
|
||||
|
||||
if len(self.bprops.classifications):
|
||||
self.layout.template_list(
|
||||
"BIM_UL_bsdd_classifications",
|
||||
@@ -199,21 +178,6 @@ class ReferenceUI:
|
||||
)
|
||||
op.obj = self.obj
|
||||
op.obj_type = self.obj_type
|
||||
row.operator("bim.get_bsdd_classification_properties", text="", icon="COPY_ID")
|
||||
|
||||
if len(self.bprops.classification_psets):
|
||||
use_only_ifc_properties = self.bprops.use_only_ifc_properties
|
||||
for pset in self.bprops.classification_psets:
|
||||
properties = pset.properties
|
||||
if use_only_ifc_properties:
|
||||
properties = [p for p in properties if p.metadata == "IFC"]
|
||||
if not properties:
|
||||
continue
|
||||
|
||||
box = self.layout.box()
|
||||
row = box.row()
|
||||
row.label(text=pset.name, icon="COPY_ID")
|
||||
bonsai.bim.helper.draw_attributes(properties, box)
|
||||
|
||||
def draw_add_file_ui(self, context):
|
||||
if not self.data.data["active_classification_library"]:
|
||||
|
||||
@@ -17,12 +17,27 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.constraint
|
||||
import ifcopenshell.util.attribute
|
||||
import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
def get_active_object(context: bpy.types.Context, obj_name: str) -> bpy.types.Object:
|
||||
if obj_name:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
else:
|
||||
assert (obj := context.active_object)
|
||||
return obj
|
||||
|
||||
|
||||
def get_selected_objects(context: bpy.types.Context, obj_name: str) -> list[bpy.types.Object]:
|
||||
if obj_name:
|
||||
objs = [bpy.data.objects[obj_name]]
|
||||
else:
|
||||
objs = context.selected_objects
|
||||
return objs
|
||||
|
||||
|
||||
class LoadObjectives(bpy.types.Operator):
|
||||
@@ -31,7 +46,7 @@ class LoadObjectives(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.constraints.clear()
|
||||
for constraint in tool.Ifc.get().by_type("IfcObjective"):
|
||||
new = props.constraints.add()
|
||||
@@ -48,7 +63,8 @@ class DisableConstraintEditingUI(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.is_editing = ""
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.is_editing = ""
|
||||
bpy.ops.bim.disable_editing_constraint()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -59,8 +75,11 @@ class EnableEditingConstraint(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
constraint: int
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.constraint_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.constraint), props.constraint_attributes)
|
||||
props.active_constraint_id = self.constraint
|
||||
@@ -73,7 +92,8 @@ class DisableEditingConstraint(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
context.scene.BIMConstraintProperties.active_constraint_id = 0
|
||||
props = tool.Blender.get_constraint_props()
|
||||
props.active_constraint_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -83,7 +103,7 @@ class AddObjective(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("constraint.add_objective", tool.Ifc.get())
|
||||
result = ifcopenshell.api.constraint.add_objective(tool.Ifc.get())
|
||||
bpy.ops.bim.load_objectives()
|
||||
bpy.ops.bim.enable_editing_constraint(constraint=result.id())
|
||||
return {"FINISHED"}
|
||||
@@ -95,7 +115,7 @@ class EditObjective(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
props = tool.Blender.get_constraint_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.constraint_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.constraint.edit_objective(
|
||||
@@ -113,12 +133,13 @@ class RemoveConstraint(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
constraint: int
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMConstraintProperties
|
||||
props = tool.Blender.get_constraint_props()
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.run(
|
||||
"constraint.remove_constraint", self.file, **{"constraint": self.file.by_id(self.constraint)}
|
||||
)
|
||||
ifcopenshell.api.constraint.remove_constraint(self.file, constraint=self.file.by_id(self.constraint))
|
||||
if props.is_editing == "IfcObjective":
|
||||
bpy.ops.bim.load_objectives()
|
||||
return {"FINISHED"}
|
||||
@@ -130,9 +151,12 @@ class EnableAssigningConstraint(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: str
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectConstraintProperties
|
||||
obj = get_active_object(context, self.obj)
|
||||
props = tool.Blender.get_object_constraint_props(obj)
|
||||
if props.available_constraint_types == "IfcObjective":
|
||||
bpy.ops.bim.load_objectives()
|
||||
props.is_adding = props.available_constraint_types
|
||||
@@ -145,9 +169,12 @@ class DisableAssigningConstraint(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: str
|
||||
|
||||
def execute(self, context):
|
||||
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
|
||||
props = obj.BIMObjectConstraintProperties
|
||||
obj = get_active_object(context, self.obj)
|
||||
props = tool.Blender.get_object_constraint_props(obj)
|
||||
props.is_adding = ""
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -159,8 +186,13 @@ class AssignConstraint(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: str
|
||||
constraint: int
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
objs = get_selected_objects(context, self.obj)
|
||||
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
|
||||
products = [
|
||||
self.file.by_id(obj_id)
|
||||
@@ -168,13 +200,10 @@ class AssignConstraint(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
|
||||
]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"constraint.assign_constraint",
|
||||
ifcopenshell.api.constraint.assign_constraint(
|
||||
self.file,
|
||||
**{
|
||||
"products": products,
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
},
|
||||
products=products,
|
||||
constraint=self.file.by_id(self.constraint),
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -186,21 +215,22 @@ class UnassignConstraint(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj: bpy.props.StringProperty()
|
||||
constraint: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: str
|
||||
constraint: int
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
objs = [bpy.data.objects[self.obj]] if self.obj else context.selected_objects
|
||||
objs = get_selected_objects(context, self.obj)
|
||||
products = [
|
||||
self.file.by_id(obj_id)
|
||||
for obj in objs
|
||||
if (obj_id := tool.Blender.get_object_bim_props(obj).ifc_definition_id)
|
||||
]
|
||||
if products:
|
||||
ifcopenshell.api.run(
|
||||
"constraint.unassign_constraint",
|
||||
ifcopenshell.api.constraint.unassign_constraint(
|
||||
self.file,
|
||||
**{
|
||||
"products": products,
|
||||
"constraint": self.file.by_id(self.constraint),
|
||||
},
|
||||
products=products,
|
||||
constraint=self.file.by_id(self.constraint),
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -32,6 +32,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
def get_available_constraint_types(self, context):
|
||||
@@ -44,6 +45,10 @@ class Constraint(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
ifc_definition_id: int
|
||||
|
||||
|
||||
class BIMConstraintProperties(PropertyGroup):
|
||||
constraint_attributes: CollectionProperty(name="Constraint Attributes", type=Attribute)
|
||||
@@ -52,7 +57,18 @@ class BIMConstraintProperties(PropertyGroup):
|
||||
active_constraint_index: IntProperty(name="Active Constraint Index")
|
||||
is_editing: StringProperty(name="Is Editing")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
constraint_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
active_constraint_id: int
|
||||
constraints: bpy.types.bpy_prop_collection_idprop[Constraint]
|
||||
active_constraint_index: int
|
||||
is_editing: str
|
||||
|
||||
|
||||
class BIMObjectConstraintProperties(PropertyGroup):
|
||||
is_adding: StringProperty(name="Is Adding")
|
||||
available_constraint_types: EnumProperty(items=get_available_constraint_types, name="Available Constraint Types")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_adding: str
|
||||
available_constraint_types: Literal["IfcObjective"]
|
||||
|
||||
@@ -16,10 +16,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.helper import draw_attributes
|
||||
from bonsai.bim.module.constraint.data import ConstraintsData, ObjectConstraintsData
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.constraint.prop import BIMConstraintProperties, BIMObjectConstraintProperties, Constraint
|
||||
|
||||
|
||||
class BIM_PT_constraints(Panel):
|
||||
@@ -39,7 +44,7 @@ class BIM_PT_constraints(Panel):
|
||||
if not ConstraintsData.is_loaded:
|
||||
ConstraintsData.load()
|
||||
|
||||
self.props = context.scene.BIMConstraintProperties
|
||||
self.props = tool.Blender.get_constraint_props()
|
||||
|
||||
if not self.props.is_editing or self.props.is_editing == "IfcObjective":
|
||||
row = self.layout.row(align=True)
|
||||
@@ -91,8 +96,8 @@ class BIM_PT_object_constraints(Panel):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
self.oprops = tool.Blender.get_object_bim_props(obj)
|
||||
self.sprops = context.scene.BIMConstraintProperties
|
||||
self.props = obj.BIMObjectConstraintProperties
|
||||
self.sprops = tool.Blender.get_constraint_props()
|
||||
self.props = tool.Blender.get_object_constraint_props(obj)
|
||||
self.file = tool.Ifc.get()
|
||||
|
||||
self.draw_add_ui()
|
||||
@@ -131,15 +136,24 @@ class BIM_PT_object_constraints(Panel):
|
||||
|
||||
|
||||
class BIM_UL_constraints(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout,
|
||||
data: BIMConstraintProperties,
|
||||
item: Constraint,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
if context.scene.BIMConstraintProperties.active_constraint_id == item.ifc_definition_id:
|
||||
if context.scene.BIMConstraintProperties.is_editing == "IfcObjective":
|
||||
if data.active_constraint_id == item.ifc_definition_id:
|
||||
if data.is_editing == "IfcObjective":
|
||||
row.operator("bim.edit_objective", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_constraint", text="", icon="CANCEL")
|
||||
elif context.scene.BIMConstraintProperties.active_constraint_id:
|
||||
elif data.active_constraint_id:
|
||||
row.operator("bim.remove_constraint", text="", icon="X").constraint = item.ifc_definition_id
|
||||
else:
|
||||
op = row.operator("bim.enable_editing_constraint", text="", icon="GREASEPENCIL")
|
||||
@@ -148,7 +162,16 @@ class BIM_UL_constraints(UIList):
|
||||
|
||||
|
||||
class BIM_UL_object_constraints(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
def draw_item(
|
||||
self,
|
||||
context,
|
||||
layout,
|
||||
data: BIMObjectConstraintProperties,
|
||||
item: Constraint,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item.name)
|
||||
|
||||
@@ -64,6 +64,7 @@ classes = (
|
||||
operator.RefreshCostScheduleCsv,
|
||||
operator.LoadCostItemElementQuantities,
|
||||
operator.LoadCostItemQuantities,
|
||||
operator.ShowAssignedCostRate,
|
||||
operator.LoadCostItemResourceQuantities,
|
||||
operator.LoadCostItemTaskQuantities,
|
||||
operator.LoadCostItemTypes,
|
||||
@@ -83,7 +84,7 @@ classes = (
|
||||
prop.CostItem,
|
||||
prop.CostItemQuantity,
|
||||
prop.CostItemType,
|
||||
prop.CostItemsMapping,
|
||||
prop.CostSchedulesMapping,
|
||||
prop.ScheduleColumn,
|
||||
prop.BIMCostProperties,
|
||||
ui.BIM_PT_cost_schedules,
|
||||
|
||||
@@ -110,6 +110,7 @@ class CostSchedulesData:
|
||||
cls._load_cost_item_quantities(cost_item, data)
|
||||
cls._load_cost_values(cost_item, data)
|
||||
cls._load_nesting_index(cost_item, data)
|
||||
cls._load_assigned_cost_rate(cost_item, data)
|
||||
results[cost_item.id()] = data
|
||||
return results
|
||||
|
||||
@@ -154,7 +155,12 @@ class CostSchedulesData:
|
||||
else:
|
||||
data["TotalCost"] = data["TotalAppliedValue"] * cost_quantity
|
||||
if is_sum:
|
||||
data["TotalAppliedValue"] = None
|
||||
pass # If it is None it doesn't allow me to assign a cost rate composed by sum
|
||||
# data["TotalAppliedValue"] = None
|
||||
|
||||
@classmethod
|
||||
def _load_assigned_cost_rate(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None:
|
||||
data["AssignedCostRate"] = tool.Cost.get_assigned_rate_cost_item(cost_item)
|
||||
|
||||
@classmethod
|
||||
def _load_cost_item_quantities(cls, cost_item: ifcopenshell.entity_instance, data: CostItem) -> None:
|
||||
@@ -248,6 +254,8 @@ class CostSchedulesData:
|
||||
) -> None:
|
||||
value_data = cost_value.get_info()
|
||||
del value_data["AppliedValue"]
|
||||
if tool.Cost.get_assigned_rate_cost_item(root_element):
|
||||
root_element = tool.Cost.get_assigned_rate_cost_item(root_element)
|
||||
if value_data["UnitBasis"]:
|
||||
data = cost_value.UnitBasis.get_info()
|
||||
data["ValueComponent"] = data["ValueComponent"].wrappedValue
|
||||
@@ -311,6 +319,7 @@ class CostItemRatesData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"schedule_of_rates": cls.schedule_of_rates(),
|
||||
"cost_schedules": cls.cost_schedules(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -322,6 +331,10 @@ class CostItemRatesData:
|
||||
if s.PredefinedType == "SCHEDULEOFRATES"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def cost_schedules(cls) -> list[tuple[str, str, str]]:
|
||||
return [(str(s.id()), s.Name or "Unnamed", "") for s in tool.Ifc.get().by_type("IfcCostSchedule")]
|
||||
|
||||
|
||||
class CostItemQuantitiesData:
|
||||
data = {}
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
# pyright: reportUnnecessaryTypeIgnoreComment=error
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import textwrap
|
||||
import ifcopenshell.api.nest
|
||||
import bonsai.tool as tool
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.cost as core
|
||||
from pathlib import Path
|
||||
from typing import get_args, TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
assert layout
|
||||
props = tool.Cost.get_cost_props()
|
||||
layout.prop(self, "name", text="Name")
|
||||
layout.prop(props, "cost_schedule_predefined_types", text="Type")
|
||||
@@ -148,8 +149,11 @@ class ExpandCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Expand this cost item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.expand_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
core.expand_cost_item(tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
|
||||
class ExpandCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -157,7 +161,6 @@ class ExpandCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Expand Cost Items"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Expand all cost items"
|
||||
cost_items: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
core.expand_cost_items(tool.Cost)
|
||||
@@ -170,8 +173,11 @@ class ContractCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Contract a cost item"
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.contract_cost_item(tool.Cost, cost_item=tool.Ifc.get().by_id(self.cost_item))
|
||||
core.contract_cost_item(tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
|
||||
class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -179,7 +185,6 @@ class ContractCostItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
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)
|
||||
@@ -191,6 +196,9 @@ class RemoveCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.remove_cost_item(tool.Ifc, tool.Cost, cost_item_id=self.cost_item)
|
||||
|
||||
@@ -268,7 +276,7 @@ class AssignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Assign Cost Item Quantity"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
|
||||
related_object_type: bpy.props.EnumProperty( # pyright: ignore [reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(tool.Cost.RELATED_OBJECT_TYPE)],
|
||||
)
|
||||
prop_name: bpy.props.StringProperty()
|
||||
@@ -608,6 +616,34 @@ class LoadCostItemQuantities(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ShowAssignedCostRate(bpy.types.Operator):
|
||||
bl_idname = "bim.show_assigned_cost_rate"
|
||||
bl_label = "Info about the assigned cost item rate"
|
||||
bl_options = {"REGISTER"}
|
||||
assigned_rate_identification: bpy.props.StringProperty()
|
||||
assigned_rate_name: bpy.props.StringProperty()
|
||||
assigned_rate_description: bpy.props.StringProperty()
|
||||
assigned_rate_total_value: bpy.props.FloatProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width=450)
|
||||
|
||||
def execute(self, context):
|
||||
# core.load_cost_item_quantities(tool.Cost) IS IT NECESSARY?
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
wrapper = textwrap.TextWrapper(width=80)
|
||||
layout.label(text=f"ID: {self.assigned_rate_identification}")
|
||||
layout.label(text=f"Name: {self.assigned_rate_name}")
|
||||
layout.label(text="Description:")
|
||||
for line in wrapper.wrap(str(self.assigned_rate_description)):
|
||||
layout.label(text=line)
|
||||
layout.label(text=f"Value: {self.assigned_rate_total_value}")
|
||||
|
||||
|
||||
class LoadCostItemElementQuantities(bpy.types.Operator):
|
||||
bl_idname = "bim.load_cost_item_element_quantities"
|
||||
bl_label = "Load Cost Item Element Quantities"
|
||||
@@ -658,7 +694,10 @@ class AssignCostValue(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
core.assign_cost_value(
|
||||
tool.Ifc, cost_item=tool.Ifc.get().by_id(self.cost_item), cost_rate=tool.Ifc.get().by_id(self.cost_rate)
|
||||
tool.Ifc,
|
||||
tool.Cost,
|
||||
cost_item=tool.Ifc.get().by_id(self.cost_item),
|
||||
cost_rate=tool.Ifc.get().by_id(self.cost_rate),
|
||||
)
|
||||
|
||||
|
||||
@@ -668,6 +707,9 @@ class ExpandCostItemRate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.expand_cost_item_rate(tool.Cost, self.cost_item)
|
||||
return {"FINISHED"}
|
||||
@@ -679,6 +721,9 @@ class ContractCostItemRate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item: int
|
||||
|
||||
def _execute(self, context):
|
||||
core.contract_cost_item_rate(tool.Cost, self.cost_item)
|
||||
return {"FINISHED"}
|
||||
@@ -745,7 +790,7 @@ class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Clear Cost Item Product Assignments"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
cost_item: bpy.props.IntProperty()
|
||||
related_object_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
|
||||
related_object_type: bpy.props.EnumProperty( # pyright: ignore [reportRedeclaration]
|
||||
items=[(i, i, "") for i in get_args(tool.Cost.RELATED_OBJECT_TYPE)],
|
||||
)
|
||||
|
||||
@@ -801,13 +846,10 @@ class ReorderCostItem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
cost_item: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
ifcopenshell.api.run(
|
||||
"nest.reorder_nesting",
|
||||
ifcopenshell.api.nest.reorder_nesting(
|
||||
tool.Ifc.get(),
|
||||
**{
|
||||
"item": tool.Ifc.get().by_id(self.cost_item),
|
||||
"new_index": self.new_index,
|
||||
},
|
||||
item=tool.Ifc.get().by_id(self.cost_item),
|
||||
new_index=self.new_index,
|
||||
)
|
||||
tool.Cost.load_cost_schedule_tree()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.cost
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.classification.data import CostClassificationsData
|
||||
from bonsai.bim.module.cost.data import CostSchedulesData, CostItemRatesData, CostItemQuantitiesData
|
||||
@@ -33,44 +34,51 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from typing import TYPE_CHECKING, Literal, Union
|
||||
|
||||
|
||||
def get_schedule_of_rates(self, context):
|
||||
def get_schedule_of_rates(self: "BIMCostProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not CostItemRatesData.is_loaded:
|
||||
CostItemRatesData.load()
|
||||
return CostItemRatesData.data["schedule_of_rates"]
|
||||
# return CostItemRatesData.data["schedule_of_rates"]
|
||||
return CostItemRatesData.data["cost_schedules"]
|
||||
|
||||
|
||||
def update_schedule_of_rates(self, context):
|
||||
def update_schedule_of_rates(self: "BIMCostProperties", context: bpy.types.Context) -> None:
|
||||
tool.Cost.load_schedule_of_rates_tree(schedule_of_rates=tool.Ifc.get().by_id(int(self.schedule_of_rates)))
|
||||
|
||||
|
||||
def get_quantity_types(self, context):
|
||||
def get_quantity_types(self: "BIMCostProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not CostSchedulesData.is_loaded:
|
||||
CostSchedulesData.load()
|
||||
return CostSchedulesData.data["quantity_types"]
|
||||
|
||||
|
||||
def get_product_quantity_names(self, context):
|
||||
def get_product_quantity_names(
|
||||
self: "BIMCostProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not CostItemQuantitiesData.is_loaded:
|
||||
CostItemQuantitiesData.load()
|
||||
return CostItemQuantitiesData.data["product_quantity_names"]
|
||||
|
||||
|
||||
def get_process_quantity_names(self, context):
|
||||
def get_process_quantity_names(
|
||||
self: "BIMCostProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not CostItemQuantitiesData.is_loaded:
|
||||
CostItemQuantitiesData.load()
|
||||
return CostItemQuantitiesData.data["process_quantity_names"]
|
||||
|
||||
|
||||
def get_resource_quantity_names(self, context):
|
||||
def get_resource_quantity_names(
|
||||
self: "BIMCostProperties", context: bpy.types.Context
|
||||
) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
if not CostItemQuantitiesData.is_loaded:
|
||||
CostItemQuantitiesData.load()
|
||||
return CostItemQuantitiesData.data["resource_quantity_names"]
|
||||
|
||||
|
||||
def update_active_cost_item_index(self, context):
|
||||
def update_active_cost_item_index(self: "BIMCostProperties", context: bpy.types.Context) -> None:
|
||||
schedule = tool.Ifc.get().by_id(self.active_cost_schedule_id)
|
||||
if schedule.PredefinedType == "SCHEDULEOFRATES":
|
||||
tool.Cost.load_cost_item_types()
|
||||
@@ -83,11 +91,11 @@ def update_cost_item_identification(self: "CostItem", context: bpy.types.Context
|
||||
props = tool.Cost.get_cost_props()
|
||||
if not props.is_cost_update_enabled or self.identification == "XXX":
|
||||
return
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Identification": self.identification}},
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.cost.edit_cost_item(
|
||||
ifc_file,
|
||||
cost_item=ifc_file.by_id(self.ifc_definition_id),
|
||||
attributes={"Identification": self.identification},
|
||||
)
|
||||
if props.active_cost_item_id == self.ifc_definition_id:
|
||||
attribute = props.cost_item_attributes["Identification"]
|
||||
@@ -98,11 +106,11 @@ def update_cost_item_name(self: "CostItem", context: bpy.types.Context) -> None:
|
||||
props = tool.Cost.get_cost_props()
|
||||
if not props.is_cost_update_enabled or self.name == "Unnamed":
|
||||
return
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.run(
|
||||
"cost.edit_cost_item",
|
||||
self.file,
|
||||
**{"cost_item": self.file.by_id(self.ifc_definition_id), "attributes": {"Name": self.name}},
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.cost.edit_cost_item(
|
||||
ifc_file,
|
||||
cost_item=ifc_file.by_id(self.ifc_definition_id),
|
||||
attributes={"Name": self.name},
|
||||
)
|
||||
if props.active_cost_item_id == self.ifc_definition_id:
|
||||
attribute = props.cost_item_attributes["Name"]
|
||||
@@ -167,12 +175,12 @@ class CostItemQuantity(PropertyGroup):
|
||||
total_cost_quantity: float
|
||||
|
||||
|
||||
class CostItemsMapping(PropertyGroup):
|
||||
cost_item_id: IntProperty(name="cost_item_id")
|
||||
class CostSchedulesMapping(PropertyGroup):
|
||||
cost_schedule_id: IntProperty(name="cost_schedule_id")
|
||||
csv_filepath: StringProperty(name="filepath")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_item_id: int
|
||||
cost_schedule_id: int
|
||||
csv_filepath: str
|
||||
|
||||
|
||||
@@ -281,7 +289,7 @@ class BIMCostProperties(PropertyGroup):
|
||||
custom_currency: StringProperty(
|
||||
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
|
||||
)
|
||||
cost_schedule_files: CollectionProperty(name="Cost Schedule Files", type=CostItemsMapping)
|
||||
cost_schedule_files: CollectionProperty(name="Cost Schedule Files", type=CostSchedulesMapping)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
cost_schedule_predefined_types: str
|
||||
@@ -336,4 +344,8 @@ class BIMCostProperties(PropertyGroup):
|
||||
show_cost_item_operators: bool
|
||||
currency: str
|
||||
custom_currency: str
|
||||
cost_schedule_files: bpy.types.bpy_prop_collection_idprop[CostItemsMapping]
|
||||
cost_schedule_files: bpy.types.bpy_prop_collection_idprop[CostSchedulesMapping]
|
||||
|
||||
@property
|
||||
def active_cost_item(self) -> Union[CostItem, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.cost_items, self.active_cost_item_index)
|
||||
|
||||
@@ -76,12 +76,12 @@ class BIM_PT_cost_schedules(Panel):
|
||||
col = row0.column()
|
||||
col.label(text="Linked CSV:")
|
||||
row_1 = col.row(align=True)
|
||||
if self.props.active_cost_schedule_id in [item.cost_item_id for item in self.props.cost_schedule_files]:
|
||||
if self.props.active_cost_schedule_id in [item.cost_schedule_id for item in self.props.cost_schedule_files]:
|
||||
file = next(
|
||||
(
|
||||
item.csv_filepath
|
||||
for item in self.props.cost_schedule_files
|
||||
if item.cost_item_id == self.props.active_cost_schedule_id
|
||||
if item.cost_schedule_id == self.props.active_cost_schedule_id
|
||||
),
|
||||
None,
|
||||
)
|
||||
@@ -608,8 +608,8 @@ class BIM_PT_cost_item_rates(Panel):
|
||||
return False
|
||||
if not CostSchedulesData.is_loaded:
|
||||
return False
|
||||
if CostSchedulesData.data["is_editing_rates"]:
|
||||
return False
|
||||
# if CostSchedulesData.data["is_editing_rates"]:
|
||||
# return False
|
||||
if total_cost_items > 0 and props.active_cost_item_index < total_cost_items:
|
||||
return True
|
||||
return False
|
||||
@@ -655,6 +655,8 @@ class BIM_UL_cost_items_trait:
|
||||
for column in props.columns:
|
||||
split2.label(text=column.name)
|
||||
split2.label(text="Total Cost")
|
||||
split2.alignment = "LEFT"
|
||||
split2.label(text="Rate")
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if item:
|
||||
@@ -685,6 +687,8 @@ class BIM_UL_cost_items_trait:
|
||||
if self.props.change_cost_item_parent:
|
||||
self.draw_parent_operator(row, item.ifc_definition_id)
|
||||
|
||||
self.draw_assigned_rate_column(split2, cost_item)
|
||||
|
||||
# TODO: reimplement "bim.copy_cost_item_values" somewhere with better UX
|
||||
|
||||
def draw_parent_operator(self, row, cost_item_id):
|
||||
@@ -724,6 +728,21 @@ class BIM_UL_cost_items_trait:
|
||||
else:
|
||||
layout.label(text="-")
|
||||
|
||||
def draw_assigned_rate_column(self, layout, cost_item):
|
||||
row = layout.row()
|
||||
row.alignment = "LEFT"
|
||||
if cost_item["AssignedCostRate"] is not None:
|
||||
op = row.operator("bim.show_assigned_cost_rate", text="", emboss=False, icon="ZOOM_IN")
|
||||
identification = cost_item["AssignedCostRate"].Identification
|
||||
op.assigned_rate_identification = identification if identification is not None else "XXX"
|
||||
op.assigned_rate_name = cost_item["AssignedCostRate"].Name or ""
|
||||
op.assigned_rate_description = cost_item["AssignedCostRate"].Description or ""
|
||||
op.assigned_rate_total_value = CostSchedulesData.data["cost_items"][cost_item["AssignedCostRate"].id()][
|
||||
"TotalAppliedValue"
|
||||
]
|
||||
else:
|
||||
row.label(text="-")
|
||||
|
||||
def draw_value_column(self, layout, cost_item):
|
||||
if cost_item["TotalAppliedValue"]:
|
||||
text = "{0:,.2f}".format(cost_item["TotalAppliedValue"]).replace(",", " ")
|
||||
|
||||
@@ -312,6 +312,7 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
import ifccsv
|
||||
|
||||
props = tool.Blender.get_csv_props()
|
||||
ifc_file: ifcopenshell.file
|
||||
if props.should_load_from_memory:
|
||||
ifc_file = tool.Ifc.get()
|
||||
else:
|
||||
|
||||
@@ -42,7 +42,10 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from pathlib import Path
|
||||
from bonsai import get_debug_info, format_debug_info
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from typing import get_args, Union
|
||||
from typing import get_args, Union, Any, TYPE_CHECKING, Literal, get_args, assert_never
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import Attribute
|
||||
|
||||
|
||||
class CopyDebugInformation(bpy.types.Operator):
|
||||
@@ -70,6 +73,7 @@ class CopyDebugInformation(bpy.types.Operator):
|
||||
print(text_with_backticks)
|
||||
print("-" * 80)
|
||||
|
||||
assert context.window_manager
|
||||
context.window_manager.clipboard = text_with_backticks
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -347,8 +351,9 @@ class SelectHighPolygonMeshes(bpy.types.Operator):
|
||||
threshold: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
assert context.view_layer
|
||||
for obj in context.view_layer.objects:
|
||||
if obj.type == "MESH" and len(obj.data.polygons) > self.threshold:
|
||||
if isinstance(obj.data, bpy.types.Mesh) and len(obj.data.polygons) > self.threshold:
|
||||
obj.select_set(True)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -361,6 +366,7 @@ class SelectHighestPolygonMeshes(bpy.types.Operator):
|
||||
percentile: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
assert context.view_layer
|
||||
objects = [obj for obj in context.view_layer.objects if obj.type == "MESH"]
|
||||
if objects:
|
||||
percentile = len(max(objects, key=lambda o: len(o.data.polygons)).data.polygons) * self.percentile / 100
|
||||
@@ -423,7 +429,7 @@ class InspectFromStepId(bpy.types.Operator):
|
||||
new.int_value = inverse.id()
|
||||
return {"FINISHED"}
|
||||
|
||||
def add_attribute(self, prop, key, value):
|
||||
def add_attribute(self, prop: "bpy.types.bpy_prop_collection_idprop[Attribute]", key: str, value: Any) -> None:
|
||||
if isinstance(value, tuple) and len(value) < 10:
|
||||
for i, item in enumerate(value):
|
||||
self.add_attribute(prop, key + f"[{i}]", item)
|
||||
@@ -459,8 +465,10 @@ class InspectFromObject(bpy.types.Operator):
|
||||
def poll(cls, context):
|
||||
if not context.active_object:
|
||||
cls.poll_message_set("No Active Object")
|
||||
return False
|
||||
elif not cls.get_active_object_ifc_definition(context):
|
||||
cls.poll_message_set("Active Object doesn't have an IFC definition")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -492,6 +500,7 @@ class PrintObjectPlacement(bpy.types.Operator):
|
||||
if self.create_empty_object:
|
||||
bpy.ops.object.empty_add(type="ARROWS")
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
assert context.active_object
|
||||
context.active_object.matrix_world = placement.transpose()
|
||||
context.active_object.matrix_world.translation *= si_conversion
|
||||
context.active_object.empty_display_size = self.arrow_size
|
||||
@@ -730,21 +739,22 @@ class PurgeUnusedElementsByClass(bpy.types.Operator, tool.Ifc.Operator, ExportHe
|
||||
tool.Ifc.get().write(self.filepath)
|
||||
|
||||
|
||||
PurgeObjectType = Literal["TYPE", "PROFILE", "STYLE", "MATERIAL", "ORGANIZATION", "APPLICATION"]
|
||||
|
||||
|
||||
class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.purge_unused_objects"
|
||||
bl_label = "Purge Unused Objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty(
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Object Type",
|
||||
items=(
|
||||
("TYPE", "Type", ""),
|
||||
("PROFILE", "Profile", ""),
|
||||
("STYLE", "Style", ""),
|
||||
("MATERIAL", "Material", ""),
|
||||
),
|
||||
items=((s, s.capitalize(), "") for s in get_args(PurgeObjectType)),
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
object_type: PurgeObjectType
|
||||
|
||||
def _execute(self, context):
|
||||
object_type = self.object_type
|
||||
if object_type == "TYPE":
|
||||
@@ -755,9 +765,10 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
purged = tool.Style.purge_unused_styles()
|
||||
elif object_type == "MATERIAL":
|
||||
purged = tool.Material.purge_unused_materials()
|
||||
elif object_type in ("APPLICATION", "ORGANIZATION"):
|
||||
purged = core.purge_unused_elements(tool.Ifc, tool.Debug, "IfcApplication")
|
||||
else:
|
||||
self.report({"ERROR"}, f"Invalid object type {object_type}.")
|
||||
return {"CANCELLED"}
|
||||
assert_never(object_type)
|
||||
|
||||
self.report({"INFO"}, f"{purged} unused {object_type.lower()}s were purged.")
|
||||
|
||||
@@ -776,6 +787,10 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Material.get_material_props()
|
||||
if props.is_editing:
|
||||
bpy.ops.bim.load_materials()
|
||||
elif object_type == "ORGANIZATION":
|
||||
props = tool.Owner.get_owner_props()
|
||||
if tool.Ifc.get_entity_by_id(props.active_organisation_id) is None:
|
||||
props.active_organisation_id = 0
|
||||
|
||||
|
||||
class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -784,23 +799,21 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "For materials currently only IfcMaterials are supported"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty(
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Object Type",
|
||||
items=(
|
||||
("TYPE", "Type", ""),
|
||||
("PROFILE", "Profile", ""),
|
||||
("STYLE", "Style", ""),
|
||||
("MATERIAL", "Material", ""),
|
||||
),
|
||||
items=((s, s.capitalize(), "") for s in get_args(PurgeObjectType)),
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
object_type: PurgeObjectType
|
||||
|
||||
def _execute(self, context):
|
||||
object_type: str = self.object_type
|
||||
if object_type in ("STYLE", "MATERIAL"):
|
||||
merged_data = tool.Debug.merge_identical_objects(object_type)
|
||||
else:
|
||||
self.report({"ERROR"}, f"Invalid object type {object_type}.")
|
||||
if object_type in ("PROFILE", "TYPE"):
|
||||
self.report({"ERROR"}, f"Unsupported object type {object_type}.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
merged_data = tool.Debug.merge_identical_objects(object_type)
|
||||
plural_object_type = f"{object_type.lower()}s"
|
||||
if merged_data:
|
||||
for element_type, element_names in merged_data.items():
|
||||
@@ -1017,6 +1030,7 @@ class RestartBlender(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
# Save preferences manually since we're restarting Blender using .execv
|
||||
# and it doens't have a chance to save them on exit.
|
||||
assert context.preferences
|
||||
if context.preferences.use_preferences_save:
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class BIM_PT_debug(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_quality_control"
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
layout = self.layout
|
||||
|
||||
props = tool.Debug.get_debug_props()
|
||||
|
||||
@@ -236,23 +236,39 @@ class DecoratorData:
|
||||
fill_cache = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
def load(cls, handler):
|
||||
cls.is_loaded = True
|
||||
cls.cut_cache = {}
|
||||
cls.layerset_cache = {}
|
||||
|
||||
text = {}
|
||||
dimension = {}
|
||||
fall = {}
|
||||
symbol = {}
|
||||
for obj in bpy.context.visible_objects:
|
||||
if not (element := tool.Ifc.get_entity(obj)):
|
||||
continue
|
||||
if tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]):
|
||||
text[obj.name] = cls.get_ifc_text_data(obj)
|
||||
if tool.Drawing.is_annotation_object_type(element, ("TEXT", "TEXT_LEADER")):
|
||||
text[obj.name] = cls.get_text_data(obj)
|
||||
if text[obj.name]["Symbol"]:
|
||||
symbol[obj.name] = cls.get_symbol_data(obj)
|
||||
elif tool.Drawing.is_annotation_object_type(
|
||||
element, ("DIMENSION", "DIAMETER", "SECTION_LEVEL", "PLAN_LEVEL", "RADIUS")
|
||||
):
|
||||
dimension[obj.name] = cls.get_dimension_data(obj)
|
||||
cls.data = {"text": text, "dimension": dimension}
|
||||
elif tool.Drawing.is_annotation_object_type(
|
||||
element, ("FALL", "SLOPE_ANGLE", "SLOPE_FRACTION", "SLOPE_PERCENT")
|
||||
):
|
||||
fall[obj.name] = cls.get_fall_data(obj)
|
||||
elif tool.Drawing.is_annotation_object_type(element, ("SYMBOL",)):
|
||||
symbol[obj.name] = cls.get_symbol_data(obj)
|
||||
cls.data = {
|
||||
"text": text,
|
||||
"dimension": dimension,
|
||||
"fall": fall,
|
||||
"symbol": symbol,
|
||||
"object_decorators": cls.object_decorators(handler),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_batting_thickness(cls, obj):
|
||||
@@ -311,7 +327,7 @@ class DecoratorData:
|
||||
return display_data
|
||||
|
||||
@classmethod
|
||||
def get_ifc_text_data(cls, obj: bpy.types.Object) -> dict:
|
||||
def get_text_data(cls, obj: bpy.types.Object) -> dict:
|
||||
"""used by Ifc Annotations with ObjectType = "TEXT" / "TEXT_LEADER"\n
|
||||
returns font size in mm for current ifc text object"""
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -354,11 +370,6 @@ class DecoratorData:
|
||||
|
||||
return {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
|
||||
|
||||
@classmethod
|
||||
def get_symbol(cls, obj: bpy.types.Object) -> Union[str, None]:
|
||||
"""used by IfcAnnotations with ObjectType MULTI_SYMBOL"""
|
||||
return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
|
||||
|
||||
@classmethod
|
||||
def get_dimension_data(cls, obj):
|
||||
"""used by Ifc Annotations with ObjectType:
|
||||
@@ -394,6 +405,59 @@ class DecoratorData:
|
||||
"custom_unit": custom_unit,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_fall_data(cls, obj):
|
||||
object_type = None
|
||||
if element := tool.Ifc.get_entity(obj):
|
||||
object_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
return {"object_type": object_type}
|
||||
|
||||
@classmethod
|
||||
def get_symbol_data(cls, obj):
|
||||
return tool.Drawing.get_annotation_symbol(tool.Ifc.get_entity(obj))
|
||||
|
||||
@classmethod
|
||||
def object_decorators(cls, handler):
|
||||
import bonsai.bim.module.drawing.decoration
|
||||
|
||||
if not bonsai.bim.module.drawing.decoration.DecorationsHandler.installed:
|
||||
return []
|
||||
|
||||
props = tool.Drawing.get_document_props()
|
||||
if (drawing := props.get_active_drawing()) is None:
|
||||
return []
|
||||
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
assert isinstance(camera, bpy.types.Object)
|
||||
collection = tool.Blender.get_object_bim_props(camera).collection
|
||||
assert collection
|
||||
|
||||
results = []
|
||||
viewport = tool.Blender.get_view3d_space()
|
||||
|
||||
for obj in collection.all_objects:
|
||||
if not obj.visible_get(viewport=viewport):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
object_type: Union[str, None] = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if object_type == "DRAWING":
|
||||
continue
|
||||
if dec := handler.decorators.get(object_type, None):
|
||||
results.append((obj, dec))
|
||||
elif isinstance(obj.data, bpy.types.Mesh):
|
||||
if object_type == "LINEWORK" and "dashed" in str(
|
||||
ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
).split(" "):
|
||||
results.append((obj, handler.decorators["HIDDEN_LINE"]))
|
||||
else:
|
||||
results.append((obj, handler.decorators["MISC"]))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class AnnotationData:
|
||||
data = {}
|
||||
|
||||
@@ -24,6 +24,7 @@ import bmesh
|
||||
import shapely
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.module.drawing.helper as helper
|
||||
@@ -37,8 +38,9 @@ from bonsai.bim.module.drawing.data import DecoratorData, DrawingsData
|
||||
from bonsai.bim.module.drawing.shaders import add_verts_sequence, add_offsets
|
||||
from bonsai.bim.module.drawing.helper import format_distance
|
||||
from timeit import default_timer as timer
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Iterator, Type, Union
|
||||
from functools import cache
|
||||
from typing import Optional, Union
|
||||
from collections.abc import Iterator
|
||||
|
||||
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
|
||||
|
||||
@@ -495,7 +497,7 @@ class BaseDecorator:
|
||||
|
||||
self.draw_label(context, text=text, line_no=line_number_start, multiline=True, **draw_label_kwargs)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
@cache
|
||||
def format_value(self, context, value, custom_unit=None):
|
||||
drawing_pset_data = DrawingsData.data["active_drawing_pset_data"]
|
||||
precision = drawing_pset_data.get("MetricPrecision", None)
|
||||
@@ -562,8 +564,7 @@ class BaseDecorator:
|
||||
MiscDecorator.decorate(self, context, obj)
|
||||
return
|
||||
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)):
|
||||
return
|
||||
|
||||
for vert in mesh.vertices:
|
||||
@@ -572,8 +573,7 @@ class BaseDecorator:
|
||||
return
|
||||
|
||||
# EMPTY objects
|
||||
symbol = DecoratorData.get_symbol(obj)
|
||||
if not symbol:
|
||||
if not (symbol := DecoratorData.data["symbol"].get(obj.name, None)):
|
||||
return
|
||||
|
||||
rotation = -Vector((1, 0)).angle_signed(annotation_dir)
|
||||
@@ -998,8 +998,6 @@ class FallDecorator(BaseDecorator):
|
||||
# generate label text
|
||||
# same function as in svgwriter.py
|
||||
def get_label_text():
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
B, A = [v.co.xyz for v in spline_points[:2]]
|
||||
rise = abs(A.z - B.z)
|
||||
O = A.copy()
|
||||
@@ -1011,8 +1009,8 @@ class FallDecorator(BaseDecorator):
|
||||
else:
|
||||
angle = 90
|
||||
|
||||
# ues SLOPE_ANGLE as default
|
||||
object_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
# uses SLOPE_ANGLE as default
|
||||
object_type = DecoratorData.data["fall"].get(obj, {}).get("object_type", None)
|
||||
if object_type in ("FALL", "SLOPE_ANGLE"):
|
||||
return f"{angle}°"
|
||||
elif object_type == "SLOPE_FRACTION":
|
||||
@@ -1023,6 +1021,7 @@ class FallDecorator(BaseDecorator):
|
||||
if angle == 90:
|
||||
return "-"
|
||||
return f"{round(angle_tg * 100)} %"
|
||||
return "NO DATA"
|
||||
|
||||
if spline_points:
|
||||
text = get_label_text()
|
||||
@@ -1650,9 +1649,15 @@ class CutDecorator:
|
||||
all_vertex_i_offset = 0
|
||||
selected_vertex_i_offset = 0
|
||||
|
||||
classes_no_cut_str = tool.Drawing.get_document_props().classes_no_cut
|
||||
classes_no_cut = [word.strip() for word in classes_no_cut_str.split(",")]
|
||||
|
||||
for obj in [o for o in bpy.context.visible_objects if o.type == "MESH"]:
|
||||
if not (element := tool.Ifc.get_entity(obj)):
|
||||
continue
|
||||
# Skip the elements in the following classes
|
||||
if element.is_a() in classes_no_cut:
|
||||
continue
|
||||
self.decorate(context, obj, element)
|
||||
|
||||
verts, edges = DecoratorData.cut_cache[element.id()]
|
||||
@@ -1892,7 +1897,7 @@ class CutDecorator:
|
||||
|
||||
|
||||
class DecorationsHandler:
|
||||
decorators_classes: list[Type[BaseDecorator]] = [
|
||||
decorators_classes: list[type[BaseDecorator]] = [
|
||||
DimensionDecorator,
|
||||
AngleDecorator,
|
||||
GridDecorator,
|
||||
@@ -1915,17 +1920,18 @@ class DecorationsHandler:
|
||||
]
|
||||
|
||||
installed = None
|
||||
handler = None
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.installed:
|
||||
cls.uninstall()
|
||||
if not DecoratorData.is_loaded:
|
||||
DecoratorData.load()
|
||||
handler = cls()
|
||||
# NOTE: we USE POST_PIXEL here so that we can use both POLYLINE_UNIFORM_COLOR
|
||||
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
|
||||
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
|
||||
if not DecoratorData.is_loaded:
|
||||
DecoratorData.load(handler)
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
@@ -1948,53 +1954,9 @@ class DecorationsHandler:
|
||||
for decorator in self.decorators.values():
|
||||
decorator.font_id = font_id
|
||||
|
||||
def get_objects_and_decorators(self, collection):
|
||||
# TODO: do it in data instead of the handler for performance?
|
||||
results = []
|
||||
viewport = bpy.context.space_data
|
||||
|
||||
for obj in collection.all_objects:
|
||||
if not obj.visible_get(viewport=viewport):
|
||||
continue
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
|
||||
if not element.is_a("IfcAnnotation"):
|
||||
continue
|
||||
|
||||
object_type: Union[str, None] = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if object_type == "DRAWING":
|
||||
continue
|
||||
|
||||
if dec := self.decorators.get(object_type, None):
|
||||
results.append((obj, dec))
|
||||
|
||||
elif isinstance(obj.data, bpy.types.Mesh):
|
||||
if object_type == "LINEWORK" and "dashed" in str(
|
||||
ifcopenshell.util.element.get_pset(element, "EPset_Annotation", "Classes")
|
||||
).split(" "):
|
||||
results.append((obj, self.decorators["HIDDEN_LINE"]))
|
||||
else:
|
||||
results.append((obj, self.decorators["MISC"]))
|
||||
|
||||
return results
|
||||
|
||||
def __call__(self, context):
|
||||
props = tool.Drawing.get_document_props()
|
||||
drawing = props.get_active_drawing()
|
||||
if drawing is None:
|
||||
return
|
||||
|
||||
camera = tool.Ifc.get_object(drawing)
|
||||
assert isinstance(camera, bpy.types.Object)
|
||||
collection = tool.Blender.get_object_bim_props(camera).collection
|
||||
assert collection
|
||||
|
||||
if not DrawingsData.is_loaded:
|
||||
DrawingsData.load()
|
||||
|
||||
object_decorators = self.get_objects_and_decorators(collection)
|
||||
for obj, decorator in object_decorators:
|
||||
for obj, decorator in DecoratorData.data["object_decorators"]:
|
||||
decorator.decorate(context, obj)
|
||||
|
||||
@@ -24,18 +24,21 @@ import bmesh
|
||||
import shutil
|
||||
import hashlib
|
||||
import shapely
|
||||
import shapely.ops
|
||||
import subprocess
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.document
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.ifcopenshell_wrapper
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.selector
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.bim.helper
|
||||
import bonsai.bim.handler
|
||||
import bonsai.tool as tool
|
||||
@@ -48,7 +51,7 @@ import bonsai.bim.export_ifc
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bonsai.bim.module.drawing.decoration import CutDecorator
|
||||
from bonsai.bim.module.drawing.data import DecoratorData
|
||||
from typing import NamedTuple, List, Union, Optional, Literal, TYPE_CHECKING, Any, TypedDict
|
||||
from typing import NamedTuple, Union, Optional, Literal, TYPE_CHECKING, Any, TypedDict
|
||||
from lxml import etree
|
||||
from math import radians
|
||||
from mathutils import Vector, Color, Matrix
|
||||
@@ -61,7 +64,7 @@ from bpy_extras.image_utils import load_image
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.drawing.prop import RenderType
|
||||
from bonsai.bim.module.project.prop import Link
|
||||
from bpy._typing import rna_enums
|
||||
from bpy.stub_internal import rna_enums
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -82,8 +85,8 @@ class profile:
|
||||
|
||||
|
||||
class LineworkContexts(NamedTuple):
|
||||
body: List[List[int]]
|
||||
annotation: List[List[int]]
|
||||
body: list[list[int]]
|
||||
annotation: list[list[int]]
|
||||
|
||||
|
||||
class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -117,22 +120,24 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.add_reference_image("INVOKE_DEFAULT", use_existing_object_by_name=obj.name)
|
||||
|
||||
|
||||
class EnableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableAddAnnotationType(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_add_annotation_type"
|
||||
bl_label = "Enable Add Annotation Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
|
||||
tool.Drawing.get_annotation_props().is_adding_type = True
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableAddAnnotationType(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_add_annotation_type"
|
||||
bl_label = "Disable Add Annotation Type"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context) -> "set[rna_enums.OperatorReturnItems]":
|
||||
tool.Drawing.get_annotation_props().is_adding_type = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddDrawing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -183,9 +188,11 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
assert context.window_manager
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
row = self.layout
|
||||
row.prop(self, "should_duplicate_annotations")
|
||||
|
||||
@@ -225,14 +232,27 @@ class CreateDrawing(bpy.types.Operator):
|
||||
+ "Add the CTRL modifier to optionally open drawings to view them as\n"
|
||||
+ "they are created"
|
||||
)
|
||||
print_all: bpy.props.BoolProperty(name="Print All", default=False, options={"SKIP_SAVE"})
|
||||
open_viewer: bpy.props.BoolProperty(name="Open in Viewer", default=False, options={"SKIP_SAVE"})
|
||||
sync: bpy.props.BoolProperty(
|
||||
print_all: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Print All",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
open_viewer: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Open in Viewer",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
sync: bpy.props.BoolProperty( # pyright: ignore[reportRedeclaration]
|
||||
name="Sync Before Creating Drawing",
|
||||
description="Could save some time if you're sure IFC and current Blender session are already in sync",
|
||||
default=True,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
print_all: bool
|
||||
open_viewer: bool
|
||||
sync: bool
|
||||
|
||||
drawing_name: str
|
||||
is_manifold_cache: dict[str, bool]
|
||||
|
||||
@@ -268,6 +288,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
assert context.scene and context.scene.camera
|
||||
|
||||
active_drawing_id = tool.Blender.get_ifc_definition_id(context.scene.camera)
|
||||
original_drawing_id = None
|
||||
if self.print_all:
|
||||
original_drawing_id = active_drawing_id
|
||||
drawings_to_print = [d.ifc_definition_id for d in self.props.drawings if d.is_selected and d.is_drawing]
|
||||
@@ -297,16 +318,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Reassign props as data is recreated during the update.
|
||||
self.cprops = tool.Drawing.get_camera_props(self.camera)
|
||||
|
||||
self.svg_writer = svgwriter.SvgWriter()
|
||||
self.svg_writer.human_scale = self.human_scale
|
||||
self.svg_writer.scale = self.scale
|
||||
self.svg_writer.camera = self.camera
|
||||
self.svg_writer.camera_width, self.svg_writer.camera_height = self.get_camera_dimensions()
|
||||
self.svg_writer.camera_projection = tuple(
|
||||
self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
|
||||
camera_dims = self.get_camera_dimensions()
|
||||
self.svg_writer = svgwriter.SvgWriter(
|
||||
camera_width=camera_dims[0],
|
||||
camera_height=camera_dims[1],
|
||||
camera=self.camera,
|
||||
camera_projection=(self.camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))),
|
||||
scale=self.scale,
|
||||
human_scale=self.human_scale,
|
||||
)
|
||||
self.svg_writer.calculate_scale()
|
||||
|
||||
self.svg_writer.setup_drawing_resource_paths(self.camera_element)
|
||||
|
||||
underlay_svg = None
|
||||
@@ -338,11 +358,14 @@ class CreateDrawing(bpy.types.Operator):
|
||||
if not self.open_viewer:
|
||||
self.report({"INFO"}, f"{len(drawings_to_print)} drawings created...")
|
||||
if self.print_all:
|
||||
assert original_drawing_id is not None
|
||||
bpy.ops.bim.activate_drawing(drawing=original_drawing_id, should_view_from_camera=False)
|
||||
return {"FINISHED"}
|
||||
|
||||
def get_camera_dimensions(self):
|
||||
def get_camera_dimensions(self) -> tuple[float, float]:
|
||||
assert bpy.context.scene
|
||||
render = bpy.context.scene.render
|
||||
assert isinstance(self.camera.data, bpy.types.Camera)
|
||||
if self.is_landscape(render):
|
||||
width = self.camera.data.ortho_scale
|
||||
height = width / render.resolution_x * render.resolution_y
|
||||
@@ -442,7 +465,10 @@ class CreateDrawing(bpy.types.Operator):
|
||||
previous_format = context.scene.render.image_settings.file_format
|
||||
space.shading.type = "RENDERED"
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
|
||||
with tool.Blender.bonsai_crash_txt("render.opengl"):
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
|
||||
space.shading.type = previous_shading
|
||||
context.scene.render.image_settings.file_format = previous_format
|
||||
|
||||
@@ -1036,7 +1062,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# file 2 only has the groups we are interested in.
|
||||
# in fact in the approach, it's only a single group
|
||||
|
||||
g2 = list(yield_groups(svg2))[0]
|
||||
g2 = next(yield_groups(svg2))
|
||||
|
||||
# Loop over the cell paths
|
||||
for pi, p in enumerate(g2.getElementsByTagName("path")):
|
||||
@@ -1818,6 +1844,7 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Drawing.get_document_props()
|
||||
active_drawing = props.drawings[props.active_drawing_index]
|
||||
assert active_drawing
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
drawing = tool.Ifc.get().by_id(active_drawing.ifc_definition_id)
|
||||
@@ -1841,7 +1868,7 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "The drawing must be generated before adding to a sheet.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
reference = ifcopenshell.api.document.add_reference(ifc_file, information=sheet)
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
@@ -1857,7 +1884,7 @@ class AddDrawingToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
Location=drawing_reference.Location,
|
||||
Description="DRAWING",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
ifcopenshell.api.document.edit_reference(ifc_file, reference=reference, attributes=attributes)
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.add_drawing(reference, drawing, sheet)
|
||||
|
||||
@@ -1928,6 +1955,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Drawing.get_document_props()
|
||||
svg2pdf_command = tool.Blender.get_addon_preferences().svg2pdf_command
|
||||
svg2dxf_command = tool.Blender.get_addon_preferences().svg2dxf_command
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
if self.create_all:
|
||||
sheets = [tool.Ifc.get().by_id(s.ifc_definition_id) for s in props.sheets if s.is_sheet and s.is_selected]
|
||||
@@ -1966,9 +1994,9 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
has_sheet_reference = True
|
||||
|
||||
if not has_sheet_reference:
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
tool.Ifc.run(
|
||||
"document.edit_reference",
|
||||
reference = ifcopenshell.api.document.add_reference(ifc_file, information=sheet)
|
||||
ifcopenshell.api.document.edit_reference(
|
||||
ifc_file,
|
||||
reference=reference,
|
||||
attributes=tool.Drawing.generate_reference_attributes(
|
||||
reference, Location=tool.Ifc.get_uri(svg, use_relative_path=True), Description="SHEET"
|
||||
@@ -2150,6 +2178,7 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
|
||||
assert context.scene
|
||||
props = tool.Drawing.get_document_props()
|
||||
if props.is_editing_drawings == False:
|
||||
bpy.ops.bim.load_drawings()
|
||||
@@ -2161,12 +2190,14 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
tool.Blender.activate_camera(tool.Drawing.import_temporary_drawing_camera(drawing))
|
||||
return {"FINISHED"}
|
||||
|
||||
viewport_position = None
|
||||
if not self.should_view_from_camera:
|
||||
viewport_position = tool.Blender.get_viewport_position()
|
||||
|
||||
core.activate_drawing_view(tool.Ifc, tool.Blender, tool.Drawing, drawing=drawing)
|
||||
|
||||
if not self.should_view_from_camera:
|
||||
assert viewport_position
|
||||
tool.Blender.set_viewport_position(viewport_position)
|
||||
|
||||
dprops.active_drawing_id = self.drawing
|
||||
@@ -2182,6 +2213,7 @@ class ActivateDrawingBase(tool.Ifc.Operator):
|
||||
|
||||
# Save drawing bounds to the .ifc file
|
||||
camera = context.scene.camera
|
||||
assert camera
|
||||
camera_props = tool.Drawing.get_camera_props(camera)
|
||||
if camera_props.update_representation(camera.matrix_world):
|
||||
bpy.ops.bim.update_representation(obj=camera.name, ifc_representation_class="")
|
||||
@@ -2732,6 +2764,7 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Drawing.get_document_props()
|
||||
active_schedule = props.schedules[props.active_schedule_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
ifc_file = tool.Ifc.get()
|
||||
schedule = tool.Ifc.get().by_id(active_schedule.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
schedule_location = tool.Drawing.get_path_with_ext(schedule.DocumentReferences[0].Location, "svg")
|
||||
@@ -2756,7 +2789,7 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, "The schedule must be generated before adding to a sheet.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
reference = ifcopenshell.api.document.add_reference(ifc_file, information=sheet)
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
@@ -2772,7 +2805,7 @@ class AddScheduleToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
Location=schedule_location,
|
||||
Description="SCHEDULE",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
ifcopenshell.api.document.edit_reference(ifc_file, reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.add_document(reference, schedule, sheet)
|
||||
@@ -2799,6 +2832,7 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props = tool.Drawing.get_document_props()
|
||||
active_reference = props.references[props.active_reference_index]
|
||||
active_sheet = tool.Drawing.get_active_sheet(context)
|
||||
ifc_file = tool.Ifc.get()
|
||||
extref = tool.Ifc.get().by_id(active_reference.ifc_definition_id)
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
extref_location = tool.Drawing.get_path_with_ext(extref.DocumentReferences[0].Location, "svg")
|
||||
@@ -2823,7 +2857,7 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
self.report({"ERROR"}, f"Cannot find reference svg by path {extref_location}.")
|
||||
return
|
||||
|
||||
reference = tool.Ifc.run("document.add_reference", information=sheet)
|
||||
reference = ifcopenshell.api.document.add_reference(ifc_file, information=sheet)
|
||||
attributes = tool.Drawing.generate_reference_attributes(
|
||||
reference,
|
||||
Identification=str(
|
||||
@@ -2839,7 +2873,7 @@ class AddReferenceToSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
Location=extref_location,
|
||||
Description="REFERENCE",
|
||||
)
|
||||
tool.Ifc.run("document.edit_reference", reference=reference, attributes=attributes)
|
||||
ifcopenshell.api.document.edit_reference(ifc_file, reference=reference, attributes=attributes)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.add_document(reference, extref, sheet)
|
||||
@@ -2912,6 +2946,7 @@ class EditTextPopup(bpy.types.Operator):
|
||||
# need to keep them in sync or move to some common function
|
||||
# NOTE: that `popup_active_attribute` is used here when it's not used in `BIM_PT_text.draw()`
|
||||
|
||||
assert self.layout
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
@@ -3186,6 +3221,7 @@ class LoadSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
sheet = tool.Ifc.get().by_id(sheet_prop.ifc_definition_id)
|
||||
document_uri = tool.Drawing.get_document_uri(sheet)
|
||||
assert document_uri is not None
|
||||
|
||||
filepath = Path(document_uri)
|
||||
if not filepath.is_file():
|
||||
@@ -3223,6 +3259,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
props = tool.Drawing.get_document_props()
|
||||
if self.document_type == "SHEET":
|
||||
row = self.layout.row()
|
||||
@@ -3238,6 +3275,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = tool.Drawing.get_document_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
sheet = tool.Ifc.get().by_id(self.props.sheets[self.props.active_sheet_index].ifc_definition_id)
|
||||
if self.document_type == "SHEET":
|
||||
core.rename_sheet(tool.Ifc, tool.Drawing, sheet=sheet, identification=self.identification, name=self.name)
|
||||
@@ -3247,8 +3285,9 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
titleblock = self.props.titleblock
|
||||
reference = sheet
|
||||
sheet = tool.Drawing.get_reference_document(reference)
|
||||
tool.Ifc.run(
|
||||
"document.edit_reference",
|
||||
assert sheet
|
||||
ifcopenshell.api.document.edit_reference(
|
||||
ifc_file,
|
||||
reference=reference,
|
||||
attributes={"Location": tool.Drawing.get_default_titleblock_path(titleblock)},
|
||||
)
|
||||
@@ -3433,12 +3472,16 @@ class SelectAssignedProduct(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
class EnableEditingElementFilter(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_element_filter"
|
||||
bl_label = "Enable Editing Element Filter"
|
||||
bl_description = "Enable editing options for the include or exclude filter"
|
||||
|
||||
bl_label = "Element Filter Mode"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_mode: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
if properties.filter_mode == "NONE":
|
||||
return "Cancel filter"
|
||||
return "Enable editing options for the include or exclude filter"
|
||||
|
||||
def _execute(self, context):
|
||||
assert context.scene
|
||||
obj = context.scene.camera
|
||||
@@ -3460,6 +3503,7 @@ class EnableEditingElementFilter(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EditElementFilter(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_element_filter"
|
||||
bl_label = "Edit Element Filter"
|
||||
bl_description = "Saves the filter"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filter_mode: bpy.props.StringProperty()
|
||||
|
||||
@@ -3584,8 +3628,8 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
texture = ifc_file.create_entity("IfcImageTexture", Mode="DIFFUSE", URLReference=image_filepath.as_posix())
|
||||
textures = [texture]
|
||||
ifc_file.create_entity("IfcTextureCoordinateGenerator", Maps=textures, Mode="COORD") # UV map
|
||||
tool.Ifc.run(
|
||||
"style.add_surface_style",
|
||||
ifcopenshell.api.style.add_surface_style(
|
||||
ifc_file,
|
||||
style=style,
|
||||
ifc_class="IfcSurfaceStyleWithTextures",
|
||||
attributes={"Textures": textures},
|
||||
|
||||
@@ -46,7 +46,8 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
BoolVectorProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Literal, Any, Callable, get_args, Union
|
||||
from typing import TYPE_CHECKING, Literal, Any, get_args, Union
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
diagram_scales_enum = []
|
||||
@@ -118,8 +119,8 @@ def update_is_nts(self: "BIMCameraProperties", context: bpy.types.Context) -> No
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
|
||||
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"IsNTS": self.is_nts})
|
||||
|
||||
|
||||
def get_diagram_scales(self: "BIMCameraProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
@@ -211,6 +212,8 @@ def update_document_name(self: "Document", context: bpy.types.Context) -> None:
|
||||
|
||||
|
||||
def update_has_underlay(self: "BIMCameraProperties", context: bpy.types.Context) -> None:
|
||||
if not self.update_props:
|
||||
return
|
||||
update_layer(self, context, "HasUnderlay", self.has_underlay)
|
||||
assert context.scene
|
||||
# making sure that camera is active
|
||||
@@ -247,8 +250,8 @@ def update_layer(self: "BIMCameraProperties", context: bpy.types.Context, name:
|
||||
if pset:
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
else:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={name: value})
|
||||
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=element, name="EPset_Drawing")
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={name: value})
|
||||
|
||||
|
||||
def get_titleblocks(self, context):
|
||||
@@ -349,7 +352,7 @@ class Sheet(PropertyGroup):
|
||||
RenderType = Literal["DEFAULT", "VIEWPORT"]
|
||||
RENDER_TYPE_ENUM_ITEMS: dict[RenderType, tuple[str, str]] = {
|
||||
"DEFAULT": ("Default", "Use default Blender render."),
|
||||
"VIEWPORT": ("Viewport", "Render active viewport."),
|
||||
"VIEWPORT": ("Viewport", "Render active viewport (using 'Rendered' viewport shading type)."),
|
||||
}
|
||||
|
||||
|
||||
@@ -449,6 +452,11 @@ class DocProperties(PropertyGroup):
|
||||
name="Classes to Wireframe",
|
||||
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
classes_no_cut: StringProperty(
|
||||
default="IfcVirtualElement, IfcSpace",
|
||||
name="Classes that are not cut",
|
||||
description="The cut decoractor will be turned off for these classes\nEx: IfcVirtualelement, IfcSpace",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
should_use_underlay_cache: bool
|
||||
@@ -490,6 +498,7 @@ class DocProperties(PropertyGroup):
|
||||
imperial_precision: str
|
||||
tolerance: float
|
||||
classes_to_wireframe: str
|
||||
classes_no_cut: str
|
||||
|
||||
def get_active_drawing(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
drawing_id = self.active_drawing_id
|
||||
@@ -522,7 +531,7 @@ class BIMCameraProperties(PropertyGroup):
|
||||
],
|
||||
default="OPENCASCADE",
|
||||
name="Linework Mode",
|
||||
update=get_update_layer_callback("LineworkMode", "linework_mode"),
|
||||
update=get_update_layer_callback("linework_mode", "LineworkMode"),
|
||||
)
|
||||
fill_mode: EnumProperty(
|
||||
items=[
|
||||
@@ -532,7 +541,7 @@ class BIMCameraProperties(PropertyGroup):
|
||||
],
|
||||
default="NONE",
|
||||
name="Fill Mode",
|
||||
update=get_update_layer_callback("FillMode", "fill_mode"),
|
||||
update=get_update_layer_callback("fill_mode", "FillMode"),
|
||||
)
|
||||
cut_mode: EnumProperty(
|
||||
items=[
|
||||
@@ -541,7 +550,7 @@ class BIMCameraProperties(PropertyGroup):
|
||||
],
|
||||
default="BISECT",
|
||||
name="Cut Mode",
|
||||
update=get_update_layer_callback("CutMode", "cut_mode"),
|
||||
update=get_update_layer_callback("cut_mode", "CutMode"),
|
||||
)
|
||||
|
||||
# EPset_Drawing.
|
||||
@@ -574,7 +583,7 @@ class BIMCameraProperties(PropertyGroup):
|
||||
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
|
||||
raster_x: IntProperty(name="Raster X", default=1000)
|
||||
raster_y: IntProperty(name="Raster Y", default=1000)
|
||||
dpi: IntProperty(name="DPI", default=75, update=get_update_layer_callback("DPI", "dpi"))
|
||||
dpi: IntProperty(name="DPI", default=75, update=get_update_layer_callback("dpi", "DPI"))
|
||||
width: FloatProperty(name="Width", default=50, subtype="DISTANCE", update=update_width_height)
|
||||
height: FloatProperty(name="Height", default=50, subtype="DISTANCE", update=update_width_height)
|
||||
# Bonsai property is needed to prevent user from using unsupported panoramic camera.
|
||||
@@ -783,7 +792,7 @@ class BIMTextProperties(PropertyGroup):
|
||||
|
||||
def get_text_edited_data(self) -> dict[str, Any]:
|
||||
"""should be called only if `is_editing`
|
||||
otherwise should use `DecoratorData.get_ifc_text_data(obj)` instead
|
||||
otherwise should use `DecoratorData.get_text_data(obj)` instead
|
||||
because this data could be out of date
|
||||
"""
|
||||
literals_data = []
|
||||
|
||||
@@ -22,6 +22,8 @@ import bpy
|
||||
import string
|
||||
import svgwrite
|
||||
import openpyxl
|
||||
import openpyxl.cell # Unnecessary, bug in typeshed.
|
||||
import openpyxl.utils # Unnecessary, bug in typeshed.
|
||||
import bonsai.tool as tool
|
||||
|
||||
from bonsai.bim.module.drawing.svgwriter import SvgWriter
|
||||
|
||||
@@ -521,6 +521,7 @@ class SheetBuilder:
|
||||
)
|
||||
titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name))
|
||||
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
|
||||
assert sheet_path is not None
|
||||
sheet_dir = os.path.dirname(sheet_path)
|
||||
|
||||
os.makedirs(sheet_dir, exist_ok=True)
|
||||
@@ -531,9 +532,9 @@ class SheetBuilder:
|
||||
ET.register_namespace("", "http://www.w3.org/2000/svg")
|
||||
ET.register_namespace("xlink", "http://www.w3.org/1999/xlink")
|
||||
|
||||
view_root = ET.parse(ootb_titleblock_path).getroot()
|
||||
view_width = self.convert_to_mm(view_root.attrib.get("width"))
|
||||
view_height = self.convert_to_mm(view_root.attrib.get("height"))
|
||||
view_root = ET.parse(titleblock_path).getroot()
|
||||
view_width = self.convert_to_mm(view_root.attrib["width"])
|
||||
view_height = self.convert_to_mm(view_root.attrib["height"])
|
||||
|
||||
sheet_tree = ET.parse(sheet_path)
|
||||
root = sheet_tree.getroot()
|
||||
|
||||
@@ -26,6 +26,7 @@ from bpy.types import SplineBezierPoints, SplinePoints
|
||||
import mathutils
|
||||
import xml.etree.ElementTree as ET
|
||||
import svgwrite
|
||||
import svgwrite.container
|
||||
import svgwrite.text
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
@@ -38,7 +39,8 @@ from bonsai.bim.module.drawing.data import DrawingsData
|
||||
from bonsai.bim.module.drawing.data import DecoratorData
|
||||
from math import pi, ceil, atan, degrees, acos
|
||||
from mathutils import geometry, Vector
|
||||
from typing import Optional, Self, Union, Sequence, Callable
|
||||
from typing import Optional, Self, Union
|
||||
from collections.abc import Callable, Sequence
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -53,7 +55,7 @@ class External(svgwrite.container.Group):
|
||||
if elem.tag.startswith(ns):
|
||||
elem.tag = elem.tag[nsl:]
|
||||
|
||||
super(External, self).__init__(**extra)
|
||||
super().__init__(**extra)
|
||||
|
||||
def get_xml(self):
|
||||
return self.xml
|
||||
@@ -61,15 +63,28 @@ class External(svgwrite.container.Group):
|
||||
|
||||
class SvgWriter:
|
||||
metadata: list[str]
|
||||
resource_paths: dict[tool.Drawing.ResourceType, Union[str, None]]
|
||||
|
||||
def __init__(self):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
camera_width: float,
|
||||
camera_height: float,
|
||||
camera: bpy.types.Object,
|
||||
camera_projection: Vector,
|
||||
scale: float = 1 / 100, # 1:100
|
||||
human_scale: str = "NTS",
|
||||
):
|
||||
self.data_dir = None
|
||||
self.human_scale = "NTS"
|
||||
self.human_scale = human_scale
|
||||
self.metadata = []
|
||||
self.scale = 1 / 100 # 1:100
|
||||
self.camera_width = None
|
||||
self.camera_height = None
|
||||
self.scale = scale
|
||||
self.camera = camera
|
||||
self.camera_width = camera_width
|
||||
self.camera_height = camera_height
|
||||
self.camera_projection = camera_projection
|
||||
self.resource_paths = {}
|
||||
self.calculate_scale()
|
||||
|
||||
def create_blank_svg(self, output_path: str) -> Self:
|
||||
self.calculate_scale()
|
||||
@@ -93,7 +108,7 @@ class SvgWriter:
|
||||
|
||||
def setup_drawing_resource_paths(self, element: ifcopenshell.entity_instance) -> None:
|
||||
pset = ifcopenshell.util.element.get_pset(element, "EPset_Drawing")
|
||||
for resource in ("Stylesheet", "Markers", "Symbols", "Patterns", "ShadingStyles"):
|
||||
for resource in tool.Drawing.RESOURCE_TYPES:
|
||||
resource_path = pset.get(resource)
|
||||
if not resource_path:
|
||||
self.resource_paths[resource] = None
|
||||
@@ -1085,7 +1100,7 @@ class SvgWriter:
|
||||
# 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)
|
||||
# normal = self.camera_projection
|
||||
# dir1 = (arc_end_pts[0] - center).normalized()
|
||||
# arc_mid_dir = (arc_end_pts[1] - center).normalized()
|
||||
|
||||
@@ -1462,9 +1477,9 @@ class SvgWriter:
|
||||
# TODO is this needlessly complex?
|
||||
return self.camera.matrix_world.inverted() @ geometry.intersect_line_plane(
|
||||
point.xyz,
|
||||
point.xyz - Vector(self.camera_projection),
|
||||
point.xyz - self.camera_projection,
|
||||
self.camera.location,
|
||||
Vector(self.camera_projection),
|
||||
self.camera_projection,
|
||||
)
|
||||
|
||||
def get_spline_points(self, spline: bpy.types.Spline) -> Union[SplineBezierPoints, SplinePoints]:
|
||||
|
||||
@@ -592,7 +592,7 @@ class BIM_PT_text(Panel):
|
||||
col.label(text=f' {literal_props.attributes["BoxAlignment"].string_value}')
|
||||
|
||||
else:
|
||||
text_data = DecoratorData.get_ifc_text_data(obj)
|
||||
text_data = DecoratorData.get_text_data(obj)
|
||||
|
||||
row = self.layout.row()
|
||||
row.operator("bim.enable_editing_text", icon="GREASEPENCIL")
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import os
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.type
|
||||
import bonsai.core.drawing as core
|
||||
import bonsai.tool as tool
|
||||
@@ -218,7 +219,7 @@ class AnnotationToolUI:
|
||||
|
||||
@classmethod
|
||||
def draw_edit_object_interface(cls, context):
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
|
||||
if DecoratorData.get_text_data(bpy.context.active_object):
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
|
||||
|
||||
@classmethod
|
||||
@@ -311,7 +312,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not bpy.context.active_object:
|
||||
return
|
||||
|
||||
if DecoratorData.get_ifc_text_data(bpy.context.active_object):
|
||||
if DecoratorData.get_text_data(bpy.context.active_object):
|
||||
bpy.ops.bim.edit_text_popup()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
|
||||
@@ -27,7 +27,7 @@ from bpy.types import SpaceView3D
|
||||
from mathutils import Vector, Matrix
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
class ItemDecorator:
|
||||
|
||||
@@ -22,7 +22,11 @@ import bmesh
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.group
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.layer
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.util.element
|
||||
@@ -50,7 +54,7 @@ from typing import Any, Union, Literal, get_args, TYPE_CHECKING, assert_never, N
|
||||
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bpy._typing import rna_enums
|
||||
from bpy.stub_internal import rna_enums
|
||||
|
||||
|
||||
class EditObjectPlacement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -470,13 +474,21 @@ class RemoveRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
representation_id: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
representation_id: int
|
||||
|
||||
def _execute(self, context):
|
||||
start_time = time()
|
||||
assert context.active_object
|
||||
core.remove_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=context.active_object,
|
||||
representation=tool.Ifc.get().by_id(self.representation_id),
|
||||
)
|
||||
operator_time = time() - start_time
|
||||
if operator_time > 10:
|
||||
self.report({"INFO"}, f"{self.bl_label} was finished in {operator_time:.2f} seconds.")
|
||||
|
||||
|
||||
class PurgeUnusedRepresentations(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -583,10 +595,11 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# We are explicitly casting to a tessellation, so remove all parametric materials.
|
||||
element_type = ifcopenshell.util.element.get_type(product)
|
||||
if element_type: # Some invalid IFCs use material sets without a type.
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type])
|
||||
ifcopenshell.api.material.unassign_material(self.file, products=[element_type])
|
||||
tool.Material.ensure_material_unassigned([element_type])
|
||||
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product])
|
||||
ifcopenshell.api.material.unassign_material(self.file, products=[product])
|
||||
tool.Material.ensure_material_unassigned([product])
|
||||
data = obj.data # Required after ensure_material_unassigned
|
||||
else:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
if extrusion := tool.Model.get_extrusion(old_representation):
|
||||
@@ -603,6 +616,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# added this as a fallback for easier transition some annotation types to 3d
|
||||
# if they were create before as 2d
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
if tool.Drawing.is_annotation_object_type(element, ("FALL", "SECTION_LEVEL", "PLAN_LEVEL")):
|
||||
context_of_items = tool.Drawing.get_annotation_context("MODEL_VIEW")
|
||||
|
||||
@@ -626,7 +640,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
representation_data["text_literal"] = tool.Geometry.get_text_literal(old_representation)
|
||||
|
||||
# TODO: replace with core.add_representation?
|
||||
new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data)
|
||||
new_representation = ifcopenshell.api.geometry.add_representation(self.file, **representation_data)
|
||||
if new_representation is None:
|
||||
self.report({"ERROR"}, "Error creating representation for Blender object.")
|
||||
return {"CANCELLED"}
|
||||
@@ -720,7 +734,7 @@ class GetRepresentationIfcParameters(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj and tool.Geometry.has_mesh_properties((data := obj.data))
|
||||
assert obj and tool.Geometry.has_mesh_properties(data := obj.data)
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
parameters = tool.Geometry.get_mesh_props(data).ifc_parameters
|
||||
self.report({"INFO"}, f"{len(parameters)} parameters found.")
|
||||
@@ -750,10 +764,10 @@ class CopyRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bm.to_mesh(obj.data)
|
||||
old_rep = tool.Geometry.get_representation_by_context(element, geometric_context)
|
||||
if old_rep:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep
|
||||
ifcopenshell.api.geometry.unassign_representation(
|
||||
tool.Ifc.get(), product=element, representation=old_rep
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", tool.Ifc.get(), representation=old_rep)
|
||||
ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), representation=old_rep)
|
||||
core.add_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
@@ -996,14 +1010,16 @@ class OverrideOutlinerDelete(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
selected_ids_data = self.get_selected_ids_data(context)
|
||||
try:
|
||||
with context.temp_override(selected_objects=list(selected_ids_data.objects)):
|
||||
bpy.ops.bim.override_object_delete(is_batch=self.is_batch)
|
||||
except RuntimeError as e:
|
||||
error_reports = tool.Blender.extract_error_reports(e)
|
||||
if not error_reports:
|
||||
raise
|
||||
tool.Blender.report_operator_errors(self, error_reports)
|
||||
|
||||
if selected_ids_data.objects:
|
||||
try:
|
||||
with context.temp_override(selected_objects=list(selected_ids_data.objects)):
|
||||
bpy.ops.bim.override_object_delete(is_batch=self.is_batch)
|
||||
except RuntimeError as e:
|
||||
error_reports = tool.Blender.extract_error_reports(e)
|
||||
if not error_reports:
|
||||
raise
|
||||
tool.Blender.report_operator_errors(self, error_reports)
|
||||
|
||||
for collection in selected_ids_data.collections:
|
||||
# Removing an aggregate object would also remove it's collection
|
||||
@@ -1177,10 +1193,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
pset = ifcopenshell.util.element.get_pset(part, self.pset_name)
|
||||
|
||||
if not pset:
|
||||
pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=part, name=self.pset_name)
|
||||
pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=part, name=self.pset_name)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
tool.Ifc.get(),
|
||||
pset=pset,
|
||||
properties={"Index": index},
|
||||
@@ -1202,18 +1217,18 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
if self.group_name in product_groups_name:
|
||||
return
|
||||
|
||||
linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), name=self.group_name)
|
||||
ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
linked_aggregate_group = ifcopenshell.api.group.add_group(tool.Ifc.get(), name=self.group_name)
|
||||
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=[element], group=linked_aggregate_group)
|
||||
|
||||
def custom_incremental_naming_for_element_assembly(old_to_new):
|
||||
for new in old_to_new.values():
|
||||
if new[0].is_a("IfcElementAssembly"):
|
||||
group_elements = [
|
||||
group_elements: list[ifcopenshell.entity_instance] = next(
|
||||
r.RelatedObjects
|
||||
for r in getattr(new[0], "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
][0]
|
||||
)
|
||||
|
||||
number = len(group_elements) - 1
|
||||
number = f"{number:02d}"
|
||||
@@ -1241,12 +1256,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator):
|
||||
for old, new in old_to_new.items():
|
||||
pset = ifcopenshell.util.element.get_pset(old, "BBIM_Linked_Aggregate")
|
||||
if pset:
|
||||
new_pset = ifcopenshell.api.run(
|
||||
"pset.add_pset", tool.Ifc.get(), product=new[0], name=self.pset_name
|
||||
)
|
||||
new_pset = ifcopenshell.api.pset.add_pset(tool.Ifc.get(), product=new[0], name=self.pset_name)
|
||||
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_pset",
|
||||
ifcopenshell.api.pset.edit_pset(
|
||||
tool.Ifc.get(),
|
||||
pset=new_pset,
|
||||
properties={"Index": pset["Index"]},
|
||||
@@ -1353,12 +1365,12 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry.delete_ifc_object(tool.Ifc.get_object(element))
|
||||
|
||||
def get_original_names(element: ifcopenshell.entity_instance) -> dict[int, dict[int, str]]:
|
||||
group = [
|
||||
group = next(
|
||||
r.RelatingGroup
|
||||
for r in getattr(element, "HasAssignments", []) or []
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if self.group_name in r.RelatingGroup.Name
|
||||
][0].id()
|
||||
).id()
|
||||
original_names[group] = {}
|
||||
|
||||
pset = ifcopenshell.util.element.get_pset(element, self.pset_name)
|
||||
@@ -1494,7 +1506,7 @@ class RefreshLinkedAggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for group in linked_aggregate_groups:
|
||||
elements = tool.Drawing.get_group_elements(tool.Ifc.get().by_id(group))
|
||||
if len(linked_aggregate_groups) > 1:
|
||||
base_instance = [e for e in elements if e in selected_parents][0]
|
||||
base_instance = next(e for e in elements if e in selected_parents)
|
||||
instances_to_refresh = elements
|
||||
|
||||
elif (len(linked_aggregate_groups) == 1) and (len(selected_parents) > 1):
|
||||
@@ -1558,8 +1570,12 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = (
|
||||
blender_op.description
|
||||
+ ".\nAlso makes sure changes are in sync with IFC."
|
||||
+ "\nIf IFC object is joined into non-IFC object, it will be removed from IFC."
|
||||
+ "\nJoining representation items only supported for joining with other representation items of the same representation."
|
||||
+ "\n\nJoining IFC object into another IFC object/Blender object will "
|
||||
+ "only merge their current representations and will remove joined object from IFC. "
|
||||
+ "Representation items are ignored for this case."
|
||||
+ "\n\nJoining representation items only supported for joining with other representation items "
|
||||
+ "of the same representation (only mesh-like items supported currently) "
|
||||
+ "or with non-IFC Blender objects."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@@ -1642,6 +1658,11 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Geometry.reload_representation(rep_obj)
|
||||
context.view_layer.update()
|
||||
tool.Root.reload_item_decorator()
|
||||
else:
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
f"Unsupported type of item to join: {item}. Currently only mesh-like items are supported.",
|
||||
)
|
||||
|
||||
def join_ifc_obj(self, context: bpy.types.Context) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
@@ -1687,7 +1708,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if obj == self.target:
|
||||
continue
|
||||
if obj.type != self.target_type:
|
||||
obj.select_set((False))
|
||||
obj.select_set(False)
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
@@ -1748,7 +1769,7 @@ class OverrideJoin(bpy.types.Operator, tool.Ifc.Operator):
|
||||
copied_item = ifcopenshell.util.element.copy_deep(
|
||||
ifc_file, item, exclude=("IfcCartesianPointList",)
|
||||
)
|
||||
new_points = processed_point_lists.get((points := item.Points))
|
||||
new_points = processed_point_lists.get(points := item.Points)
|
||||
if new_points is None:
|
||||
new_points = ifcopenshell.util.element.copy_deep(ifc_file, points)
|
||||
dim = item.Dim
|
||||
@@ -2186,7 +2207,9 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
item = tool.Geometry.get_active_representation(obj)
|
||||
assert item
|
||||
if tool.Geometry.is_meshlike_item(item):
|
||||
if tool.Geometry.is_geometric_data(obj.data) and obj.data.polygons:
|
||||
if tool.Geometry.is_geometric_data(obj.data) and (
|
||||
item.is_a("IfcVertex") or item.is_a("IfcEdge") or obj.data.polygons
|
||||
):
|
||||
tool.Geometry.edit_meshlike_item(obj)
|
||||
else:
|
||||
tool.Geometry.import_item(obj)
|
||||
@@ -2229,8 +2252,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
curves.append(profile.OuterCurve)
|
||||
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||
curves.extend(profile.InnerCurves)
|
||||
new_footprint = ifcopenshell.api.run(
|
||||
"geometry.add_footprint_representation",
|
||||
new_footprint = ifcopenshell.api.geometry.add_footprint_representation(
|
||||
tool.Ifc.get(),
|
||||
context=footprint_context,
|
||||
curves=curves,
|
||||
@@ -2245,8 +2267,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint
|
||||
)
|
||||
else:
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation",
|
||||
ifcopenshell.api.geometry.assign_representation(
|
||||
tool.Ifc.get(),
|
||||
product=element,
|
||||
representation=new_footprint,
|
||||
@@ -2359,7 +2380,7 @@ class EnableEditingRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
item.tags += ","
|
||||
item.tags += tag
|
||||
|
||||
if tool.Geometry.has_mesh_properties((data := obj.data)):
|
||||
if tool.Geometry.has_mesh_properties(data := obj.data):
|
||||
representation = tool.Geometry.get_data_representation(data)
|
||||
assert representation
|
||||
|
||||
@@ -2552,7 +2573,8 @@ class EnableEditingRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# set dropdown to currently active style
|
||||
representation_item_id = props.active_item.ifc_definition_id
|
||||
assert (active_item := props.active_item)
|
||||
representation_item_id = active_item.ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
style = tool.Style.get_representation_item_style(representation_item)
|
||||
if style:
|
||||
@@ -2571,8 +2593,14 @@ class EditRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.is_editing_item_style = False
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
surface_style = ifc_file.by_id(int(props.representation_item_style))
|
||||
representation_item_id = props.active_item.ifc_definition_id
|
||||
surface_style_id = tool.Blender.get_enum_safe(props, "representation_item_style")
|
||||
if surface_style_id in (None, "-"):
|
||||
surface_style = None
|
||||
else:
|
||||
surface_style = ifc_file.by_id(int(props.representation_item_style))
|
||||
|
||||
assert (active_item := props.active_item)
|
||||
representation_item_id = active_item.ifc_definition_id
|
||||
representation_item = ifc_file.by_id(representation_item_id)
|
||||
|
||||
tool.Style.assign_style_to_representation_item(representation_item, surface_style)
|
||||
|
||||
@@ -32,7 +32,6 @@ classes = (
|
||||
operator.SetGroupVisibility,
|
||||
operator.ToggleGroup,
|
||||
operator.UnassignGroup,
|
||||
prop.ExpandedGroups,
|
||||
prop.Group,
|
||||
prop.BIMGroupProperties,
|
||||
ui.BIM_PT_groups,
|
||||
@@ -43,9 +42,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMGroupProperties = bpy.props.PointerProperty(type=prop.BIMGroupProperties)
|
||||
bpy.types.Scene.ExpandedGroups = bpy.props.PointerProperty(type=prop.ExpandedGroups)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMGroupProperties
|
||||
del bpy.types.Scene.ExpandedGroups
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.group
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
import json
|
||||
@@ -31,8 +31,8 @@ class LoadGroups(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
self.expanded_groups = json.loads(context.scene.ExpandedGroups.json_string)
|
||||
self.props = tool.Blender.get_group_props()
|
||||
self.expanded_groups = json.loads(self.props.expanded_groups_json)
|
||||
self.props.groups.clear()
|
||||
|
||||
groups = [
|
||||
@@ -79,12 +79,13 @@ class ToggleGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
option: bpy.props.StringProperty(name="Expand or Collapse")
|
||||
|
||||
def _execute(self, context):
|
||||
expanded_groups = set(json.loads(context.scene.ExpandedGroups.json_string))
|
||||
props = tool.Blender.get_group_props()
|
||||
expanded_groups = set(json.loads(props.expanded_groups_json))
|
||||
if self.option == "Expand":
|
||||
expanded_groups.add(self.ifc_definition_id)
|
||||
elif self.ifc_definition_id in expanded_groups:
|
||||
expanded_groups.remove(self.ifc_definition_id)
|
||||
context.scene.ExpandedGroups.json_string = json.dumps(list(expanded_groups))
|
||||
props.expanded_groups_json = json.dumps(list(expanded_groups))
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -95,8 +96,9 @@ class DisableGroupEditingUI(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
context.scene.BIMGroupProperties.is_editing = False
|
||||
context.scene.BIMGroupProperties.active_group_id = 0
|
||||
props = tool.Blender.get_group_props()
|
||||
props.is_editing = False
|
||||
props.active_group_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -107,10 +109,10 @@ class AddGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
result = ifcopenshell.api.run("group.add_group", tool.Ifc.get())
|
||||
result = ifcopenshell.api.group.add_group(tool.Ifc.get())
|
||||
if self.group:
|
||||
ifcopenshell.api.run(
|
||||
"group.assign_group", tool.Ifc.get(), products=[result], group=tool.Ifc.get().by_id(self.group)
|
||||
ifcopenshell.api.group.assign_group(
|
||||
tool.Ifc.get(), products=[result], group=tool.Ifc.get().by_id(self.group)
|
||||
)
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
@@ -122,7 +124,7 @@ class EditGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
props = tool.Blender.get_group_props()
|
||||
attributes = bonsai.bim.helper.export_attributes(props.group_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.group.edit_group(ifc_file, group=ifc_file.by_id(props.active_group_id), attributes=attributes)
|
||||
@@ -138,7 +140,7 @@ class RemoveGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
ifcopenshell.api.run("group.remove_group", self.file, **{"group": self.file.by_id(self.group)})
|
||||
ifcopenshell.api.group.remove_group(self.file, group=self.file.by_id(self.group))
|
||||
bpy.ops.bim.load_groups()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -150,7 +152,7 @@ class EnableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
group: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMGroupProperties
|
||||
props = tool.Blender.get_group_props()
|
||||
props.group_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.group), props.group_attributes)
|
||||
props.active_group_id = self.group
|
||||
@@ -163,7 +165,8 @@ class DisableEditingGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
context.scene.BIMGroupProperties.active_group_id = 0
|
||||
props = tool.Blender.get_group_props()
|
||||
props.active_group_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -183,9 +186,9 @@ class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if not self.is_assigning:
|
||||
return bpy.ops.bim.unassign_group(group=self.group)
|
||||
products = [
|
||||
tool.Ifc.get_entity(o)
|
||||
element
|
||||
for o in tool.Blender.get_selected_objects(include_active=False)
|
||||
if tool.Ifc.get_entity(o)
|
||||
if (element := tool.Ifc.get_entity(o))
|
||||
]
|
||||
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=products, group=tool.Ifc.get().by_id(self.group))
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
from bonsai.bim.module.pset.data import refresh as refresh_pset
|
||||
from bpy.types import PropertyGroup
|
||||
import json
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty,
|
||||
@@ -31,16 +31,13 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
def update_active_group_index(self, context):
|
||||
refresh_pset()
|
||||
|
||||
|
||||
class ExpandedGroups(StrProperty):
|
||||
json_string: StringProperty(name="JSON String", default="[]")
|
||||
|
||||
|
||||
class Group(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
@@ -48,6 +45,13 @@ class Group(PropertyGroup):
|
||||
has_children: BoolProperty(name="Has Children", default=False)
|
||||
tree_depth: IntProperty(name="Tree Depth")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
ifc_definition_id: int
|
||||
is_expanded: bool
|
||||
has_children: bool
|
||||
tree_depth: int
|
||||
|
||||
|
||||
class BIMGroupProperties(PropertyGroup):
|
||||
group_attributes: CollectionProperty(name="Group Attributes", type=Attribute)
|
||||
@@ -55,8 +59,16 @@ class BIMGroupProperties(PropertyGroup):
|
||||
groups: CollectionProperty(name="Groups", type=Group)
|
||||
active_group_index: IntProperty(name="Active Group Index", update=update_active_group_index)
|
||||
active_group_id: IntProperty(name="Active Group Id")
|
||||
expanded_groups_json: StringProperty(name="JSON String", default="[]")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
group_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_editing: bool
|
||||
groups: bpy.types.bpy_prop_collection_idprop[Group]
|
||||
active_group_index: int
|
||||
active_group_id: int
|
||||
expanded_groups_json: str
|
||||
|
||||
@property
|
||||
def active_group(self):
|
||||
if self.active_group_index < len(self.groups):
|
||||
return self.groups[self.active_group_index]
|
||||
def active_group(self) -> Union[Group, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.groups, self.active_group_index)
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.helper import draw_attributes
|
||||
from bonsai.bim.module.group.data import GroupsData, ObjectGroupsData
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.group.prop import BIMGroupProperties, Group
|
||||
|
||||
|
||||
class BIM_PT_groups(Panel):
|
||||
@@ -39,7 +44,7 @@ class BIM_PT_groups(Panel):
|
||||
def draw(self, context):
|
||||
if not GroupsData.is_loaded:
|
||||
GroupsData.load()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
self.props = tool.Blender.get_group_props()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=f"{GroupsData.data['total_groups']} Groups Found", icon="OUTLINER")
|
||||
@@ -99,7 +104,8 @@ class BIM_PT_object_groups(Panel):
|
||||
def draw(self, context):
|
||||
if not ObjectGroupsData.is_loaded:
|
||||
ObjectGroupsData.load()
|
||||
self.props = context.scene.BIMGroupProperties
|
||||
assert self.layout
|
||||
self.props = tool.Blender.get_group_props()
|
||||
|
||||
for group in ObjectGroupsData.data["groups"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -113,7 +119,17 @@ class BIM_PT_object_groups(Panel):
|
||||
|
||||
|
||||
class BIM_UL_groups(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
def draw_item(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
layout: bpy.types.UILayout,
|
||||
data: BIMGroupProperties,
|
||||
item: Group,
|
||||
icon,
|
||||
active_data,
|
||||
active_propname,
|
||||
index: int,
|
||||
):
|
||||
if item:
|
||||
row = layout.row(align=True)
|
||||
for i in range(0, item.tree_depth):
|
||||
|
||||
@@ -153,7 +153,7 @@ class IfcGitData:
|
||||
|
||||
@classmethod
|
||||
def commit(cls):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if cls.repo() and len(props.ifcgit_commits) > 0:
|
||||
item = props.ifcgit_commits[props.commit_index]
|
||||
try:
|
||||
@@ -163,7 +163,7 @@ class IfcGitData:
|
||||
|
||||
@classmethod
|
||||
def current_revision(cls):
|
||||
props = bpy.context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if cls.repo() and cls.repo().head.is_valid() and len(props.ifcgit_commits) > 0:
|
||||
return tool.IfcGitRepo.repo.commit()
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ class CloneRepo(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if (
|
||||
props.remote_url
|
||||
and props.local_folder
|
||||
@@ -80,7 +80,7 @@ class CloneRepo(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
core.clone_repo(tool.IfcGit, props.remote_url, props.local_folder, self)
|
||||
props.remote_url = ""
|
||||
refresh()
|
||||
@@ -112,7 +112,7 @@ class CommitChanges(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
repo = IfcGitData.data["repo"]
|
||||
if props.commit_message == "":
|
||||
return False
|
||||
@@ -148,7 +148,7 @@ class AddTag(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if props.new_tag_name == "":
|
||||
return False
|
||||
repo = IfcGitData.data["repo"]
|
||||
@@ -218,7 +218,7 @@ class DisplayRevision(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
@@ -253,7 +253,7 @@ class SwitchRevision(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if props.ifcgit_commits:
|
||||
return True
|
||||
|
||||
@@ -274,7 +274,7 @@ class Merge(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
if IfcGitData.data["ifcmerge_exe"] and props.ifcgit_commits and not IfcGitData.data["is_detached"]:
|
||||
return True
|
||||
return False
|
||||
@@ -296,8 +296,7 @@ class Push(bpy.types.Operator):
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
repo = IfcGitData.data["repo"]
|
||||
core.push(tool.IfcGit, repo, props.select_remote, self)
|
||||
return {"FINISHED"}
|
||||
@@ -311,8 +310,7 @@ class Fetch(bpy.types.Operator):
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
repo = IfcGitData.data["repo"]
|
||||
remote = repo.remotes[props.select_remote]
|
||||
remote.fetch()
|
||||
@@ -329,7 +327,7 @@ class AddRemote(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
IfcGitData.make_sure_is_loaded()
|
||||
props = context.scene.IfcGitProperties
|
||||
props = tool.IfcGit.get_ifcgit_props()
|
||||
repo = IfcGitData.data["repo"]
|
||||
if (
|
||||
not repo
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
@@ -8,11 +9,10 @@ from bpy.props import (
|
||||
EnumProperty,
|
||||
)
|
||||
from bonsai.bim.module.ifcgit.data import IfcGitData
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
def git_branches(self, context):
|
||||
"""branches enum"""
|
||||
|
||||
def git_branches(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
# NOTE "Python must keep a reference to the strings returned by
|
||||
# the callback or Blender will misbehave or even crash"
|
||||
IfcGitData.data["branch_names"] = sorted([branch.name for branch in IfcGitData.data["repo"].heads])
|
||||
@@ -29,9 +29,7 @@ def git_branches(self, context):
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["branch_names"]]
|
||||
|
||||
|
||||
def git_remotes(self, context):
|
||||
"""remotes enum"""
|
||||
|
||||
def git_remotes(self: "IfcGitProperties", context: bpy.types.Context) -> tool.Blender.BLENDER_ENUM_ITEMS:
|
||||
IfcGitData.data["remote_names"] = sorted([remote.name for remote in IfcGitData.data["remotes"]])
|
||||
|
||||
if "origin" in IfcGitData.data["remote_names"]:
|
||||
@@ -41,12 +39,11 @@ def git_remotes(self, context):
|
||||
return [(myname, myname, myname) for myname in IfcGitData.data["remote_names"]]
|
||||
|
||||
|
||||
def update_revlist(self, context):
|
||||
def update_revlist(self: "IfcGitProperties", context: bpy.types.Context) -> None:
|
||||
"""wrapper to trigger update of the revision list"""
|
||||
|
||||
bpy.ops.ifcgit.refresh()
|
||||
props = context.scene.IfcGitProperties
|
||||
props.commit_index = 0
|
||||
self.commit_index = 0
|
||||
|
||||
|
||||
class IfcGitTag(PropertyGroup):
|
||||
@@ -61,6 +58,10 @@ class IfcGitTag(PropertyGroup):
|
||||
default="",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
message: str
|
||||
|
||||
|
||||
class IfcGitListItem(PropertyGroup):
|
||||
"""Group of properties representing an item in the list."""
|
||||
@@ -89,6 +90,14 @@ class IfcGitListItem(PropertyGroup):
|
||||
)
|
||||
tags: CollectionProperty(type=IfcGitTag, name="List of revision tags")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
hexsha: str
|
||||
relevant: bool
|
||||
author_name: str
|
||||
author_email: str
|
||||
message: str
|
||||
tags: bpy.types.bpy_prop_collection_idprop[IfcGitTag]
|
||||
|
||||
|
||||
class IfcGitProperties(PropertyGroup):
|
||||
|
||||
@@ -140,3 +149,17 @@ class IfcGitProperties(PropertyGroup):
|
||||
],
|
||||
update=update_revlist,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
ifcgit_commits: bpy.types.bpy_prop_collection_idprop[IfcGitListItem]
|
||||
commit_index: int
|
||||
commit_message: str
|
||||
new_branch_name: str
|
||||
new_tag_name: str
|
||||
new_tag_message: str
|
||||
remote_name: str
|
||||
remote_url: str
|
||||
local_folder: str
|
||||
display_branch: str
|
||||
select_remote: str
|
||||
ifcgit_filter: Literal["all", "tagged", "relevant"]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user