mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-28 07:49:59 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cd799d4d1 | |||
| 7e4a7e093d |
@@ -1,23 +0,0 @@
|
||||
name: Bug Report
|
||||
description: Crashes, error messages, and broken features
|
||||
type: bug
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug Description
|
||||
placeholder: |
|
||||
Describe what problem occurred and what you expected to happen instead.
|
||||
|
||||
1. To reproduce this, open file '...'
|
||||
2. Click on '....'
|
||||
3. See error
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Attachments
|
||||
description: "Private files can be uploaded to https://ifcopenshell.org/upload.html - only viewed by core developers and will be deleted afterwards. Please submit your report first then upload private files afterwards."
|
||||
placeholder: "If applicable, add screenshots to help explain your problem. Please also drag-drop any files necessary to show the error (rename the file extension from .ifc to .txt to upload)."
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Debug and Error Output
|
||||
description: "If this is in Bonsai, paste the output from the Copy Debug Information option in Bonsai. It can be found under Quality and Coordination -> Quality Control -> Debug. If this is a general software issue, if relevant include details about IfcOpenShell version, operating system, Python version, etc."
|
||||
render: yes
|
||||
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Community Forums
|
||||
url: https://community.osarch.org/
|
||||
about: Have a question? Want to discuss an idea? Try the OSArch forums instead.
|
||||
- name: Live Chat
|
||||
url: https://osarch.org/chat
|
||||
about: Have a really confusing issue? Want real-time support?
|
||||
@@ -1,8 +0,0 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or improvement
|
||||
type: Feature
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature Description
|
||||
placeholder: "Describe a feature you'd like us to add, or a change to the user interface, design, or workflow for usability. If it's not obvious, explain why this feature is awesome. Note that feature requests must be specific and measurable."
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Dispatch Build IfcOpenShell
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
trigger-workflows:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
workflow:
|
||||
- 'Build IfcOpenShell Linux'
|
||||
- 'Build IfcOpenShell Linux ARM'
|
||||
- 'Build IfcOpenShell OSX'
|
||||
- 'Build IfcOpenShell WASM / Pyodide'
|
||||
- 'Build IfcOpenShell Windows'
|
||||
steps:
|
||||
- name: Trigger binary build workflows
|
||||
uses: benc-uk/workflow-dispatch@v1
|
||||
with:
|
||||
workflow: ${{ matrix.workflow }}
|
||||
@@ -44,11 +44,7 @@ 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)
|
||||
@@ -121,6 +117,22 @@ 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
|
||||
brew link --overwrite python@3.12
|
||||
brew link --overwrite python@3.13
|
||||
# https://github.com/rust-lang/rustup/pull/3989/files
|
||||
brew install --overwrite awscli
|
||||
|
||||
- name: Upload .zip archives to S3
|
||||
run: |
|
||||
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
repository: pyodide/pyodide
|
||||
ref: '0.28.0a3'
|
||||
ref: '0.26.4'
|
||||
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' >> script.sh
|
||||
echo 'cd pyodide' > script.sh
|
||||
echo 'make && pip install ./pyodide-build' >> 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/22.16.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> 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 '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}-cp313-cp313-emscripten_4_0_9_wasm32.whl
|
||||
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp312-cp312-emscripten_3_1_58_wasm32.whl
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
container: rockylinux:8
|
||||
|
||||
steps:
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
name: Build IfcOpenShell Linux ARM
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
container: arm64v8/rockylinux:8
|
||||
|
||||
steps:
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
yum update -y
|
||||
yum install -y gcc gcc-c++ git autoconf automake bison make zip cmake python3 \
|
||||
bzip2 patch mesa-libGL-devel libffi-devel fontconfig-devel \
|
||||
sqlite-devel bzip2-devel zlib-devel openssl-devel xz-devel \
|
||||
readline-devel ncurses-devel libffi-devel libuuid-devel git-lfs \
|
||||
findutils
|
||||
git config --global --add safe.directory '*'
|
||||
|
||||
- name: Install aws cli
|
||||
run: |
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
./aws/install
|
||||
rm -rf awscliv2.zip aws
|
||||
aws --version
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
ref: rockylinux8-arm64
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
[ -n "$install_root" ] && find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \; || true
|
||||
|
||||
# Not supported on docker
|
||||
# - name: ccache
|
||||
# uses: hendrikmuhs/ccache-action@v1
|
||||
# with:
|
||||
# key: ${GITHUB_WORKFLOW}-rockylinux8-x64
|
||||
|
||||
- name: Run Build Script
|
||||
run: |
|
||||
CXXFLAGS="-O3" CFLAGS="-O3 ${DARWIN_C_SOURCE}" ADD_COMMIT_SHA=1 BUILD_CFG=Release python3 ./nix/build-all.py --diskcleanup
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
cd build
|
||||
for install_dir in $(find $(find . -maxdepth 4 -name install) -mindepth 1 -maxdepth 1 -type d); do
|
||||
test -f $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz || tar -czf $(dirname "$install_dir")/cache-$(basename "$install_dir").tar.gz -C $(dirname "$install_dir") $(basename "$install_dir");
|
||||
done
|
||||
|
||||
- name: Commit and Push Changes to Build Repository
|
||||
run: |
|
||||
cd build
|
||||
git config user.name "IfcOpenBot"
|
||||
git config user.email "ifcopenbot@ifcopenshell.org"
|
||||
git add "$(find . -maxdepth 4 -name install)/*.tar.gz"
|
||||
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
||||
git push || true
|
||||
|
||||
- name: Package .zip archives
|
||||
run: |
|
||||
VERSION=v`cat VERSION`
|
||||
cd ./build/`uname`/*/install/ifcopenshell
|
||||
mkdir ~/output
|
||||
|
||||
ls -d python-* | while read py_version; do
|
||||
postfix=`echo ${py_version: -1} | sed s/[0-9]//`
|
||||
numbers=`echo $py_version | grep -oE '[0-9]+\.[0-9]+' | tr -d '.'`
|
||||
py_version_major=python-${numbers}$postfix
|
||||
pushd . > /dev/null
|
||||
cd $py_version
|
||||
if [ ! -d ifcopenshell ]; then
|
||||
mkdir ../ifcopenshell_
|
||||
mv * ../ifcopenshell_
|
||||
mv ../ifcopenshell_ ifcopenshell
|
||||
fi
|
||||
[ -d ifcopenshell/__pycache__ ] && rm -rf ifcopenshell/__pycache__
|
||||
find ifcopenshell -name "*.pyc" -delete
|
||||
zip -r -qq ifcopenshell-${py_version_major}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip ifcopenshell/*
|
||||
mv *.zip ~/output
|
||||
popd > /dev/null
|
||||
done
|
||||
|
||||
cd bin
|
||||
rm *.zip || true
|
||||
ls | while read exe; do
|
||||
zip -qq -r ${exe}-${VERSION}-${GITHUB_SHA:0:7}-linuxarm64.zip $exe
|
||||
done
|
||||
mv *.zip ~/output
|
||||
cd ..
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_UPLOAD_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_UPLOAD_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Upload .zip archives to S3
|
||||
run: |
|
||||
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: windows-2022
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: _deps-vs2022-x64-installed
|
||||
path: _deps-vs2019-x64-installed
|
||||
ref: windows-${{ matrix.arch }}
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2022-x64-installed
|
||||
cd _deps-vs2019-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\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
SET IFCOS_INSTALL_PYTHON=FALSE
|
||||
cd win
|
||||
echo y | call build-deps.cmd vs2022-x64 Release
|
||||
echo y | call build-deps.cmd vs2019-x64 Release
|
||||
SET PYTHONHOME=C:\Python\${{ matrix.python }}
|
||||
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
|
||||
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
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2022-x64-installed
|
||||
cd _deps-vs2019-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-vs2022-x64-installed
|
||||
cd _deps-vs2019-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-vs2022-x64/bin
|
||||
cd _installed-vs2019-x64/bin
|
||||
Get-ChildItem -Path . | ForEach-Object {
|
||||
echo $_
|
||||
$exe = $_.Name
|
||||
@@ -118,13 +118,5 @@ jobs:
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Upload .zip Archives to S3
|
||||
env:
|
||||
AWS_DEBUG: 1
|
||||
AWS_RETRY_MODE: standard
|
||||
AWS_MAX_ATTEMPTS: 3
|
||||
run: |
|
||||
dir "$env:USERPROFILE\output"
|
||||
foreach ($zip in Get-ChildItem -Path "$env:USERPROFILE\output" -Filter *.zip) {
|
||||
aws s3 cp "$($zip.FullName)" s3://ifcopenshell-builds/ --debug
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
aws s3 cp $env:USERPROFILE\output s3://ifcopenshell-builds/ --recursive
|
||||
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -14,51 +17,57 @@ jobs:
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.9"
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Step 1 - install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
uv tool install ruff
|
||||
uv tool install black
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install 'black>=24.10.0'
|
||||
|
||||
# black doesn't catch all syntax errors, so we check them explicitly.
|
||||
- name: Check syntax errors
|
||||
id: syntax-errors
|
||||
# NOTE: This would suffice, however it is less informative in terms of the 3 possible outcomes
|
||||
# - name: QA Step - check linting
|
||||
# shell: bash
|
||||
# id: linting
|
||||
# run: |
|
||||
# python3 -m black .
|
||||
|
||||
# QA STEP
|
||||
- name: QA Step - check linting
|
||||
shell: bash
|
||||
id: linting
|
||||
run: |
|
||||
ERROR=0
|
||||
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
|
||||
python3 -m black --check . \
|
||||
&& exit 0 \
|
||||
|| (echo "exit_code=$?" >> "$GITHUB_OUTPUT" && exit 1);
|
||||
continue-on-error: true
|
||||
|
||||
- name: Black formatter
|
||||
id: black
|
||||
run: |
|
||||
uvx black --diff --check .
|
||||
continue-on-error: true
|
||||
# OUTCOME 1 of QA STEP
|
||||
- name: QA Step - no linting errors
|
||||
if: steps.linting.outcome == 'success'
|
||||
shell: bash
|
||||
run: |-
|
||||
echo "::notice::QA step linting succeeded"
|
||||
exit 0;
|
||||
|
||||
- name: Ruff check
|
||||
id: ruff
|
||||
run: |
|
||||
uvx ruff check
|
||||
continue-on-error: true
|
||||
# OUTCOME 2i of QA STEP
|
||||
- name: QA Step - unprettified code with no syntax errors
|
||||
if: steps.linting.outputs.exit_code == 1
|
||||
shell: bash
|
||||
run: |-
|
||||
echo "::group::QA step succeeded with warnings"
|
||||
echo "::warning::one or more files contains unformatted code but no syntax errors";
|
||||
echo "::notice::please run the linter before pushing!";
|
||||
echo "::endgroup::"
|
||||
exit 0;
|
||||
|
||||
- name: Final check
|
||||
run: |
|
||||
ERROR=0
|
||||
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.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
|
||||
# OUTCOME 2ii of QA STEP
|
||||
- name: QA Step - code contains syntax errors
|
||||
if: steps.linting.outputs.exit_code == 123
|
||||
shell: bash
|
||||
run: |-
|
||||
echo "::group::QA step failed"
|
||||
echo "::error::one or more files contains syntax errors";
|
||||
echo "::notice::please run the linter and fix syntax errors before pushing!";
|
||||
echo "::endgroup::"
|
||||
exit 1;
|
||||
|
||||
@@ -99,12 +99,12 @@ jobs:
|
||||
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
|
||||
path: bonsai_unstable_repo
|
||||
|
||||
- name: Download Blender and run critical tests
|
||||
- name: Update index.json on extensions repo
|
||||
run: |
|
||||
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
||||
set -x -e
|
||||
|
||||
# Download Blender.
|
||||
wget -q -O blender.tar.xz https://download.blender.org/release/Blender4.4/blender-4.4.0-linux-x64.tar.xz
|
||||
wget -q -O blender.tar.xz https://ftp.nluug.nl/pub/graphics/blender/release/Blender4.3/blender-4.3.0-linux-x64.tar.xz
|
||||
tar -xf blender.tar.xz
|
||||
|
||||
# Setup Blender.
|
||||
@@ -112,25 +112,25 @@ jobs:
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
# Setup unstable repo to get Bonsai build.
|
||||
cd bonsai_unstable_repo
|
||||
pip install -r requirements.txt
|
||||
python setup_extensions_repo.py --last-tag
|
||||
cd ..
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
git add index.json
|
||||
git add readme.md
|
||||
git commit -m "Update index.json"
|
||||
git push
|
||||
- name: Run bonsai tests
|
||||
run: |
|
||||
set -x -e
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
bonsai_zip="$(pwd)/$(ls bonsai_unstable_repo/bonsai_py311*-linux-x64.zip)"
|
||||
|
||||
# Install Bonsai.
|
||||
blender --version
|
||||
blender --command extension install-file -r user_default -e $bonsai_zip
|
||||
blender --command extension list
|
||||
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
|
||||
|
||||
# Reregister Bonsai.
|
||||
# Note that running it in background might miss some errors
|
||||
# (e.g. tools are not registered in background mode).
|
||||
blender --background --python IfcOpenShell/src/bonsai/scripts/reregister_bonsai.py
|
||||
|
||||
# Install sverchok.
|
||||
wget -q -O sverchok.zip https://github.com/nortikin/sverchok/archive/refs/heads/master.zip
|
||||
# ifcsverchok expecting sverchok to be named "sverchok" and not "sverchok-master".
|
||||
unzip -q sverchok.zip
|
||||
@@ -139,43 +139,18 @@ jobs:
|
||||
rm -r sverchok
|
||||
blender --command extension install-file -r user_default sverchok.zip
|
||||
|
||||
# Install ifcsverchok.
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
|
||||
cd IfcOpenShell/src/ifcsverchok
|
||||
make dist
|
||||
sverchok_zip="$(pwd)/dist/$(ls dist)"
|
||||
blender --command extension install-file -r user_default $sverchok_zip
|
||||
|
||||
- name: Update index.json on extensions repo
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
# Setup Blender.
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
cd bonsai_unstable_repo
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
git add index.json
|
||||
git add readme.md
|
||||
git commit -m "Update index.json"
|
||||
git push
|
||||
|
||||
- name: Run bonsai tests
|
||||
run: |
|
||||
set -x -e
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
# Install Sun Position extension.
|
||||
blender --online-mode --command extension sync
|
||||
blender --online-mode --command extension install --enable --sync sun_position
|
||||
|
||||
cd IfcOpenShell/src/bonsai
|
||||
cd ../bonsai
|
||||
pip install pytest-blender
|
||||
pip install pytest-bdd
|
||||
blender --background --python scripts/setup_pytest.py
|
||||
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
|
||||
make test
|
||||
|
||||
@@ -24,6 +24,10 @@ jobs:
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Windows 32bit",
|
||||
short_name: win32,
|
||||
}
|
||||
- {
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
|
||||
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
- name: Version + date str
|
||||
id: verdate
|
||||
run: echo "verdate=${{ steps.version.outputs.version }}alpha${{ steps.date.outputs.date }}" >> $GITHUB_OUTPUT
|
||||
run: echo "verdate='${{ steps.version.outputs.version }}alpha${{ steps.date.outputs.date }}'" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
test:
|
||||
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}
|
||||
name: ${{ matrix.platform.distver }}-${{ matrix.pyver.name }}-${{ matrix.variant }}
|
||||
needs: activate
|
||||
runs-on: ${{ matrix.platform.distver }}
|
||||
defaults:
|
||||
@@ -48,67 +48,57 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [novtk, all]
|
||||
pyver: [
|
||||
# { name: py311, distver: '3.11'},
|
||||
{ name: py312, distver: '3.12'}
|
||||
]
|
||||
platform: [
|
||||
{ name: win, distver: windows-latest, pkg_dir: 'win-64' },
|
||||
{ name: linux, distver: ubuntu-latest, pkg_dir: 'linux-64' },
|
||||
{ name: macOS-arm, distver: macos-latest, pkg_dir: 'osx-arm64' },
|
||||
{ name: macOS-x86, distver: macos-13, pkg_dir: 'osx-64' }
|
||||
{ name: macOS, distver: macos-latest, pkg_dir: 'osx-64' }
|
||||
]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
- name: Set up swap space
|
||||
if: runner.os == 'Linux'
|
||||
uses: pierotofy/set-swap-space@master
|
||||
uses: pierotofy/set-swap-space@v1.0
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
|
||||
- name: set ARTIFACTS ENV vars
|
||||
shell: bash
|
||||
run: |
|
||||
pwd
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
echo "ARTIFACTS_DIR=D:/a/artifacts" >> $GITHUB_ENV
|
||||
elif [[ "$RUNNER_OS" == "macOS" ]]; then
|
||||
echo "ARTIFACTS_DIR=/Users/runner/work/artifacts" >> $GITHUB_ENV
|
||||
elif [[ "$RUNNER_OS" == "Linux" ]]; then
|
||||
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download and extract MacOSX SDK
|
||||
if: ${{ matrix.platform.name == 'macOS-x86' }}
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
curl -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz | tar -xvJf - -C /Users/runner/work/
|
||||
|
||||
- uses: mamba-org/setup-micromamba@v2 # https://github.com/mamba-org/setup-micromamba
|
||||
- uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba
|
||||
with:
|
||||
environment-name: test-env
|
||||
create-args: >-
|
||||
python=3.12
|
||||
python=3.11
|
||||
anaconda-client
|
||||
rattler-build
|
||||
boa
|
||||
|
||||
- name: create conda package dist dir
|
||||
run: |
|
||||
mkdir -p ${{ env.ARTIFACTS_DIR }}
|
||||
mkdir -p ${{ github.workspace }}/dist
|
||||
|
||||
- name: build & test ifcopenshell
|
||||
run: |
|
||||
rattler-build build -r conda/recipe.yaml --output-dir '${{ env.ARTIFACTS_DIR }}'
|
||||
conda mambabuild . --python ${{ matrix.pyver.distver }} --no-remove-work-dir --output-folder '${{ github.workspace }}/dist' --variants='{variant: ${{ matrix.variant }}}'
|
||||
working-directory: ./conda
|
||||
env:
|
||||
VERSION_OVERRIDE: ${{ needs.activate.outputs.verdate }}
|
||||
|
||||
- name: upload to anaconda
|
||||
if: ${{ matrix.platform.name == 'win' }}
|
||||
run: |
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell '${{ env.ARTIFACTS_DIR }}/${{ matrix.platform.pkg_dir }}/*.conda'
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell '${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2'
|
||||
|
||||
- name: upload to anaconda
|
||||
if: ${{ matrix.platform.name != 'win' }}
|
||||
run: |
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ env.ARTIFACTS_DIR }}/${{ matrix.platform.pkg_dir }}/*.conda
|
||||
anaconda -t ${{ secrets.ANACONDA_TOKEN }} upload --force --user ifcopenshell ${{ github.workspace }}/dist/${{ matrix.platform.pkg_dir }}/*.tar.bz2
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
jobs:
|
||||
|
||||
activate:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
!startsWith(github.event.head_commit.message, 'Release ') &&
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- run: echo ok go
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
-
|
||||
name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
|
||||
-
|
||||
name: Build ifcopenshell
|
||||
@@ -44,7 +44,6 @@ 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 \
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
make package
|
||||
working-directory: build
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
@@ -84,7 +83,7 @@ jobs:
|
||||
path: build/assets/Ifc*
|
||||
|
||||
deliver:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
name: Docker Build, Tag, Push
|
||||
|
||||
@@ -119,6 +118,6 @@ jobs:
|
||||
repository: aecgeeks/ifcopenshell
|
||||
# Since the dispatch is set to `tag`, `github.ref_name` should evaluate to the pushed tag
|
||||
# On a workflow dispatch, `ref_name` will take on the value from the dispatch payload
|
||||
tags: aecgeeks/ifcopenshell:${{ github.ref_name }}${{ github.ref_name == github.event.repository.default_branch && ',aecgeeks/ifcopenshell:22.04' }}
|
||||
tags: aecgeeks/ifcopenshell:${{ github.ref_name }}${{ github.ref_name == github.event.repository.default_branch && ',aecgeeks/ifcopenshell:latest' }}
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
|
||||
@@ -44,8 +44,6 @@ 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
|
||||
|
||||
@@ -25,6 +25,10 @@ jobs:
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Windows 32bit",
|
||||
short_name: win32,
|
||||
}
|
||||
- {
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
@@ -39,8 +43,6 @@ 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
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- run: echo ok go
|
||||
|
||||
compile-and-test:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
steps:
|
||||
@@ -48,6 +48,7 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install xmlschema xsdata numpy lxml pytest isodate lark networkx tabulate python-dateutil shapely
|
||||
pip install mathutils
|
||||
pip install src/bcf --no-deps
|
||||
pip install https://github.com/Andrej730/aud/archive/refs/heads/master-reduced-size.zip
|
||||
|
||||
@@ -82,7 +83,6 @@ 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 \
|
||||
@@ -128,21 +128,11 @@ 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.
|
||||
ERROR=0
|
||||
make test || ERROR=1
|
||||
cd ../bcf && make test || ERROR=1
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
cd ../bsdd && make test || ERROR=1
|
||||
cd ../bsdd && make test
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test || ERROR=1
|
||||
cd ../ifcpatch && make test || ERROR=1
|
||||
pip install -e ../ifctester --no-deps
|
||||
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
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -9,8 +9,6 @@ on:
|
||||
paths:
|
||||
- 'src/pyodide/**'
|
||||
- '.github/workflows/publish-pyodide-demo-app.yml'
|
||||
branches:
|
||||
- v0.8.0
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
|
||||
@@ -4,7 +4,6 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9].[0-9].[0-9]*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
@@ -46,7 +45,6 @@ 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
-3
@@ -86,13 +86,12 @@ 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
|
||||
|
||||
# ifcopenshell swig and compiled files
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper*.so
|
||||
src/ifcopenshell-python/ifcopenshell/_ifcopenshell_wrapper.so
|
||||
src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# apple
|
||||
@@ -107,4 +106,4 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
bonsaiDecoratorForLoads.code-workspace
|
||||
|
||||
dev_environment.bat
|
||||
|
||||
@@ -18,12 +18,7 @@
|
||||
################################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
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 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON) # not necessary, but encouraged
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -1294,8 +1289,6 @@ 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,7 +9,6 @@ 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%" ^
|
||||
@@ -50,7 +49,3 @@ if errorlevel 1 exit 1
|
||||
ninja install -j 1
|
||||
|
||||
if errorlevel 1 exit 1
|
||||
|
||||
python %RECIPE_DIR%/update_version_init.py %PKG_VERSION% %SP_DIR%/ifcopenshell/__init__.py
|
||||
|
||||
if errorlevel 1 exit 1
|
||||
+1
-4
@@ -16,7 +16,6 @@ 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 \
|
||||
@@ -47,6 +46,4 @@ cmake ${CMAKE_ARGS} -G Ninja \
|
||||
|
||||
ninja
|
||||
|
||||
ninja install -j 1
|
||||
|
||||
python "${RECIPE_DIR}/update_version_init.py" "${PKG_VERSION}" "${SP_DIR}/ifcopenshell/__init__.py"
|
||||
ninja install -j 1
|
||||
@@ -1,5 +1,6 @@
|
||||
python:
|
||||
- 3.12
|
||||
variant:
|
||||
- novtk
|
||||
- all
|
||||
|
||||
occt:
|
||||
- 7.8.1
|
||||
@@ -23,15 +24,14 @@ cxx_compiler_version:
|
||||
- '12' # [linux]
|
||||
- '16' # [osx]
|
||||
c_stdlib_version:
|
||||
- 2.17 # [linux]
|
||||
- 10.13 # [osx and x86_64]
|
||||
- 11.0 # [osx and arm64]
|
||||
- '2.12' # [linux]
|
||||
- '10.13' # [osx]
|
||||
hdf5:
|
||||
- 1.14.6
|
||||
- 1.14.3
|
||||
libboost_devel:
|
||||
- '1.86'
|
||||
- '1.84'
|
||||
libxml2:
|
||||
- 2.13
|
||||
- '2'
|
||||
mpfr:
|
||||
- '4'
|
||||
gmp:
|
||||
@@ -47,12 +47,12 @@ target_platform:
|
||||
- win-64 # [win]
|
||||
- linux-64 # [linux]
|
||||
- osx-64 # [osx]
|
||||
macos_machine: # [osx]
|
||||
- x86_64-apple-darwin13.4.0 # [osx and x86_64]
|
||||
- arm64-apple-darwin20.0.0 # [osx and arm64]
|
||||
MACOSX_DEPLOYMENT_TARGET: # [osx]
|
||||
- 11.0 # [osx and arm64]
|
||||
- 10.13 # [osx and x86_64]
|
||||
macos_machine:
|
||||
- x86_64-apple-darwin13.4.0
|
||||
MACOSX_DEPLOYMENT_TARGET:
|
||||
- '10.13'
|
||||
MACOSX_SDK_VERSION:
|
||||
- '10.13'
|
||||
|
||||
CONDA_BUILD_SYSROOT: # [osx]
|
||||
- "/Users/runner/work/MacOSX10.13.sdk" # [osx and x86_64]
|
||||
CONDA_BUILD_SYSROOT:
|
||||
- "/Users/runner/work/MacOSX10.13.sdk" # [osx]
|
||||
@@ -1,78 +1,64 @@
|
||||
context:
|
||||
version: ${{ env.get("VERSION_OVERRIDE", default="0.8.2.1") }}
|
||||
build: 1
|
||||
{% set version = environ.get('VERSION_OVERRIDE', '0.8.0') %}
|
||||
{% set build = 0 %}
|
||||
|
||||
# Higher number -> Always prioritize "novtk" variant over "all" variant
|
||||
{% set build = build + 200 %} # [variant == "novtk"]
|
||||
{% set build = build + 100 %} # [variant == "all"]
|
||||
|
||||
package:
|
||||
name: ifcopenshell
|
||||
version: ${{ version }}
|
||||
version: {{ version }}
|
||||
|
||||
source:
|
||||
- path: ..
|
||||
path: ../
|
||||
|
||||
|
||||
build:
|
||||
number: ${{ build }}
|
||||
dynamic_linking:
|
||||
binary_relocation: ${{ true if osx }}
|
||||
number: {{ build }}
|
||||
skip: true # [py<39]
|
||||
binary_relocation: false # [osx]
|
||||
string: py{{ CONDA_PY }}_{{ variant }}_h{{ PKG_HASH }}_{{ build }}
|
||||
run_exports:
|
||||
- {{ pin_subpackage('ifcopenshell', max_pin='x.x.x') }} *{{ variant }}*
|
||||
|
||||
|
||||
requirements:
|
||||
build:
|
||||
- if: build_platform != target_platform
|
||||
then:
|
||||
- python
|
||||
- cross-python_${{ target_platform }}
|
||||
- cmake <4
|
||||
- cmake
|
||||
- ninja
|
||||
- swig >=4.1.1
|
||||
- ${{ stdlib("c") }}
|
||||
- ${{ compiler('c') }}
|
||||
- ${{ compiler('cxx') }}
|
||||
- {{ stdlib("c") }}
|
||||
- {{ compiler('c') }}
|
||||
- {{ compiler('cxx') }}
|
||||
host:
|
||||
- python
|
||||
- libboost-devel
|
||||
- occt
|
||||
- occt *=*{{ variant }}*
|
||||
- libxml2
|
||||
- cgal-cpp
|
||||
- hdf5
|
||||
- eigen
|
||||
- mpfr
|
||||
- nlohmann_json
|
||||
- gmp
|
||||
- gmp # [unix]
|
||||
- mpir # [win]
|
||||
- zlib
|
||||
run:
|
||||
- python
|
||||
- shapely
|
||||
- typing_extensions
|
||||
- ${{ pin_compatible('occt', upper_bound='x.x.x') }}
|
||||
- ${{ pin_compatible('cgal-cpp', upper_bound='x.x') }}
|
||||
run_exports:
|
||||
- ${{ pin_subpackage('ifcopenshell', upper_bound='x.x.x') }}
|
||||
- {{ pin_compatible('occt', max_pin='x.x.x') }} *{{ variant }}*
|
||||
- {{ pin_compatible('cgal-cpp', max_pin='x.x') }}
|
||||
|
||||
|
||||
test:
|
||||
imports:
|
||||
- ifcopenshell
|
||||
|
||||
tests:
|
||||
- python:
|
||||
imports:
|
||||
- ifcopenshell
|
||||
pip_check: false
|
||||
- script:
|
||||
- python -c "import ifcopenshell; assert ifcopenshell.version == '${{ version }}', 'print(ifcopenshell.version)'"
|
||||
requirements:
|
||||
run:
|
||||
- occt * *novtk* # Ensure that even though compiled against OCCT with VTK, it can still run with OCCT without VTK
|
||||
- pytest
|
||||
- python-dateutil
|
||||
- xmlschema
|
||||
- xsdata
|
||||
- lxml
|
||||
- isodate
|
||||
- lark
|
||||
- networkx
|
||||
- tabulate
|
||||
- shapely
|
||||
|
||||
about:
|
||||
home: https://ifcopenshell.org
|
||||
license: LGPL-3.0-or-later
|
||||
license_file: COPYING
|
||||
summary: IfcOpenShell is a library to support the IFC file format
|
||||
summary: 'IfcOpenShell is a library to support the IFC file format'
|
||||
description: |
|
||||
IfcOpenShell
|
||||
============
|
||||
@@ -307,9 +293,8 @@ about:
|
||||
[MSYS2]: https://msys2.github.io/ "MSYS2"
|
||||
[win/readme.md]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/win/readme.md "win/readme.md"
|
||||
[nix/build-all.py]: https://github.com/IfcOpenShell/IfcOpenShell/tree/master/nix/build-all.py "nix/build-all.py"
|
||||
homepage: https://ifcopenshell.org
|
||||
repository: https://github.com/IfcOpenShell/IfcOpenShell
|
||||
documentation: https://ifcopenshell.org/
|
||||
doc_url: https://ifcopenshell.org/
|
||||
dev_url: https://github.com/IfcOpenShell/IfcOpenShell
|
||||
|
||||
extra:
|
||||
recipe-maintainers:
|
||||
@@ -1,32 +0,0 @@
|
||||
import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
def update_version(file_path: str, version: str) -> None:
|
||||
"""Update the version string in the given __init__.py file."""
|
||||
file_path = Path(file_path)
|
||||
|
||||
# Read the file and replace the version
|
||||
file_contents = file_path.read_text(encoding="utf-8")
|
||||
new_contents = re.sub(r'version = "0\.0\.0"', f'version = "{version}"', file_contents)
|
||||
|
||||
# Write the updated contents back to the file
|
||||
file_path.write_text(new_contents, encoding="utf-8")
|
||||
|
||||
print(f"Updated version in {file_path} to {version}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Update the version string in a Python file.")
|
||||
parser.add_argument(
|
||||
"version",
|
||||
type=str,
|
||||
help="The version string to replace '0.0.0' with (e.g., '1.2.3')."
|
||||
)
|
||||
parser.add_argument(
|
||||
"file",
|
||||
type=str,
|
||||
help="The path to the __init__.py file where the version will be updated."
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
update_version(args.file, args.version)
|
||||
+14
-41
@@ -69,7 +69,6 @@ import sysconfig
|
||||
import ssl
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
import time
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
|
||||
@@ -89,14 +88,14 @@ OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.86.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.13.8"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
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.6.x-branch"
|
||||
CGAL_VERSION = "5.3"
|
||||
USD_VERSION = "23.05"
|
||||
TBB_VERSION = "2021.9.0"
|
||||
|
||||
@@ -324,7 +323,7 @@ def run_autoconf(arg1, configure_args, cwd):
|
||||
if "wasm" in flags:
|
||||
wasm.append("emconfigure")
|
||||
|
||||
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)
|
||||
run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)
|
||||
|
||||
|
||||
def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
|
||||
@@ -337,7 +336,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]}", f"-DCMAKE_CXX_FLAGS='{os.environ['CXXFLAGS']}'", f"-DCMAKE_C_FLAGS='{os.environ['CFLAGS']}'", f"-DCMAKE_SHARED_LINKER_FLAGS={os.environ['LDFLAGS']}"], cwd=cwd)
|
||||
run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}", f"-DBUILD_SHARED_LIBS={OFF_ON[not BUILD_STATIC]}"], cwd=cwd)
|
||||
|
||||
|
||||
def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
@@ -351,7 +350,7 @@ def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
|
||||
run([git, "clone", "--recursive", clone_url, target_dir])
|
||||
else:
|
||||
logger.info(f"directory '{target_dir}' already cloned. Pulling latest changes.")
|
||||
run([git, "-C", target_dir, "fetch", "--all", "--tags", "--force"])
|
||||
run([git, "-C", target_dir, "fetch", "--all", "--tags"])
|
||||
|
||||
# detect whether we are on a branch and pull
|
||||
if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
|
||||
@@ -363,7 +362,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, shell=None, pre_compile_subs=[], 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, 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
|
||||
@@ -386,14 +385,7 @@ 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):
|
||||
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
|
||||
urlretrieve(url, os.path.join(build_dir, download_path))
|
||||
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:
|
||||
@@ -412,8 +404,6 @@ 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}")
|
||||
@@ -440,9 +430,6 @@ 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)
|
||||
@@ -464,12 +451,6 @@ 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}... ")
|
||||
@@ -497,6 +478,9 @@ 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
|
||||
|
||||
@@ -507,11 +491,7 @@ CFLAGS = os.environ.get("CFLAGS", "")
|
||||
LDFLAGS = os.environ.get("LDFLAGS", "")
|
||||
|
||||
ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
|
||||
|
||||
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:
|
||||
if "wasm" not in flags and 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:
|
||||
@@ -537,7 +517,6 @@ 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
|
||||
|
||||
@@ -674,8 +653,8 @@ if "libxml2" in targets:
|
||||
"--without-iconv",
|
||||
"--without-lzma"
|
||||
],
|
||||
download_url=f"https://download.gnome.org/sources/libxml2/{'.'.join(LIBXML2_VERSION.split('.')[0:2])}/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.xz"
|
||||
download_url="ftp://xmlsoft.org/libxml2/",
|
||||
download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
|
||||
)
|
||||
|
||||
if "OpenCOLLADA" in targets:
|
||||
@@ -708,11 +687,9 @@ 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
|
||||
@@ -742,7 +719,6 @@ 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
|
||||
|
||||
@@ -788,7 +764,6 @@ 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"
|
||||
)
|
||||
@@ -817,7 +792,7 @@ if "cgal" in targets:
|
||||
download_url="https://github.com/CGAL/cgal.git",
|
||||
download_name="cgal",
|
||||
download_tool=download_tool_git,
|
||||
revision=CGAL_VERSION
|
||||
revision=f"v{CGAL_VERSION}"
|
||||
)
|
||||
|
||||
if "usd" in targets:
|
||||
@@ -867,7 +842,6 @@ 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],
|
||||
@@ -973,7 +947,6 @@ 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 --py313 IfcOpenShell-Python
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py312 IfcOpenShell-Python
|
||||
mv package/ifcopenshell .
|
||||
cp pyodide/setup.py .
|
||||
|
||||
|
||||
+24
-53
@@ -1,64 +1,35 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = "src/.*.pyi?$"
|
||||
include = '''
|
||||
src/(
|
||||
bcf
|
||||
|bcfserver
|
||||
|bonsai
|
||||
|bsdd
|
||||
|foundationserver
|
||||
|ifc2ca
|
||||
|ifc4d
|
||||
|ifc5d
|
||||
|ifcbimtester
|
||||
|ifcblender
|
||||
|ifccityjson
|
||||
|ifcclash
|
||||
|ifccsv
|
||||
|ifcdiff
|
||||
|ifcfm
|
||||
|ifcpatch
|
||||
|ifctester
|
||||
|ifcopenshell-python
|
||||
|ifcsverchok
|
||||
|opencdeserver
|
||||
)/.*.py$
|
||||
'''
|
||||
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
|
||||
"UP045", # Optional to X | None
|
||||
"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] # pyright: ignore [reportAttributeAccessIssue]
|
||||
field_type = field.type.__args__[0] # type: 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,7 +6,6 @@ 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
|
||||
@@ -48,7 +47,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,6 +1,5 @@
|
||||
"""BCF XML V2 handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
@@ -32,7 +31,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:
|
||||
@@ -100,7 +99,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()}
|
||||
@@ -149,7 +148,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.
|
||||
|
||||
@@ -173,7 +172,7 @@ class BcfXml:
|
||||
cls,
|
||||
project_name: Optional[str] = None,
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> BcfXml:
|
||||
) -> "BcfXml":
|
||||
"""
|
||||
Create a new BcfXml object.
|
||||
|
||||
@@ -262,7 +261,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,20 +14,16 @@
|
||||
# Currently extensions support for v2 is only read-only.
|
||||
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import Optional
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: list[str] = field(
|
||||
priority: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
@@ -39,12 +35,12 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: list[str] = field(
|
||||
snippet_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
@@ -56,12 +52,12 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: list[str] = field(
|
||||
stage: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
@@ -73,12 +69,12 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: list[str] = field(
|
||||
topic_label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
@@ -90,12 +86,12 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: list[str] = field(
|
||||
topic_status: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
@@ -107,12 +103,12 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: list[str] = field(
|
||||
topic_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
@@ -124,12 +120,12 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: list[str] = field(
|
||||
user: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "UserIdType",
|
||||
@@ -141,7 +137,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -41,7 +38,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -56,7 +53,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFile:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -112,7 +109,7 @@ class HeaderFile:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReference:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -150,7 +147,7 @@ class TopicDocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -165,7 +162,7 @@ class TopicRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -201,7 +198,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -261,9 +258,9 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
file: list[HeaderFile] = field(
|
||||
file: List[HeaderFile] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
@@ -274,9 +271,9 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_link: list[str] = field(
|
||||
reference_link: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
@@ -308,7 +305,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
labels: list[str] = field(
|
||||
labels: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Labels",
|
||||
@@ -388,7 +385,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
document_reference: list[TopicDocumentReference] = field(
|
||||
document_reference: List[TopicDocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
@@ -396,7 +393,7 @@ class Topic:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
related_topic: list[TopicRelatedTopic] = field(
|
||||
related_topic: List[TopicRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
@@ -428,7 +425,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
@@ -446,7 +443,7 @@ class Markup:
|
||||
"required": True,
|
||||
}
|
||||
)
|
||||
comment: list[Comment] = field(
|
||||
comment: List[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
@@ -454,7 +451,7 @@ class Markup:
|
||||
"namespace": "",
|
||||
},
|
||||
)
|
||||
viewpoints: list[ViewPoint] = field(
|
||||
viewpoints: List[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Viewpoints",
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
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(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -24,7 +21,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectExtension:
|
||||
project: Optional[Project] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
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(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
detailed_version: Optional[str] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
@@ -11,7 +8,7 @@ class BitmapFormat(Enum):
|
||||
JPG = "JPG"
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -38,7 +35,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -63,7 +60,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -88,7 +85,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
@@ -113,7 +110,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -131,12 +128,12 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -154,9 +151,9 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -166,12 +163,12 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -181,7 +178,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -199,7 +196,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -239,7 +236,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -284,7 +281,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmap:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -333,9 +330,9 @@ class VisualizationInfoBitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: list[ComponentColoringColor] = field(
|
||||
color: List[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
@@ -345,7 +342,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
exceptions: Optional[ComponentVisibilityExceptions] = field(
|
||||
default=None,
|
||||
@@ -363,12 +360,12 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: list[ClippingPlane] = field(
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
@@ -377,12 +374,12 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: list[Line] = field(
|
||||
line: List[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
@@ -392,7 +389,7 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -424,7 +421,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
@@ -465,7 +462,7 @@ class VisualizationInfo:
|
||||
"type": "Element",
|
||||
},
|
||||
)
|
||||
bitmap: list[VisualizationInfoBitmap] = field(
|
||||
bitmap: List[VisualizationInfoBitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""BCF XML V2 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import datetime
|
||||
import tempfile
|
||||
import uuid
|
||||
@@ -153,7 +152,7 @@ class TopicHandler:
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> TopicHandler:
|
||||
) -> "TopicHandler":
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
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.parse
|
||||
import urllib
|
||||
import uuid
|
||||
import webbrowser
|
||||
from re import A
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
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(b"You have now authenticated :) You may now close this browser window.")
|
||||
self.wfile.write("You have now authenticated :) You may now close this browser window.".encode("utf-8"))
|
||||
|
||||
|
||||
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,6 +1,5 @@
|
||||
"""BCF XML V3 handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
@@ -31,7 +30,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:
|
||||
@@ -117,7 +116,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.
|
||||
|
||||
@@ -142,7 +141,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.
|
||||
|
||||
@@ -240,7 +239,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,11 +1,8 @@
|
||||
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}
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Document:
|
||||
filename: str = field(
|
||||
metadata={
|
||||
@@ -37,12 +34,12 @@ class Document:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfoDocuments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document: list[Document] = field(
|
||||
document: List[Document] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Document",
|
||||
@@ -52,7 +49,7 @@ class DocumentInfoDocuments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfo:
|
||||
documents: Optional[DocumentInfoDocuments] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
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}
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
priority: list[str] = field(
|
||||
priority: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Priority",
|
||||
@@ -22,12 +19,12 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
snippet_type: list[str] = field(
|
||||
snippet_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "SnippetType",
|
||||
@@ -39,12 +36,12 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
stage: list[str] = field(
|
||||
stage: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Stage",
|
||||
@@ -56,12 +53,12 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_label: list[str] = field(
|
||||
topic_label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicLabel",
|
||||
@@ -73,12 +70,12 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_status: list[str] = field(
|
||||
topic_status: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicStatus",
|
||||
@@ -90,12 +87,12 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
topic_type: list[str] = field(
|
||||
topic_type: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "TopicType",
|
||||
@@ -107,12 +104,12 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
user: list[str] = field(
|
||||
user: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "User",
|
||||
@@ -124,7 +121,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -47,7 +44,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -62,7 +59,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentReference:
|
||||
document_guid: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -103,7 +100,7 @@ class DocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class File:
|
||||
filename: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -160,12 +157,12 @@ class File:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
label: list[str] = field(
|
||||
label: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Label",
|
||||
@@ -177,12 +174,12 @@ class TopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicReferenceLinks:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
reference_link: list[str] = field(
|
||||
reference_link: List[str] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ReferenceLink",
|
||||
@@ -194,7 +191,7 @@ class TopicReferenceLinks:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopicsRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -209,7 +206,7 @@ class TopicRelatedTopicsRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -249,7 +246,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -315,12 +312,12 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFiles:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
file: list[File] = field(
|
||||
file: List[File] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "File",
|
||||
@@ -330,12 +327,12 @@ class HeaderFiles:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReferences:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
document_reference: list[DocumentReference] = field(
|
||||
document_reference: List[DocumentReference] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "DocumentReference",
|
||||
@@ -345,12 +342,12 @@ class TopicDocumentReferences:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopics:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
related_topic: list[TopicRelatedTopicsRelatedTopic] = field(
|
||||
related_topic: List[TopicRelatedTopicsRelatedTopic] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "RelatedTopic",
|
||||
@@ -360,12 +357,12 @@ class TopicRelatedTopics:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicViewpoints:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
view_point: list[ViewPoint] = field(
|
||||
view_point: List[ViewPoint] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ViewPoint",
|
||||
@@ -375,7 +372,7 @@ class TopicViewpoints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
files: Optional[HeaderFiles] = field(
|
||||
default=None,
|
||||
@@ -387,12 +384,12 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicComments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
comment: list[Comment] = field(
|
||||
comment: List[Comment] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Comment",
|
||||
@@ -402,7 +399,7 @@ class TopicComments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_links: Optional[TopicReferenceLinks] = field(
|
||||
default=None,
|
||||
@@ -599,7 +596,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
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(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -28,7 +25,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectInfo:
|
||||
project: Project = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
version_id: str = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
@@ -12,7 +8,7 @@ class BitmapFormat(Enum):
|
||||
JPG = "jpg"
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -43,7 +39,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -68,7 +64,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -93,7 +89,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: bool = field(
|
||||
default=False,
|
||||
@@ -118,7 +114,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Bitmap:
|
||||
format: BitmapFormat = field(
|
||||
metadata={
|
||||
@@ -166,7 +162,7 @@ class Bitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -184,12 +180,12 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColorComponents:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -199,9 +195,9 @@ class ComponentColoringColorComponents:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -210,12 +206,12 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
component: list[Component] = field(
|
||||
component: List[Component] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Component",
|
||||
@@ -224,7 +220,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -242,7 +238,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -292,7 +288,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -348,7 +344,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -370,7 +366,7 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -395,12 +391,12 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmaps:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
bitmap: list[Bitmap] = field(
|
||||
bitmap: List[Bitmap] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Bitmap",
|
||||
@@ -409,12 +405,12 @@ class VisualizationInfoBitmaps:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
clipping_plane: list[ClippingPlane] = field(
|
||||
clipping_plane: List[ClippingPlane] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "ClippingPlane",
|
||||
@@ -423,12 +419,12 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
|
||||
line: list[Line] = field(
|
||||
line: List[Line] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Line",
|
||||
@@ -437,9 +433,9 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: list[ComponentColoringColor] = field(
|
||||
color: List[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "Color",
|
||||
@@ -448,7 +444,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
selection: Optional[ComponentSelection] = field(
|
||||
default=None,
|
||||
@@ -473,7 +469,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""BCF XML V3 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
@@ -92,12 +91,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 {}
|
||||
@@ -140,7 +139,7 @@ class TopicHandler:
|
||||
topic_type: str = "",
|
||||
topic_status: str = "",
|
||||
xml_handler: Optional[AbstractXmlParserSerializer] = None,
|
||||
) -> TopicHandler:
|
||||
) -> "TopicHandler":
|
||||
"""
|
||||
Create a new BCF topic.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Iterable, Optional, Literal, Union
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""XML Parser and Serializer factories."""
|
||||
|
||||
from typing import Optional, Protocol, TypeVar
|
||||
from typing import Optional, Protocol, Type, 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,7 +16,6 @@ dependencies = [
|
||||
"xsdata>=24.4",
|
||||
"numpy",
|
||||
"ifcopenshell",
|
||||
"requests",
|
||||
]
|
||||
version = "0.0.0"
|
||||
classifiers = [
|
||||
|
||||
+63
-16
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
SHELL := sh
|
||||
PYTHON:=python3.11
|
||||
PIP:=pip3.11
|
||||
PATCH:=patch
|
||||
@@ -47,7 +46,6 @@ VERSION_MINOR:=$(shell cat '../../VERSION' | cut -d '.' -f 2)
|
||||
VERSION_PATCH:=$(shell cat '../../VERSION' | cut -d '.' -f 3)
|
||||
VERSION_DATE:=$(shell date '+%y%m%d')
|
||||
LAST_COMMIT_HASH:=$(shell git rev-parse HEAD)
|
||||
LAST_COMMIT_DATE:=$(shell git show -s --format=%cI)
|
||||
PYVERSION:=py310
|
||||
PYPI_IMP:=cp
|
||||
|
||||
@@ -87,7 +85,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=ec13294
|
||||
OLD:=1d27161
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -138,7 +136,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 >=0.8, <0.10" --dest=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download cjio --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,15 +191,13 @@ 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
|
||||
cd build && wget -O ifc2json.zip https://github.com/IFCJSON-Team/IFC2JSON_python/archive/refs/heads/master.zip
|
||||
# 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 && 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 && \
|
||||
@@ -237,7 +233,7 @@ endif
|
||||
|
||||
# required for three-way git merging
|
||||
ifeq ($(PLATFORM), win)
|
||||
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2025-01-26/ifcmerge.zip
|
||||
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2024-06-24/ifcmerge.zip
|
||||
cd build/bonsai/libs/bin && unzip ifcmerge.zip && rm ifcmerge.zip
|
||||
else
|
||||
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge
|
||||
@@ -258,7 +254,6 @@ ifeq ($(IS_STABLE), TRUE)
|
||||
else
|
||||
$(SED) "s/0.0.0/$(VERSION)-alpha$(VERSION_DATE)/" build/bonsai/blender_manifest.toml
|
||||
$(SED) "s/8888888/$(LAST_COMMIT_HASH)/" build/bonsai/__init__.py
|
||||
$(SED) "s/9999999/$(LAST_COMMIT_DATE)/" build/bonsai/__init__.py
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' build/pyproject.toml
|
||||
endif
|
||||
|
||||
@@ -283,6 +278,64 @@ endif
|
||||
fi
|
||||
mv build/wheels/*.whl build/bonsai/wheels/
|
||||
|
||||
# Ugly patch to accomodate MacOS universal builds not being recognized by Blender.
|
||||
# Blender doesn't look into the wheel contents, it just checks the name.
|
||||
# So this hack works without working out some way to repack those wheels.
|
||||
# See: https://projects.blender.org/blender/blender/issues/125091
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
# Has universal and x86_64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/lxml-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed -E "s/macosx_10_([0-9]+)_x86_64\.macosx_11_0_arm64\.macosx_10_\1_universal2/macosx_10_\1_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# Has only universal build.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/greenlet-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# has universal and x86_64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/fonttools-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# Has only universal build.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/charset_normalizer-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
# Has universal and arm64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/MarkupSafe-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed -E "s/macosx_10_([0-9]+)_x86_64\.macosx_11_0_arm64\.macosx_10_\1_universal2/macosx_10_\1_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/greenlet-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/charset_normalizer-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
endif
|
||||
|
||||
# Safeguard for unhandled universal files.
|
||||
wheels=$$(find build/bonsai/wheels/*universal2*.whl); \
|
||||
if [ -n "$$wheels" ]; then \
|
||||
echo "Found universal2 wheel files:"; \
|
||||
echo "$$wheels"; \
|
||||
echo "Error: universal2 wheel files are not supported by Blender!"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Temporary workaround for Blender not handling non-3.11 wheels #5743.
|
||||
# Use '-n' as on macos x86-64 doesn't have a binary wheel and it autoincludes 'cp311'.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
@@ -349,12 +402,6 @@ else
|
||||
pytest test/tool/test_$(MODULE).py
|
||||
endif
|
||||
|
||||
# Reregistering test is not added to the standard test suite because during unregister
|
||||
# Blender removes all Bonsai dependencies breaking dev-environment symlinks.
|
||||
.PHONY: test-reregister
|
||||
test-reregister:
|
||||
blender --python scripts/reregister_bonsai.py
|
||||
|
||||
.PHONY: qa
|
||||
qa:
|
||||
black .
|
||||
|
||||
Binary file not shown.
@@ -28,10 +28,6 @@ IN_BLENDER = sys.modules.get("bpy", None)
|
||||
if IN_BLENDER:
|
||||
import bpy
|
||||
|
||||
# This file is executed twice - first as a bonsai-extension
|
||||
# and then as a bonsai-package.
|
||||
IN_PACKAGE = __package__ == "bonsai"
|
||||
|
||||
import re
|
||||
import platform
|
||||
import traceback
|
||||
@@ -40,12 +36,10 @@ import uuid
|
||||
import shutil
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import Union, Any
|
||||
from collections.abc import Generator
|
||||
from typing import Union, Any, Generator
|
||||
|
||||
|
||||
last_commit_hash = "8888888"
|
||||
last_commit_date = "9999999"
|
||||
|
||||
|
||||
def get_last_commit_hash() -> Union[str, None]:
|
||||
@@ -57,12 +51,6 @@ def get_last_commit_hash() -> Union[str, None]:
|
||||
return last_commit_hash[:7]
|
||||
|
||||
|
||||
def get_last_commit_date() -> Union[str, None]:
|
||||
if last_commit_date == str(9_999999):
|
||||
return None
|
||||
return last_commit_date
|
||||
|
||||
|
||||
# Accessed from bonsai extension:
|
||||
bbim_semver: dict[str, Any] = {}
|
||||
|
||||
@@ -108,7 +96,6 @@ def get_debug_info():
|
||||
"blender_version": bpy.app.version_string,
|
||||
"bonsai_version": bbim_version,
|
||||
"bonsai_commit_hash": get_last_commit_hash(),
|
||||
"bonsai_commit_date": get_last_commit_date(),
|
||||
"last_actions": last_actions,
|
||||
"last_error": last_error,
|
||||
}
|
||||
@@ -223,21 +210,16 @@ if IN_BLENDER:
|
||||
info["binary_python_version"] = version
|
||||
return info
|
||||
|
||||
def update_commit_data() -> None:
|
||||
try:
|
||||
import git
|
||||
try:
|
||||
import git
|
||||
|
||||
global last_commit_hash
|
||||
global last_commit_date
|
||||
path = Path(__file__).resolve().parent
|
||||
repo = git.Repo(str(path), search_parent_directories=True)
|
||||
last_commit_hash = repo.head.object.hexsha
|
||||
last_commit_date = repo.head.object.committed_datetime.isoformat()
|
||||
except:
|
||||
pass
|
||||
|
||||
if IN_PACKAGE:
|
||||
update_commit_data()
|
||||
# We can't just use __file__ as bonsai/__init__.py is typically not symlinked
|
||||
# as Blender have errors symlinking main addon package file.
|
||||
path = Path(__file__).resolve().parent
|
||||
repo = git.Repo(str(path), search_parent_directories=True)
|
||||
last_commit_hash = repo.head.object.hexsha
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
import ifcopenshell.api
|
||||
@@ -253,12 +235,6 @@ if IN_BLENDER:
|
||||
|
||||
ifcopenshell.api.add_pre_listener("*", "action_logger", log_api)
|
||||
|
||||
def purge_cache():
|
||||
"""Purge cache left from previous session (e.g. after reload or update)."""
|
||||
import bonsai.tool as tool
|
||||
|
||||
tool.Blender.get_bonsai_version.cache_clear()
|
||||
|
||||
def register():
|
||||
if platform.system() == "Windows":
|
||||
clean_up_dlls_safe_links()
|
||||
@@ -276,7 +252,6 @@ if IN_BLENDER:
|
||||
bonsai.REINSTALLED_BBIM_VERSION = current_version
|
||||
|
||||
bonsai.bim.register()
|
||||
purge_cache()
|
||||
|
||||
def unregister():
|
||||
if platform.system() == "Windows":
|
||||
|
||||
@@ -20,10 +20,9 @@ import os
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import importlib
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from . import handler, ui, prop, operator
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
from bonsai.translations import translations_dict
|
||||
@@ -85,7 +84,6 @@ modules = {
|
||||
"covering": None,
|
||||
"web": None,
|
||||
"light": None,
|
||||
"alignment": None,
|
||||
# Uncomment this line to enable loading of the demo module. Happy hacking!
|
||||
# The name "demo" must correlate to a folder name in `bim/module/`.
|
||||
# "demo": None,
|
||||
@@ -102,7 +100,6 @@ classes = [
|
||||
operator.BIM_OT_delete_object,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_select_entity,
|
||||
operator.BIM_OT_select_entity_by_guid,
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.BIM_OT_multiple_file_selector,
|
||||
@@ -119,12 +116,13 @@ classes = [
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SelectDir,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectCacheDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
operator.SetTab,
|
||||
operator.SwitchTab,
|
||||
operator.ShowSystemInfo,
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
prop.ObjProperty,
|
||||
@@ -153,10 +151,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,
|
||||
@@ -225,12 +223,7 @@ def on_register(scene):
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
# Prevent crashes in Blender 4.4.0, see #6420.
|
||||
if issubclass(cls, (ImportHelper, ExportHelper)):
|
||||
assert getattr(cls, "bl_description", "") or cls.__doc__, cls
|
||||
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_register)
|
||||
bpy.app.handlers.undo_post.append(handler.undo_post)
|
||||
bpy.app.handlers.redo_post.append(handler.redo_post)
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
@@ -145,7 +147,7 @@
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "THEME",
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
@@ -160,6 +162,7 @@
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
@@ -186,6 +189,7 @@
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
@@ -204,6 +208,7 @@
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
@@ -293,6 +298,8 @@
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
@@ -391,7 +398,7 @@
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "THEME",
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
@@ -406,6 +413,7 @@
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
@@ -432,6 +440,7 @@
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
@@ -450,6 +459,7 @@
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": false,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
@@ -510,7 +520,7 @@
|
||||
"scene.render.border_min_x": 0.0,
|
||||
"scene.render.border_min_y": 0.0,
|
||||
"scene.render.dither_intensity": 1.0,
|
||||
"scene.render.engine": "BLENDER_EEVEE_NEXT",
|
||||
"scene.render.engine": "BLENDER_EEVEE",
|
||||
"scene.render.film_transparent": false,
|
||||
"scene.render.filter_size": 1.5,
|
||||
"scene.render.fps": 24,
|
||||
@@ -539,6 +549,8 @@
|
||||
"scene.render.simplify_gpencil_shader_fx": true,
|
||||
"scene.render.simplify_gpencil_tint": true,
|
||||
"scene.render.simplify_gpencil_view_fill": true,
|
||||
"scene.render.simplify_shadows": 1.0,
|
||||
"scene.render.simplify_shadows_render": 1.0,
|
||||
"scene.render.simplify_subdivision": 6,
|
||||
"scene.render.simplify_subdivision_render": 6,
|
||||
"scene.render.simplify_volumes": 1.0,
|
||||
@@ -637,7 +649,7 @@
|
||||
"scene.display.shading.use_scene_world_render": false,
|
||||
"scene.display.shading.use_studiolight_view_rotation": true,
|
||||
"scene.display.shading.use_world_space_lighting": false,
|
||||
"scene.display.shading.wireframe_color_type": "THEME",
|
||||
"scene.display.shading.wireframe_color_type": "MATERIAL",
|
||||
"scene.display.shading.xray_alpha": 0.5,
|
||||
"scene.display.shading.xray_alpha_wireframe": 0.0,
|
||||
"scene.display.light_direction": [
|
||||
@@ -652,6 +664,7 @@
|
||||
"scene.display.shadow_focus": 0.0,
|
||||
"scene.display.shadow_shift": 0.10000000149011612,
|
||||
"scene.display.viewport_aa": "FXAA",
|
||||
"space.overlay.backwire_opacity": 0.5,
|
||||
"space.overlay.bone_wire_alpha": 1.0,
|
||||
"space.overlay.display_handle": "SELECTED",
|
||||
"space.overlay.fade_inactive_alpha": 0.4000000059604645,
|
||||
@@ -678,6 +691,7 @@
|
||||
"space.overlay.show_edge_crease": true,
|
||||
"space.overlay.show_edge_seams": true,
|
||||
"space.overlay.show_edge_sharp": true,
|
||||
"space.overlay.show_edges": false,
|
||||
"space.overlay.show_extra_edge_angle": false,
|
||||
"space.overlay.show_extra_edge_length": false,
|
||||
"space.overlay.show_extra_face_angle": false,
|
||||
@@ -696,6 +710,7 @@
|
||||
"space.overlay.show_motion_paths": true,
|
||||
"space.overlay.show_object_origins": true,
|
||||
"space.overlay.show_object_origins_all": false,
|
||||
"space.overlay.show_occlude_wire": false,
|
||||
"space.overlay.show_onion_skins": false,
|
||||
"space.overlay.show_ortho_grid": true,
|
||||
"space.overlay.show_outline_selected": true,
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 420 B |
Binary file not shown.
|
Before Width: | Height: | Size: 415 B |
Binary file not shown.
|
Before Width: | Height: | Size: 519 B |
Binary file not shown.
|
Before Width: | Height: | Size: 513 B |
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1');
|
||||
FILE_NAME('/dev/null','2025-03-27T17:30:27+05:00',(''),(''),'IfcOpenShell 0.0.0','IfcOpenShell 0.0.0','Nobody');
|
||||
FILE_NAME('/dev/null','2024-03-03T16:03:58+11:00',(),(),'IfcOpenShell v0.7.0-eaa2aa05a','IfcOpenShell v0.7.0-eaa2aa05a','Nobody');
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
@@ -29,7 +29,7 @@ DATA;
|
||||
#22=IFCMATERIALLAYERSET((#24),$,$);
|
||||
#23=IFCRELASSOCIATESMATERIAL('0khP_Smdj12f2JrOTRbRKj',$,$,$,(#21),#22);
|
||||
#24=IFCMATERIALLAYER(#20,0.05,$,$,$,$,$);
|
||||
#25=IFCRELDECLARES('31JriXdev6BP2sC9lcxdC1',$,$,$,#2,(#59,#92,#38,#82,#30,#96,#77,#63,#42,#54,#87,#153,#34,#21,#67,#210,#26,#48,#72));
|
||||
#25=IFCRELDECLARES('31JriXdev6BP2sC9lcxdC1',$,$,$,#2,(#26,#48,#72,#59,#92,#30,#96,#63,#21,#54,#87,#210,#153,#34,#67,#38,#82,#77,#42));
|
||||
#26=IFCWALLTYPE('3R5onkTtX1IxdCMPoThRaw',$,'WAL100',$,$,$,$,$,$,.NOTDEFINED.);
|
||||
#27=IFCMATERIALLAYERSET((#29),$,$);
|
||||
#28=IFCRELASSOCIATESMATERIAL('0$mXRPCT16nP6Q70XFNvZv',$,$,$,(#26),#27);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION($,'2;1');
|
||||
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',$,$,'EPset_Drawing','EPset_Drawing',$);
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset_Drawing',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2));
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#2,#3,#4,#5,#6,#7,#8,#9,#10,#11,#12,#13,#14,#15,#16,#17,#18,#19,#20,#21,#22,#23,#24,#25,#26,#27));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -14,7 +14,7 @@ DATA;
|
||||
#7=IFCSIMPLEPROPERTYTEMPLATE('2j2ZEZR8X5tONm7kli5hM6',$,'HasLinework','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#8=IFCSIMPLEPROPERTYTEMPLATE('1ttChRysH9UuEX2FeMj5Hu',$,'HasAnnotation','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#9=IFCSIMPLEPROPERTYTEMPLATE('2NPPxuABv1huDTVh32TFgw',$,'GlobalReferencing','',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','Comma separated list of selector expressions to evaluate for each drawing elementand add results to their ''class'' attribute.\X2\000A\X0\E.g. ''Name, id'' would add to ''class'' value similar to ''Name-Wall id-1220''.\X2\000A\X0\Then it can be used to applied css styles based on the resulting class.\X2\000A\X0\If attribute is not present on the element, then it won''t be added to it''s ''class''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#10=IFCSIMPLEPROPERTYTEMPLATE('10hT_1zrzEbRRKMXYAWvtD',$,'Metadata','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#11=IFCSIMPLEPROPERTYTEMPLATE('3Z0BXPSG5CWgtI33ioV7aj',$,'Include','Selector expression to include ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#12=IFCSIMPLEPROPERTYTEMPLATE('1RVts_g3PAw98PJA2yL3bO',$,'Exclude','Selector expression to exclude ifc elements in the drawing',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#13=IFCSIMPLEPROPERTYTEMPLATE('0c1$8NpYDEaBiJrj16jHIo',$,'Stylesheet','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
@@ -32,6 +32,5 @@ DATA;
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('3LHwCrOcb6Y8ozfJZ7Ay$c',$,'LineworkMode','Method to use for line work',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('2iwERDOW55Pf4hCbuFRe1Q',$,'FillMode','Method to fill areas seen in projection',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('1YF$qLzBzF19Io8aB2N8cE',$,'CutMode','Method for cutting geometry',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#28=IFCSIMPLEPROPERTYTEMPLATE('1YSnFzurrEyRNtoLdmmddP',$,'BringToFront','The objects with these SVG classes will render in front of all other objects.Ex: IfcBeam, IfcColumn',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -28,13 +28,11 @@ DATA;
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('1UDakJ5_f7kBhggNSW4$h5',$,'SymbolsPath','Default symbols SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('0d53LEtgLDQxnv__NfgH7i',$,'PatternsPath','Default patterns SVG',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('26qFNMv7nCHgU6Jd7Anga5',$,'ShadingStylesPath','Default shading styles',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/DIMENSION,IfcAnnotation/RADIUS,IfcAnnotation/DIAMETER,IfcTypeProduct',(#25,#26,#27,#28,#30));
|
||||
#24=IFCPROPERTYSETTEMPLATE('0I9merLinF5Ap$aZwaclgm',$,'BBIM_Dimension','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation/DIMENSION,IfcTypeProduct',(#25,#26,#27,#28));
|
||||
#25=IFCSIMPLEPROPERTYTEMPLATE('1rL2AbQsXD8RbpoWH5pYOV',$,'ShowDescriptionOnly','Hide the measurement values and show only annotation description',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#26=IFCSIMPLEPROPERTYTEMPLATE('0SVyOfB0rC2xNfdRYf3XvY',$,'SuppressZeroInches','Suppress 0 inch values in dimension annotation text (for example: 12'' - 0" -> 12'')',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
|
||||
#27=IFCSIMPLEPROPERTYTEMPLATE('2bUmj458PBqPAtUoI3MXsb',$,'TextPrefix','Text to add before annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#28=IFCSIMPLEPROPERTYTEMPLATE('0bnzttUb9BPuN597uNTXOE',$,'TextSuffix','Text to add after annotation measurement value',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#29=IFCSIMPLEPROPERTYTEMPLATE('2pJmUDpB50VBdCOib1zcJJ',$,'Newline_At','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#30=IFCSIMPLEPROPERTYTEMPLATE('2TJn72t_v2cvBUG916Dpev',$,'CustomUnit','Dimension''s custom unit',.P_ENUMERATEDVALUE.,'IfcText',$,#31,$,$,$,.READWRITE.);
|
||||
#31=IFCPROPERTYENUMERATION('CustomUnit',(IFCTEXT('Feet and Inches - Fractional'),IFCTEXT('Feet - Decimal'),IFCTEXT('Inches - Fractional'),IFCTEXT('Inches - Decimal'),IFCTEXT('Meters'),IFCTEXT('Decimeters'),IFCTEXT('Centimeters'),IFCTEXT('Millimeters')),$);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../libraries/IFC2X3 Demo Library.ifc
|
||||
@@ -1 +0,0 @@
|
||||
../../libraries/IFC4 Demo Library.ifc
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
../../libraries/IFC4X3 Demo Library.ifc
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/static/js/jquery.min.js"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/static/js/jquery.min.js"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/static/js/jquery.min.js"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/static/js/jquery.min.js"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/static/js/jquery.min.js"
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
|
||||
@@ -20,6 +20,7 @@ from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
@@ -47,6 +48,7 @@ class IfcExporter:
|
||||
self.set_header()
|
||||
IfcStore.update_cache()
|
||||
self.sync_all_objects()
|
||||
self.sync_edited_objects()
|
||||
tool.Project.save_linked_models_to_ifc()
|
||||
extension = self.ifc_export_settings.output_file.split(".")[-1].lower()
|
||||
if extension == "ifczip":
|
||||
@@ -76,7 +78,11 @@ 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.UTC).astimezone().replace(microsecond=0).isoformat()
|
||||
datetime.datetime.utcnow()
|
||||
.replace(tzinfo=datetime.timezone.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(
|
||||
@@ -96,14 +102,42 @@ class IfcExporter:
|
||||
result = self.sync_object_placement(obj)
|
||||
if result:
|
||||
results.append(result)
|
||||
result = self.sync_object_material(obj)
|
||||
# TODO: sync_object_material always returns None
|
||||
# so it's never really appended
|
||||
if result:
|
||||
results.append(result)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
return results
|
||||
|
||||
def sync_edited_objects(self) -> list[ifcopenshell.entity_instance]:
|
||||
results: list[ifcopenshell.entity_instance] = []
|
||||
for obj in IfcStore.edited_objs.copy():
|
||||
if not obj:
|
||||
continue
|
||||
if not tool.Blender.is_valid_data_block(obj):
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
results.append(element)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
IfcStore.edited_objs.clear()
|
||||
return results
|
||||
|
||||
def sync_object_material(self, obj: bpy.types.Object) -> None:
|
||||
if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
|
||||
return
|
||||
if not self.has_changed_materials(obj):
|
||||
return
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
|
||||
checksum = obj.data.BIMMeshProperties.material_checksum
|
||||
return checksum != tool.Geometry.get_material_checksum(obj)
|
||||
|
||||
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = self.file.by_id(tool.Blender.get_object_bim_props(obj).ifc_definition_id)
|
||||
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if tool.Geometry.is_scaled(obj):
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
# update_representation might not apply scale if the object has openings
|
||||
|
||||
@@ -24,18 +24,19 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.api.owner.settings
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
import weakref
|
||||
import bonsai.core.owner as core_owner
|
||||
from bpy.app.handlers import persistent
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.owner.prop import get_user_person, get_user_organisation
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
from bonsai.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
|
||||
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
|
||||
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
|
||||
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
|
||||
from bonsai.bim.module.nest.decorator import NestDecorator
|
||||
from mathutils import Vector
|
||||
from math import cos
|
||||
from typing import Union
|
||||
from collections.abc import Callable
|
||||
from math import cos, degrees
|
||||
from typing import Union, Callable
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
@@ -55,24 +56,23 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
|
||||
return
|
||||
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
props = tool.Style.get_material_style_props(obj)
|
||||
props = obj.BIMStyleProperties
|
||||
if ifc_definition_id := props.ifc_definition_id:
|
||||
if props.is_renaming:
|
||||
props.is_renaming = False
|
||||
props.is_renmaing = False
|
||||
return
|
||||
tool.Ifc.get().by_id(ifc_definition_id).Name = obj.name
|
||||
IfcStore.get_file().by_id(ifc_definition_id).Name = obj.name
|
||||
refresh_ui_data()
|
||||
return
|
||||
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
if not props.ifc_definition_id:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
|
||||
if props.is_renaming:
|
||||
props.is_renaming = False
|
||||
if obj.BIMObjectProperties.is_renaming:
|
||||
obj.BIMObjectProperties.is_renaming = False
|
||||
return
|
||||
|
||||
element = tool.Ifc.get().by_id(props.ifc_definition_id)
|
||||
element = IfcStore.get_file().by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
if "/" in obj.name:
|
||||
object_name = obj.name
|
||||
element_name = obj.name.split("/", 1)[1]
|
||||
@@ -88,8 +88,8 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
|
||||
if not element.is_a("IfcRoot"):
|
||||
return
|
||||
element.Name = element_name
|
||||
if props.collection:
|
||||
props.collection.name = object_name
|
||||
if obj.BIMObjectProperties.collection:
|
||||
obj.BIMObjectProperties.collection.name = object_name
|
||||
refresh_ui_data()
|
||||
|
||||
|
||||
@@ -108,44 +108,29 @@ def update_bim_tool_props():
|
||||
return
|
||||
mode = bpy.context.mode
|
||||
current_tool = bpy.context.workspace.tools.from_space_view3d_mode(mode)
|
||||
if not current_tool or current_tool.idname not in tool.Blender.get_list_of_tools():
|
||||
if not current_tool or current_tool.idname not in LIST_OF_TOOLS:
|
||||
return
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
|
||||
props = tool.Model.get_model_props()
|
||||
aprops = tool.Drawing.get_annotation_props()
|
||||
TOOLS_TO_CLASSES_MAP = tool.Blender.get_tools_to_classes_map()
|
||||
is_annotation_tool = current_tool.idname == "bim.annotation_tool"
|
||||
if element.is_a("IfcTypeProduct") or element.is_a("IfcProduct"):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
is_bim_tool = current_tool.idname == "bim.bim_tool"
|
||||
|
||||
if is_annotation_tool and (object_type := tool.Drawing.get_annotation_type_object_type(element_type)):
|
||||
aprops.object_type = object_type
|
||||
aprops.relating_type_id = str(element_type.id())
|
||||
return
|
||||
|
||||
if is_bim_tool:
|
||||
props.ifc_class = element_type.is_a()
|
||||
|
||||
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
|
||||
props.relating_type_id = str(element_type.id())
|
||||
|
||||
if is_annotation_tool:
|
||||
return
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
|
||||
props = bpy.context.scene.BIMModelProperties
|
||||
if element.is_a("IfcElementType") or element.is_a("IfcElement"):
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
is_bim_tool = current_tool.idname == "bim.bim_tool"
|
||||
if is_bim_tool:
|
||||
props.ifc_class = element_type.is_a()
|
||||
if is_bim_tool or TOOLS_TO_CLASSES_MAP.get(current_tool.idname) == element_type.is_a():
|
||||
props.relating_type_id = str(element_type.id())
|
||||
extrusion = tool.Model.get_extrusion(representation)
|
||||
if not extrusion:
|
||||
return
|
||||
|
||||
def get_x_angle(extrusion: ifcopenshell.entity_instance) -> float:
|
||||
def get_x_angle(extrusion):
|
||||
x, y, z = extrusion.ExtrudedDirection.DirectionRatios
|
||||
x_angle = Vector((0, 1)).angle_signed(Vector((y, z)))
|
||||
return x_angle
|
||||
@@ -203,9 +188,6 @@ def refresh_ui_data():
|
||||
and it need to be refreshed manually if needed.
|
||||
"""
|
||||
from bonsai.bim import modules
|
||||
import bonsai.bim.ui
|
||||
|
||||
bonsai.bim.ui.refresh()
|
||||
|
||||
for name, value in modules.items():
|
||||
try:
|
||||
@@ -222,113 +204,71 @@ def refresh_ui_data():
|
||||
if isinstance(tool.Ifc.get(), ifcopenshell.sqlite):
|
||||
tool.Ifc.get().clear_cache()
|
||||
|
||||
props = tool.Drawing.get_document_props()
|
||||
props.should_draw_decorations = props.should_draw_decorations
|
||||
bpy.context.scene.DocProperties.should_draw_decorations = bpy.context.scene.DocProperties.should_draw_decorations
|
||||
if bpy.context.scene.WebProperties.is_connected:
|
||||
tool.Web.send_webui_data()
|
||||
|
||||
|
||||
@persistent
|
||||
def loadIfcStore(scene: bpy.types.Scene) -> None:
|
||||
def loadIfcStore(scene):
|
||||
IfcStore.purge()
|
||||
refresh_ui_data()
|
||||
if not tool.Ifc.get():
|
||||
if not IfcStore.get_file():
|
||||
return
|
||||
tool.Ifc.schema()
|
||||
IfcStore.get_schema()
|
||||
IfcStore.relink_all_objects()
|
||||
|
||||
|
||||
@persistent
|
||||
def undo_post(scene: bpy.types.Scene) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
if IfcStore.last_transaction != props.last_transaction:
|
||||
IfcStore.last_transaction = props.last_transaction
|
||||
IfcStore.undo(until_key=props.last_transaction)
|
||||
def undo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.undo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
refresh_ui_data()
|
||||
tool.Ifc.rebuild_element_maps()
|
||||
|
||||
|
||||
@persistent
|
||||
def redo_post(scene: bpy.types.Scene) -> None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
if IfcStore.last_transaction != props.last_transaction:
|
||||
IfcStore.last_transaction = props.last_transaction
|
||||
IfcStore.redo(until_key=props.last_transaction)
|
||||
def redo_post(scene):
|
||||
if IfcStore.last_transaction != bpy.context.scene.BIMProperties.last_transaction:
|
||||
IfcStore.last_transaction = bpy.context.scene.BIMProperties.last_transaction
|
||||
IfcStore.redo(until_key=bpy.context.scene.BIMProperties.last_transaction)
|
||||
refresh_ui_data()
|
||||
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:
|
||||
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]
|
||||
# TODO: cache this for even faster application retrieval. It honestly makes a difference on long scripts.
|
||||
version = tool.Blender.get_bonsai_version()
|
||||
for element in ifc.by_type("IfcApplication"):
|
||||
if element.ApplicationIdentifier == "Bonsai" and element.Version == version:
|
||||
return element
|
||||
application = ifcopenshell.api.owner.add_application(
|
||||
return ifcopenshell.api.run(
|
||||
"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 = ifcopenshell.api.owner.add_person(ifc)
|
||||
person = tool.Ifc.run("owner.add_person")
|
||||
if (organization := next(iter(ifc.by_type("IfcOrganization")), None)) is None:
|
||||
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)
|
||||
organization = tool.Ifc.run("owner.add_organisation")
|
||||
pao = tool.Ifc.run("owner.add_person_and_organisation", person=person, organisation=organization)
|
||||
return pao
|
||||
|
||||
|
||||
def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
|
||||
def viewport_shading_changed_callback(area):
|
||||
shading = area.spaces.active.shading.type
|
||||
if shading == "RENDERED":
|
||||
tool.Style.get_style_props().active_style_type = "External"
|
||||
bpy.context.scene.BIMStylesProperties.active_style_type = "External"
|
||||
|
||||
|
||||
def subscribe_to_viewport_shading_changes():
|
||||
@@ -383,19 +323,13 @@ def load_post(scene):
|
||||
tool.Blender.setup_tabs()
|
||||
|
||||
if tool.Ifc.get() and bpy.data.is_saved:
|
||||
props = tool.Blender.get_bim_props()
|
||||
props.has_blend_warning = True
|
||||
bpy.context.scene.BIMProperties.has_blend_warning = True
|
||||
|
||||
# Bonsai overlays
|
||||
georeference_props = tool.Georeference.get_georeference_props()
|
||||
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()
|
||||
georeference_props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
aggregate_props = bpy.context.scene.BIMAggregateProperties
|
||||
nest_props = bpy.context.scene.BIMNestProperties
|
||||
model_props = bpy.context.scene.BIMModelProperties
|
||||
if georeference_props.should_visualise:
|
||||
GeoreferenceDecorator.install(bpy.context)
|
||||
if aggregate_props.aggregate_decorator:
|
||||
@@ -407,7 +341,7 @@ def load_post(scene):
|
||||
if model_props.show_slab_direction:
|
||||
SlabDirectionDecorator.install(bpy.context)
|
||||
|
||||
if preferences.should_use_snap and (scene := bpy.context.scene):
|
||||
if scene := bpy.context.scene:
|
||||
# Snapping is off by default in Blender, but in BIM, it's more useful to be on
|
||||
scene.tool_settings.use_snap = True
|
||||
# Match default Bonsai snaps
|
||||
|
||||
@@ -20,20 +20,21 @@ from __future__ import annotations
|
||||
import importlib
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.doc import get_attribute_doc, get_predefined_type_doc, get_property_doc
|
||||
from mathutils import geometry
|
||||
from mathutils import Vector
|
||||
import bonsai.tool as tool
|
||||
from typing import Optional, Any, Union, TYPE_CHECKING
|
||||
from collections.abc import Callable, Iterable
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from typing import Optional, Callable, Any, Union, Iterable, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bonsai.bim.prop
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bonsai.bim.module.search.prop import BIMFilterGroup
|
||||
|
||||
# ImportCallback return values:
|
||||
# - None - property should be imported by default workflow
|
||||
@@ -47,7 +48,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def draw_attributes(
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: list[bonsai.bim.prop.Attribute],
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
@@ -77,8 +78,8 @@ def draw_attribute(
|
||||
elif value_name == "filepath_value":
|
||||
attribute.filepath_value.layout_file_select(layout, filter_glob=attribute.filter_glob, text=attribute.name)
|
||||
elif attribute.name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"):
|
||||
props = tool.Sequence.get_work_schedule_props()
|
||||
for item in props.durations_attributes:
|
||||
propis = bpy.context.scene.BIMWorkScheduleProperties
|
||||
for item in propis.durations_attributes:
|
||||
if item.name == attribute.name:
|
||||
duration_props = item
|
||||
layout.label(text=attribute.name)
|
||||
@@ -116,30 +117,26 @@ def draw_attribute(
|
||||
|
||||
def import_attributes(
|
||||
ifc_class: str,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
data: dict[str, Any],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
schema = tool.Ifc.schema()
|
||||
assert (entity := schema.declaration_by_name(ifc_class).as_entity())
|
||||
for attribute in entity.all_attributes():
|
||||
for attribute in IfcStore.get_schema().declaration_by_name(ifc_class).all_attributes():
|
||||
import_attribute(attribute, props, data, callback=callback)
|
||||
|
||||
|
||||
# A more elegant attribute importer signature, intended to supersede import_attributes
|
||||
def import_attributes2(
|
||||
element: Union[str, ifcopenshell.entity_instance],
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
assert (entity := tool.Ifc.schema().declaration_by_name(element).as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
attributes = tool.Ifc.schema().declaration_by_name(element).as_entity().all_attributes()
|
||||
info = {a.name(): None for a in attributes}
|
||||
info["type"] = element
|
||||
else:
|
||||
assert (entity := element.wrapped_data.declaration().as_entity())
|
||||
attributes = entity.all_attributes()
|
||||
attributes = element.wrapped_data.declaration().as_entity().all_attributes()
|
||||
info = element.get_info()
|
||||
for attribute in attributes:
|
||||
import_attribute(attribute, props, info, callback=callback)
|
||||
@@ -147,7 +144,7 @@ def import_attributes2(
|
||||
|
||||
def import_attribute(
|
||||
attribute: W.attribute,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
data: dict[str, Any],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
@@ -261,7 +258,7 @@ def add_attribute_description(attribute_blender: bonsai.bim.prop.Attribute, attr
|
||||
|
||||
|
||||
def export_attributes(
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
callback: Optional[ExportCallback] = None,
|
||||
) -> dict[str, Any]:
|
||||
attributes: dict[str, Any] = {}
|
||||
@@ -273,15 +270,6 @@ def export_attributes(
|
||||
return attributes
|
||||
|
||||
|
||||
def process_exported_entity_attribute(attributes: dict[str, Any], attribute_name: str) -> None:
|
||||
entity_id = attributes[attribute_name]
|
||||
if entity_id is None:
|
||||
# Maybe it was removed by now and enum is invalid.
|
||||
del attributes[attribute_name]
|
||||
else:
|
||||
attributes[attribute_name] = tool.Ifc.get().by_id(int(entity_id))
|
||||
|
||||
|
||||
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
|
||||
|
||||
|
||||
@@ -306,10 +294,7 @@ def prop_with_search(
|
||||
should_click_ok: bool = False,
|
||||
original_operator_path: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> bpy.types.UILayout:
|
||||
"""
|
||||
:return: Added row.
|
||||
"""
|
||||
):
|
||||
# kwargs are layout.prop arguments (text, icon, etc.)
|
||||
row = layout.row(align=True)
|
||||
row.prop(data, prop_name, **kwargs)
|
||||
@@ -323,7 +308,6 @@ def prop_with_search(
|
||||
op.original_operator_path = original_operator_path or ""
|
||||
except TypeError: # Prop is not iterable
|
||||
pass
|
||||
return row
|
||||
|
||||
|
||||
def get_enum_items(
|
||||
@@ -364,23 +348,8 @@ def get_enum_items(
|
||||
return items
|
||||
|
||||
|
||||
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)
|
||||
def draw_expandable_panel(layout, context, label: str, ui_func, default_closed: bool = True):
|
||||
header, panel = layout.panel(label, default_closed=default_closed)
|
||||
header.label(text=label)
|
||||
if panel:
|
||||
ui_func(panel, context)
|
||||
@@ -405,31 +374,20 @@ def convert_property_group_from_si(property_group: bpy.types.PropertyGroup, skip
|
||||
setattr(property_group, prop_name, prop_value)
|
||||
|
||||
|
||||
def draw_filter(
|
||||
layout: bpy.types.UILayout,
|
||||
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup],
|
||||
data,
|
||||
module: str,
|
||||
) -> None:
|
||||
def draw_filter(layout: bpy.types.UILayout, filter_groups, data, module: str) -> None:
|
||||
if not data.is_loaded:
|
||||
data.load()
|
||||
|
||||
sprops = tool.Search.get_search_props()
|
||||
sprops = bpy.context.scene.BIMSearchProperties
|
||||
|
||||
if tool.Ifc.get():
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{len(data.data['saved_searches'])} Saved Searches")
|
||||
|
||||
row.operator("bim.select_entity_by_guid", text="", icon="CON_OBJECTSOLVER")
|
||||
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
|
||||
|
||||
@@ -31,11 +31,9 @@ import ifcopenshell.ifcopenshell_wrapper
|
||||
import bonsai
|
||||
import bonsai.bim.handler
|
||||
import bonsai.tool as tool
|
||||
from ifcopenshell.file import UndoSystemError
|
||||
from pathlib import Path
|
||||
from bonsai.tool.brick import BrickStore
|
||||
from typing import Union, Optional, TypedDict, NotRequired, Literal
|
||||
from collections.abc import Callable
|
||||
from typing import Set, Union, Optional, TypedDict, Callable, NotRequired, cast
|
||||
|
||||
|
||||
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
|
||||
@@ -70,7 +68,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 = ""
|
||||
@@ -107,8 +105,10 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def get_file():
|
||||
if IfcStore.file is None:
|
||||
props = tool.Blender.get_bim_props()
|
||||
IfcStore.set_path(props.ifc_file)
|
||||
IfcStore.path = cast(str, bpy.context.scene.BIMProperties.ifc_file)
|
||||
# Interpret relative paths as relative to .blend file.
|
||||
if IfcStore.path and not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
@@ -116,21 +116,13 @@ class IfcStore:
|
||||
print(f"Failed to load file {IfcStore.path}. Error details: {e}")
|
||||
return IfcStore.file
|
||||
|
||||
@staticmethod
|
||||
def set_path(value):
|
||||
IfcStore.path = value
|
||||
# Interpret relative paths as relative to .blend file.
|
||||
if IfcStore.path and not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
|
||||
@staticmethod
|
||||
def get_cache():
|
||||
if IfcStore.cache is None and IfcStore.path:
|
||||
props = tool.Blender.get_bim_props()
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
os.makedirs(props.cache_dir, exist_ok=True)
|
||||
IfcStore.cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
os.makedirs(bpy.context.scene.BIMProperties.cache_dir, exist_ok=True)
|
||||
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
|
||||
cache_path = Path(IfcStore.cache_path)
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
serializer_settings = ifcopenshell.geom.serializer_settings()
|
||||
@@ -170,8 +162,7 @@ class IfcStore:
|
||||
assert IfcStore.file
|
||||
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
|
||||
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
|
||||
props = tool.Blender.get_bim_props()
|
||||
new_cache_path = os.path.join(props.cache_dir, f"{ifc_hash}.h5")
|
||||
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
@@ -187,7 +178,6 @@ class IfcStore:
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
extension = path.split(".")[-1]
|
||||
props = tool.Project.get_project_props()
|
||||
if extension.lower() == "ifczip":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
with zipfile.ZipFile(path, "r") as zip_ref:
|
||||
@@ -197,7 +187,7 @@ class IfcStore:
|
||||
return
|
||||
elif extension.lower() == "ifcxml":
|
||||
IfcStore.file = ifcopenshell.file(ifcopenshell.ifcopenshell_wrapper.parse_ifcxml(path))
|
||||
elif props.should_stream:
|
||||
elif bpy.context.scene.BIMProjectProperties.should_stream:
|
||||
IfcStore.file = ifcopenshell.open(path, should_stream=True)
|
||||
else:
|
||||
IfcStore.file = ifcopenshell.open(path)
|
||||
@@ -205,8 +195,9 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def get_schema() -> ifcopenshell.ifcopenshell_wrapper.schema_definition:
|
||||
if IfcStore.file is None:
|
||||
props = tool.Project.get_project_props()
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(props.export_schema)
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(
|
||||
bpy.context.scene.BIMProjectProperties.export_schema
|
||||
)
|
||||
elif IfcStore.schema is None:
|
||||
IfcStore.schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(IfcStore.file.schema_identifier)
|
||||
return IfcStore.schema
|
||||
@@ -256,7 +247,7 @@ class IfcStore:
|
||||
# refactor this class and deprecate usage of IfcStore in favour of
|
||||
# tools.
|
||||
if not isinstance(obj, (bpy.types.Object, bpy.types.Material)):
|
||||
tool.Geometry.get_mesh_props(obj).ifc_definition_id = element.id()
|
||||
obj.BIMMeshProperties.ifc_definition_id = element.id()
|
||||
return
|
||||
|
||||
existing_obj = IfcStore.id_map.get(element.id(), None)
|
||||
@@ -271,11 +262,9 @@ class IfcStore:
|
||||
IfcStore.guid_map[global_id] = obj
|
||||
|
||||
if element.is_a("IfcSurfaceStyle"):
|
||||
props = tool.Style.get_material_style_props(obj)
|
||||
props.ifc_definition_id = element.id()
|
||||
obj.BIMStyleProperties.ifc_definition_id = element.id()
|
||||
else:
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props.ifc_definition_id = element.id()
|
||||
obj.BIMObjectProperties.ifc_definition_id = element.id()
|
||||
|
||||
tool.Ifc.setup_listeners(obj)
|
||||
|
||||
@@ -413,50 +402,15 @@ class IfcStore:
|
||||
@staticmethod
|
||||
def purge_blender_ifc_data(obj: IFC_CONNECTED_TYPE) -> None:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
props = tool.Style.get_material_style_props(obj)
|
||||
props.ifc_definition_id = 0
|
||||
obj.BIMStyleProperties.ifc_definition_id = 0
|
||||
else: # bpy.types.Object
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props.ifc_definition_id = 0
|
||||
obj.BIMObjectProperties.ifc_definition_id = 0
|
||||
|
||||
@staticmethod
|
||||
def get_ifc_file_undo_callback(callback_type: Literal["UNDO", "REDO"]):
|
||||
def callback(_) -> None:
|
||||
try:
|
||||
if callback_type == "UNDO":
|
||||
tool.Ifc.get().undo()
|
||||
else:
|
||||
tool.Ifc.get().redo()
|
||||
except Exception as e:
|
||||
# Persistent callbacks errors are not visible from UI and we set `last_error`
|
||||
# to make it visible.
|
||||
error_msg = ""
|
||||
# In theory it should always be UndoSystemError, but just to be safe.
|
||||
if isinstance(e, UndoSystemError):
|
||||
error_msg += "Undo transaction operations:\n"
|
||||
transaction = e.transaction
|
||||
for operation in transaction.operations:
|
||||
error_msg += f"- {str(operation)}\n"
|
||||
# Show it in system console too, not just in last message.
|
||||
print(error_msg)
|
||||
error_msg += traceback.format_exc()
|
||||
bonsai.last_error = error_msg
|
||||
raise
|
||||
|
||||
return callback
|
||||
|
||||
@staticmethod
|
||||
def execute_ifc_operator(
|
||||
operator: tool.Ifc.Operator,
|
||||
context: bpy.types.Context,
|
||||
event=None,
|
||||
method: Literal["EXECUTE", "INVOKE", "MODAL"] = "EXECUTE",
|
||||
) -> set[str]:
|
||||
def execute_ifc_operator(operator: tool.Ifc.Operator, context: bpy.types.Context, is_invoke=False) -> set[str]:
|
||||
bonsai.last_actions.append({"type": "operator", "name": operator.bl_idname})
|
||||
props = tool.Blender.get_bim_props()
|
||||
props.is_dirty = True
|
||||
# Modals don't nest, and Blender handles the loop that continuously calls modal()
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction) or (method == "MODAL")
|
||||
bpy.context.scene.BIMProperties.is_dirty = True
|
||||
is_top_level_operator = not bool(IfcStore.current_transaction)
|
||||
|
||||
if is_top_level_operator:
|
||||
IfcStore.begin_transaction(operator)
|
||||
@@ -474,9 +428,7 @@ class IfcStore:
|
||||
if tool.Ifc.get():
|
||||
tool.Ifc.get().end_transaction()
|
||||
IfcStore.add_transaction_operation(
|
||||
operator,
|
||||
rollback=IfcStore.get_ifc_file_undo_callback("UNDO"),
|
||||
commit=IfcStore.get_ifc_file_undo_callback("REDO"),
|
||||
operator, rollback=lambda d: tool.Ifc.get().undo(), commit=lambda d: tool.Ifc.get().redo()
|
||||
)
|
||||
if BrickStore.graph is not None: # `if BrickStore.graph` by itself takes ages.
|
||||
BrickStore.end_transaction()
|
||||
@@ -484,12 +436,10 @@ class IfcStore:
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
|
||||
try:
|
||||
if method == "EXECUTE":
|
||||
if is_invoke:
|
||||
result = getattr(operator, "_invoke")(context, None)
|
||||
else:
|
||||
result = getattr(operator, "_execute")(context)
|
||||
elif method == "INVOKE":
|
||||
result = getattr(operator, "_invoke")(context, event)
|
||||
elif method == "MODAL":
|
||||
result = getattr(operator, "_modal")(context, event)
|
||||
except:
|
||||
bonsai.last_error = traceback.format_exc()
|
||||
# Try to ensure undo will work since Blender undo does work in case of errors.
|
||||
@@ -501,27 +451,12 @@ class IfcStore:
|
||||
end_top_level_operator()
|
||||
raise
|
||||
|
||||
if method == "MODAL":
|
||||
if result == {"FINISHED"}:
|
||||
end_top_level_operator()
|
||||
elif result == {"CANCELLED"}:
|
||||
# Please read the docs: https://docs.blender.org/api/current/bpy.types.Operator.html
|
||||
# > "when an operator returns {'CANCELLED'}, no undo step will be created".
|
||||
# This means that if your modal edits IFC data, then the user
|
||||
# cancels it, Blender's undo history will not be in sync with
|
||||
# Bonsai / IfcOpenShell's undo history. Instead of hoping for
|
||||
# Bonsai devs to remember to handle the "cancel" state (i.e.
|
||||
# detect escape keypress) and return {"FINISHED"}, we instead
|
||||
# always enforce an undo step.
|
||||
bpy.ops.ed.undo_push(message=f"Cancel {operator.bl_idname}")
|
||||
end_top_level_operator()
|
||||
else:
|
||||
end_top_level_operator()
|
||||
end_top_level_operator()
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def begin_transaction(operator: tool.Ifc.Operator) -> None:
|
||||
IfcStore.current_transaction = str(uuid.uuid4()) + operator.__class__.__name__
|
||||
IfcStore.current_transaction = str(uuid.uuid4())
|
||||
operator.transaction_key = IfcStore.current_transaction
|
||||
|
||||
@staticmethod
|
||||
@@ -535,8 +470,7 @@ class IfcStore:
|
||||
) -> None:
|
||||
key = getattr(operator, "transaction_key", None)
|
||||
data = getattr(operator, "transaction_data", None)
|
||||
props = tool.Blender.get_bim_props()
|
||||
props.last_transaction = key
|
||||
bpy.context.scene.BIMProperties.last_transaction = key
|
||||
IfcStore.last_transaction = key
|
||||
rollback = rollback or getattr(operator, "rollback", lambda data: True)
|
||||
commit = commit or getattr(operator, "commit", lambda data: True)
|
||||
|
||||
@@ -20,12 +20,10 @@ from __future__ import annotations
|
||||
import bpy
|
||||
import time
|
||||
import json
|
||||
import ifcpatch
|
||||
import logging
|
||||
import traceback
|
||||
import mathutils
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import multiprocessing
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
@@ -36,11 +34,10 @@ import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape
|
||||
import bonsai.tool as tool
|
||||
from itertools import chain, accumulate
|
||||
from bonsai.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from bonsai.tool.loader import OBJECT_DATA_TYPE
|
||||
from typing import Union, Optional, Any, Literal
|
||||
from collections.abc import Iterable
|
||||
from ifcopenshell.util.shape import MatrixType
|
||||
from typing import Dict, Union, Optional, Any, cast
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
@@ -48,7 +45,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
|
||||
@@ -72,13 +69,8 @@ class MaterialCreator:
|
||||
if isinstance(mesh, bpy.types.Curve):
|
||||
return
|
||||
|
||||
if mesh.get("has_layer_styles", None) == True:
|
||||
return
|
||||
|
||||
self.mesh = mesh
|
||||
self.obj = obj
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
self.parse_element_type_material_styles(element)
|
||||
self.parsed_meshes.add(self.mesh.name)
|
||||
if not self.ifc_import_settings.load_indexed_maps:
|
||||
self.load_texture_maps(shape_has_openings)
|
||||
@@ -88,22 +80,9 @@ class MaterialCreator:
|
||||
|
||||
def load_existing_materials(self) -> None:
|
||||
for material in bpy.data.materials:
|
||||
if ifc_definition_id := tool.Blender.get_ifc_definition_id(material):
|
||||
if ifc_definition_id := material.BIMStyleProperties.ifc_definition_id:
|
||||
self.styles[ifc_definition_id] = material
|
||||
|
||||
def parse_element_type_material_styles(self, element: ifcopenshell.entity_instance) -> None:
|
||||
if self.mesh["ios_materials"]:
|
||||
return # Already has materials assign to the representation itself
|
||||
# Otherwise, we need to check for material styles on the element, since
|
||||
# create_shape on types only works on representations.
|
||||
mprops = tool.Geometry.get_mesh_props(self.mesh)
|
||||
context = tool.Ifc.get().by_id(mprops.ifc_definition_id).ContextOfItems
|
||||
for material in ifcopenshell.util.element.get_materials(element):
|
||||
if style := ifcopenshell.util.representation.get_material_style(material, context):
|
||||
self.mesh["ios_materials"] = (style.id(),)
|
||||
self.mesh["ios_material_ids"] = [0] * len(self.mesh.polygons)
|
||||
break
|
||||
|
||||
def get_ifc_coordinate(self, material: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get IfcTextureCoordinate"""
|
||||
texture_style = tool.Style.get_texture_style(material)
|
||||
@@ -117,7 +96,7 @@ class MaterialCreator:
|
||||
return coords
|
||||
# TODO: support IfcTextureMap
|
||||
if coords.is_a("IfcTextureMap"):
|
||||
print("WARNING. IfcTextureMap texture coordinates is not supported.")
|
||||
print(f"WARNING. IfcTextureMap texture coordinates is not supported.")
|
||||
return
|
||||
|
||||
def load_texture_maps(self, shape_has_openings: bool) -> None:
|
||||
@@ -175,7 +154,7 @@ class MaterialCreator:
|
||||
if -1 in self.mesh["ios_material_ids"]:
|
||||
material_to_slot[-1] = get_empty_slot_index()
|
||||
|
||||
material_index = np.array([material_to_slot[mat_id] for mat_id in self.mesh["ios_material_ids"]], dtype="I")
|
||||
material_index = [material_to_slot[mat_id] for mat_id in self.mesh["ios_material_ids"]]
|
||||
self.mesh.polygons.foreach_set("material_index", material_index)
|
||||
|
||||
def resolve_all_stylable_representation_items(
|
||||
@@ -199,16 +178,11 @@ class MaterialCreator:
|
||||
|
||||
|
||||
class IfcImporter:
|
||||
file: ifcopenshell.file
|
||||
"""Either provided by user as an attribute or will be loaded from ``input_file`` during ``execute()``."""
|
||||
|
||||
elements: set[ifcopenshell.entity_instance]
|
||||
"""Set of IfcElements to import. Excluding ``gross_elements`` and ```native_elements``."""
|
||||
|
||||
def __init__(self, ifc_import_settings: IfcImportSettings):
|
||||
self.ifc_import_settings = ifc_import_settings
|
||||
tool.Loader.set_settings(ifc_import_settings)
|
||||
self.diff = None
|
||||
self.file: ifcopenshell.file = None
|
||||
self.project = None
|
||||
self.has_existing_project = False
|
||||
# element guids to blender collections mapping
|
||||
@@ -230,7 +204,7 @@ class IfcImporter:
|
||||
self.progress = 0
|
||||
|
||||
self.material_creator = MaterialCreator(ifc_import_settings, self)
|
||||
classes_to_wireframe_str = tool.Drawing.get_document_props().classes_to_wireframe
|
||||
classes_to_wireframe_str = bpy.context.scene.DocProperties.classes_to_wireframe
|
||||
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
|
||||
|
||||
def profile_code(self, message: str) -> None:
|
||||
@@ -301,39 +275,26 @@ 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()
|
||||
|
||||
def process_context_filter(self) -> None:
|
||||
"""Setup contexts. Necessary for importing elements representations."""
|
||||
contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
if len(contexts) > 100: # Probably something strange happening. Encountered from Revizto.
|
||||
print("Warning! Excessive contexts were found and merged where applicable.")
|
||||
uniques = {}
|
||||
i = 0
|
||||
for element in contexts:
|
||||
data = "-".join([str(a) for a in element])
|
||||
if unique := uniques.get(data, None):
|
||||
ifcopenshell.util.element.replace_element(element, unique)
|
||||
self.file.remove(element)
|
||||
i += 1
|
||||
else:
|
||||
uniques[data] = element
|
||||
print(f"Replaced {i} IfcGeometricRepresentationContext")
|
||||
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(self.file)
|
||||
tool.Loader.settings.context_settings = tool.Loader.create_settings()
|
||||
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
|
||||
|
||||
def process_element_filter(self) -> None:
|
||||
elements: list[ifcopenshell.entity_instance]
|
||||
if self.ifc_import_settings.has_filter:
|
||||
elements = list(self.ifc_import_settings.elements)
|
||||
self.elements = self.ifc_import_settings.elements
|
||||
if isinstance(self.elements, set):
|
||||
self.elements = list(self.elements)
|
||||
# TODO: enable filtering for annotations
|
||||
else:
|
||||
if self.file.schema in ("IFC2X3", "IFC4"):
|
||||
elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
|
||||
self.elements = self.file.by_type("IfcElement") + self.file.by_type("IfcProxy")
|
||||
else:
|
||||
elements = self.file.by_type("IfcElement")
|
||||
self.elements = self.file.by_type("IfcElement")
|
||||
|
||||
drawing_groups = [g for g in self.file.by_type("IfcGroup") if g.ObjectType == "DRAWING"]
|
||||
drawing_annotations = set()
|
||||
@@ -343,16 +304,16 @@ class IfcImporter:
|
||||
self.annotations = set([a for a in self.file.by_type("IfcAnnotation")])
|
||||
self.annotations -= drawing_annotations
|
||||
|
||||
elements = [e for e in elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
|
||||
self.elements = [e for e in self.elements if not e.is_a("IfcFeatureElement") or e.is_a("IfcSurfaceFeature")]
|
||||
if self.ifc_import_settings.element_limit_mode == "UNLIMITED":
|
||||
self.elements = set(elements)
|
||||
self.elements = set(self.elements)
|
||||
else:
|
||||
offset = self.ifc_import_settings.element_offset
|
||||
offset_limit = offset + self.ifc_import_settings.element_limit
|
||||
self.elements = set(elements[offset:offset_limit])
|
||||
self.elements = set(self.elements[offset:offset_limit])
|
||||
|
||||
if self.ifc_import_settings.has_filter or self.ifc_import_settings.element_limit_mode != "UNLIMITED":
|
||||
self.element_types = {t for e in self.elements if (t := ifcopenshell.util.element.get_type(e))}
|
||||
self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements])
|
||||
else:
|
||||
self.element_types = set(self.file.by_type("IfcTypeProduct"))
|
||||
|
||||
@@ -390,7 +351,6 @@ class IfcImporter:
|
||||
return results
|
||||
|
||||
def parse_native_elements(self) -> None:
|
||||
# TODO: move to `process_element_filter` to incapsulate all `self.elements` logic.
|
||||
if not self.ifc_import_settings.should_load_geometry:
|
||||
return
|
||||
if not self.file.by_type("IfcSweptDiskSolid"):
|
||||
@@ -428,8 +388,7 @@ class IfcImporter:
|
||||
|
||||
rep = representation
|
||||
while True:
|
||||
# Tekla 2023 has missing items, though it's invalid IFC.
|
||||
if rep.Items and len(rep.Items) == 1 and rep.Items[0].is_a("IfcMappedItem"):
|
||||
if len(rep.Items) == 1 and rep.Items[0].is_a("IfcMappedItem"):
|
||||
rep_matrix = ifcopenshell.util.placement.get_mappeditem_transformation(rep.Items[0])
|
||||
if not np.allclose(rep_matrix, np.eye(4)):
|
||||
matrix = rep_matrix @ matrix
|
||||
@@ -461,7 +420,7 @@ class IfcImporter:
|
||||
return False
|
||||
|
||||
def calculate_model_offset(self) -> None:
|
||||
props = tool.Georeference.get_georeference_props()
|
||||
props = bpy.context.scene.BIMGeoreferenceProperties
|
||||
if self.ifc_import_settings.false_origin_mode == "MANUAL":
|
||||
tool.Loader.set_manual_blender_offset(self.file)
|
||||
elif self.ifc_import_settings.false_origin_mode == "AUTOMATIC":
|
||||
@@ -502,16 +461,14 @@ class IfcImporter:
|
||||
if grid.WAxes:
|
||||
self.create_grid_axes(grid.WAxes, grid_obj, grid_placement)
|
||||
|
||||
def create_grid_axes(
|
||||
self, axes: list[ifcopenshell.entity_instance], grid_obj: bpy.types.Object, grid_placement: MatrixType
|
||||
) -> None:
|
||||
def create_grid_axes(self, axes, grid_obj, grid_placement):
|
||||
for axis in axes:
|
||||
shape = tool.Loader.create_generic_shape(axis.AxisCurve)
|
||||
mesh = self.create_mesh(axis, shape)
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(axis), mesh)
|
||||
obj.show_in_front = True
|
||||
self.link_element(axis, obj)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement))
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, grid_placement.copy()))
|
||||
|
||||
def create_element_types(self):
|
||||
for element_type in self.element_types:
|
||||
@@ -536,9 +493,8 @@ class IfcImporter:
|
||||
pass
|
||||
elif shape:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
if mesh is not None:
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
else:
|
||||
self.ifc_import_settings.logger.error("Failed to generate shape for %s", element)
|
||||
break
|
||||
@@ -596,7 +552,6 @@ class IfcImporter:
|
||||
return self.create_generic_sqlite_elements(elements)
|
||||
|
||||
if self.ifc_import_settings.should_load_geometry:
|
||||
elements = elements.copy() # Prevent argument mutation.
|
||||
context_settings = (
|
||||
tool.Loader.settings.gross_context_settings if is_gross else tool.Loader.settings.context_settings
|
||||
)
|
||||
@@ -620,7 +575,6 @@ class IfcImporter:
|
||||
obj.hide_select = True
|
||||
|
||||
def create_generic_sqlite_elements(self, elements: set[ifcopenshell.entity_instance]) -> None:
|
||||
assert isinstance(self.file, ifcopenshell.sql.sqlite)
|
||||
self.geometry_cache = self.file.get_geometry([e.id() for e in elements])
|
||||
for geometry_id, geometry in self.geometry_cache["geometry"].items():
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(type("Geometry", (), {"id": geometry_id}))
|
||||
@@ -630,9 +584,21 @@ class IfcImporter:
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
if geometry["faces"]:
|
||||
mesh = tool.Loader.create_mesh_from_shape(
|
||||
mesh=mesh, faces=geometry["faces"].reshape(-1, 3), verts=verts.reshape(-1, 3)
|
||||
)
|
||||
num_vertices = len(verts) // 3
|
||||
total_faces = len(geometry["faces"])
|
||||
loop_start = range(0, total_faces, 3)
|
||||
num_loops = total_faces // 3
|
||||
loop_total = [3] * num_loops
|
||||
num_vertex_indices = len(geometry["faces"])
|
||||
|
||||
mesh.vertices.add(num_vertices)
|
||||
mesh.vertices.foreach_set("co", verts)
|
||||
mesh.loops.add(num_vertex_indices)
|
||||
mesh.loops.foreach_set("vertex_index", geometry["faces"])
|
||||
mesh.polygons.add(num_loops)
|
||||
mesh.polygons.foreach_set("loop_start", loop_start)
|
||||
mesh.polygons.foreach_set("loop_total", loop_total)
|
||||
mesh.update()
|
||||
else:
|
||||
e = geometry["edges"]
|
||||
v = verts
|
||||
@@ -725,15 +691,9 @@ class IfcImporter:
|
||||
def create_structural_point_connections(self):
|
||||
for product in self.file.by_type("IfcStructuralPointConnection"):
|
||||
# TODO: make this based off ifcopenshell. See #1409
|
||||
|
||||
representation = tool.Structural.get_vertex_representation(product)
|
||||
if not representation:
|
||||
print(
|
||||
"WARNING. Skipping invalid IfcStructuralPointConnection - "
|
||||
f"element has no valid representation:\n{product}."
|
||||
)
|
||||
continue
|
||||
|
||||
representation: ifcopenshell.entity_instance = next(
|
||||
rep for rep in product.Representation.Representations if rep.RepresentationType == "Vertex"
|
||||
)
|
||||
mesh = tool.Loader.create_structural_point_connection_mesh(representation)
|
||||
if mesh is None:
|
||||
continue
|
||||
@@ -775,9 +735,12 @@ class IfcImporter:
|
||||
def create_pointclouds(self, products: set[ifcopenshell.entity_instance]) -> set[ifcopenshell.entity_instance]:
|
||||
result = set()
|
||||
for product in products:
|
||||
if representation := self.get_pointcloud_representation(product):
|
||||
if pointcloud := self.create_pointcloud(product, representation):
|
||||
representation = self.get_pointcloud_representation(product)
|
||||
if representation is not None:
|
||||
pointcloud = self.create_pointcloud(product, representation)
|
||||
if pointcloud is not None:
|
||||
result.add(pointcloud)
|
||||
|
||||
return result
|
||||
|
||||
def create_pointcloud(
|
||||
@@ -825,9 +788,8 @@ class IfcImporter:
|
||||
materials_updated = bool(mesh)
|
||||
if mesh is None:
|
||||
mesh = self.create_mesh(element, shape)
|
||||
if mesh is not None:
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
self.meshes[mesh_name] = mesh
|
||||
else:
|
||||
mesh = None
|
||||
|
||||
@@ -838,10 +800,11 @@ class IfcImporter:
|
||||
if element.is_a(ifcclass):
|
||||
obj.display_type = "WIRE"
|
||||
|
||||
if shape and mesh:
|
||||
if shape:
|
||||
# We use numpy here because Blender mathutils.Matrix is not accurate enough
|
||||
mat = ifcopenshell.util.shape.get_shape_matrix(shape)
|
||||
mat = np.array(shape.transformation.matrix).reshape((4, 4), order="F")
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, mat))
|
||||
assert mesh # Type checker.
|
||||
if not materials_updated:
|
||||
self.material_creator.create(element, obj, mesh, tool.Geometry.does_shape_has_openings(shape))
|
||||
elif mesh: # When does this occur?
|
||||
@@ -857,7 +820,7 @@ class IfcImporter:
|
||||
return obj
|
||||
|
||||
def load_existing_meshes(self) -> None:
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes if m.library is None})
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes})
|
||||
|
||||
def merge_materials_by_colour(self):
|
||||
cleaned_materials = {}
|
||||
@@ -912,19 +875,17 @@ class IfcImporter:
|
||||
|
||||
def load_file(self):
|
||||
self.ifc_import_settings.logger.info("loading file %s", self.ifc_import_settings.input_file)
|
||||
props = tool.Blender.get_bim_props()
|
||||
if not props.ifc_file:
|
||||
props.ifc_file = self.ifc_import_settings.input_file
|
||||
self.file = tool.Ifc.get()
|
||||
if not bpy.context.scene.BIMProperties.ifc_file:
|
||||
bpy.context.scene.BIMProperties.ifc_file = self.ifc_import_settings.input_file
|
||||
self.file = IfcStore.get_file()
|
||||
|
||||
def calculate_unit_scale(self):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
tool.Loader.set_unit_scale(self.unit_scale)
|
||||
|
||||
def set_units(self) -> None:
|
||||
def set_units(self):
|
||||
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
|
||||
return # Geometry is optional in IFC
|
||||
props = tool.Blender.get_bim_props()
|
||||
for unit in assignment.Units:
|
||||
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
@@ -944,27 +905,26 @@ class IfcImporter:
|
||||
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "AREAUNIT":
|
||||
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
|
||||
try:
|
||||
props.area_unit = "{}{}".format(
|
||||
bpy.context.scene.BIMProperties.area_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
except: # Probably an invalid unit.
|
||||
props.area_unit = "SQUARE_METRE"
|
||||
bpy.context.scene.BIMProperties.area_unit = "SQUARE_METRE"
|
||||
elif unit.is_a("IfcNamedUnit") and unit.UnitType == "VOLUMEUNIT":
|
||||
name = unit.Name if unit.is_a("IfcSIUnit") else unit.Name.lower()
|
||||
try:
|
||||
props.volume_unit = "{}{}".format(
|
||||
bpy.context.scene.BIMProperties.volume_unit = "{}{}".format(
|
||||
unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name
|
||||
)
|
||||
except: # Probably an invalid unit.
|
||||
props.volume_unit = "CUBIC_METRE"
|
||||
bpy.context.scene.BIMProperties.volume_unit = "CUBIC_METRE"
|
||||
|
||||
def create_project(self):
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
self.project = {"ifc": project}
|
||||
obj = tool.Ifc.get_object(project)
|
||||
if obj:
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
self.project["blender"] = props.collection
|
||||
self.project["blender"] = obj.BIMObjectProperties.collection
|
||||
self.has_existing_project = True
|
||||
return
|
||||
self.project["blender"] = bpy.data.collections.new(
|
||||
@@ -974,16 +934,10 @@ class IfcImporter:
|
||||
obj.hide_select = True
|
||||
self.project["blender"].objects.link(obj)
|
||||
self.project["blender"].BIMCollectionProperties.obj = obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props.collection = self.collections[project.GlobalId] = self.project["blender"]
|
||||
obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"]
|
||||
|
||||
def create_styles(self) -> None:
|
||||
styles = self.file.by_type("IfcSurfaceStyle")
|
||||
if len(styles) > self.ifc_import_settings.style_limit: # Probably something strange happening
|
||||
print("Warning! Excessive styles were found and merged where applicable.")
|
||||
ifcpatch.execute({"file": self.file, "recipe": "MergeStyles", "arguments": []})
|
||||
styles = self.file.by_type("IfcSurfaceStyle")
|
||||
for style in styles:
|
||||
for style in self.file.by_type("IfcSurfaceStyle"):
|
||||
self.create_style(style)
|
||||
|
||||
def create_style(self, style: ifcopenshell.entity_instance) -> None:
|
||||
@@ -996,11 +950,10 @@ class IfcImporter:
|
||||
self.material_creator.styles[style.id()] = blender_material
|
||||
|
||||
style_elements = tool.Style.get_style_elements(blender_material)
|
||||
props = tool.Style.get_material_style_props(blender_material)
|
||||
if tool.Style.has_blender_external_style(style_elements):
|
||||
props.active_style_type = "External"
|
||||
blender_material.BIMStyleProperties.active_style_type = "External"
|
||||
else:
|
||||
props.active_style_type = "Shading"
|
||||
blender_material.BIMStyleProperties.active_style_type = "Shading"
|
||||
|
||||
def place_objects_in_collections(self) -> None:
|
||||
for ifc_definition_id, obj in self.added_data.items():
|
||||
@@ -1008,10 +961,10 @@ class IfcImporter:
|
||||
tool.Collector.assign(obj, should_clean_users_collection=False)
|
||||
|
||||
def is_curve_annotation(self, element: ifcopenshell.entity_instance) -> bool:
|
||||
object_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
object_type = element.ObjectType
|
||||
return (
|
||||
object_type in tool.Drawing.ANNOTATION_TYPES_DATA
|
||||
and tool.Drawing.ANNOTATION_TYPES_DATA[object_type].data_type == "curve"
|
||||
and tool.Drawing.ANNOTATION_TYPES_DATA[object_type][3] == "curve"
|
||||
)
|
||||
|
||||
def get_drawing_group(self, element):
|
||||
@@ -1034,7 +987,7 @@ class IfcImporter:
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
) -> bpy.types.Curve:
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
if hasattr(shape, "geometry"):
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
geometry = shape
|
||||
@@ -1064,10 +1017,10 @@ class IfcImporter:
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
shape: Union[ifcopenshell.geom.ShapeElementType, ifcopenshell.geom.ShapeType],
|
||||
cartesian_point_offset: Union[npt.NDArray[np.float64], Literal[False]] = None,
|
||||
) -> Union[bpy.types.Mesh, None]:
|
||||
cartesian_point_offset=None,
|
||||
) -> bpy.types.Mesh:
|
||||
try:
|
||||
if isinstance(shape, ifcopenshell.geom.ShapeElementType):
|
||||
if hasattr(shape, "geometry"):
|
||||
# shape is ShapeElementType
|
||||
geometry = shape.geometry
|
||||
else:
|
||||
@@ -1076,40 +1029,44 @@ 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, None)):
|
||||
if old_mesh := bpy.data.meshes.get(mesh_name):
|
||||
old_mesh.name = mesh_name + ".old"
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
|
||||
verts = ifcopenshell.util.shape.get_vertices(geometry)
|
||||
if cartesian_point_offset is False:
|
||||
verts = geometry.verts
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
elif cartesian_point_offset is not None:
|
||||
verts = verts - cartesian_point_offset
|
||||
verts_array = np.array(geometry.verts)
|
||||
offset = np.array([-cartesian_point_offset[0], -cartesian_point_offset[1], -cartesian_point_offset[2]])
|
||||
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
|
||||
verts = offset_verts.tolist()
|
||||
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
mesh["cartesian_point_offset"] = (
|
||||
f"{cartesian_point_offset[0]},{cartesian_point_offset[1]},{cartesian_point_offset[2]}"
|
||||
)
|
||||
elif verts.size and tool.Loader.is_point_far_away(verts[0], is_meters=True):
|
||||
elif geometry.verts and tool.Loader.is_point_far_away(
|
||||
(geometry.verts[0], geometry.verts[1], geometry.verts[2]), is_meters=True
|
||||
):
|
||||
# Shift geometry close to the origin based off that first vert it found
|
||||
offset = verts[0]
|
||||
verts = verts - offset
|
||||
verts_array = np.array(geometry.verts)
|
||||
offset = np.array([-geometry.verts[0], -geometry.verts[1], -geometry.verts[2]])
|
||||
offset_verts = verts_array + np.tile(offset, len(verts_array) // 3)
|
||||
verts = offset_verts.tolist()
|
||||
|
||||
mesh["has_cartesian_point_offset"] = True
|
||||
mesh["cartesian_point_offset"] = f"{offset[0]},{offset[1]},{offset[2]}"
|
||||
mesh["cartesian_point_offset"] = f"{geometry.verts[0]},{geometry.verts[1]},{geometry.verts[2]}"
|
||||
else:
|
||||
verts = geometry.verts
|
||||
mesh["has_cartesian_point_offset"] = False
|
||||
|
||||
mesh = tool.Loader.convert_geometry_to_mesh(
|
||||
return tool.Loader.convert_geometry_to_mesh(
|
||||
geometry,
|
||||
mesh,
|
||||
verts=verts,
|
||||
load_indexed_maps=self.ifc_import_settings.load_indexed_maps,
|
||||
)
|
||||
# E.g. `create_mesh` is also used for IfcRelSpaceBoundary.
|
||||
if element.is_a("IfcObjectDefinition"):
|
||||
return tool.Loader.slice_layerset_mesh(element, mesh)
|
||||
return mesh
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
|
||||
import traceback
|
||||
@@ -1117,10 +1074,9 @@ class IfcImporter:
|
||||
print(traceback.format_exc())
|
||||
|
||||
def set_default_context(self):
|
||||
rprops = tool.Root.get_root_props()
|
||||
for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"):
|
||||
if subcontext.ContextIdentifier == "Body":
|
||||
rprops.contexts = str(subcontext.id())
|
||||
bpy.context.scene.BIMRootProperties.contexts = str(subcontext.id())
|
||||
break
|
||||
|
||||
def link_element(self, element: ifcopenshell.entity_instance, obj: IFC_CONNECTED_TYPE) -> None:
|
||||
@@ -1138,22 +1094,7 @@ class IfcImporter:
|
||||
bpy.ops.view3d.view_selected()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
def setup_arrays(
|
||||
self,
|
||||
*,
|
||||
annotations_to_import: Optional[set[ifcopenshell.entity_instance]] = None,
|
||||
openings_to_import: Optional[set[ifcopenshell.entity_instance]] = None,
|
||||
) -> None:
|
||||
# During base import some elements are skipped.
|
||||
is_base_import = True
|
||||
elements_to_import: set[ifcopenshell.entity_instance] = set()
|
||||
if annotations_to_import is not None:
|
||||
is_base_import = False
|
||||
elements_to_import = annotations_to_import
|
||||
elif openings_to_import is not None:
|
||||
is_base_import = False
|
||||
elements_to_import = openings_to_import
|
||||
|
||||
def setup_arrays(self):
|
||||
for pset in self.file.by_type("IfcPropertySet"):
|
||||
if pset.Name != "BBIM_Array":
|
||||
continue
|
||||
@@ -1161,24 +1102,16 @@ class IfcImporter:
|
||||
continue
|
||||
data = json.loads(data)
|
||||
for rel in pset.DefinesOccurrence:
|
||||
element: ifcopenshell.entity_instance
|
||||
for element in rel.RelatedObjects:
|
||||
if is_base_import:
|
||||
if element.is_a("IfcAnnotation") or element.is_a("IfcOpeningElement"):
|
||||
continue
|
||||
else:
|
||||
if element not in elements_to_import:
|
||||
continue
|
||||
for i in range(len(data)):
|
||||
tool.Blender.Modifier.Array.set_children_lock_state(element, i, True)
|
||||
tool.Blender.Modifier.Array.constrain_children_to_parent(element)
|
||||
|
||||
|
||||
class IfcImportSettings:
|
||||
input_file: Union[str, None] = None
|
||||
logger: Union[logging.Logger, None] = None
|
||||
|
||||
def __init__(self):
|
||||
self.logger: logging.Logger = None
|
||||
self.input_file = None
|
||||
self.diff_file = None
|
||||
self.geometry_library = "opencascade"
|
||||
self.should_use_cpu_multiprocessing = True
|
||||
@@ -1189,7 +1122,6 @@ class IfcImportSettings:
|
||||
self.deflection_tolerance = 0.001
|
||||
self.angular_tolerance = 0.5
|
||||
self.void_limit = 30
|
||||
self.style_limit = 300
|
||||
# Locations greater than 1km are not considered "small sites" according to the georeferencing guide
|
||||
# Users can configure this if they have to handle larger sites but beware of surveying precision
|
||||
self.distance_limit = 1000
|
||||
@@ -1199,20 +1131,18 @@ class IfcImportSettings:
|
||||
self.element_limit_mode = "UNLIMITED"
|
||||
self.element_offset = 0
|
||||
self.element_limit = 30000
|
||||
self.has_filter = False
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.should_setup_viewport_camera = True
|
||||
self.contexts: list[ifcopenshell.entity_instance] = []
|
||||
self.context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.elements: Iterable[ifcopenshell.entity_instance] = set()
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.load_indexed_maps = False
|
||||
|
||||
@staticmethod
|
||||
def factory(
|
||||
context=None, input_file: Optional[str] = None, logger: Optional[logging.Logger] = None
|
||||
) -> IfcImportSettings:
|
||||
scene_diff = tool.Blender.get_diff_props()
|
||||
def factory(context=None, input_file=None, logger=None):
|
||||
scene_diff = bpy.context.scene.DiffProperties
|
||||
props = tool.Project.get_project_props()
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
@@ -1229,7 +1159,6 @@ class IfcImportSettings:
|
||||
settings.deflection_tolerance = props.deflection_tolerance
|
||||
settings.angular_tolerance = props.angular_tolerance
|
||||
settings.void_limit = props.void_limit
|
||||
settings.style_limit = props.style_limit
|
||||
settings.distance_limit = props.distance_limit
|
||||
settings.false_origin_mode = props.false_origin_mode
|
||||
try:
|
||||
|
||||
@@ -20,7 +20,6 @@ import blf
|
||||
import bpy
|
||||
import gpu
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from bpy_extras import view3d_utils
|
||||
@@ -132,9 +131,6 @@ class AggregateDecorator:
|
||||
return shader
|
||||
|
||||
def draw_custom_batch(self, coords, color):
|
||||
indices = None
|
||||
if not tool.Blender.validate_shader_batch_data(coords, indices):
|
||||
return
|
||||
shader = self.dotted_line_shader()
|
||||
|
||||
arc_lengths = [0]
|
||||
@@ -145,7 +141,6 @@ class AggregateDecorator:
|
||||
shader,
|
||||
"LINE_STRIP",
|
||||
{"position": coords, "arcLength": arc_lengths},
|
||||
indices=indices,
|
||||
)
|
||||
|
||||
matrix = bpy.context.region_data.perspective_matrix
|
||||
@@ -155,15 +150,14 @@ class AggregateDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
|
||||
return
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_aggregate(self, context):
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
if context.scene.BIMAggregateProperties.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
|
||||
@@ -192,13 +186,7 @@ class AggregateDecorator:
|
||||
aggregates.append(obj)
|
||||
continue
|
||||
|
||||
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]
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
aggregates.append(tool.Ifc.get_object(aggregate))
|
||||
|
||||
@@ -216,7 +204,9 @@ 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)])
|
||||
parts = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate))
|
||||
if context.scene.BIMAggregateProperties.in_aggregate_mode:
|
||||
return
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate))
|
||||
parts_objs = [tool.Ifc.get_object(p) for p in parts]
|
||||
|
||||
indices, edges = create_bounding_box(parts_objs)
|
||||
@@ -253,8 +243,6 @@ class AggregateModeDecorator:
|
||||
cls.is_installed = False
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
|
||||
return
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
@@ -265,7 +253,7 @@ class AggregateModeDecorator:
|
||||
return
|
||||
region = context.region
|
||||
rv3d = region.data
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
props = context.scene.BIMAggregateProperties
|
||||
|
||||
aggregate_obj = props.editing_aggregate
|
||||
if not aggregate_obj:
|
||||
@@ -296,7 +284,7 @@ class AggregateModeDecorator:
|
||||
def draw_aggregate_empty(self, context):
|
||||
if context.mode == "EDIT_MESH":
|
||||
return
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
props = context.scene.BIMAggregateProperties
|
||||
aggregate_obj = props.editing_aggregate
|
||||
if not aggregate_obj:
|
||||
return
|
||||
@@ -335,7 +323,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 = tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(aggregate_obj))
|
||||
parts = ifcopenshell.util.element.get_parts(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,13 +19,11 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.group
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.aggregate as core
|
||||
import bonsai.core.spatial
|
||||
from typing import TYPE_CHECKING
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -33,30 +31,23 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Assign Object To Aggregation"
|
||||
bl_description = (
|
||||
"Assign object as an aggregate to the selected IFC elements.\n\n"
|
||||
"If called from Object Aggregates UI, then either 'Relating Whole' or 'Related Part' must be provided.\n"
|
||||
"If called from Bonsai UI, then either 'Relating Whole' or 'Related Part' must be provided.\n"
|
||||
"If called directly, active object will be considered a relating whole"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
relating_object: bpy.props.IntProperty()
|
||||
related_object: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
relating_object: int
|
||||
related_object: int
|
||||
|
||||
def _execute(self, context):
|
||||
relating_obj = None
|
||||
if self.relating_object:
|
||||
relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object))
|
||||
assert relating_obj
|
||||
elif self.related_object:
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get().by_id(self.related_object))
|
||||
if aggregate:
|
||||
relating_obj = tool.Ifc.get_object(aggregate)
|
||||
assert relating_obj
|
||||
else:
|
||||
elif context.active_object:
|
||||
relating_obj = context.active_object
|
||||
|
||||
if not relating_obj:
|
||||
self.report({"ERROR"}, "No relating object is provided.")
|
||||
return
|
||||
@@ -76,7 +67,7 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
props = context.scene.BIMAggregateProperties
|
||||
if relating_obj == props.editing_aggregate and props.in_aggregate_mode:
|
||||
new_editing_obj = props.editing_objects.add()
|
||||
new_editing_obj.obj = obj
|
||||
@@ -114,7 +105,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.pset.remove_pset(tool.Ifc.get(), product=element, pset=pset)
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset)
|
||||
|
||||
|
||||
class BIM_OT_enable_editing_aggregate(bpy.types.Operator):
|
||||
@@ -142,11 +133,12 @@ class BIM_OT_disable_editing_aggregate(bpy.types.Operator):
|
||||
|
||||
|
||||
class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""Add aggregate to selected IFC elements."""
|
||||
"""Add aggregate to IFC element"""
|
||||
|
||||
bl_idname = "bim.add_aggregate"
|
||||
bl_label = "Add Aggregate"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
ifc_class: bpy.props.StringProperty(name="IFC Class", default="IfcElementAssembly")
|
||||
aggregate_name: bpy.props.StringProperty(name="Name", default="Default_Name")
|
||||
|
||||
@@ -166,7 +158,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
aggregate = self.create_aggregate(context, ifc_class, self.aggregate_name)
|
||||
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -191,7 +183,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj)
|
||||
|
||||
def create_aggregate(self, context: bpy.types.Context, ifc_class: str, aggregate_name: str) -> bpy.types.Object:
|
||||
def create_aggregate(self, context, ifc_class, aggregate_name):
|
||||
aggregate = bpy.data.objects.new(aggregate_name, None)
|
||||
aggregate.location = context.scene.cursor.location
|
||||
bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class)
|
||||
@@ -302,7 +294,6 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if not aggregate:
|
||||
@@ -314,8 +305,8 @@ 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 = ifc_file.by_id(pset["id"])
|
||||
ifcopenshell.api.pset.remove_pset(ifc_file, product=part, pset=pset)
|
||||
pset = tool.Ifc.get().by_id(pset["id"])
|
||||
ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=part, pset=pset)
|
||||
|
||||
linked_aggregate_group = next(
|
||||
r.RelatingGroup
|
||||
@@ -323,7 +314,7 @@ class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, tool.Ifc.Operato
|
||||
if r.is_a("IfcRelAssignsToGroup")
|
||||
if "BBIM_Linked_Aggregate" in r.RelatingGroup.Name
|
||||
)
|
||||
ifcopenshell.api.group.unassign_group(ifc_file, group=linked_aggregate_group, products=[aggregate])
|
||||
tool.Ifc.run("group.unassign_group", group=linked_aggregate_group, products=[aggregate])
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -388,7 +379,7 @@ class BIM_OT_disable_aggregate_mode(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
core.exit_aggregate_mode(tool.Aggregate)
|
||||
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -398,7 +389,7 @@ class BIM_OT_toggle_aggregate_mode_local_view(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
props = context.scene.BIMAggregateProperties
|
||||
objs = [o.obj for o in props.editing_objects]
|
||||
if props.in_aggregate_mode:
|
||||
if context.space_data.local_view:
|
||||
|
||||
@@ -33,7 +33,6 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
from bonsai.bim.module.aggregate.decorator import AggregateDecorator, AggregateModeDecorator
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
|
||||
@@ -99,27 +98,15 @@ class BIMObjectAggregateProperties(PropertyGroup):
|
||||
poll=poll_related_object,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
relating_object: Union[bpy.types.Object, None]
|
||||
related_object: Union[bpy.types.Object, None]
|
||||
|
||||
|
||||
class Objects(bpy.types.PropertyGroup):
|
||||
obj: PointerProperty(type=bpy.types.Object)
|
||||
previous_display_type: bpy.props.StringProperty(default="TEXTURED")
|
||||
previous_hide_select: bpy.props.BoolProperty(default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: Union[bpy.types.Object, None]
|
||||
previous_display_type: str
|
||||
previous_hide_select: bool
|
||||
|
||||
|
||||
class BIMAggregateProperties(PropertyGroup):
|
||||
in_aggregate_mode: BoolProperty(name="In Edit Mode", update=update_aggregate_mode_decorator)
|
||||
editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
|
||||
previous_editing_aggregate: PointerProperty(name="Editing Aggregate", type=bpy.types.Object)
|
||||
editing_objects: CollectionProperty(type=Objects)
|
||||
not_editing_objects: CollectionProperty(type=Objects)
|
||||
aggregate_decorator: BoolProperty(
|
||||
@@ -127,16 +114,3 @@ class BIMAggregateProperties(PropertyGroup):
|
||||
default=False,
|
||||
update=update_aggregate_decorator,
|
||||
)
|
||||
previous_state: BoolProperty(
|
||||
name="True if it was previously in aggregate mode",
|
||||
default=False,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
in_aggregate_mode: bool
|
||||
editing_aggregate: Union[bpy.types.Object, None]
|
||||
previous_editing_aggregate: Union[bpy.types.Object, None]
|
||||
editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
|
||||
not_editing_objects: bpy.types.bpy_prop_collection_idprop[Objects]
|
||||
aggregate_decorator: bool
|
||||
previous_state: bool
|
||||
|
||||
@@ -34,12 +34,12 @@ class BIM_PT_aggregate(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not (obj := context.active_object):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
@@ -49,14 +49,11 @@ class BIM_PT_aggregate(Panel):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text="Aggregate Decorator")
|
||||
props = tool.Aggregate.get_aggregate_props()
|
||||
icon = "HIDE_OFF" if props.aggregate_decorator else "HIDE_ON"
|
||||
row.prop(props, "aggregate_decorator", icon=icon, text="")
|
||||
row.prop(context.scene.BIMAggregateProperties, "aggregate_decorator", icon="HIDE_OFF", text="")
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
assert (obj := context.active_object)
|
||||
props = tool.Aggregate.get_object_aggregate_props(obj)
|
||||
props = context.active_object.BIMObjectAggregateProperties
|
||||
|
||||
if props.is_editing:
|
||||
row = layout.row()
|
||||
@@ -69,9 +66,9 @@ class BIM_PT_aggregate(Panel):
|
||||
col.enabled = False
|
||||
op = col.operator("bim.aggregate_assign_object", icon="CHECKMARK")
|
||||
if props.relating_object:
|
||||
op.relating_object = tool.Blender.get_object_bim_props(props.relating_object).ifc_definition_id
|
||||
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
|
||||
elif props.related_object:
|
||||
op.related_object = tool.Blender.get_object_bim_props(props.related_object).ifc_definition_id
|
||||
op.related_object = props.related_object.BIMObjectProperties.ifc_definition_id
|
||||
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
|
||||
return
|
||||
else:
|
||||
@@ -79,8 +76,10 @@ class BIM_PT_aggregate(Panel):
|
||||
if AggregateData.data["has_relating_object"]:
|
||||
row.label(text=AggregateData.data["relating_object_label"], icon="TRIA_UP")
|
||||
op = row.operator("bim.select_aggregate", icon="OBJECT_DATA", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.select_parts = False
|
||||
op = row.operator("bim.select_aggregate", icon="RESTRICT_SELECT_OFF", text="")
|
||||
op.obj = context.active_object.name
|
||||
op.select_parts = True
|
||||
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
|
||||
row.operator("bim.add_aggregate", icon="ADD", text="")
|
||||
@@ -116,14 +115,14 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not (obj := context.active_object):
|
||||
if not context.active_object:
|
||||
return False
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props = context.active_object.BIMObjectProperties
|
||||
if not props.ifc_definition_id:
|
||||
return False
|
||||
if not tool.Ifc.get_object_by_identifier(props.ifc_definition_id):
|
||||
if not IfcStore.get_element(props.ifc_definition_id):
|
||||
return False
|
||||
if not tool.Ifc.get().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -132,8 +131,9 @@ class BIM_PT_linked_aggregate(Panel):
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
assert (obj := context.active_object)
|
||||
assert (element := tool.Ifc.get_entity(obj))
|
||||
obj = context.active_object
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
props = obj.BIMObjectAggregateProperties
|
||||
row = layout.row(align=True)
|
||||
|
||||
if element.Decomposes:
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 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
|
||||
|
||||
# from . import ui, prop, operator
|
||||
from . import operator
|
||||
|
||||
classes = (operator.ImportAlignmentCSV,)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(operator.ImportAlignmentCSV.bl_idname, text="Alignment (.csv)")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
@@ -1,112 +0,0 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.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/>.
|
||||
|
||||
# pyright: reportUnnecessaryTypeIgnoreComment=error
|
||||
|
||||
import os
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.alignment.add_stationing_to_alignment
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import time
|
||||
import calendar
|
||||
import isodate
|
||||
import bonsai.core.sequence as core
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.module.sequence.helper as helper
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.sequence
|
||||
import ifcopenshell.util.selector
|
||||
from datetime import datetime
|
||||
from dateutil import parser, relativedelta
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
|
||||
class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "bim.import_alignment_csv"
|
||||
bl_label = "Import Alignment CSV"
|
||||
bl_description = " Import alignment from the provided .csv file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filename_ext = ".csv"
|
||||
filter_glob: bpy.props.StringProperty(default="*.csv", options={"HIDDEN"})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if ifc_file is None:
|
||||
cls.poll_message_set("No IFC file is loaded.")
|
||||
return False
|
||||
elif ifc_file.schema != "IFC4X3":
|
||||
cls.poll_message_set("Schema must be IFC4x3.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
start = time.time()
|
||||
alignment = ifcopenshell.api.alignment.create_alignment_from_csv(self.file, self.filepath)
|
||||
ifcopenshell.api.alignment.create_geometric_representation(self.file, alignment)
|
||||
ifcopenshell.api.alignment.add_stationing_to_alignment(self.file, alignment=alignment, start_station=0.0)
|
||||
|
||||
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
|
||||
sites = self.file.by_type("IfcSite")
|
||||
for site in sites:
|
||||
ifcopenshell.api.spatial.reference_structure(self.file, products=[alignment], relating_structure=site)
|
||||
|
||||
# process the generated IfcReferent for the alignment
|
||||
for rel in alignment.IsNestedBy:
|
||||
for referent in rel.RelatedObjects:
|
||||
if referent.is_a("IfcReferent"):
|
||||
referent_obj = bpy.data.objects.new(tool.Loader.get_name(referent), None)
|
||||
tool.Geometry.link(referent, referent_obj)
|
||||
tool.Collector.assign(referent_obj, should_clean_users_collection=False)
|
||||
|
||||
# an alignment can be an aggregation of multiple child alignments (ie. multiple verticals for a single horizontal)
|
||||
# get all the alignment curves
|
||||
curves = []
|
||||
for rel in alignment.IsDecomposedBy:
|
||||
for agg in rel.RelatedObjects:
|
||||
if agg.is_a("IfcAlignment"):
|
||||
curves.append(ifcopenshell.api.alignment.get_curve(agg)) # 3D curve
|
||||
|
||||
# if there aren't any curves from aggregation, then there is only a single vertical or no vertical
|
||||
if len(curves) == 0:
|
||||
curves.append(ifcopenshell.api.alignment.get_curve(alignment))
|
||||
|
||||
settings = ifcopenshell.geom.settings()
|
||||
for curve in curves:
|
||||
shape = ifcopenshell.geom.create_shape(settings, curve)
|
||||
|
||||
# create a new Blender mesh
|
||||
mesh_name = tool.Loader.get_mesh_name_from_shape(shape)
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
m = tool.Loader.convert_geometry_to_mesh(shape, mesh)
|
||||
|
||||
# create a new Blender object
|
||||
alignment_obj = bpy.data.objects.new(tool.Loader.get_name(alignment), m)
|
||||
|
||||
# link the blender object to with the alignment element
|
||||
tool.Geometry.link(alignment, alignment_obj)
|
||||
|
||||
# assign the object to the blender collections
|
||||
tool.Collector.assign(alignment_obj, should_clean_users_collection=False)
|
||||
|
||||
self.report({"INFO"}, "Imported in %s seconds" % (time.time() - start))
|
||||
@@ -26,42 +26,31 @@ import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.attribute as core
|
||||
import bonsai.core.spatial
|
||||
from typing import TYPE_CHECKING
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
def get_objs_for_operation(
|
||||
operator_properties: "AttributesOperator", context: bpy.types.Context
|
||||
) -> list[bpy.types.Object]:
|
||||
def get_objs_for_operation(operator_properties, context):
|
||||
if operator_properties.obj:
|
||||
return [bpy.data.objects[operator_properties.obj]]
|
||||
if operator_properties.mass_operation:
|
||||
return context.selected_objects[:]
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
return [obj]
|
||||
return [context.active_object]
|
||||
|
||||
|
||||
class AttributesOperator:
|
||||
class EnableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
bl_description = "ALT + Left Click to enable editing attributes on all selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: str
|
||||
mass_operation: bool
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mass_operation = event.alt
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
bl_idname = "bim.enable_editing_attributes"
|
||||
bl_label = "Enable Editing Attributes"
|
||||
bl_description = "ALT + Left Click to enable editing attributes on all selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def enable_editing_attribute_on_obj(self, obj: bpy.types.Object) -> None:
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
def enable_editing_attribute_on_obj(self, obj):
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
@@ -99,14 +88,20 @@ class EnableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingAttributes(bpy.types.Operator, AttributesOperator):
|
||||
class DisableEditingAttributes(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_attributes"
|
||||
bl_label = "Disable Editing Attributes"
|
||||
bl_description = "ALT + Left Click to disable editing attributes on all selected objects"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty(options={"SKIP_SAVE"})
|
||||
mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
|
||||
def disable_editing_attributes_on_obj(self, obj: bpy.types.Object) -> None:
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
def invoke(self, context, event):
|
||||
self.mass_operation = event.alt
|
||||
return self.execute(context)
|
||||
|
||||
def disable_editing_attributes_on_obj(self, obj):
|
||||
props = obj.BIMAttributeProperties
|
||||
props.is_editing_attributes = False
|
||||
|
||||
def execute(self, context):
|
||||
@@ -122,9 +117,9 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
self.file = IfcStore.get_file()
|
||||
obj = tool.Blender.get_active_object(is_selected=False)
|
||||
if not obj or not (element := tool.Ifc.get_entity(obj)):
|
||||
if not (element := tool.Ifc.get_entity(obj)):
|
||||
return
|
||||
|
||||
def callback(attributes, prop):
|
||||
@@ -136,7 +131,7 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
attributes[prop.name] = None
|
||||
return True
|
||||
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes)
|
||||
|
||||
@@ -171,12 +166,13 @@ class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element.GlobalId = ifcopenshell.guid.new()
|
||||
|
||||
obj = context.active_object
|
||||
if not obj or not (props := tool.Blender.get_object_attribute_props(obj)).is_editing_attributes:
|
||||
if not obj or not obj.BIMAttributeProperties.is_editing_attributes:
|
||||
return {"FINISHED"}
|
||||
|
||||
props = obj.BIMAttributeProperties
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
if not element or not element.is_a("IfcRoot"):
|
||||
if not element.is_a("IfcRoot"):
|
||||
return {"FINISHED"}
|
||||
|
||||
if self.use_selected and obj in context.selected_objects:
|
||||
@@ -196,10 +192,7 @@ class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
obj = tool.Blender.get_active_object()
|
||||
assert obj
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
value = props.attributes[self.name].get_value()
|
||||
value = tool.Blender.get_active_object().BIMAttributeProperties.attributes.get(self.name).get_value()
|
||||
total = core.copy_attribute_to_selection(
|
||||
tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value
|
||||
)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.prop import StrProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -29,13 +30,8 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class BIMAttributeProperties(PropertyGroup):
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
is_editing_attributes: BoolProperty(name="Is Editing Attributes")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_editing_attributes: bool
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bonsai.bim.helper
|
||||
import bpy.types
|
||||
from bpy.types import Panel
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.attribute.data import AttributesData
|
||||
import bonsai.tool as tool
|
||||
|
||||
|
||||
def draw_ui(context: bpy.types.Context, layout: bpy.types.UILayout, attributes) -> None:
|
||||
def draw_ui(context, layout, attributes):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Blender.get_object_attribute_props(obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
|
||||
@@ -113,7 +113,7 @@ class AuginCreateNewModel(bpy.types.Operator):
|
||||
context.scene.collection.objects.link(cam_obj)
|
||||
context.scene.camera = cam_obj
|
||||
|
||||
tmpdir = tempfile.mkdtemp(dir=tool.Blender.get_addon_preferences().tmp_dir or None)
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
thumb_path = os.path.join(tmpdir, "thumb.png")
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = thumb_path
|
||||
@@ -122,16 +122,15 @@ class AuginCreateNewModel(bpy.types.Operator):
|
||||
context.scene.render.image_settings.file_format = old_file_format
|
||||
context.scene.render.filepath = old_filepath
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
client.upload_file(bim_props.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(context.scene.BIMProperties.ifc_file, result["s3_bucket"], result["model_path"])
|
||||
client.upload_file(thumb_path, result["s3_bucket"], result["thumb_path"])
|
||||
|
||||
# Notify done
|
||||
url = "https://server.auge.pro.br/API/v3/augin_rest.php/files_uploaded"
|
||||
payload = {
|
||||
"user_token": props.token,
|
||||
"ifc_filesize": os.path.getsize(bim_props.ifc_file),
|
||||
"model_filesize": os.path.getsize(bim_props.ifc_file),
|
||||
"ifc_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
|
||||
"model_filesize": os.path.getsize(context.scene.BIMProperties.ifc_file),
|
||||
"thumb_filesize": os.path.getsize(thumb_path),
|
||||
"model_upload_path": result["model_path"],
|
||||
"thumb_upload_path": result["thumb_path"],
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy.types
|
||||
import bonsai.tool as tool
|
||||
|
||||
|
||||
class BIM_PT_augin(bpy.types.Panel):
|
||||
@@ -48,8 +47,7 @@ class BIM_PT_augin(bpy.types.Panel):
|
||||
row = layout.row()
|
||||
row.label(text="Logged in as " + props.username)
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
if not bim_props.ifc_file:
|
||||
if not context.scene.BIMProperties.ifc_file:
|
||||
row = layout.row()
|
||||
row.label(text="No IFC Found")
|
||||
return
|
||||
|
||||
@@ -21,7 +21,6 @@ import bpy
|
||||
import bcf
|
||||
import bcf.bcfxml
|
||||
import bcf.v2.bcfxml
|
||||
import bonsai.tool as tool
|
||||
from typing import Union
|
||||
|
||||
|
||||
@@ -31,8 +30,7 @@ class BcfStore:
|
||||
@classmethod
|
||||
def get_bcfxml(cls) -> Union[bcf.bcfxml.BcfXml, None]:
|
||||
if not cls.bcfxml:
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
bcf_filepath = props.bcf_file
|
||||
bcf_filepath = bpy.context.scene.BCFProperties.bcf_file
|
||||
if not os.path.isabs(bcf_filepath):
|
||||
bcf_filepath = os.path.abspath(os.path.join(bpy.path.abspath("//"), bcf_filepath))
|
||||
if bcf_filepath:
|
||||
@@ -48,7 +46,7 @@ class BcfStore:
|
||||
@classmethod
|
||||
def set(cls, bcfxml: Union[bcf.bcfxml.BcfXml, None], filepath: str) -> None:
|
||||
cls.bcfxml = bcfxml
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = bpy.context.scene.BCFProperties
|
||||
props.bcf_file = filepath
|
||||
|
||||
# Set bcf_version prop on load.
|
||||
|
||||
@@ -42,8 +42,8 @@ import ifcopenshell.util.unit
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.module.bcf.prop as bcf_prop
|
||||
import bonsai.bim.module.bcf.bcfstore as bcfstore
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from pathlib import Path
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from math import radians, degrees, atan, tan, cos, sin
|
||||
from mathutils import Vector, Matrix, Euler, geometry
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
@@ -55,7 +55,7 @@ class NewBcfProject(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
bcf_v2 = props.bcf_version == "2"
|
||||
bcf_class = bcf.v2.bcfxml.BcfXml if bcf_v2 else bcf.v3.bcfxml.BcfXml
|
||||
bcfxml = bcf_class.create_new("New Project")
|
||||
@@ -64,14 +64,12 @@ class NewBcfProject(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBcfProject(bpy.types.Operator, ImportHelper):
|
||||
class LoadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_project"
|
||||
bl_label = "Load BCF Project"
|
||||
bl_description = "Load the BCF file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
filename_ext = ".bcf"
|
||||
|
||||
def execute(self, context):
|
||||
# Operator is also used when new project is created by not yet saved.
|
||||
@@ -105,12 +103,15 @@ class LoadBcfProject(bpy.types.Operator, ImportHelper):
|
||||
assert bcfxml.project
|
||||
if bcfxml.project.name is None:
|
||||
bcfxml.project.name = nameless
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props.name = bcfxml.project.name
|
||||
context.scene.BCFProperties.name = bcfxml.project.name
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
self.report({"INFO"}, f"BCF Project '{Path(self.filepath).name}' is loaded.")
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class UnloadBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_bcf_project"
|
||||
@@ -130,7 +131,7 @@ class LoadBcfTopics(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
props.topics.clear()
|
||||
# workaround, one non standard topic would break reading entire bcf
|
||||
# ignored these topics ATM
|
||||
@@ -162,8 +163,7 @@ class LoadBcfTopic(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
topic = bcfxml.topics[self.topic_guid]
|
||||
bcfxml.get_header(self.topic_guid)
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
new = props.topics[self.topic_index]
|
||||
new = context.scene.BCFProperties.topics[self.topic_index]
|
||||
data_map = {
|
||||
"name": topic.guid,
|
||||
"title": topic.topic.title,
|
||||
@@ -244,8 +244,7 @@ class LoadBcfComments(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
blender_topic = props.topics.get(self.topic_guid)
|
||||
blender_topic = context.scene.BCFProperties.topics.get(self.topic_guid)
|
||||
blender_topic.comments.clear()
|
||||
for comment in bcfxml.topics[self.topic_guid].comments:
|
||||
new = blender_topic.comments.add()
|
||||
@@ -275,9 +274,7 @@ class EditBcfProjectName(bpy.types.Operator):
|
||||
|
||||
# Bonsai creates default project on load.
|
||||
assert bcfxml.project
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
bcfxml.project.name = props.name
|
||||
bcfxml.project.name = context.scene.BCFProperties.name
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -287,7 +284,7 @@ class EditBcfTopicName(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
@@ -303,7 +300,7 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
@@ -340,15 +337,13 @@ class EditBcfTopic(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SaveBcfProject(bpy.types.Operator, ExportHelper):
|
||||
class SaveBcfProject(bpy.types.Operator):
|
||||
bl_idname = "bim.save_bcf_project"
|
||||
bl_label = "Save BCF Project"
|
||||
bl_description = "Save active BCF project by the provided filepath."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.bcfzip", options={"HIDDEN"})
|
||||
save_current_bcf: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
filename_ext = ".bcf"
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
@@ -365,7 +360,8 @@ class SaveBcfProject(bpy.types.Operator, ExportHelper):
|
||||
self.filepath = str(path)
|
||||
return self.execute(context)
|
||||
|
||||
return ExportHelper.invoke(self, context, event)
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class AddBcfTopic(bpy.types.Operator):
|
||||
@@ -375,15 +371,13 @@ class AddBcfTopic(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return props.author
|
||||
return context.scene.BCFProperties.author
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
bcfxml.add_topic("New Topic", "", props.author)
|
||||
bcfxml.add_topic("New Topic", "", context.scene.BCFProperties.author)
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -395,9 +389,9 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
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.")
|
||||
@@ -409,7 +403,7 @@ class AddBcfBimSnippet(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
is_external = "://" in props.bim_snippet_reference
|
||||
@@ -441,7 +435,7 @@ class AddBcfRelatedTopic(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
related_topics = tool.Bcf.get_topic_related_topics(topic)
|
||||
@@ -468,15 +462,14 @@ class AddBcfHeaderFile(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return props.file_reference
|
||||
return context.scene.BCFProperties.file_reference
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
|
||||
@@ -526,10 +519,9 @@ class ViewBcfTopic(bpy.types.Operator):
|
||||
topic_guid: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
for index, topic in enumerate(props.topics):
|
||||
for index, topic in enumerate(context.scene.BCFProperties.topics):
|
||||
if topic.name.lower() == self.topic_guid.lower():
|
||||
props.active_topic_index = index
|
||||
context.scene.BCFProperties.active_topic_index = index
|
||||
break
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -554,7 +546,7 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
blender_camera = context.scene.camera
|
||||
assert blender_camera
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
|
||||
@@ -683,7 +675,7 @@ class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
if not bcfxml:
|
||||
return False
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
topic = props.active_topic
|
||||
if not topic:
|
||||
return False
|
||||
@@ -698,7 +690,7 @@ class RemoveBcfViewpoint(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
del topic.viewpoints[blender_topic.viewpoints]
|
||||
@@ -723,7 +715,7 @@ class RemoveBcfFile(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
header_files = tool.Bcf.get_topic_header_files(topic)
|
||||
@@ -741,14 +733,13 @@ class RemoveBcfTopic(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return props.topics
|
||||
return context.scene.BCFProperties.topics
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
del bcfxml.topics[props.active_topic.name]
|
||||
bpy.ops.bim.load_bcf_topics()
|
||||
return {"FINISHED"}
|
||||
@@ -761,14 +752,13 @@ class AddBcfReferenceLink(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return bool(props.reference_link)
|
||||
return context.scene.BCFProperties.reference_link
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
reference_links = tool.Bcf.get_topic_reference_links(topic)
|
||||
@@ -786,15 +776,14 @@ class AddBcfDocumentReference(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return bool(props.document_reference)
|
||||
return context.scene.BCFProperties.document_reference
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
|
||||
@@ -866,14 +855,13 @@ class AddBcfLabel(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
return bool(props.label)
|
||||
return context.scene.BCFProperties.label
|
||||
|
||||
def execute(self, context):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
new = blender_topic.labels.add()
|
||||
@@ -895,7 +883,7 @@ class EditBcfReferenceLinks(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
reference_links = [r.name for r in blender_topic.reference_links]
|
||||
@@ -912,7 +900,7 @@ class EditBcfLabels(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
labels = [l.name for l in blender_topic.labels]
|
||||
@@ -930,7 +918,7 @@ class RemoveBcfReferenceLink(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
reference_links = tool.Bcf.get_topic_reference_links(topic)
|
||||
@@ -950,7 +938,7 @@ class RemoveBcfLabel(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
labels = tool.Bcf.get_topic_labels(topic)
|
||||
@@ -969,7 +957,7 @@ class RemoveBcfBimSnippet(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
tool.Bcf.set_topic_bim_snippet(topic, None)
|
||||
@@ -990,7 +978,7 @@ class RemoveBcfDocumentReference(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
|
||||
@@ -1055,7 +1043,7 @@ class RemoveBcfRelatedTopic(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
related_topics = tool.Bcf.get_topic_related_topics(topic)
|
||||
@@ -1075,7 +1063,7 @@ class RemoveBcfComment(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comments = topic.comments
|
||||
@@ -1096,7 +1084,7 @@ class EditBcfComment(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
blender_comment = blender_topic.comments.get(self.comment_guid)
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
@@ -1104,7 +1092,7 @@ class EditBcfComment(bpy.types.Operator):
|
||||
if comment.guid == self.comment_guid:
|
||||
comment.comment = blender_comment.comment
|
||||
comment.modified_date = XmlDateTime.now()
|
||||
comment.modified_author = props.author
|
||||
comment.modified_author = context.scene.BCFProperties.author
|
||||
bpy.ops.bim.load_bcf_comments(topic_guid=topic.guid)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1117,7 +1105,7 @@ class AddBcfComment(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
if not props.comment:
|
||||
cls.poll_message_set("No comment to add.")
|
||||
return False
|
||||
@@ -1140,7 +1128,7 @@ class AddBcfComment(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
bcf_v2 = (bcfxml.version.version_id or "").startswith("2")
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
comments = topic.comments
|
||||
@@ -1148,7 +1136,7 @@ class AddBcfComment(bpy.types.Operator):
|
||||
if bcf_v2:
|
||||
comment = bcf.v2.model.Comment(
|
||||
date=XmlDateTime.now(),
|
||||
author=props.author,
|
||||
author=context.scene.BCFProperties.author,
|
||||
comment=props.comment,
|
||||
guid=str(uuid.uuid4()),
|
||||
)
|
||||
@@ -1161,7 +1149,7 @@ class AddBcfComment(bpy.types.Operator):
|
||||
else:
|
||||
comment = bcf.v3.model.Comment(
|
||||
date=XmlDateTime.now(),
|
||||
author=props.author,
|
||||
author=context.scene.BCFProperties.author,
|
||||
comment=props.comment,
|
||||
guid=str(uuid.uuid4()),
|
||||
)
|
||||
@@ -1191,7 +1179,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
if blender_topic is None:
|
||||
cls.poll_message_set("No topic is active.")
|
||||
@@ -1205,11 +1193,11 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
self.file = IfcStore.get_file()
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
blender_topic = props.active_topic
|
||||
topic = bcfxml.topics[blender_topic.name]
|
||||
if self.viewpoint_guid:
|
||||
@@ -1283,7 +1271,6 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
cam_aspect: float,
|
||||
context: bpy.types.Context,
|
||||
) -> None:
|
||||
assert isinstance(obj.data, bpy.types.Camera)
|
||||
if viewpoint.visualization_info.orthogonal_camera:
|
||||
camera = viewpoint.visualization_info.orthogonal_camera
|
||||
obj.data.type = "ORTHO"
|
||||
@@ -1316,7 +1303,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
[0, 0, 0, 1],
|
||||
)
|
||||
)
|
||||
props = tool.Georeference.get_georeference_props()
|
||||
props = context.scene.BIMGeoreferenceProperties
|
||||
if props.has_blender_offset:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
matrix = ifcopenshell.util.geolocation.global2local(
|
||||
@@ -1355,7 +1342,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
|
||||
objs: list[bpy.types.Object] = []
|
||||
for global_id in exception_global_ids:
|
||||
obj = tool.Ifc.get_object_by_identifier(global_id)
|
||||
obj = IfcStore.get_element(global_id)
|
||||
if obj and context.view_layer.objects.get(obj.name):
|
||||
assert isinstance(obj, bpy.types.Object)
|
||||
objs.append(obj)
|
||||
@@ -1401,7 +1388,6 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
self.hide_spaces(context)
|
||||
|
||||
def hide_spaces(self, context: bpy.types.Context) -> None:
|
||||
assert context.area
|
||||
old = context.area.type
|
||||
context.area.type = "VIEW_3D"
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
@@ -1415,7 +1401,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
return
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for global_id in selected_global_ids:
|
||||
obj = tool.Ifc.get_object_by_identifier(global_id)
|
||||
obj = IfcStore.get_element(global_id)
|
||||
if obj:
|
||||
obj.select_set(True)
|
||||
obj.hide_set(False)
|
||||
@@ -1428,7 +1414,7 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
for acomponent in acoloring.component:
|
||||
global_id_colours.setdefault(acomponent.ifc_guid, acoloring.color)
|
||||
for global_id, color in global_id_colours.items():
|
||||
obj = tool.Ifc.get_object_by_identifier(global_id)
|
||||
obj = IfcStore.get_element(global_id)
|
||||
if obj:
|
||||
obj.color = self.hex_to_rgb(color)
|
||||
|
||||
@@ -1531,51 +1517,58 @@ class OpenBcfReferenceLink(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
webbrowser.open(props.topic_links[self.index].name)
|
||||
webbrowser.open(context.scene.BCFProperties.topic_links[self.index].name)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectBcfHeaderFile(bpy.types.Operator, ImportHelper):
|
||||
class SelectBcfHeaderFile(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_header_file"
|
||||
bl_label = "Select BCF Header File"
|
||||
bl_description = "Select filepath for BCF header reference."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml;*.ifcjson", options={"HIDDEN"})
|
||||
filename_ext = ".ifc"
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props.file_reference = self.filepath
|
||||
context.scene.BCFProperties.file_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
class SelectBcfBimSnippetReference(bpy.types.Operator, ImportHelper):
|
||||
|
||||
class SelectBcfBimSnippetReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_bim_snippet_reference"
|
||||
bl_label = "Select BCF BIM Snippet Reference"
|
||||
bl_description = "Select filepath for BCF snippet reference."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props.bim_snippet_reference = self.filepath
|
||||
context.scene.BCFProperties.bim_snippet_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
class SelectBcfDocumentReference(bpy.types.Operator, ImportHelper):
|
||||
|
||||
class SelectBcfDocumentReference(bpy.types.Operator):
|
||||
bl_idname = "bim.select_bcf_document_reference"
|
||||
bl_label = "Select BCF Document Reference"
|
||||
bl_description = "Select filepath for BCF document reference."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
if self.filepath:
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props.document_reference = self.filepath
|
||||
context.scene.BCFProperties.document_reference = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class LoadBcfHeaderIfcFile(bpy.types.Operator):
|
||||
bl_idname = "bim.load_bcf_header_ifc_file"
|
||||
@@ -1592,8 +1585,7 @@ class LoadBcfHeaderIfcFile(bpy.types.Operator):
|
||||
assert bcfxml
|
||||
|
||||
bcf_path = tool.Bcf.get_path()
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
topic = bcfxml.topics[props.active_topic.name]
|
||||
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
|
||||
entity = tool.Bcf.get_topic_header_files(topic)[self.index]
|
||||
ifc_path = bcf.agnostic.topic.extract_file(topic, entity)
|
||||
bpy.ops.bim.load_project(filepath=ifc_path)
|
||||
@@ -1613,8 +1605,7 @@ class ExtractBcfFile(bpy.types.Operator):
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
topic = bcfxml.topics[props.active_topic.name]
|
||||
topic = bcfxml.topics[context.scene.BCFProperties.active_topic.name]
|
||||
|
||||
if self.entity_type == "HEADER_FILE":
|
||||
entity = tool.Bcf.get_topic_header_files(topic)[self.index]
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from . import bcfstore
|
||||
from bonsai.bim.prop import StrProperty
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -31,8 +30,10 @@ 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
|
||||
|
||||
|
||||
bcfviewpoints_enum = None
|
||||
@@ -94,7 +95,7 @@ def getBcfViewpoints(self, context, force_update=False):
|
||||
global bcfviewpoints_enum
|
||||
if bcfviewpoints_enum is None or force_update: # Retrieving Viewpoints is slow. Make sure we only do when needed
|
||||
bcfviewpoints_enum = []
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
props = context.scene.BCFProperties
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
assert bcfxml
|
||||
topic = props.active_topic
|
||||
@@ -109,12 +110,6 @@ class BcfBimSnippet(PropertyGroup):
|
||||
type: StringProperty(name="Type")
|
||||
is_external: BoolProperty(name="Is External")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
schema: str
|
||||
reference: str
|
||||
type: str
|
||||
is_external: bool
|
||||
|
||||
|
||||
class BcfDocumentReference(PropertyGroup):
|
||||
reference: StringProperty(name="Reference")
|
||||
@@ -122,12 +117,6 @@ class BcfDocumentReference(PropertyGroup):
|
||||
guid: StringProperty(name="GUID")
|
||||
is_external: BoolProperty(name="Is External")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
reference: str
|
||||
description: str
|
||||
guid: str
|
||||
is_external: bool
|
||||
|
||||
|
||||
class BcfComment(PropertyGroup):
|
||||
name: StringProperty(name="GUID")
|
||||
@@ -139,16 +128,6 @@ class BcfComment(PropertyGroup):
|
||||
modified_author: StringProperty(name="Modified Author")
|
||||
is_editable: BoolProperty(name="Is Editable", default=False, update=updateBcfCommentIsEditable)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
date: str
|
||||
author: str
|
||||
comment: str
|
||||
viewpoint: str
|
||||
modified_date: str
|
||||
modified_author: str
|
||||
is_editable: bool
|
||||
|
||||
|
||||
def get_extensions_items(
|
||||
self: "BCFProperties", context: bpy.types.Context, edit_text: str, extensions_attr: str
|
||||
@@ -204,30 +183,6 @@ class BcfTopic(PropertyGroup):
|
||||
comments: CollectionProperty(name="Comments", type=BcfComment)
|
||||
is_editable: BoolProperty(name="Edit Topic Attributes", default=False, update=updateBcfTopicIsEditable)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
title: str
|
||||
type: str
|
||||
status: str
|
||||
priority: str
|
||||
stage: str
|
||||
creation_date: str
|
||||
creation_author: str
|
||||
modified_date: str
|
||||
modified_author: str
|
||||
assigned_to: str
|
||||
due_date: str
|
||||
description: str
|
||||
viewpoints: str
|
||||
files: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
reference_links: bpy.types.bpy_prop_collection_idprop[BcfReferenceLink]
|
||||
labels: bpy.types.bpy_prop_collection_idprop[BcfLabel]
|
||||
bim_snippet: BcfBimSnippet
|
||||
document_references: bpy.types.bpy_prop_collection_idprop[BcfDocumentReference]
|
||||
related_topics: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
comments: bpy.types.bpy_prop_collection_idprop[BcfComment]
|
||||
is_editable: bool
|
||||
|
||||
|
||||
def get_related_topics(self: "BCFProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
global RELATED_TOPICS_ENUM_ITEMS
|
||||
@@ -280,30 +235,7 @@ class BCFProperties(PropertyGroup):
|
||||
comment: StringProperty(default="", name="Comment")
|
||||
has_related_viewpoint: BoolProperty(name="Has Related Viewpoint", default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bcf_file: str
|
||||
bcf_version: str
|
||||
comment_text_width: int
|
||||
name: str
|
||||
author: str
|
||||
topics: bpy.types.bpy_prop_collection_idprop[BcfTopic]
|
||||
active_topic_index: int
|
||||
file_reference: str
|
||||
file_ifc_project: str
|
||||
file_ifc_spatial_structure_element: str
|
||||
reference_link: str
|
||||
label: str
|
||||
bim_snippet_reference: str
|
||||
bim_snippet_type: str
|
||||
bim_snippet_schema: str
|
||||
document_reference: str
|
||||
document_reference_description: str
|
||||
document_description: str
|
||||
related_topic: str
|
||||
comment: str
|
||||
has_related_viewpoint: bool
|
||||
|
||||
def clear_input_fields(self) -> None:
|
||||
def clear_input_fields(self):
|
||||
self.file_reference = ""
|
||||
self.file_ifc_project = ""
|
||||
self.file_ifc_spatial_structure_element = ""
|
||||
@@ -318,7 +250,7 @@ class BCFProperties(PropertyGroup):
|
||||
self.has_related_viewpoint = False
|
||||
|
||||
@property
|
||||
def active_topic(self) -> Union[BcfTopic, None]:
|
||||
def active_topic(self):
|
||||
if len(self.topics) == 0:
|
||||
return None
|
||||
if self.active_topic_index < 0:
|
||||
@@ -327,5 +259,5 @@ class BCFProperties(PropertyGroup):
|
||||
self.active_topic_index = len(self.topics) - 1
|
||||
return self.topics[self.active_topic_index]
|
||||
|
||||
def refresh_topic(self, context: bpy.types.Context) -> None:
|
||||
def refresh_topic(self, context):
|
||||
refreshBcfTopic(self, context)
|
||||
|
||||
@@ -37,7 +37,8 @@ class BIM_PT_bcf(Panel):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
if not bcfstore.BcfStore.get_bcfxml():
|
||||
row = layout.row(align=True)
|
||||
@@ -62,17 +63,18 @@ class BIM_PT_bcf(Panel):
|
||||
row = layout.row()
|
||||
row.prop(props, "author")
|
||||
|
||||
props = context.scene.BCFProperties
|
||||
row = layout.row()
|
||||
row.template_list("BIM_UL_topics", "", props, "topics", props, "active_topic_index")
|
||||
col = row.column(align=True)
|
||||
col.operator("bim.add_bcf_topic", icon="ADD", text="")
|
||||
col.operator("bim.remove_bcf_topic", icon="REMOVE", text="")
|
||||
|
||||
topic = props.active_topic
|
||||
if topic is not None:
|
||||
if props.active_topic_index < len(props.topics):
|
||||
topic = props.active_topic
|
||||
is_editable = topic.is_editable
|
||||
col.prop(topic, "is_editable", icon="CHECKMARK" if topic.is_editable else "GREASEPENCIL", icon_only=True)
|
||||
|
||||
topic = props.active_topic
|
||||
row = layout.row()
|
||||
row.enabled = is_editable
|
||||
row.prop(topic, "description", text="")
|
||||
@@ -129,7 +131,8 @@ class BIM_PT_bcf_metadata(Panel):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
bcfxml = bcfstore.BcfStore.get_bcfxml()
|
||||
if not bcfxml or props.active_topic_index >= len(props.topics):
|
||||
@@ -293,7 +296,8 @@ class BIM_PT_bcf_comments(Panel):
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
props = tool.Bcf.get_bcf_props()
|
||||
scene = context.scene
|
||||
props = scene.BCFProperties
|
||||
|
||||
if props.active_topic_index >= len(props.topics):
|
||||
layout.label(text="No BCF project is loaded")
|
||||
|
||||
@@ -49,9 +49,9 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
bpy.types.Object.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBoundaryProperties
|
||||
del bpy.types.Object.BIMBoundaryProperties
|
||||
del bpy.types.Object.bim_boundary_properties
|
||||
|
||||
@@ -43,8 +43,6 @@ class BoundaryDecorator:
|
||||
cls.installed = None
|
||||
|
||||
def draw_batch(self, shader_type, content_pos, color, indices=None):
|
||||
if not tool.Blender.validate_shader_batch_data(content_pos, indices):
|
||||
return
|
||||
shader = self.line_shader if shader_type == "LINES" else self.shader
|
||||
batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices)
|
||||
shader.uniform_float("color", color)
|
||||
@@ -80,8 +78,7 @@ class BoundaryDecorator:
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
props = tool.Boundary.get_boundary_props()
|
||||
for boundary in props.boundaries:
|
||||
for boundary in context.scene.BIMBoundaryProperties.boundaries:
|
||||
obj = boundary.obj
|
||||
if not obj or not obj.data: # A boundary may not have data if it has no connection geometry
|
||||
continue
|
||||
|
||||
@@ -25,8 +25,6 @@ import mathutils
|
||||
import numpy as np
|
||||
import multiprocessing
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.boundary
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.shape
|
||||
@@ -42,7 +40,6 @@ from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||
from bonsai.bim.module.boundary.decorator import BoundaryDecorator
|
||||
import bonsai.core
|
||||
import bonsai.core.geometry
|
||||
from typing import Union, Optional
|
||||
|
||||
|
||||
def disable_editing_boundary_geometry(context):
|
||||
@@ -60,7 +57,7 @@ def disable_editing_boundary_geometry(context):
|
||||
|
||||
|
||||
class Loader:
|
||||
def __init__(self, operator: Optional[bpy.types.Operator] = None):
|
||||
def __init__(self, operator=None):
|
||||
self.operator = operator
|
||||
self.ifc_file = None
|
||||
self.logger = None
|
||||
@@ -69,20 +66,17 @@ class Loader:
|
||||
self.fallback_settings = self.load_fallback_settings()
|
||||
self.load_importer()
|
||||
|
||||
def create_mesh(self, boundary: ifcopenshell.entity_instance) -> Union[bpy.types.Mesh, None]:
|
||||
def create_mesh(self, boundary):
|
||||
# ConnectionGeometry is optional in IFC schema for some reasons.
|
||||
if not boundary.ConnectionGeometry:
|
||||
return None
|
||||
assert self.ifc_importer
|
||||
assert self.operator
|
||||
surface = boundary.ConnectionGeometry.SurfaceOnRelatingElement
|
||||
# Workaround for invalid geometry provided by Revit. See https://github.com/Autodesk/revit-ifc/issues/270
|
||||
if surface.is_a("IfcCurveBoundedPlane") and not getattr(surface, "InnerBoundaries", None):
|
||||
surface.InnerBoundaries = ()
|
||||
try:
|
||||
shape = ifcopenshell.geom.create_shape(self.settings, surface)
|
||||
mesh = self.ifc_importer.create_mesh(boundary, shape)
|
||||
assert mesh
|
||||
mesh = self.ifc_importer.create_mesh(None, shape)
|
||||
tool.Loader.link_mesh(shape, mesh)
|
||||
except RuntimeError:
|
||||
# Fallback solution for invalid geometry provided by Revit. (InnerBoundaries cuting OuterBoundary)
|
||||
@@ -102,8 +96,7 @@ class Loader:
|
||||
return None
|
||||
mesh = bpy.data.meshes.new(str(surface.id()))
|
||||
bm = bmesh.new()
|
||||
coords = ifcopenshell.util.shape.get_vertices(shape)
|
||||
verts = [bm.verts.new(coord) for coord in coords]
|
||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||
bm.faces.new(verts)
|
||||
for inner_boundary in surface.InnerBoundaries:
|
||||
try:
|
||||
@@ -114,15 +107,13 @@ class Loader:
|
||||
f"Skipping an inner boundary for IfcRelSpaceBoundary with ID {boundary.id()}. Geometry might be invalid",
|
||||
)
|
||||
return None
|
||||
coords = ifcopenshell.util.shape.get_vertices(shape)
|
||||
verts = [bm.verts.new(coord) for coord in coords]
|
||||
bm.faces.new(verts)
|
||||
verts = [bm.verts.new(shape.verts[i : i + 3]) for i in range(0, len(shape.verts), 3)]
|
||||
for i in range(len(verts) - 1):
|
||||
bm.edges.new(verts[i : i + 2])
|
||||
bm.edges.new((verts[-1], verts[0]))
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
tool.Ifc.link(surface, mesh)
|
||||
mesh.BIMMeshProperties.ifc_definition_id = surface.id()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
matrix = mathutils.Matrix(
|
||||
ifcopenshell.util.placement.get_axis2placement(surface.BasisSurface.Position).tolist()
|
||||
@@ -139,7 +130,7 @@ class Loader:
|
||||
settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
|
||||
return settings
|
||||
|
||||
def load_importer(self) -> None:
|
||||
def load_importer(self):
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
self.logger = logging.getLogger("ImportIFC")
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, self.logger)
|
||||
@@ -288,7 +279,7 @@ class SelectProjectBoundaries(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def get_colour(ifc_boundary: ifcopenshell.entity_instance) -> tuple[float, float, float, float]:
|
||||
def get_colour(ifc_boundary):
|
||||
"""Return a color depending on IfcClass given"""
|
||||
product_colors = {
|
||||
"IfcWall": (0.7, 0.3, 0, 1),
|
||||
@@ -324,8 +315,7 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
for obj in context.visible_objects:
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
if not props.ifc_definition_id:
|
||||
if not obj.BIMObjectProperties.ifc_definition_id:
|
||||
continue
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element.is_a("IfcRelSpaceBoundary"):
|
||||
@@ -364,9 +354,7 @@ class EnableEditingBoundary(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = True
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
@@ -385,9 +373,7 @@ class DisableEditingBoundary(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
bprops.is_editing = False
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
setattr(bprops, blender_property, None)
|
||||
@@ -400,17 +386,14 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
boundary = tool.Ifc.get_entity(obj)
|
||||
assert boundary
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
attributes = dict()
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
entity = tool.Ifc.get_entity(obj)
|
||||
attributes[blender_property] = entity
|
||||
ifcopenshell.api.boundary.edit_attributes(tool.Ifc.get(), entity=boundary, **attributes)
|
||||
ifcopenshell.api.run("boundary.edit_attributes", tool.Ifc.get(), entity=boundary, **attributes)
|
||||
bpy.ops.bim.disable_editing_boundary()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -427,7 +410,7 @@ class UpdateBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
tool.Boundary.move_origin_to_space_origin(context.active_object)
|
||||
settings = tool.Boundary.get_assign_connection_geometry_settings(context.active_object)
|
||||
ifcopenshell.api.boundary.assign_connection_geometry(tool.Ifc.get(), **settings)
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", tool.Ifc.get(), **settings)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -536,7 +519,6 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Boundary.get_boundary_props()
|
||||
to_delete = set()
|
||||
spaces = set()
|
||||
for obj in context.selected_objects:
|
||||
@@ -556,7 +538,7 @@ class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for boundary, boundary_obj in to_delete:
|
||||
tool.Ifc.unlink(element=boundary)
|
||||
bpy.data.objects.remove(boundary_obj)
|
||||
props.boundaries.clear()
|
||||
context.scene.BIMBoundaryProperties.boundaries.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -567,7 +549,7 @@ class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Boundary.get_boundary_props()
|
||||
props = context.scene.BIMBoundaryProperties
|
||||
# filter not decorated boundaries and add decorations for them
|
||||
decorated_boundaries = set([i.obj for i in props.boundaries])
|
||||
active_boundaries = set()
|
||||
@@ -593,82 +575,84 @@ class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.add_boundary"
|
||||
bl_label = "Add Boundary"
|
||||
bl_description = (
|
||||
"There are two options for this operator:\n"
|
||||
"- just 1 IfcSpace is selected - generate a boundary based on "
|
||||
"potential nearby (0.1m) boundary elements.\n"
|
||||
"- 1 IfcSpace and 1 IfcElement are selected - generate a boundary "
|
||||
"for IfcSpace and use selected IfcElement as boundary's building element."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
parent_boundaries: list[ifcopenshell.entity_instance] = []
|
||||
relating_space = None
|
||||
related_building_element = None
|
||||
relating_space_obj = None
|
||||
related_building_element_obj = None
|
||||
parent_boundaries = []
|
||||
|
||||
objs = tool.Blender.get_selected_objects()
|
||||
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
|
||||
}
|
||||
objs = context.selected_objects
|
||||
|
||||
if len(ifc_objects) not in (1, 2):
|
||||
self.report({"ERROR"}, "Please select one or two IFC elements.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if not any(element.is_a("IfcSpace") for element in ifc_objects):
|
||||
self.report({"ERROR"}, "One of the selected objects must be an IfcSpace.")
|
||||
return {"CANCELLED"}
|
||||
if not any((element := tool.Ifc.get_entity(obj)) and element.is_a("IfcSpace") for obj in objs):
|
||||
self.report({"INFO"}, "No IfcSpace elements selected.")
|
||||
return {"FINISHED"}
|
||||
|
||||
if len(objs) == 2:
|
||||
# Validate input.
|
||||
space = next((e for e in ifc_objects.keys() if e.is_a("IfcSpace")), None)
|
||||
if not space:
|
||||
self.report({"ERROR"}, "One of the selected objects must be an IfcSpace.")
|
||||
return {"CANCELLED"}
|
||||
element = next((e for e in ifc_objects.keys() if e.is_a("IfcElement")), None)
|
||||
if not element:
|
||||
self.report({"ERROR"}, "One of the selected objects must be an IfcElement.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# The user may select two objects, a space and its related building element
|
||||
for obj in objs:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
if element.is_a("IfcSpace"):
|
||||
relating_space = element
|
||||
relating_space_obj = obj
|
||||
else:
|
||||
related_building_element = element
|
||||
related_building_element_obj = obj
|
||||
parent_boundary = self.create_element_boundary(
|
||||
context, space, ifc_objects[space], element, ifc_objects[element]
|
||||
context, relating_space, relating_space_obj, related_building_element, related_building_element_obj
|
||||
)
|
||||
|
||||
if not parent_boundary:
|
||||
self.report({"ERROR"}, "Failed to create a boundary.")
|
||||
return {"FINISHED"}
|
||||
parent_boundaries.append(parent_boundary)
|
||||
|
||||
if parent_boundary:
|
||||
parent_boundaries.append(parent_boundary)
|
||||
elif len(objs) == 1:
|
||||
space = next(iter(ifc_objects))
|
||||
if not space.is_a("IfcSpace"):
|
||||
self.report({"ERROR"}, "1 element selected but it's not an IfcSpace - please, select IfcSpace..")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# New prototype, old code not yet removed. Still testing.
|
||||
res = self.auto_generate_boundaries(space, ifc_objects[space])
|
||||
if isinstance(res, str):
|
||||
self.report({"ERROR"}, res)
|
||||
return {"FINISHED"}
|
||||
parent_boundaries.extend(res)
|
||||
space = tool.Ifc.get_entity(objs[0])
|
||||
if space.is_a("IfcSpace"):
|
||||
self.auto_generate_boundaries(space, objs[0])
|
||||
elif len(objs) == 1:
|
||||
# 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
|
||||
|
||||
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)
|
||||
obj.select_set(True)
|
||||
|
||||
def auto_generate_boundaries(
|
||||
self, space: ifcopenshell.entity_instance, space_obj: bpy.types.Object
|
||||
) -> Union[str, list[ifcopenshell.entity_instance]]:
|
||||
"""
|
||||
:return: list of created boundaries or a string with error description.
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
props = tool.Model.get_model_props()
|
||||
boundaries: list[ifcopenshell.entity_instance] = []
|
||||
assert isinstance(space_obj.data, bpy.types.Mesh)
|
||||
|
||||
def auto_generate_boundaries(self, space, space_obj):
|
||||
# Identify all potential building elements
|
||||
# TODO: don't select everything, use AABB culling in Blender
|
||||
building_elements = (
|
||||
@@ -677,14 +661,6 @@ 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:
|
||||
@@ -696,15 +672,12 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
shapes = {}
|
||||
settings = ifcopenshell.geom.settings()
|
||||
settings.set("disable-opening-subtractions", True)
|
||||
iterator = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count(), include=include)
|
||||
iterator = ifcopenshell.geom.iterator(settings, tool.Ifc.get(), multiprocessing.cpu_count(), include=include)
|
||||
if iterator.initialize():
|
||||
while True:
|
||||
tree.add_element(iterator.get_native())
|
||||
shape = iterator.get()
|
||||
shapes[shape.id] = {
|
||||
"verts": ifcopenshell.util.shape.get_vertices(shape.geometry),
|
||||
"faces": ifcopenshell.util.shape.get_faces(shape.geometry),
|
||||
}
|
||||
shapes[shape.id] = shape
|
||||
if not iterator.next():
|
||||
break
|
||||
|
||||
@@ -712,12 +685,12 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
building_elements = [e for e in tree.select(space, extend=0.1) if e != space]
|
||||
|
||||
if not building_elements:
|
||||
return "No building elements found to create boundaries."
|
||||
return
|
||||
|
||||
# Create a dissolved bmesh for the space
|
||||
space_bm = bmesh.new()
|
||||
space_bm.from_mesh(space_obj.data)
|
||||
bmesh.ops.dissolve_limit(space_bm, angle_limit=pi * 2 / 360, verts=space_bm.verts[:], edges=space_bm.edges[:])
|
||||
bmesh.ops.dissolve_limit(space_bm, angle_limit=pi * 2 / 360, verts=space_bm.verts, edges=space_bm.edges)
|
||||
|
||||
# Create dissolved bmeshes for all boundary elements
|
||||
building_element_bms = {}
|
||||
@@ -725,11 +698,13 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bm = bmesh.new()
|
||||
shape = shapes[building_element.id()]
|
||||
|
||||
for vert in shape["verts"]:
|
||||
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
|
||||
for vert in verts:
|
||||
bm.verts.new(Vector(vert))
|
||||
bm.verts.ensure_lookup_table()
|
||||
|
||||
for face in shape["faces"]:
|
||||
faces = ifcopenshell.util.shape.get_faces(shape.geometry)
|
||||
for face in faces:
|
||||
bm.faces.new([bm.verts[i] for i in face])
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
@@ -793,7 +768,9 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# we cheat by using the exterior boundary to mean "gross".
|
||||
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
|
||||
|
||||
parent_boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
|
||||
parent_boundary = tool.Ifc.run(
|
||||
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
|
||||
)
|
||||
if building_element.is_a("IfcVirtualElement"):
|
||||
parent_boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
||||
else:
|
||||
@@ -825,7 +802,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
exterior_boundary_polygon, space_face_matrix
|
||||
)
|
||||
self.set_boundary_name(parent_boundary)
|
||||
boundaries.append(parent_boundary)
|
||||
|
||||
for rel in getattr(building_element, "HasOpenings", []):
|
||||
opening = rel.RelatedOpeningElement
|
||||
@@ -871,7 +847,9 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if opening_polygon.intersection(exterior_boundary_polygon).area == 0:
|
||||
continue
|
||||
|
||||
boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
|
||||
boundary = tool.Ifc.run(
|
||||
"root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class
|
||||
)
|
||||
boundary.RelatingSpace = space
|
||||
boundary.RelatedBuildingElement = filling or opening
|
||||
boundary.ConnectionGeometry = self.create_connection_geometry_from_polygon(
|
||||
@@ -885,39 +863,28 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if boundary.is_a() != "IfcRelSpaceBoundary":
|
||||
boundary.ParentBoundary = parent_boundary
|
||||
self.set_boundary_name(boundary)
|
||||
boundaries.append(boundary)
|
||||
|
||||
return boundaries
|
||||
|
||||
def create_element_boundary(
|
||||
self,
|
||||
context: bpy.types.Context,
|
||||
space: ifcopenshell.entity_instance,
|
||||
space_obj: bpy.types.Object,
|
||||
building_element: ifcopenshell.entity_instance,
|
||||
building_element_obj: bpy.types.Object,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Create element boundary for IfcSpace and IfcElement as building element.
|
||||
self, context, relating_space, relating_space_obj, related_building_element, related_building_element_obj
|
||||
):
|
||||
if not relating_space or not related_building_element:
|
||||
return
|
||||
|
||||
:return: IfcRelSpaceBoundary or ``None`` if couldn't find a face on ``space_obj``
|
||||
that would be close enough to ``related_building_element_obj``.
|
||||
"""
|
||||
props = tool.Model.get_model_props()
|
||||
# Find which face on space should be bounded to related building element
|
||||
# TODO: Handle round wall where multiple faces need to be bound to the same related building element
|
||||
space_bm = bmesh.new()
|
||||
assert isinstance(space_obj.data, bpy.types.Mesh)
|
||||
space_bm.from_mesh(space_obj.data)
|
||||
bmesh.ops.dissolve_limit(space_bm, angle_limit=pi * 2 / 360, verts=space_bm.verts[:], edges=space_bm.edges[:])
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(relating_space_obj.data)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi * 2 / 360, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
target_distance = inf
|
||||
target_face = None
|
||||
to_building_obj_space = building_element_obj.matrix_world.inverted()
|
||||
for face in space_bm.faces:
|
||||
centroid = space_obj.matrix_world @ face.calc_center_median()
|
||||
raycast = building_element_obj.closest_point_on_mesh(to_building_obj_space @ centroid, distance=1)
|
||||
for face in bm.faces:
|
||||
centroid = relating_space_obj.matrix_world @ face.calc_center_median()
|
||||
raycast = related_building_element_obj.closest_point_on_mesh(
|
||||
related_building_element_obj.matrix_world.inverted() @ centroid, distance=1
|
||||
)
|
||||
if raycast[0]:
|
||||
distance = (building_element_obj.matrix_world @ raycast[1] - centroid).length
|
||||
distance = (related_building_element_obj.matrix_world @ raycast[1] - centroid).length
|
||||
if distance < target_distance:
|
||||
target_face = face
|
||||
target_distance = distance
|
||||
@@ -932,7 +899,9 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
target_face_polygon = shapely.Polygon([tuple((target_face_matrix_i @ v).xy) for v in target_face_verts])
|
||||
|
||||
related_building_element_polygon = self.get_flattened_polygon(building_element, space_obj, target_face_matrix_i)
|
||||
related_building_element_polygon = self.get_flattened_polygon(
|
||||
related_building_element, relating_space_obj, target_face_matrix_i
|
||||
)
|
||||
|
||||
gross_boundary_polygon = target_face_polygon.intersection(related_building_element_polygon)
|
||||
|
||||
@@ -948,8 +917,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
):
|
||||
return
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
parent_boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
|
||||
parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
||||
parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||
# Set to EXTERNAL by default and turn later to internal if there is a corresponding boundary relating to an
|
||||
# internal space
|
||||
@@ -962,14 +930,14 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# space.
|
||||
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
|
||||
|
||||
for rel in getattr(building_element, "HasOpenings", []):
|
||||
for rel in getattr(related_building_element, "HasOpenings", []):
|
||||
opening = rel.RelatedOpeningElement
|
||||
if not opening.HasFillings:
|
||||
continue
|
||||
# TODO: HasFilling is a zero to many relationship. How to handle many ?
|
||||
filling = opening.HasFillings[0].RelatedBuildingElement
|
||||
|
||||
opening_polygon = self.get_flattened_polygon(opening, space_obj, target_face_matrix_i)
|
||||
opening_polygon = self.get_flattened_polygon(opening, relating_space_obj, target_face_matrix_i)
|
||||
|
||||
# An inner boundary is supposed to overlap its parent boundary according to IFC4 documentation
|
||||
# so we extend our exterior boundary with all opening which has a filling
|
||||
@@ -984,11 +952,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
|
||||
boundary = ifcopenshell.api.root.create_entity(ifc_file, ifc_class=props.boundary_class)
|
||||
boundary.RelatingSpace = space
|
||||
boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class)
|
||||
boundary.RelatingSpace = relating_space
|
||||
boundary.RelatedBuildingElement = filling
|
||||
boundary.ConnectionGeometry = connection_geometry
|
||||
if building_element.is_a("IfcVirtualElement"):
|
||||
if related_building_element.is_a("IfcVirtualElement"):
|
||||
boundary.PhysicalOrVirtualBoundary = "VIRTUAL"
|
||||
else:
|
||||
boundary.PhysicalOrVirtualBoundary = "PHYSICAL"
|
||||
@@ -1000,23 +968,20 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(
|
||||
exterior_boundary_polygon, target_face_matrix
|
||||
)
|
||||
parent_boundary.RelatingSpace = space
|
||||
parent_boundary.RelatedBuildingElement = building_element
|
||||
parent_boundary.RelatingSpace = relating_space
|
||||
parent_boundary.RelatedBuildingElement = related_building_element
|
||||
parent_boundary.ConnectionGeometry = connection_geometry
|
||||
self.set_boundary_name(parent_boundary)
|
||||
return parent_boundary
|
||||
|
||||
def get_face_matrix(self, p1: Vector, p2: Vector, p3: Vector) -> Matrix:
|
||||
def get_face_matrix(self, p1, p2, p3):
|
||||
edge1 = p2 - p1
|
||||
edge2 = p3 - p1
|
||||
normal = edge1.cross(edge2)
|
||||
assert isinstance(normal, Vector)
|
||||
|
||||
z_axis = normal.normalized()
|
||||
x_axis = p2 - p1
|
||||
x_axis.normalize()
|
||||
y_axis = z_axis.cross(x_axis)
|
||||
assert isinstance(y_axis, Vector)
|
||||
|
||||
mat = Matrix()
|
||||
mat.col[0][:3] = x_axis
|
||||
@@ -1086,7 +1051,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
return surface
|
||||
|
||||
def set_boundary_name(self, boundary: ifcopenshell.entity_instance):
|
||||
def set_boundary_name(self, boundary):
|
||||
"""
|
||||
By convention 1stLevel and 2ndLevel boundary have specific name and description
|
||||
See https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRelSpaceBoundary.htm
|
||||
|
||||
@@ -30,24 +30,23 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
def space_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
def space_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
|
||||
return False
|
||||
|
||||
|
||||
def boundary_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
def boundary_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
return False
|
||||
|
||||
|
||||
def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
def element_filter(self, object):
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcElement")
|
||||
@@ -61,16 +60,6 @@ class BIMObjectBoundaryProperties(PropertyGroup):
|
||||
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_editing: bool
|
||||
relating_space: Union[bpy.types.Object, None]
|
||||
related_building_element: Union[bpy.types.Object, None]
|
||||
parent_boundary: Union[bpy.types.Object, None]
|
||||
corresponding_boundary: Union[bpy.types.Object, None]
|
||||
|
||||
|
||||
class BIMBoundaryProperties(PropertyGroup):
|
||||
boundaries: bpy.props.CollectionProperty(type=ObjProperty, description="Decorated boundaries")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
boundaries: bpy.types.bpy_prop_collection_idprop[ObjProperty]
|
||||
boundaries: bpy.props.CollectionProperty(type=ObjProperty)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user