mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-07 16:31:37 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc90340b3b | |||
| db075c7160 |
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Error Description and Steps to Reproduce**
|
||||
|
||||
<!--
|
||||
Describe what problem occurred and what you expected to happen instead.
|
||||
|
||||
1. To reproduce this, open file '...'
|
||||
2. Click on '....'
|
||||
3. See error
|
||||
-->
|
||||
|
||||
**Attachments**
|
||||
|
||||
<!--
|
||||
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). Private files can be uploaded to https://ifcopenshell.org/upload.html - only viewed by core developers and will be deleted afterwards.
|
||||
-->
|
||||
|
||||
**Debug information**
|
||||
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
**Feature Description**
|
||||
|
||||
<!--
|
||||
Describe a feature you'd like us to add. 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 }}
|
||||
@@ -1,136 +0,0 @@
|
||||
name: Build IfcOpenShell OSX
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos
|
||||
runner: macos-13
|
||||
arch: x64
|
||||
oldarch:
|
||||
- os: macos
|
||||
runner: macos-14
|
||||
arch: arm64
|
||||
oldarch: m1
|
||||
|
||||
steps:
|
||||
- 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: ${{ matrix.os }}-${{ matrix.arch }}
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install git bison autoconf automake libffi cmake findutils
|
||||
echo "$(brew --prefix findutils)/libexec/gnubin" >> $GITHUB_PATH
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: ${GITHUB_WORKFLOW}-${{ matrix.os }}
|
||||
|
||||
- name: Run Build Script
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" == "macos" ]; then
|
||||
DARWIN_C_SOURCE=-D_DARWIN_C_SOURCE
|
||||
fi
|
||||
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`/*/10.9/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}-macos${{ matrix.oldarch }}64.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}-macos${{ matrix.oldarch }}64.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: Overwrite aws cli
|
||||
run: |
|
||||
# Error: The `brew link` step did not complete successfully
|
||||
# The formula built, but is not symlinked into /usr/local
|
||||
# Could not symlink bin/idle3
|
||||
# Target /usr/local/bin/idle3
|
||||
# already exists. You may want to remove it:
|
||||
# rm '/usr/local/bin/idle3'
|
||||
#
|
||||
# To force the link and overwrite all conflicting files:
|
||||
# brew link --overwrite python@3.13
|
||||
# https://github.com/rust-lang/rustup/pull/3989/files
|
||||
brew install --overwrite awscli | true
|
||||
|
||||
- name: Upload .zip archives to S3
|
||||
run: |
|
||||
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Build IfcOpenShell WASM / Pyodide
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
path: IfcOpenShell
|
||||
|
||||
- name: Checkout Pyodide
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
repository: pyodide/pyodide
|
||||
ref: '0.26.4'
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
path: pyodide
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
VERSION=`cat IfcOpenShell/VERSION`
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/meta.yaml
|
||||
sed -i s/0.8.0/$VERSION/g IfcOpenShell/pyodide/setup.py
|
||||
echo '#!/usr/bin/bash' > script.sh
|
||||
echo 'cd pyodide' > script.sh
|
||||
echo 'make && pip install ./pyodide-build' >> script.sh
|
||||
echo 'cd ..' >> script.sh
|
||||
echo 'mkdir -p packages/ifcopenshell' >> script.sh
|
||||
echo 'cp IfcOpenShell/pyodide/meta.yaml packages/ifcopenshell' >> script.sh
|
||||
echo 'PYODIDE_ROOT=/src/pyodide \' >> script.sh
|
||||
echo 'PATH=/src/pyodide/emsdk/emsdk:/src/pyodide/emsdk/emsdk/node/20.18.0_64bit/bin:/src/pyodide/emsdk/emsdk/upstream/emscripten:$PATH \' >> script.sh
|
||||
echo 'pyodide build-recipes ifcopenshell --install' >> script.sh
|
||||
chmod +x script.sh
|
||||
sed -i s/--tty// pyodide/run_docker
|
||||
pyodide/run_docker ./script.sh
|
||||
mv dist/ifcopenshell-$VERSION-py3-none-any.whl dist/ifcopenshell-$VERSION+${GITHUB_SHA:0:7}-cp312-cp312-emscripten_3_1_58_wasm32.whl
|
||||
|
||||
- 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 dist s3://ifcopenshell-builds/ --recursive --exclude "*" --include "*.whl"
|
||||
@@ -1,116 +0,0 @@
|
||||
name: Build IfcOpenShell Linux
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: ubuntu-22.04
|
||||
container: 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-x86_64.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-x64
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
install_root=$(find ./build -maxdepth 4 -name install)
|
||||
find "$install_root" -type f -name 'cache-*.tar.gz' -maxdepth 1 -exec tar -xzf {} -C "$install_root" \;
|
||||
|
||||
# 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}-linux64.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}-linux64.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
|
||||
@@ -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
|
||||
@@ -1,122 +0,0 @@
|
||||
name: Build IfcOpenShell Windows
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_ifcopenshell:
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: ['3.9.11', '3.10.3', '3.11.8', '3.12.1', '3.13.0']
|
||||
arch: ['x64']
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: _deps-vs2019-x64-installed
|
||||
ref: windows-${{ matrix.arch }}
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
choco install -y sed 7zip.install awscli
|
||||
|
||||
- name: Install Python
|
||||
run: |
|
||||
$installer = "python-${{ matrix.python }}-amd64.exe"
|
||||
$url = "https://www.python.org/ftp/python/${{ matrix.python }}/$installer"
|
||||
Invoke-WebRequest -Uri $url -OutFile $installer
|
||||
Start-Process -Wait -FilePath .\$installer -ArgumentList '/quiet InstallAllUsers=0 PrependPath=0 Include_test=0 TargetDir=C:\Python\${{ matrix.python }}'
|
||||
Remove-Item .\$installer
|
||||
|
||||
- name: Unpack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
Get-ChildItem -Path . -Filter 'cache-*.zip' | ForEach-Object {
|
||||
7z x $_.FullName
|
||||
}
|
||||
|
||||
- name: Run Build Script
|
||||
shell: cmd
|
||||
run: |
|
||||
setlocal EnableDelayedExpansion
|
||||
SET PYTHON_VERSION=${{ matrix.python }}
|
||||
for /f "tokens=1,2,3 delims=." %%a in ("%PYTHON_VERSION%") do (
|
||||
set PY_VER_MAJOR_MINOR=%%a%%b
|
||||
)
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
|
||||
SET IFCOS_INSTALL_PYTHON=FALSE
|
||||
cd win
|
||||
echo y | call build-deps.cmd vs2019-x64 Release
|
||||
SET PYTHONHOME=C:\Python\${{ matrix.python }}
|
||||
call run-cmake.bat vs2019-x64 -DENABLE_BUILD_OPTIMIZATIONS=On -DGLTF_SUPPORT=ON -DADD_COMMIT_SHA=ON -DVERSION_OVERRIDE=ON
|
||||
call install-ifcopenshell.bat vs2019-x64 Release
|
||||
|
||||
- name: Pack Dependencies
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
Get-ChildItem -Path . -Directory | ForEach-Object {
|
||||
$cacheFile = "cache-$($_.Name).zip"
|
||||
echo $cacheFile
|
||||
if (!(Test-Path $cacheFile)) {
|
||||
7z a $cacheFile $_.FullName
|
||||
}
|
||||
}
|
||||
|
||||
- name: Commit and Push Changes to Build Repository
|
||||
run: |
|
||||
cd _deps-vs2019-x64-installed
|
||||
git config user.name "IfcOpenBot"
|
||||
git config user.email "ifcopenbot@ifcopenshell.org"
|
||||
git add *.zip
|
||||
git commit -m "Update build artifacts [skip ci]" || echo "No changes to commit"
|
||||
git push || echo "Push failed"
|
||||
|
||||
- name: Package .zip Archives
|
||||
run: |
|
||||
$VERSION = 'v' + ((Get-Content VERSION).Trim())
|
||||
$SHA = ${env:GITHUB_SHA}.Substring(0, 7)
|
||||
$OUTPUT_DIR = "$env:USERPROFILE\output"
|
||||
New-Item -ItemType Directory -Force -Path $OUTPUT_DIR
|
||||
|
||||
if ("${{ matrix.python }}" -eq "3.9.11") {
|
||||
# only for the first python version the executables are assembled for upload
|
||||
cd _installed-vs2019-x64/bin
|
||||
Get-ChildItem -Path . | ForEach-Object {
|
||||
echo $_
|
||||
$exe = $_.Name
|
||||
$baseName = $exe.Substring(0, $exe.Length - 4)
|
||||
$zipName = "${baseName}-$VERSION-$SHA-win64.zip"
|
||||
7z a $zipName $exe
|
||||
}
|
||||
mv *.zip $OUTPUT_DIR
|
||||
}
|
||||
|
||||
$pyVersion = "${{ matrix.python }}"
|
||||
$pyVersionMajor = ($pyVersion -split '\.')[0..1] -join ''
|
||||
cd C:\Python\${{ matrix.python }}\Lib\site-packages
|
||||
Remove-Item -Recurse -Force ifcopenshell\__pycache__ -ErrorAction SilentlyContinue
|
||||
Get-ChildItem -Path ifcopenshell -Filter "*.pyc" -Recurse | Remove-Item -Force
|
||||
$zipName = "ifcopenshell-python-$pyVersionMajor-$VERSION-$SHA-win64.zip"
|
||||
7z a $zipName ifcopenshell
|
||||
mv $zipName $OUTPUT_DIR
|
||||
|
||||
- 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 $env:USERPROFILE\output s3://ifcopenshell-builds/ --recursive
|
||||
@@ -1,73 +0,0 @@
|
||||
name: ci-black-formatting
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.12"
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Action - checkout repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Step 1 - install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install 'black>=24.10.0'
|
||||
|
||||
# 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: |
|
||||
python3 -m black --check . \
|
||||
&& exit 0 \
|
||||
|| (echo "exit_code=$?" >> "$GITHUB_OUTPUT" && exit 1);
|
||||
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;
|
||||
|
||||
# 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;
|
||||
|
||||
# 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;
|
||||
@@ -0,0 +1,12 @@
|
||||
name: ci-black-formatting
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-formatting:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: psf/black@stable
|
||||
@@ -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.3/blender-4.3.2-linux-x64.tar.xz
|
||||
wget -q -O blender.tar.xz https://ftp.nluug.nl/pub/graphics/blender/release/Blender4.2/blender-4.2.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,41 +139,19 @@ 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
|
||||
blender --online-mode --background --python-expr "import bpy; \
|
||||
bpy.ops.extensions.package_install(repo_index=0, pkg_id='sun_position'); \
|
||||
bpy.ops.preferences.addon_enable(module='bl_ext.blender_org.sun_position'); bpy.ops.wm.save_userpref()"
|
||||
|
||||
cd IfcOpenShell/src/bonsai
|
||||
cd ../bonsai
|
||||
pip install pytest-blender
|
||||
blender --background --python scripts/setup_pytest.py
|
||||
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
|
||||
|
||||
@@ -24,6 +24,10 @@ jobs:
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Windows 32bit",
|
||||
short_name: win32,
|
||||
}
|
||||
- {
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
|
||||
@@ -25,6 +25,10 @@ jobs:
|
||||
name: "Windows 64bit",
|
||||
short_name: win64,
|
||||
}
|
||||
- {
|
||||
name: "Windows 32bit",
|
||||
short_name: win32,
|
||||
}
|
||||
- {
|
||||
name: "Linux 64bit",
|
||||
short_name: linux64
|
||||
|
||||
@@ -15,7 +15,6 @@ on:
|
||||
- 'src/ifcparse/**'
|
||||
- 'src/ifcwrap/**'
|
||||
- 'src/qtviewer/**'
|
||||
- 'src/svgfill/**'
|
||||
- 'src/serializers/**'
|
||||
- 'conda/**'
|
||||
- 'cmake/**'
|
||||
@@ -24,15 +23,15 @@ on:
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell' &&
|
||||
!contains(github.event.head_commit.message, 'skip ci')
|
||||
steps:
|
||||
- run: echo ok go
|
||||
|
||||
compile-and-test:
|
||||
runs-on: ubuntu-22.04
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -48,6 +47,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
|
||||
|
||||
@@ -126,7 +126,6 @@ jobs:
|
||||
python tests.py
|
||||
cd ../src/ifcopenshell-python
|
||||
mv ifcopenshell ifcopenshell-local # Force testing on installed module
|
||||
pip install -e ../ifcpatch --no-deps # Needed for sql.py tests.
|
||||
make test
|
||||
cd ../bcf && make test
|
||||
pip install requests
|
||||
@@ -134,5 +133,4 @@ jobs:
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
pip install -e ../ifctester --no-deps
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
name: Build and Deploy Unstable Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v0.8.0 # Trigger the workflow on pushes to the default branch which is currently v0.8.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd src/bonsai/docs # Navigate to the docs directory
|
||||
pip install -r requirements.txt # Install dependencies from requirements.txt
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
cd src/bonsai/docs # Navigate to the docs directory
|
||||
make html # Build the documentation
|
||||
|
||||
- name: Deploy to GitHub Pages (Unstable)
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} # SSH key for deployment
|
||||
external_repository: IfcOpenShell/bonsaibim_org_docs_unstable # Target repository
|
||||
publish_branch: main # Branch to deploy to
|
||||
cname: docs-unstable.bonsaibim.org # Custom domain for unstable docs
|
||||
publish_dir: src/bonsai/docs/_build/html # Directory containing built docs
|
||||
@@ -1,36 +0,0 @@
|
||||
name: Build and Deploy Stable Documentation
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd src/bonsai/docs
|
||||
pip install -r requirements.txt # Run pip install from the docs directory
|
||||
|
||||
- name: Build documentation
|
||||
run: |
|
||||
cd src/bonsai/docs
|
||||
make html
|
||||
|
||||
- name: Deploy to GitHub Pages (Stable)
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
|
||||
external_repository: IfcOpenShell/bonsaibim_org_docs
|
||||
publish_branch: main
|
||||
cname: docs.bonsaibim.org
|
||||
publish_dir: src/bonsai/docs/_build/html
|
||||
@@ -1,50 +0,0 @@
|
||||
name: Deploy Pyodide Demo App to GitHub Pages
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/pyodide/**'
|
||||
- '.github/workflows/publish-pyodide-demo-app.yml'
|
||||
branches:
|
||||
- v0.8.0
|
||||
|
||||
jobs:
|
||||
activate:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.repository == 'IfcOpenShell/IfcOpenShell'
|
||||
steps:
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
build:
|
||||
needs: activate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (recursive)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload static files as artifact
|
||||
id: deployment
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: src/pyodide/demo-app/
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -81,12 +81,6 @@ src/bonsai/test/files/temp
|
||||
src/bonsai/test/files/basic.ifc.cache.blend
|
||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
||||
|
||||
# bonsai data
|
||||
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/drawings
|
||||
src/bonsai/layouts
|
||||
|
||||
@@ -104,6 +98,3 @@ src/ifcopenshell-python/ifcopenshell/ifcopenshell_wrapper.py
|
||||
|
||||
# Brickschema
|
||||
src/bonsai/bonsai/bim/schema/Brick.ttl
|
||||
|
||||
bonsaiDecoratorForLoads.code-workspace
|
||||
dev_environment.bat
|
||||
|
||||
@@ -17,9 +17,3 @@
|
||||
[submodule "docs/cpp-api/assets/doxygen-awesome-css"]
|
||||
path = docs/cpp-api/assets/doxygen-awesome-css
|
||||
url = https://github.com/jothepro/doxygen-awesome-css.git
|
||||
[submodule "src/ifcopenshell-python/ifcopenshell/simple_spf"]
|
||||
path = src/ifcopenshell-python/ifcopenshell/simple_spf
|
||||
url = https://github.com/IfcOpenShell/step-file-parser
|
||||
[submodule "src/pyodide/demo-app/wheels"]
|
||||
path = src/pyodide/demo-app/wheels
|
||||
url = https://github.com/IfcOpenShell/wasm-wheels
|
||||
|
||||
@@ -6,7 +6,6 @@ IfcOpenShell
|
||||
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/88302/34901387-e2dd-4a0c-8e38-9ffc32a66cde">
|
||||
</p>
|
||||
|
||||
|
||||
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
|
||||
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and [IFC4x3 Add2]. Extensive geometric support
|
||||
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
|
||||
|
||||
+13
-55
@@ -77,7 +77,7 @@ option(HDF5_SUPPORT "Enable HDF5 support (requires HDF5, zlib)" ON)
|
||||
option(WITH_PROJ "Enable output of Earth-Centered Earth-Fixed glTF output using the PROJ library" OFF)
|
||||
option(IFCXML_SUPPORT "Build IfcParse with ifcXML support (requires libxml2)." ON)
|
||||
option(USD_SUPPORT "Build IfcConvert with USD support (requires pixar's USD library)." OFF)
|
||||
option(CITYJSON_SUPPORT "Build IfcConvert with CityJSON support (requires CityJSON library)." OFF)
|
||||
option(CITYJSON_SUPPORT "Build IfcConvert with CityJSON support (requires CityJSON library)." ON)
|
||||
option(WITH_RELATIONSHIP_VALIDATION "Build IfcConvert with option to validate geometrical relationships." OFF)
|
||||
|
||||
option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only instead of system-wide." OFF)
|
||||
@@ -196,25 +196,13 @@ foreach(option_flag IN LISTS option_flags)
|
||||
convert_env_var_to_bool("${option_flag}")
|
||||
endforeach()
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
|
||||
|
||||
macro(clear_wasm_sysroot)
|
||||
if(WASM_BUILD)
|
||||
# when using the nix/build-all.py build script we should not
|
||||
# look into the sysroot for most of the dependencies but rather
|
||||
# in the designated build/ folder created by the script.
|
||||
set(CMAKE_FIND_ROOT_PATH_BACKUP "${CMAKE_FIND_ROOT_PATH}")
|
||||
set(CMAKE_FIND_ROOT_PATH "")
|
||||
endif()
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
|
||||
endmacro()
|
||||
|
||||
macro(restore_wasm_sysroot)
|
||||
if(WASM_BUILD)
|
||||
# reset to use sysroot
|
||||
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
|
||||
endif()
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
endmacro()
|
||||
|
||||
if(WITH_CGAL)
|
||||
add_definitions(-DIFOPSH_WITH_CGAL)
|
||||
@@ -235,9 +223,7 @@ endif()
|
||||
|
||||
if(GLTF_SUPPORT OR CITYJSON_SUPPORT)
|
||||
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
|
||||
clear_wasm_sysroot()
|
||||
find_path(json_header_path "nlohmann/json.hpp" HINTS ${JSON_INCLUDE_DIR})
|
||||
restore_wasm_sysroot()
|
||||
set(JSON_INCLUDE_DIR ${json_header_path})
|
||||
|
||||
if(json_header_path)
|
||||
@@ -344,17 +330,13 @@ if(USE_MMAP)
|
||||
add_definitions(-DUSE_MMAP)
|
||||
endif()
|
||||
|
||||
clear_wasm_sysroot()
|
||||
find_package(Boost REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
restore_wasm_sysroot()
|
||||
message(STATUS "Boost include files found in ${Boost_INCLUDE_DIRS}")
|
||||
message(STATUS "Boost libraries found in ${Boost_LIBRARY_DIRS}")
|
||||
|
||||
if(NOT MINIMAL_BUILD)
|
||||
# libxml2 is required for IFCXML (optional) and SVGFILL (mandatory)
|
||||
clear_wasm_sysroot()
|
||||
find_package(LibXml2 REQUIRED)
|
||||
restore_wasm_sysroot()
|
||||
endif()
|
||||
|
||||
if(IFCXML_SUPPORT)
|
||||
@@ -370,7 +352,6 @@ if(BUILD_IFCGEOM)
|
||||
# Open CASCADE
|
||||
if(WITH_OPENCASCADE)
|
||||
if("${OCC_INCLUDE_DIR}" STREQUAL "")
|
||||
clear_wasm_sysroot()
|
||||
find_path(OCC_INCLUDE_DIR Standard_Version.hxx
|
||||
PATHS
|
||||
/usr/include/occt
|
||||
@@ -378,7 +359,6 @@ if(BUILD_IFCGEOM)
|
||||
/usr/include/opencascade
|
||||
REQUIRED
|
||||
)
|
||||
restore_wasm_sysroot()
|
||||
|
||||
if(OCC_INCLUDE_DIR)
|
||||
message(STATUS "Found Open CASCADE include files in: ${OCC_INCLUDE_DIR}")
|
||||
@@ -438,9 +418,7 @@ if(BUILD_IFCGEOM)
|
||||
message(STATUS "Looking for Open CASCADE library files in: ${OCC_LIBRARY_DIR}")
|
||||
endif()
|
||||
|
||||
clear_wasm_sysroot()
|
||||
find_library(libTKernel NAMES TKernel TKerneld PATHS ${OCC_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
restore_wasm_sysroot()
|
||||
|
||||
if(libTKernel)
|
||||
message(STATUS "Required Open Cascade Library files found")
|
||||
@@ -663,6 +641,11 @@ if(HDF5_SUPPORT)
|
||||
set(SWIG_DEFINES ${SWIG_DEFINES} -DWITH_HDF5)
|
||||
endif(HDF5_SUPPORT)
|
||||
|
||||
if(WASM_BUILD)
|
||||
# reset to use sysroot
|
||||
set(CMAKE_FIND_ROOT_PATH "${CMAKE_FIND_ROOT_PATH_BACKUP}")
|
||||
endif()
|
||||
|
||||
if(ENABLE_BUILD_OPTIMIZATIONS)
|
||||
if(MSVC)
|
||||
# NOTE: RelWithDebInfo and Release use O2 (= /Ox /Gl /Gy/ = Og /Oi /Ot /Oy /Ob2 /Gs /GF /Gy) by default,
|
||||
@@ -942,10 +925,8 @@ endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
if(WITH_CGAL)
|
||||
clear_wasm_sysroot()
|
||||
find_library(libGMP NAMES gmp mpir PATHS ${GMP_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
find_library(libMPFR NAMES mpfr PATHS ${MPFR_LIBRARY_DIR} NO_DEFAULT_PATH)
|
||||
restore_wasm_sysroot()
|
||||
if(NOT libGMP)
|
||||
message(FATAL_ERROR "Unable to find GMP library files, aborting")
|
||||
endif()
|
||||
@@ -1148,7 +1129,6 @@ if(ADD_COMMIT_SHA)
|
||||
message("git found: ${GIT_EXECUTABLE} with version ${GIT_VERSION_STRING}")
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} branch -a --contains HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE git_branches
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
@@ -1166,18 +1146,11 @@ if(ADD_COMMIT_SHA)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE git_sha
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
message(STATUS "IfcOpenShell branch: \"${git_branch}\"")
|
||||
message(STATUS "IfcOpenShell commit: \"${git_sha}\"")
|
||||
|
||||
if ("${git_branch}" STREQUAL "" OR "${git_sha}" STREQUAL "")
|
||||
message(FATAL_ERROR "Unable to determine commit sha and/or branch")
|
||||
endif()
|
||||
|
||||
add_definitions(-DIFCOPENSHELL_BRANCH=${git_branch})
|
||||
add_definitions(-DIFCOPENSHELL_COMMIT=${git_sha})
|
||||
endif()
|
||||
@@ -1228,20 +1201,13 @@ install(TARGETS IfcParse
|
||||
)
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# install(FILES ${IFCGEOM_H_FILES}
|
||||
# DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
# )
|
||||
|
||||
install(FILES ${SCHEMA_AGNOSTIC_H_FILES}
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom
|
||||
)
|
||||
|
||||
foreach(kernel ${GEOMETRY_KERNELS})
|
||||
file(GLOB IFCGEOM_H_FILES ../src/ifcgeom/kernels/${kernel}/*.h)
|
||||
install(FILES ${IFCGEOM_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom/kernels/${kernel}
|
||||
)
|
||||
endforeach()
|
||||
install(FILES ${SCHEMA_AGNOSTIC_H_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/ifcgeom_schema_agnostic
|
||||
)
|
||||
|
||||
install(TARGETS ${IFCGEOM_SCHEMA_LIBRARIES} ${kernel_libraries} IfcGeom
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
@@ -1257,23 +1223,15 @@ if(BUILD_CONVERT)
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
|
||||
install(FILES ${SERIALIZERS_H_FILES}
|
||||
install(FILES ${SERIALIZERS_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/serializers/
|
||||
)
|
||||
|
||||
install(FILES ${SERIALIZERS_S_H_FILES}
|
||||
install(FILES ${SERIALIZERS_S_FILES}
|
||||
DESTINATION ${INCLUDEDIR}/serializers/schema_dependent
|
||||
)
|
||||
endif(BUILD_CONVERT)
|
||||
|
||||
if(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
install(TARGETS geometry_serializer ${geometry_serializer_libraries}
|
||||
ARCHIVE DESTINATION ${LIBDIR}
|
||||
LIBRARY DESTINATION ${LIBDIR}
|
||||
RUNTIME DESTINATION ${BINDIR}
|
||||
)
|
||||
endif(BUILD_CONVERT OR BUILD_IFCPYTHON)
|
||||
|
||||
# Cmake uninstall target
|
||||
if(NOT TARGET uninstall)
|
||||
configure_file(
|
||||
|
||||
+9
-23
@@ -48,10 +48,6 @@
|
||||
# on OS X El Capitan with homebrew: #
|
||||
# $ brew install git bison autoconf automake libffi cmake #
|
||||
# #
|
||||
# on RHEL-related distros: #
|
||||
# $ yum install git gcc gcc-c++ autoconf bison make cmake #
|
||||
# mesa-libGL-devel libffi-devel fontconfig-devel bzip2 #
|
||||
# automake patch #
|
||||
###############################################################################
|
||||
import logging
|
||||
import os
|
||||
@@ -82,11 +78,11 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.0"]
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1"]
|
||||
JSON_VERSION = "v3.6.1"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
BOOST_VERSION = "1.86.0"
|
||||
BOOST_VERSION = "1.80.0"
|
||||
PCRE_VERSION = "8.41"
|
||||
LIBXML2_VERSION = "2.9.11"
|
||||
SWIG_VERSION = "4.0.2"
|
||||
@@ -307,7 +303,7 @@ if platform.system() == "Darwin":
|
||||
BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")
|
||||
|
||||
OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
|
||||
BOOST_LOCATION = f"https://github.com/boostorg/boost/releases/download/boost-{BOOST_VERSION}/"
|
||||
BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"
|
||||
|
||||
# Helper functions
|
||||
|
||||
@@ -350,7 +346,6 @@ 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"])
|
||||
|
||||
# detect whether we are on a branch and pull
|
||||
if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
|
||||
@@ -465,9 +460,6 @@ def build_dependency(name, mode, build_tool_args, download_url, download_name, d
|
||||
shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
|
||||
logger.info(f"\rInstalled {name} \n")
|
||||
|
||||
if "diskcleanup" in flags:
|
||||
shutil.rmtree(build_dir, ignore_errors=True)
|
||||
|
||||
cecho("Collecting dependencies:", GREEN)
|
||||
|
||||
# Set compiler flags for 32bit builds on 64bit system
|
||||
@@ -479,7 +471,7 @@ if platform.system() == "Darwin":
|
||||
ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS
|
||||
|
||||
if "wasm" in flags:
|
||||
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fwasm-exceptions"))
|
||||
ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))
|
||||
|
||||
# If the linker supports GC sections, set it up to reduce binary file size
|
||||
# -fPIC is required for the shared libraries to work
|
||||
@@ -528,7 +520,7 @@ if 'hdf5' in targets:
|
||||
# not supported
|
||||
orig = [os.environ[f] for f in compiler_flags]
|
||||
for f in compiler_flags:
|
||||
os.environ[f] = re.sub(r"-flto(=\w+)?", "", os.environ[f])
|
||||
os.environ[f] = re.sub("-flto(=\w+)?", "", os.environ[f])
|
||||
|
||||
HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
|
||||
build_dependency(
|
||||
@@ -618,7 +610,7 @@ if USE_OCCT and "occ" in targets:
|
||||
"-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
|
||||
f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
|
||||
],
|
||||
download_url = "https://github.com/Open-Cascade-SAS/OCCT",
|
||||
download_url = "https://git.dev.opencascade.org/repos/occt.git",
|
||||
download_name = "occt",
|
||||
download_tool=download_tool_git,
|
||||
patch=patches,
|
||||
@@ -738,16 +730,14 @@ if "boost" in targets:
|
||||
"--with-thread",
|
||||
"--with-date_time",
|
||||
"--with-iostreams",
|
||||
"--with-filesystem",
|
||||
f"link={LINK_TYPE}",
|
||||
*toolset,
|
||||
*map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
|
||||
*map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
|
||||
"stage", "-s", "NO_BZIP2=1"],
|
||||
download_url=BOOST_LOCATION,
|
||||
# don't remember what this is, but fail on 1.86
|
||||
# patch="./patches/boost/boostorg_regex_62.patch",
|
||||
download_name=f"boost-{BOOST_VERSION}-b2-nodocs.tar.gz"
|
||||
patch="./patches/boost/boostorg_regex_62.patch",
|
||||
download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
|
||||
)
|
||||
if "wasm" in flags:
|
||||
# only supported on nix for now
|
||||
@@ -990,11 +980,7 @@ if "IfcOpenShell-Python" in targets:
|
||||
if platform.system() != "Darwin":
|
||||
if BUILD_CFG == "Release":
|
||||
# TODO: This symbol name depends on the Python version?
|
||||
so = glob.glob(os.path.join(module_dir, "_ifcopenshell_wrapper*.so"))[0]
|
||||
if "wasm" in flags:
|
||||
run(['wasm-strip', so, '-k', "dylink.0"])
|
||||
else:
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", so], cwd=module_dir)
|
||||
run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", glob.glob(os.path.join(module_dir, "_ifcopenshell_wrapper*.so"))[0]], cwd=module_dir)
|
||||
|
||||
return module_dir
|
||||
|
||||
|
||||
+2
-3
@@ -3,12 +3,11 @@ package:
|
||||
version: 0.8.0
|
||||
|
||||
source:
|
||||
path: ../../IfcOpenShell
|
||||
path: IfcOpenShell
|
||||
|
||||
build:
|
||||
script: |
|
||||
BUILD_CFG=Release python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py312 IfcOpenShell-Python
|
||||
mv package/ifcopenshell .
|
||||
python nix/build-all.py --without-hdf5 --without-opencollada --without-swig --without-pcre -v --wasm --py310 IfcOpenShell-Python
|
||||
cp pyodide/setup.py .
|
||||
|
||||
about:
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='ifcopenshell',
|
||||
setup(name='IfcOpenShell',
|
||||
version='0.8.0',
|
||||
description='IfcOpenShell is an open source (LGPL) software library for working with the Industry Foundation Classes (IFC) file format.',
|
||||
author='Thomas Krijnen',
|
||||
author_email='thomas@aecgeeks.com',
|
||||
url='http://ifcopenshell.org',
|
||||
packages=find_packages(),
|
||||
package_data={'': ['*.so', '*.json']},
|
||||
package_data={'': ['*.so']},
|
||||
)
|
||||
|
||||
@@ -27,9 +27,7 @@ include = '''
|
||||
extend-exclude = '''
|
||||
src/ifcopenshell-python/ifcopenshell/express/*
|
||||
|src/ifcopenshell-python/ifcopenshell/mvd/*
|
||||
|src/ifcopenshell-python/ifcopenshell/simple_spf/*
|
||||
|src/ifc2ca/templates/*
|
||||
|src/ifcconvert/cityjson/*
|
||||
'''
|
||||
|
||||
[tool.pyright]
|
||||
|
||||
+51
-27
@@ -46,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
|
||||
|
||||
@@ -86,7 +85,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=c49ca69
|
||||
OLD:=0e5008d
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -178,8 +177,6 @@ endif
|
||||
# Is not platform specific but has platform specific dependencies.
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download python-socketio[asyncio_client] $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download aiohttp $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
# Required to access platform specific paths
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download platformdirs --dest=./wheels
|
||||
# Required by light module
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download pytz --dest=./wheels
|
||||
cd build && . env/$(VENV_ACTIVATE) && $(PIP) download tzfpy $(PYPI_PLATFORM) --python-version $(PYPI_VERSION) --implementation $(PYPI_IMP) --only-binary=:all: --dest=./wheels
|
||||
@@ -215,7 +212,7 @@ endif
|
||||
# Brickschema requires pkg_resources which is provided by Blender.
|
||||
# Provides Brickschema functionality
|
||||
# For now lets bundle the latest nightly schema
|
||||
cd build/bonsai/bim/data/brick/ && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
cd build/bonsai/bim/schema && wget https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl
|
||||
|
||||
# Required for hipped roof generation
|
||||
cd build && wget https://github.com/prochitecture/bpypolyskel/archive/refs/heads/master.zip
|
||||
@@ -229,16 +226,8 @@ endif
|
||||
)" bdist_wheel
|
||||
cp -r build/bpypolyskel-master/dist/*.whl build/wheels/
|
||||
|
||||
# folder for executable files
|
||||
mkdir -p build/bonsai/libs/bin
|
||||
|
||||
# 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 && unzip ifcmerge.zip && rm ifcmerge.zip
|
||||
else
|
||||
cd build/bonsai/libs/bin && wget https://raw.githubusercontent.com/brunopostle/ifcmerge/main/ifcmerge && chmod +x ifcmerge
|
||||
endif
|
||||
# Required for Desktop icon and file association
|
||||
cp -r bonsai/libs/desktop build/bonsai/libs/
|
||||
|
||||
# Generate translations module for Bonsai build
|
||||
git clone https://github.com/IfcOpenShell/bonsai-translations.git build/working
|
||||
@@ -255,7 +244,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
|
||||
|
||||
@@ -280,11 +268,54 @@ endif
|
||||
fi
|
||||
mv build/wheels/*.whl build/bonsai/wheels/
|
||||
|
||||
# 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'.
|
||||
# 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 "s/-cp39-/-cp$(PYNUMBER)-/"); \
|
||||
mv --update=none "$$prev_whl_name" "$$whl_name";
|
||||
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";
|
||||
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";
|
||||
|
||||
# has universal and arm builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/fonttools-*.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
|
||||
|
||||
ifneq ($(PLATFORM), linux)
|
||||
# Safeguard: in case one of `pip download` will break,
|
||||
@@ -302,7 +333,6 @@ endif
|
||||
rm -rf build/bonsai/bim/
|
||||
rm -rf build/bonsai/core/
|
||||
rm -rf build/bonsai/tool/
|
||||
rm -rf build/bonsai/libs/
|
||||
|
||||
ifeq ($(IS_STABLE), TRUE)
|
||||
cd build && zip -r bonsai_$(PYVERSION)-$(VERSION)-$(BLENDER_PLATFORM).zip ./bonsai
|
||||
@@ -346,12 +376,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.
@@ -40,7 +40,6 @@ from typing import Union, Any, Generator
|
||||
|
||||
|
||||
last_commit_hash = "8888888"
|
||||
last_commit_date = "9999999"
|
||||
|
||||
|
||||
def get_last_commit_hash() -> Union[str, None]:
|
||||
@@ -52,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] = {}
|
||||
|
||||
@@ -103,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,
|
||||
}
|
||||
@@ -226,7 +218,6 @@ if IN_BLENDER:
|
||||
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
|
||||
|
||||
@@ -326,7 +317,7 @@ if IN_BLENDER:
|
||||
# But Blender currently doesn't support separate builds for different Python version,
|
||||
# so those issues might still slip in.
|
||||
box.label(text="Bonsai installed for wrong Python version.")
|
||||
box.label(text=f"Expected binary version: {py}. Got: {binary_py}.")
|
||||
box.label(text=f"Expected: {py}. Got: {binary_py}.")
|
||||
# On reinstallation, dependencies versions doesn't change, so Blender will just ignore new dependencies.
|
||||
# So, we need to make user will disable an extension (just uninstallation won't remove dependencies).
|
||||
# Blender restart doesn't seem to be required in that case
|
||||
@@ -334,13 +325,7 @@ if IN_BLENDER:
|
||||
box.label(text="Try reinstalling with the correct Python version.")
|
||||
box.label(text="Before reinstallation make sure to")
|
||||
box.label(text="DISABLE Bonsai (uninstallation won't help).")
|
||||
if py == "3.11":
|
||||
box.label(text="You can download correct version below.")
|
||||
else:
|
||||
box.label(text="Since you're using Python >3.11,")
|
||||
box.label(text="installation from Blender extensions platform")
|
||||
box.label(text="is not supported and you need to download")
|
||||
box.label(text="and install Bonsai from the link below.")
|
||||
box.label(text="You can download correct version below.")
|
||||
|
||||
layout.operator("bim.copy_debug_information", text="Copy Error Message To Clipboard")
|
||||
op = layout.operator("bim.open_uri", text="How Can I Fix This?")
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# addon writes tmp stuff directly to its dir
|
||||
/data/
|
||||
@@ -20,7 +20,8 @@ import os
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import importlib
|
||||
from . import handler, ui, prop, operator
|
||||
from pathlib import Path
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
@@ -83,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,
|
||||
@@ -99,25 +99,23 @@ classes = [
|
||||
operator.BIM_OT_add_section_plane,
|
||||
operator.BIM_OT_delete_object,
|
||||
operator.BIM_OT_remove_section_plane,
|
||||
operator.BIM_OT_select_entity,
|
||||
operator.BIM_OT_select_object,
|
||||
operator.BIM_OT_show_description,
|
||||
operator.BIM_OT_multiple_file_selector,
|
||||
operator.ClippingPlaneCutWithCappings,
|
||||
operator.CloseBlendWarning,
|
||||
operator.CloseError,
|
||||
operator.CopyTextToClipboard,
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
operator.OpenPath,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RemoveIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SelectDir,
|
||||
operator.SelectDataDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectSchemaDir,
|
||||
operator.SelectURIAttribute,
|
||||
operator.SetTab,
|
||||
operator.SwitchTab,
|
||||
@@ -137,8 +135,6 @@ classes = [
|
||||
prop.BIMMeshProperties,
|
||||
prop.BIMFacet,
|
||||
prop.BIMFilterGroup,
|
||||
prop.BIMSnapProperties,
|
||||
prop.BIMSnapGroups,
|
||||
ui.BIM_UL_clipping_plane,
|
||||
ui.BIM_UL_generic,
|
||||
ui.BIM_UL_topics,
|
||||
@@ -194,8 +190,6 @@ classes = [
|
||||
# TODO: move this somewhere else and clean it up
|
||||
ui.BIM_PT_section_plane,
|
||||
ui.BIM_PT_section_with_cappings,
|
||||
ui.BIM_PT_decorators_overlay,
|
||||
ui.BIM_PT_snappping,
|
||||
]
|
||||
|
||||
for mod in modules.values():
|
||||
@@ -228,8 +222,6 @@ def register():
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
bpy.app.handlers.load_post.append(handler.loadIfcStore)
|
||||
bpy.types.Scene.BIMProperties = bpy.props.PointerProperty(type=prop.BIMProperties)
|
||||
bpy.types.Scene.BIMSnapProperties = bpy.props.PointerProperty(type=prop.BIMSnapProperties)
|
||||
bpy.types.Scene.BIMSnapGroups = bpy.props.PointerProperty(type=prop.BIMSnapGroups)
|
||||
bpy.types.Screen.BIMAreaProperties = bpy.props.CollectionProperty(type=prop.BIMAreaProperties)
|
||||
bpy.types.Screen.BIMTabProperties = bpy.props.PointerProperty(type=prop.BIMTabProperties)
|
||||
bpy.types.Collection.BIMCollectionProperties = bpy.props.PointerProperty(type=prop.BIMCollectionProperties)
|
||||
@@ -264,12 +256,6 @@ def register():
|
||||
icons = icon_preview
|
||||
bpy.app.translations.register("bonsai", translations_dict)
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
tool.Blender.ensure_bin_in_path()
|
||||
# RestrictedContext doesn't allow accessing scene attribute, postpone it for a bit.
|
||||
bpy.app.timers.register(tool.Blender.setup_user_data_dir, first_interval=0.1)
|
||||
|
||||
|
||||
def unregister():
|
||||
global icons
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
@@ -1 +0,0 @@
|
||||
This directory stores Brickschema definitions in TTL format.
|
||||
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
@@ -1 +0,0 @@
|
||||
This directory stores generated drawings in SVG format.
|
||||
|
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 it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',(),(),'EPset_Drawing','EPset
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/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));
|
||||
#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));
|
||||
#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.);
|
||||
@@ -28,9 +28,5 @@ DATA;
|
||||
#21=IFCSIMPLEPROPERTYTEMPLATE('0nYMT3OSj5gArVniCWZRtv',$,'ShadingStyles','',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
|
||||
#22=IFCSIMPLEPROPERTYTEMPLATE('3VWG22eZXBdQwdKlzMeVQH',$,'CurrentShadingStyle','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#23=IFCSIMPLEPROPERTYTEMPLATE('28mJ$GDxb7kBbKFH_rACMQ',$,'AngleDecimalPlaces','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#24=IFCSIMPLEPROPERTYTEMPLATE('0EP4WR7eb1IR$rqEJ_XRVX',$,'DPI','DPI of rasterized underlays',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#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.);
|
||||
ENDSEC;
|
||||
END-ISO-10303-21;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
ISO-10303-21;
|
||||
HEADER;
|
||||
FILE_DESCRIPTION($,'2;1');
|
||||
FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',$,$,'Psets_BBIM_Annotation','Psets_BBIM_Annotation',$);
|
||||
FILE_DESCRIPTION((),'2;1');
|
||||
FILE_NAME('Psets_BBIM_Annotation.ifc','2020-01-01T00:00:00',(),(),'Psets_BBIM_Annotation','Psets_BBIM_Annotation',$);
|
||||
FILE_SCHEMA(('IFC4'));
|
||||
ENDSEC;
|
||||
DATA;
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4,#29));
|
||||
#1=IFCPROPERTYSETTEMPLATE('3VuPUwdCD2Qx3XDDRs0R1N',$,'EPset_Annotation','',.PSET_TYPEDRIVENOVERRIDE.,'IfcAnnotation,IfcTypeProduct',(#2,#3,#4));
|
||||
#2=IFCSIMPLEPROPERTYTEMPLATE('2P7JN79n96Q9pElZ83LKe4',$,'ZIndex','',.P_SINGLEVALUE.,'IfcInteger',$,$,$,$,$,.READWRITE.);
|
||||
#3=IFCSIMPLEPROPERTYTEMPLATE('1Wpx_r2xj1_9w5JpI0QRJy',$,'Symbol','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
#4=IFCSIMPLEPROPERTYTEMPLATE('3q0oxMUKP47vZ4jnyG$dDb',$,'Classes','Classes separarated by spaces that end up in classes for this element in svg. Can be used to specify the text font size: small - 1.8mm; regular - 2.5mm; large - 3.5mm; header - 5mm; title - 7mm. By default regular size is used.',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
|
||||
@@ -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,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
@@ -1 +0,0 @@
|
||||
This directory stores generated tabular schedules in SVG format.
|
||||
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
@@ -1 +0,0 @@
|
||||
This directory stores generated sheets in SVG format. Sheets typically contain drawings and schedules.
|
||||
@@ -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
|
||||
@@ -18,7 +18,11 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
sio_port = 8080 # default port
|
||||
|
||||
sio = socketio.AsyncServer(cors_allowed_origins="*", async_mode="aiohttp", max_http_buffer_size=10000000)
|
||||
sio = socketio.AsyncServer(
|
||||
cors_allowed_origins="*",
|
||||
async_mode="aiohttp",
|
||||
max_http_buffer_size=10000000
|
||||
)
|
||||
app = web.Application()
|
||||
sio.attach(app)
|
||||
|
||||
|
||||
@@ -269,7 +269,13 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
},
|
||||
});
|
||||
|
||||
createDownloadIcon(blenderId);
|
||||
var downloadCsv = $('<a><i class="fa-solid fa-file-csv"></i></a>')
|
||||
.css("margin-left", "10px")
|
||||
.css("cursor", "pointer");
|
||||
tableTitle.append(downloadCsv);
|
||||
downloadCsv.on("click", function () {
|
||||
table.download("csv", "data.csv");
|
||||
});
|
||||
|
||||
table.on("rowSelected", function (row) {
|
||||
var index = row.getIndex(); // the guid of the object
|
||||
@@ -295,18 +301,6 @@ function addTableElement(blenderId, csvData, filename) {
|
||||
});
|
||||
}
|
||||
|
||||
function createDownloadIcon(blenderId) {
|
||||
const table = Tabulator.findTable("#table-" + blenderId)[0];
|
||||
const tableTitle = $("#title-" + blenderId);
|
||||
var downloadCsv = $('<a><i class="fa-solid fa-file-csv"></i></a>')
|
||||
.css("margin-left", "10px")
|
||||
.css("cursor", "pointer");
|
||||
tableTitle.append(downloadCsv);
|
||||
downloadCsv.on("click", function () {
|
||||
table.download("csv", "data.csv");
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update table and filename
|
||||
function updateTableElement(blenderId, csvData, filename) {
|
||||
// check if the headers are the same
|
||||
@@ -335,7 +329,6 @@ function updateTableElement(blenderId, csvData, filename) {
|
||||
}
|
||||
}
|
||||
$("#title-" + blenderId).text(filename);
|
||||
createDownloadIcon(blenderId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
@@ -48,7 +49,6 @@ class IfcExporter:
|
||||
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":
|
||||
with tempfile.TemporaryDirectory() as unzipped_path:
|
||||
@@ -115,12 +115,17 @@ class IfcExporter:
|
||||
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)
|
||||
try:
|
||||
if isinstance(obj, bpy.types.Material):
|
||||
# TODO: do we add materials to edited_objs?
|
||||
continue
|
||||
else:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
results.append(element)
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
except ReferenceError:
|
||||
pass # The object is likely deleted
|
||||
IfcStore.edited_objs.clear()
|
||||
return results
|
||||
|
||||
@@ -132,12 +137,13 @@ class IfcExporter:
|
||||
bpy.ops.bim.update_representation(obj=obj.name)
|
||||
|
||||
def has_changed_materials(self, obj: bpy.types.Object) -> bool:
|
||||
mprops = tool.Geometry.get_mesh_props(obj.data)
|
||||
checksum = mprops.material_checksum
|
||||
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 not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
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
|
||||
@@ -145,13 +151,7 @@ class IfcExporter:
|
||||
if tool.Geometry.is_scaled(obj):
|
||||
print(f"WARNING. Object '{obj.name}' scales ({obj.scale[:]}) are reset during project save.")
|
||||
obj.scale = (1.0, 1.0, 1.0)
|
||||
else:
|
||||
# Return and don't handle is_moved as
|
||||
# updata_representation will run edit_object_placement if object is scaled
|
||||
# and had no openings.
|
||||
return element
|
||||
if not tool.Ifc.is_moved(obj):
|
||||
return
|
||||
return element
|
||||
if element.is_a("IfcGridAxis"):
|
||||
return self.sync_grid_axis_object_placement(obj, element)
|
||||
if not hasattr(element, "ObjectPlacement"):
|
||||
|
||||
@@ -24,15 +24,14 @@ import ifcopenshell.util.unit
|
||||
import ifcopenshell.api.owner.settings
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
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.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 bonsai.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
|
||||
from mathutils import Vector
|
||||
from math import cos
|
||||
from math import cos, degrees
|
||||
from typing import Union, Callable
|
||||
|
||||
|
||||
@@ -53,24 +52,19 @@ 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)
|
||||
if ifc_definition_id := props.ifc_definition_id:
|
||||
if props.is_renaming:
|
||||
props.is_renaming = False
|
||||
return
|
||||
tool.Ifc.get().by_id(ifc_definition_id).Name = obj.name
|
||||
refresh_ui_data()
|
||||
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
|
||||
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]
|
||||
@@ -86,8 +80,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()
|
||||
|
||||
|
||||
@@ -106,44 +100,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
|
||||
@@ -155,7 +134,7 @@ def update_bim_tool_props():
|
||||
if AuthoringData.data["active_material_usage"] == "LAYER2":
|
||||
x_angle = get_x_angle(extrusion)
|
||||
axis = tool.Model.get_wall_axis(obj)["reference"]
|
||||
props.extrusion_depth = abs(extrusion.Depth * si_conversion * cos(x_angle))
|
||||
props.extrusion_depth = extrusion.Depth * si_conversion * cos(x_angle)
|
||||
props.length = (axis[1] - axis[0]).length
|
||||
props.x_angle = x_angle
|
||||
|
||||
@@ -201,9 +180,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:
|
||||
@@ -220,38 +196,35 @@ 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()
|
||||
|
||||
@@ -284,10 +257,10 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
|
||||
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():
|
||||
@@ -332,6 +305,8 @@ def load_post(scene):
|
||||
# After appending the workspace to ensure BIM viewport is affected.
|
||||
subscribe_to_viewport_shading_changes()
|
||||
|
||||
bpy.ops.bim.override_escape("INVOKE_DEFAULT")
|
||||
|
||||
# To improve usability for new users, we hijack the scene properties
|
||||
# tab. We override default scene properties panels with our own poll
|
||||
# to hide them unless the user has chosen to view Blender properties.
|
||||
@@ -342,27 +317,4 @@ 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
|
||||
|
||||
# Bonsai overlays
|
||||
georeference_props = tool.Georeference.get_georeference_props()
|
||||
aggregate_props = bpy.context.scene.BIMAggregateProperties
|
||||
nest_props = bpy.context.scene.BIMNestProperties
|
||||
model_props = tool.Model.get_model_props()
|
||||
if georeference_props.should_visualise:
|
||||
GeoreferenceDecorator.install(bpy.context)
|
||||
if aggregate_props.aggregate_decorator:
|
||||
AggregateDecorator.install(bpy.context)
|
||||
if nest_props.nest_decorator:
|
||||
NestDecorator.install(bpy.context)
|
||||
if model_props.show_wall_axis:
|
||||
WallAxisDecorator.install(bpy.context)
|
||||
if model_props.show_slab_direction:
|
||||
SlabDirectionDecorator.install(bpy.context)
|
||||
|
||||
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
|
||||
scene.tool_settings.snap_elements_base = {"EDGE", "EDGE_PERPENDICULAR", "VERTEX", "EDGE_MIDPOINT", "FACE"}
|
||||
bpy.context.scene.BIMProperties.has_blend_warning = True
|
||||
|
||||
@@ -20,19 +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 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
|
||||
@@ -46,15 +48,12 @@ 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,
|
||||
callback: Optional[Callable[[bonsai.bim.prop.Attribute, bpy.types.UILayout], None]] = None,
|
||||
) -> None:
|
||||
"""Draw editable UI for prop.Attributes.
|
||||
|
||||
You can set attribute active in popup with `active_attribute`
|
||||
"""you can set attribute active in popup with `active_attribute`
|
||||
meaning you will be able to type into attribute's field without having to click
|
||||
on it first
|
||||
"""
|
||||
@@ -63,21 +62,22 @@ def draw_attributes(
|
||||
if attribute == popup_active_attribute:
|
||||
row.activate_init = True
|
||||
draw_attribute(attribute, row, copy_operator)
|
||||
if callback:
|
||||
callback(attribute, row)
|
||||
|
||||
|
||||
def draw_attribute(
|
||||
attribute: bonsai.bim.prop.Attribute, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
) -> None:
|
||||
value_name = attribute.get_value_name(display_only=True)
|
||||
value_name = attribute.get_value_name()
|
||||
if not value_name:
|
||||
layout.label(text=attribute.name)
|
||||
return
|
||||
if value_name == "enum_value":
|
||||
prop_with_search(layout, attribute, "enum_value", text=attribute.name)
|
||||
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:
|
||||
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
|
||||
propis = bpy.context.scene.BIMWorkScheduleProperties
|
||||
for item in propis.durations_attributes:
|
||||
if item.name == attribute.name:
|
||||
duration_props = item
|
||||
layout.label(text=attribute.name)
|
||||
@@ -92,42 +92,37 @@ def draw_attribute(
|
||||
layout.prop(
|
||||
attribute,
|
||||
value_name,
|
||||
text=attribute.display_name,
|
||||
text=attribute.name,
|
||||
)
|
||||
|
||||
if attribute.is_uri:
|
||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||
op.data_path = attribute.path_from_id("string_value")
|
||||
elif attribute.special_type in ("DATE", "DATETIME"):
|
||||
op = layout.operator("bim.datepicker", text="", icon="TIME")
|
||||
op.target_prop = attribute.path_from_id("string_value")
|
||||
op.include_time = attribute.special_type == "DATETIME"
|
||||
|
||||
if attribute.is_optional:
|
||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||
|
||||
if attribute.name == "GlobalId":
|
||||
layout.operator("bim.generate_global_id", icon="FILE_REFRESH", text="")
|
||||
elif copy_operator:
|
||||
|
||||
if copy_operator:
|
||||
op = layout.operator(f"{copy_operator}", text="", icon="COPYDOWN")
|
||||
op.name = attribute.name
|
||||
|
||||
|
||||
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()
|
||||
for attribute in schema.declaration_by_name(ifc_class).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):
|
||||
@@ -143,7 +138,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:
|
||||
@@ -152,7 +147,7 @@ def import_attribute(
|
||||
if isinstance(data_type, tuple) or data_type == "entity":
|
||||
callback(attribute.name(), None, data) if callback else None
|
||||
return
|
||||
new: bonsai.bim.prop.Attribute = props.add()
|
||||
new = props.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
@@ -169,57 +164,31 @@ def import_attribute(
|
||||
new.string_value = "" if new.is_null else str(data[attribute.name()]).replace("\n", "\\n")
|
||||
if attribute.type_of_attribute().declared_type().name() == "IfcURIReference":
|
||||
new.is_uri = True
|
||||
elif attribute.type_of_attribute()._is("IfcDate"):
|
||||
new.special_type = "DATE"
|
||||
elif attribute.type_of_attribute()._is("IfcDateTime"):
|
||||
new.special_type = "DATETIME"
|
||||
elif data_type == "boolean":
|
||||
new.bool_value = False if new.is_null else bool(data[attribute.name()])
|
||||
elif data_type == "integer":
|
||||
new.int_value = 0 if new.is_null else int(data[attribute.name()])
|
||||
elif data_type == "float":
|
||||
attribute_type = attribute.type_of_attribute()
|
||||
if attribute_type._is("IfcLengthMeasure"):
|
||||
new.special_type = "LENGTH"
|
||||
elif attribute_type._is("IfcForceMeasure"):
|
||||
new.special_type = "FORCE"
|
||||
new.float_value = 0.0 if new.is_null else float(data[attribute.name()])
|
||||
elif data_type == "enum":
|
||||
attribute_type = attribute.type_of_attribute()
|
||||
is_logical = str(attribute_type) == "<type IfcLogical: <logical>>"
|
||||
enum_value = data[new.name]
|
||||
if is_logical:
|
||||
new.special_type = "LOGICAL"
|
||||
enum_items = ("TRUE", "FALSE", "UNKNOWN")
|
||||
new.enum_items = json.dumps(enum_items)
|
||||
if enum_value is not None and enum_value != "UNKNOWN":
|
||||
# IfcOpenShell returns bool if IfcLogical is True/False.
|
||||
enum_value = "TRUE" if enum_value else "FALSE"
|
||||
else:
|
||||
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
|
||||
new.enum_items = json.dumps(enum_items)
|
||||
add_attribute_enum_items_descriptions(new, enum_items)
|
||||
|
||||
if enum_value is not None:
|
||||
new.enum_value = enum_value
|
||||
enum_items = ifcopenshell.util.attribute.get_enum_items(attribute)
|
||||
new.enum_items = json.dumps(enum_items)
|
||||
add_attribute_enum_items_descriptions(new, enum_items)
|
||||
if data[new.name]:
|
||||
new.enum_value = data[new.name]
|
||||
add_attribute_description(new, data)
|
||||
add_attribute_min_max(attribute, new)
|
||||
add_attribute_min_max(new)
|
||||
|
||||
|
||||
ATTRIBUTE_MIN_MAX_CONSTRAINTS = {"IfcMaterialLayer": {"Priority": {"value_min": 0, "value_max": 100}}}
|
||||
|
||||
|
||||
def add_attribute_min_max(attribute: W.attribute, attribute_blender: bonsai.bim.prop.Attribute) -> None:
|
||||
def add_attribute_min_max(attribute_blender: bonsai.bim.prop.Attribute) -> None:
|
||||
if attribute_blender.ifc_class in ATTRIBUTE_MIN_MAX_CONSTRAINTS:
|
||||
constraints = ATTRIBUTE_MIN_MAX_CONSTRAINTS[attribute_blender.ifc_class].get(attribute_blender.name, {})
|
||||
for constraint, value in constraints.items():
|
||||
setattr(attribute_blender, constraint, value)
|
||||
setattr(attribute_blender, constraint + "_constraint", True)
|
||||
attribute_type = attribute.type_of_attribute()
|
||||
|
||||
if attribute_type._is("IfcPositiveLengthMeasure") or attribute_type._is("IfcNonNegativeLengthMeasure"):
|
||||
attribute_blender.value_min = 0.0
|
||||
attribute_blender.value_min_constraint = True
|
||||
|
||||
|
||||
def add_attribute_enum_items_descriptions(
|
||||
@@ -257,7 +226,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] = {}
|
||||
@@ -272,31 +241,14 @@ def export_attributes(
|
||||
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
|
||||
|
||||
|
||||
def get_display_value(value: str, float_decimal_precision: int = 6) -> str:
|
||||
"""
|
||||
This will get rid of the floating point precision artifacts in float values stored as a string
|
||||
"""
|
||||
try:
|
||||
digits = len(value.split(".")[1])
|
||||
value = float(value)
|
||||
if digits > 6: # Maximal decimal float precision
|
||||
value = round(value, float_decimal_precision)
|
||||
except (ValueError, IndexError): # Not castable to a float or no decimal places (eg integer)
|
||||
pass
|
||||
return str(value)
|
||||
|
||||
|
||||
def prop_with_search(
|
||||
layout: bpy.types.UILayout,
|
||||
data: ENUM_ITEMS_DATA,
|
||||
prop_name: str,
|
||||
should_click_ok: bool = False,
|
||||
should_click_ok_to_validate: 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)
|
||||
@@ -306,11 +258,10 @@ def prop_with_search(
|
||||
row.context_pointer_set(name="data", data=data)
|
||||
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
|
||||
op.prop_name = prop_name
|
||||
op.should_click_ok = should_click_ok
|
||||
op.should_click_ok_to_validate = should_click_ok_to_validate
|
||||
op.original_operator_path = original_operator_path or ""
|
||||
except TypeError: # Prop is not iterable
|
||||
pass
|
||||
return row
|
||||
|
||||
|
||||
def get_enum_items(
|
||||
@@ -351,13 +302,6 @@ def get_enum_items(
|
||||
return items
|
||||
|
||||
|
||||
def draw_expandable_panel(layout, context, label: str, ui_func, default_closed: bool = True):
|
||||
header, panel = layout.panel(label, default_closed=default_closed)
|
||||
header.label(text=label)
|
||||
if panel:
|
||||
ui_func(panel, context)
|
||||
|
||||
|
||||
def convert_property_group_from_si(property_group: bpy.types.PropertyGroup, skip_props: tuple[str, ...] = ()) -> None:
|
||||
"""Method converts property group values from si to current ifc project units
|
||||
|
||||
@@ -377,16 +321,11 @@ 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)
|
||||
|
||||
@@ -45,14 +45,10 @@ class OperationData(TypedDict):
|
||||
obj: str
|
||||
|
||||
|
||||
class EditObjectOperationData(TypedDict):
|
||||
obj: str
|
||||
|
||||
|
||||
class Operation(TypedDict):
|
||||
rollback: Callable
|
||||
commit: Callable
|
||||
data: Union[OperationData, EditObjectOperationData, None]
|
||||
data: Union[OperationData, None]
|
||||
|
||||
|
||||
class TransactionStep(TypedDict):
|
||||
@@ -105,8 +101,9 @@ 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 = bpy.context.scene.BIMProperties.ifc_file
|
||||
if not os.path.isabs(IfcStore.path):
|
||||
IfcStore.path = os.path.abspath(os.path.join(bpy.path.abspath("//"), IfcStore.path))
|
||||
if IfcStore.path:
|
||||
try:
|
||||
IfcStore.load_file(IfcStore.path)
|
||||
@@ -114,21 +111,12 @@ 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")
|
||||
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
cache_path = Path(IfcStore.cache_path)
|
||||
cache_settings = ifcopenshell.geom.settings()
|
||||
serializer_settings = ifcopenshell.geom.serializer_settings()
|
||||
@@ -168,8 +156,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.data_dir, "cache", f"{ifc_hash}.h5")
|
||||
IfcStore.cache = None
|
||||
try:
|
||||
shutil.move(IfcStore.cache_path, new_cache_path)
|
||||
@@ -185,7 +172,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:
|
||||
@@ -195,7 +181,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)
|
||||
@@ -203,8 +189,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
|
||||
@@ -254,7 +241,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)
|
||||
@@ -269,11 +256,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)
|
||||
|
||||
@@ -310,28 +295,7 @@ class IfcStore:
|
||||
if "guid" in data:
|
||||
IfcStore.guid_map[data["guid"]] = obj
|
||||
tool.Ifc.setup_listeners(obj)
|
||||
|
||||
@staticmethod
|
||||
def history_edit_object(obj: bpy.types.Object, *, finish_editing: bool) -> None:
|
||||
if not IfcStore.history:
|
||||
return
|
||||
|
||||
commit, rollback = IfcStore.commit_edit_object, IfcStore.rollback_edit_object
|
||||
if finish_editing:
|
||||
commit, rollback = rollback, commit
|
||||
|
||||
data = EditObjectOperationData(obj=obj.name)
|
||||
IfcStore.history[-1]["operations"].append(Operation(rollback=rollback, commit=commit, data=data))
|
||||
|
||||
@staticmethod
|
||||
def commit_edit_object(data: EditObjectOperationData) -> None:
|
||||
obj = bpy.data.objects[data["obj"]]
|
||||
IfcStore.edited_objs.add(obj)
|
||||
|
||||
@staticmethod
|
||||
def rollback_edit_object(data: EditObjectOperationData) -> None:
|
||||
obj = bpy.data.objects[data["obj"]]
|
||||
IfcStore.edited_objs.discard(obj)
|
||||
# TODO We're handling id_map and guid_map, but what about edited_objs? This might cause big problems.
|
||||
|
||||
@staticmethod
|
||||
def rollback_unlink_element(data: OperationData) -> None:
|
||||
@@ -411,24 +375,18 @@ 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
|
||||
# NOTE: in theory this will also remove listeners added by other addons
|
||||
# though never had a report when this would be a problem.
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
|
||||
@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)
|
||||
@@ -454,12 +412,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.
|
||||
@@ -471,27 +427,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
|
||||
@@ -505,8 +446,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)
|
||||
|
||||
+163
-155
@@ -20,12 +20,9 @@ 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,10 +33,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 Dict, Union, Optional, Any, Literal
|
||||
from ifcopenshell.util.shape import MatrixType
|
||||
from typing import Dict, Union, Optional, Any
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
@@ -71,38 +68,19 @@ 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)
|
||||
self.load_texture_maps(shape_has_openings)
|
||||
self.assign_material_slots_to_faces()
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
del self.mesh["ios_materials"]
|
||||
|
||||
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)
|
||||
@@ -116,7 +94,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:
|
||||
@@ -174,7 +152,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(
|
||||
@@ -216,16 +194,14 @@ class IfcImporter:
|
||||
self.meshes: dict[str, OBJECT_DATA_TYPE] = {}
|
||||
self.mesh_shapes = {}
|
||||
self.time = 0
|
||||
self.unit_scale = 1.0
|
||||
self.unit_scale = 1
|
||||
# ifc definition ids to blender elements mapping
|
||||
self.added_data: dict[int, IFC_CONNECTED_TYPE] = {}
|
||||
self.native_elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.native_data: dict[str, Any] = {}
|
||||
self.native_elements = set()
|
||||
self.native_data = {}
|
||||
self.progress = 0
|
||||
|
||||
self.material_creator = MaterialCreator(ifc_import_settings, self)
|
||||
classes_to_wireframe_str = tool.Drawing.get_document_props().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:
|
||||
if not self.time:
|
||||
@@ -278,8 +254,6 @@ class IfcImporter:
|
||||
self.profile_code("Place objects in collections")
|
||||
self.setup_arrays()
|
||||
self.profile_code("Setup arrays")
|
||||
tool.Project.load_linked_models_from_ifc()
|
||||
self.profile_code("Load linked models")
|
||||
self.add_project_to_scene()
|
||||
self.profile_code("Add project to scene")
|
||||
if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type("IfcElement")) < 1000:
|
||||
@@ -300,20 +274,6 @@ class IfcImporter:
|
||||
bpy.context.window_manager.progress_end()
|
||||
|
||||
def process_context_filter(self) -> None:
|
||||
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)
|
||||
@@ -369,7 +329,7 @@ class IfcImporter:
|
||||
if self.gross_elements:
|
||||
print("Warning! Excessive voids were found and skipped for the following elements:")
|
||||
for element in self.gross_elements:
|
||||
print(f"{element} - {len(getattr(element, 'HasOpenings', []))} openings")
|
||||
print(element)
|
||||
|
||||
def get_spatial_elements_filtered_by_elements(
|
||||
self, elements: set[ifcopenshell.entity_instance]
|
||||
@@ -442,7 +402,7 @@ class IfcImporter:
|
||||
matrix[2][3] *= self.unit_scale
|
||||
|
||||
# Single swept disk solids (e.g. rebar) are better natively represented as beveled curves
|
||||
if tool.Loader.is_native_swept_disk_solid(element, resolved_representation):
|
||||
if self.is_native_swept_disk_solid(element, resolved_representation):
|
||||
self.native_data[element.GlobalId] = {
|
||||
"matrix": matrix,
|
||||
"context": context,
|
||||
@@ -451,10 +411,27 @@ class IfcImporter:
|
||||
"type": "IfcSweptDiskSolid",
|
||||
}
|
||||
return True
|
||||
|
||||
def is_native_swept_disk_solid(
|
||||
self, element: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> bool:
|
||||
items = [i["item"] for i in ifcopenshell.util.representation.resolve_items(representation)]
|
||||
if len(items) == 1 and items[0].is_a("IfcSweptDiskSolid"):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
elif len(items) and ( # See #2508 why we accommodate for invalid IFCs here
|
||||
items[0].is_a("IfcSweptDiskSolid")
|
||||
and len({i.is_a() for i in items}) == 1
|
||||
and len({i.Radius for i in items}) == 1
|
||||
):
|
||||
if tool.Blender.Modifier.is_railing(element):
|
||||
return False
|
||||
return True
|
||||
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":
|
||||
@@ -495,16 +472,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:
|
||||
@@ -529,9 +504,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
|
||||
@@ -559,17 +533,13 @@ class IfcImporter:
|
||||
native_data = self.native_data[element.GlobalId]
|
||||
mesh_name = f"{native_data['context'].id()}/{native_data['geometry_id']}"
|
||||
mesh = self.meshes.get(mesh_name)
|
||||
|
||||
curve_thickness = None
|
||||
if mesh is None:
|
||||
if native_data["type"] == "IfcSweptDiskSolid":
|
||||
mesh, curve_thickness = tool.Loader.create_native_swept_disk_solid(element, mesh_name, native_data)
|
||||
mesh = self.create_native_swept_disk_solid(element, mesh_name, native_data)
|
||||
tool.Ifc.link(tool.Ifc.get().by_id(native_data["geometry_id"]), mesh)
|
||||
mesh.name = mesh_name
|
||||
self.meshes[mesh_name] = mesh
|
||||
obj = self.create_product(element, mesh=mesh)
|
||||
tool.Loader.setup_native_swept_disk_solid_thickness(obj, curve_thickness)
|
||||
|
||||
self.create_product(element, mesh=mesh)
|
||||
print("Done creating geometry")
|
||||
|
||||
def create_spatial_elements(self) -> None:
|
||||
@@ -612,7 +582,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}))
|
||||
@@ -622,9 +591,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
|
||||
@@ -717,15 +698,24 @@ 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: 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
|
||||
tool.Ifc.link(representation, mesh)
|
||||
|
||||
placement_matrix = ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement)
|
||||
vertex = None
|
||||
context = None
|
||||
representation = None
|
||||
for subelement in self.file.traverse(product.Representation):
|
||||
if subelement.is_a("IfcVertex") and subelement.VertexGeometry.is_a("IfcCartesianPoint"):
|
||||
vertex = list(subelement.VertexGeometry.Coordinates)
|
||||
elif subelement.is_a("IfcGeometricRepresentationContext"):
|
||||
context = subelement
|
||||
elif subelement.is_a("IfcTopologyRepresentation"):
|
||||
representation = subelement
|
||||
if not vertex or not context or not representation:
|
||||
continue # TODO implement non cartesian point vertexes
|
||||
|
||||
mesh_name = tool.Geometry.get_representation_name(representation)
|
||||
mesh = bpy.data.meshes.new(mesh_name)
|
||||
mesh.from_pydata([mathutils.Vector(vertex) * self.unit_scale], [], [])
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(product), mesh)
|
||||
self.set_matrix_world(obj, tool.Loader.apply_blender_offset_to_matrix_world(obj, placement_matrix))
|
||||
self.link_element(product, obj)
|
||||
@@ -761,9 +751,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(
|
||||
@@ -811,23 +804,21 @@ 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
|
||||
|
||||
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
|
||||
self.link_element(element, obj)
|
||||
if getattr(element, "HasOpenings", None):
|
||||
tool.Geometry.lock_scale(obj)
|
||||
|
||||
for ifcclass in self.classes_to_wireframe_list:
|
||||
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?
|
||||
@@ -845,6 +836,43 @@ class IfcImporter:
|
||||
def load_existing_meshes(self) -> None:
|
||||
self.meshes.update({m.name: m for m in bpy.data.meshes})
|
||||
|
||||
def create_native_swept_disk_solid(
|
||||
self, element: ifcopenshell.entity_instance, mesh_name: str, native_data: dict[str, Any]
|
||||
) -> bpy.types.Curve:
|
||||
# TODO: georeferencing?
|
||||
curve = bpy.data.curves.new(mesh_name, type="CURVE")
|
||||
curve.dimensions = "3D"
|
||||
curve.resolution_u = 2
|
||||
polyline = curve.splines.new("POLY")
|
||||
|
||||
for item_data in ifcopenshell.util.representation.resolve_items(native_data["representation"]):
|
||||
item = item_data["item"]
|
||||
matrix = item_data["matrix"]
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
matrix[2][3] *= self.unit_scale
|
||||
# TODO: support inner radius, start param, and end param
|
||||
geometry = tool.Loader.create_generic_shape(item.Directrix)
|
||||
if not geometry:
|
||||
continue
|
||||
e = geometry.edges
|
||||
v = geometry.verts
|
||||
vertices = [list(matrix @ [v[i], v[i + 1], v[i + 2], 1]) for i in range(0, len(v), 3)]
|
||||
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
|
||||
v2 = None
|
||||
for edge in edges:
|
||||
v1 = vertices[edge[0]]
|
||||
if v1 != v2:
|
||||
polyline = curve.splines.new("POLY")
|
||||
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v1)
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = native_data["matrix"] @ mathutils.Vector(v2)
|
||||
|
||||
curve.bevel_depth = self.unit_scale * item.Radius
|
||||
curve.use_fill_caps = True
|
||||
return curve
|
||||
|
||||
def merge_materials_by_colour(self):
|
||||
cleaned_materials = {}
|
||||
for m in bpy.data.materials:
|
||||
@@ -898,20 +926,18 @@ 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:
|
||||
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
|
||||
def set_units(self):
|
||||
if not (units := self.file.by_type("IfcUnitAssignment")):
|
||||
return # Geometry is optional in IFC
|
||||
props = tool.Blender.get_bim_props()
|
||||
for unit in assignment.Units:
|
||||
for unit in units[0].Units:
|
||||
if unit.is_a("IfcNamedUnit") and unit.UnitType == "LENGTHUNIT":
|
||||
if unit.is_a("IfcSIUnit"):
|
||||
bpy.context.scene.unit_settings.system = "METRIC"
|
||||
@@ -930,27 +956,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(
|
||||
@@ -960,16 +985,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:
|
||||
@@ -982,11 +1001,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():
|
||||
@@ -994,10 +1012,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):
|
||||
@@ -1042,7 +1060,11 @@ class IfcImporter:
|
||||
v2 = vertices[edge[1]]
|
||||
polyline.points.add(1)
|
||||
polyline.points[-1].co = mathutils.Vector(v2)
|
||||
edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
|
||||
# TODO: remove error handling after we update build in Bonsai.
|
||||
try:
|
||||
edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
|
||||
except AttributeError:
|
||||
edges_item_ids = []
|
||||
curve["ios_edges_item_ids"] = edges_item_ids
|
||||
return curve
|
||||
|
||||
@@ -1050,8 +1072,8 @@ 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 hasattr(shape, "geometry"):
|
||||
# shape is ShapeElementType
|
||||
@@ -1066,36 +1088,35 @@ class IfcImporter:
|
||||
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(
|
||||
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
|
||||
return tool.Loader.convert_geometry_to_mesh(geometry, mesh, verts=verts)
|
||||
except:
|
||||
self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
|
||||
import traceback
|
||||
@@ -1103,10 +1124,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:
|
||||
@@ -1152,7 +1172,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
|
||||
@@ -1165,16 +1184,15 @@ class IfcImportSettings:
|
||||
self.has_filter = None
|
||||
self.should_filter_spatial_elements = True
|
||||
self.should_setup_viewport_camera = True
|
||||
self.contexts: list[ifcopenshell.entity_instance] = []
|
||||
self.contexts = []
|
||||
self.context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.gross_context_settings: list[ifcopenshell.geom.main.settings] = []
|
||||
self.elements: set[ifcopenshell.entity_instance] = set()
|
||||
self.load_indexed_maps = False
|
||||
|
||||
@staticmethod
|
||||
def factory(context=None, input_file=None, logger=None):
|
||||
scene_diff = tool.Blender.get_diff_props()
|
||||
props = tool.Project.get_project_props()
|
||||
scene_diff = bpy.context.scene.DiffProperties
|
||||
props = bpy.context.scene.BIMProjectProperties
|
||||
settings = IfcImportSettings()
|
||||
settings.input_file = input_file
|
||||
if logger is None:
|
||||
@@ -1190,27 +1208,17 @@ 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:
|
||||
settings.false_origin = [float(o) for o in props.false_origin.split(",")[:3]]
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise Exception(
|
||||
f"Failed to set false origin from string '{props.false_origin}'.\n"
|
||||
f"Error: {str(e)}.\nSee above for the details."
|
||||
)
|
||||
except:
|
||||
settings.false_origin = [0, 0, 0]
|
||||
try:
|
||||
settings.project_north = float(props.project_north)
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
raise Exception(
|
||||
f"Failed to set project north from string '{props.project_north}'.\n"
|
||||
f"Error: {str(e)}.\nSee above for the details."
|
||||
)
|
||||
except:
|
||||
settings.project_north = 0
|
||||
settings.element_limit_mode = props.element_limit_mode
|
||||
settings.element_offset = props.element_offset
|
||||
settings.element_limit = props.element_limit
|
||||
settings.load_indexed_maps = props.load_indexed_maps
|
||||
return settings
|
||||
|
||||
@@ -30,11 +30,7 @@ classes = (
|
||||
operator.BIM_OT_aggregate_unassign_object,
|
||||
operator.BIM_OT_break_link_to_other_aggregates,
|
||||
operator.BIM_OT_select_linked_aggregates,
|
||||
operator.BIM_OT_disable_aggregate_mode,
|
||||
operator.BIM_OT_toggle_aggregate_mode_local_view,
|
||||
prop.BIMObjectAggregateProperties,
|
||||
prop.Objects,
|
||||
prop.BIMAggregateProperties,
|
||||
ui.BIM_PT_aggregate,
|
||||
ui.BIM_PT_linked_aggregate,
|
||||
)
|
||||
@@ -42,9 +38,7 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Object.BIMObjectAggregateProperties = bpy.props.PointerProperty(type=prop.BIMObjectAggregateProperties)
|
||||
bpy.types.Scene.BIMAggregateProperties = bpy.props.PointerProperty(type=prop.BIMAggregateProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Object.BIMObjectAggregateProperties
|
||||
del bpy.types.Scene.BIMAggregateProperties
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2024 Bruno Perdigão <contact@brunopo.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 blf
|
||||
import bpy
|
||||
import gpu
|
||||
import ifcopenshell
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import SpaceView3D
|
||||
from bpy_extras import view3d_utils
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
from mathutils import Vector
|
||||
from bonsai.bim.module.geometry.decorator import ItemDecorator
|
||||
|
||||
|
||||
def transparent_color(color, alpha=0.1):
|
||||
color = [i for i in color]
|
||||
color[3] = alpha
|
||||
return color
|
||||
|
||||
|
||||
def create_bounding_box(objs):
|
||||
# Initialize the bounding box coordinates
|
||||
min_x, min_y, min_z = float("inf"), float("inf"), float("inf")
|
||||
max_x, max_y, max_z = float("-inf"), float("-inf"), float("-inf")
|
||||
|
||||
# Iterate over the selected objects
|
||||
for obj in objs:
|
||||
if not obj.data:
|
||||
continue
|
||||
# Get the object's bounding box coordinates
|
||||
|
||||
bbox_world = [obj.matrix_world @ Vector(b) for b in obj.bound_box]
|
||||
obj_min = []
|
||||
obj_min.append(min([b[0] for b in bbox_world]))
|
||||
obj_min.append(min([b[1] for b in bbox_world]))
|
||||
obj_min.append(min([b[2] for b in bbox_world]))
|
||||
obj_max = []
|
||||
obj_max.append(max([b[0] for b in bbox_world]))
|
||||
obj_max.append(max([b[1] for b in bbox_world]))
|
||||
obj_max.append(max([b[2] for b in bbox_world]))
|
||||
|
||||
# Update the overall bounding box coordinates
|
||||
min_x = min(min_x, min(obj_min[0], obj_max[0]))
|
||||
min_y = min(min_y, min(obj_min[1], obj_max[1]))
|
||||
min_z = min(min_z, min(obj_min[2], obj_max[2]))
|
||||
max_x = max(max_x, max(obj_min[0], obj_max[0]))
|
||||
max_y = max(max_y, max(obj_min[1], obj_max[1]))
|
||||
max_z = max(max_z, max(obj_min[2], obj_max[2]))
|
||||
|
||||
indices = [
|
||||
(min_x, min_y, min_z),
|
||||
(max_x, min_y, min_z),
|
||||
(max_x, max_y, min_z),
|
||||
(min_x, max_y, min_z),
|
||||
(min_x, min_y, max_z),
|
||||
(max_x, min_y, max_z),
|
||||
(max_x, max_y, max_z),
|
||||
(min_x, max_y, max_z),
|
||||
]
|
||||
|
||||
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7)]
|
||||
|
||||
return indices, edges
|
||||
|
||||
|
||||
class AggregateDecorator:
|
||||
is_installed = False
|
||||
handlers = []
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.is_installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_aggregate, (context,), "WINDOW", "POST_VIEW"))
|
||||
cls.is_installed = True
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
for handler in cls.handlers:
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
cls.is_installed = False
|
||||
|
||||
def dotted_line_shader(self):
|
||||
vert_out = gpu.types.GPUStageInterfaceInfo("my_interface")
|
||||
vert_out.smooth("FLOAT", "v_ArcLength")
|
||||
|
||||
shader_info = gpu.types.GPUShaderCreateInfo()
|
||||
shader_info.push_constant("MAT4", "u_ViewProjectionMatrix")
|
||||
shader_info.push_constant("FLOAT", "u_Scale")
|
||||
shader_info.vertex_in(0, "VEC3", "position")
|
||||
shader_info.vertex_in(1, "FLOAT", "arcLength")
|
||||
shader_info.vertex_out(vert_out)
|
||||
shader_info.fragment_out(0, "VEC4", "FragColor")
|
||||
shader_info.push_constant("VEC4", "color")
|
||||
|
||||
shader_info.vertex_source(
|
||||
"void main()"
|
||||
"{"
|
||||
" v_ArcLength = arcLength;"
|
||||
" gl_Position = u_ViewProjectionMatrix * vec4(position, 1.0f);"
|
||||
"}"
|
||||
)
|
||||
|
||||
shader_info.fragment_source(
|
||||
"void main()" "{" " if (step(sin(v_ArcLength * u_Scale), 0.4) == 1) discard;" " FragColor = color;" "}"
|
||||
)
|
||||
|
||||
shader = gpu.shader.create_from_info(shader_info)
|
||||
del vert_out
|
||||
del shader_info
|
||||
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]
|
||||
for a, b in zip(coords[:-1], coords[1:]):
|
||||
arc_lengths.append(arc_lengths[-1] + (a - b).length)
|
||||
|
||||
batch = batch_for_shader(
|
||||
shader,
|
||||
"LINE_STRIP",
|
||||
{"position": coords, "arcLength": arc_lengths},
|
||||
indices=indices,
|
||||
)
|
||||
|
||||
matrix = bpy.context.region_data.perspective_matrix
|
||||
shader.uniform_float("color", color)
|
||||
shader.uniform_float("u_ViewProjectionMatrix", matrix)
|
||||
shader.uniform_float("u_Scale", 25)
|
||||
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):
|
||||
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
|
||||
decorator_color_error = self.addon_prefs.decorator_color_error
|
||||
decorator_color_unselected = self.addon_prefs.decorator_color_unselected
|
||||
decorator_color_background = self.addon_prefs.decorator_color_background
|
||||
theme = context.preferences.themes.items()[0][1]
|
||||
selected_object_color = (*theme.view_3d.object_active, 1)
|
||||
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
self.shader.bind()
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind()
|
||||
# POLYLINE_UNIFORM_COLOR specific uniforms
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
aggregates = []
|
||||
if not (selected_objects := context.selected_objects):
|
||||
return
|
||||
for obj in selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcElement"):
|
||||
return
|
||||
|
||||
parts = ifcopenshell.util.element.get_parts(element)
|
||||
if parts:
|
||||
aggregates.append(obj)
|
||||
continue
|
||||
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
aggregates.append(tool.Ifc.get_object(aggregate))
|
||||
|
||||
aggregates = set(aggregates)
|
||||
for aggregate in aggregates:
|
||||
self.line_shader.uniform_float("lineWidth", 1.0)
|
||||
color = decorator_color_unselected
|
||||
if aggregate in selected_objects:
|
||||
color = selected_object_color
|
||||
size = aggregate.empty_display_size
|
||||
location = aggregate.location
|
||||
line_x = (location - Vector((size, 0.0, 0.0)), location + Vector((size, 0.0, 0.0)))
|
||||
self.draw_batch("LINES", line_x, color, [(0, 1)])
|
||||
line_y = (location - Vector((0.0, size, 0.0)), location + Vector((0.0, size, 0.0)))
|
||||
self.draw_batch("LINES", line_y, color, [(0, 1)])
|
||||
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
|
||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||
if 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)
|
||||
self.line_shader.uniform_float("lineWidth", 0.5)
|
||||
self.draw_batch("LINES", indices, color, edges)
|
||||
line = (Vector(indices[0]), location)
|
||||
self.draw_custom_batch(line, decorator_color_unselected)
|
||||
|
||||
|
||||
class AggregateModeDecorator:
|
||||
is_installed = False
|
||||
handlers = []
|
||||
|
||||
@classmethod
|
||||
def install(cls, context):
|
||||
if cls.is_installed:
|
||||
cls.uninstall()
|
||||
handler = cls()
|
||||
cls.handlers.append(
|
||||
SpaceView3D.draw_handler_add(handler.draw_aggregate_name, (context,), "WINDOW", "POST_PIXEL")
|
||||
)
|
||||
cls.handlers.append(
|
||||
SpaceView3D.draw_handler_add(handler.draw_aggregate_empty, (context,), "WINDOW", "POST_VIEW")
|
||||
)
|
||||
cls.is_installed = True
|
||||
|
||||
@classmethod
|
||||
def uninstall(cls):
|
||||
for handler in cls.handlers:
|
||||
try:
|
||||
SpaceView3D.draw_handler_remove(handler, "WINDOW")
|
||||
except ValueError:
|
||||
pass
|
||||
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)
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_aggregate_name(self, context):
|
||||
if context.mode == "EDIT_MESH":
|
||||
return
|
||||
region = context.region
|
||||
rv3d = region.data
|
||||
props = context.scene.BIMAggregateProperties
|
||||
|
||||
aggregate_obj = props.editing_aggregate
|
||||
if not aggregate_obj:
|
||||
return
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
self.font_id = 0
|
||||
font_size = tool.Blender.scale_font_size(12)
|
||||
blf.size(self.font_id, font_size)
|
||||
blf.enable(self.font_id, blf.SHADOW)
|
||||
blf.shadow(self.font_id, 6, 0, 0, 0, 1)
|
||||
color = self.addon_prefs.decorator_color_selected
|
||||
if aggregate_obj in context.selected_objects:
|
||||
color = self.addon_prefs.decorator_color_selected
|
||||
blf.color(self.font_id, *color)
|
||||
text = aggregate_obj.name
|
||||
text_coords = view3d_utils.location_3d_to_region_2d(region, rv3d, aggregate_obj.location)
|
||||
if not text_coords:
|
||||
return
|
||||
text_length = blf.dimensions(self.font_id, text)
|
||||
text_coords[0] -= text_length[0] / 2
|
||||
text_coords[1] -= 20
|
||||
blf.position(self.font_id, text_coords[0], text_coords[1], 0)
|
||||
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
blf.draw(self.font_id, text)
|
||||
|
||||
def draw_aggregate_empty(self, context):
|
||||
if context.mode == "EDIT_MESH":
|
||||
return
|
||||
props = context.scene.BIMAggregateProperties
|
||||
aggregate_obj = props.editing_aggregate
|
||||
if not aggregate_obj:
|
||||
return
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
|
||||
self.line_shader.bind()
|
||||
self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height))
|
||||
self.line_shader.uniform_float("lineWidth", 2.0)
|
||||
theme = context.preferences.themes.items()[0][1]
|
||||
selected_object_color = (*theme.view_3d.object_active, 1)
|
||||
color = self.addon_prefs.decorator_color_selected
|
||||
if aggregate_obj in context.selected_objects:
|
||||
color = selected_object_color
|
||||
size = aggregate_obj.empty_display_size
|
||||
location = aggregate_obj.location
|
||||
line_x = (location - Vector((size, 0.0, 0.0)), location + Vector((size, 0.0, 0.0)))
|
||||
self.draw_batch("LINES", line_x, color, [(0, 1)])
|
||||
line_y = (location - Vector((0.0, size, 0.0)), location + Vector((0.0, size, 0.0)))
|
||||
self.draw_batch("LINES", line_y, color, [(0, 1)])
|
||||
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
|
||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate_obj))
|
||||
if parts:
|
||||
for part in parts:
|
||||
part_obj = tool.Ifc.get_object(part)
|
||||
if part.is_a("IfcElementAssembly"):
|
||||
self.line_shader.uniform_float("lineWidth", 1.0)
|
||||
size = part_obj.empty_display_size
|
||||
location = part_obj.location
|
||||
color = self.addon_prefs.decorator_color_unselected
|
||||
if part_obj in context.selected_objects:
|
||||
color = selected_object_color
|
||||
line_x = (location - Vector((size, 0.0, 0.0)), location + Vector((size, 0.0, 0.0)))
|
||||
self.draw_batch("LINES", line_x, color, [(0, 1)])
|
||||
line_y = (location - Vector((0.0, size, 0.0)), location + Vector((0.0, size, 0.0)))
|
||||
self.draw_batch("LINES", line_y, color, [(0, 1)])
|
||||
line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size)))
|
||||
self.draw_batch("LINES", line_z, color, [(0, 1)])
|
||||
parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate_obj))
|
||||
|
||||
color = self.addon_prefs.decorator_color_selected
|
||||
parts_objs = [tool.Ifc.get_object(p) for p in parts]
|
||||
indices, edges = create_bounding_box(parts_objs)
|
||||
self.line_shader.uniform_float("lineWidth", 0.5)
|
||||
self.draw_batch("LINES", indices, color, edges)
|
||||
@@ -66,10 +66,6 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
|
||||
relating_obj=relating_obj,
|
||||
related_obj=obj,
|
||||
)
|
||||
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
|
||||
except core.IncompatibleAggregateError:
|
||||
self.report({"ERROR"}, f"Cannot aggregate {obj.name} to {relating_obj.name}")
|
||||
except core.AggregateRepresentationError:
|
||||
@@ -369,33 +365,3 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_disable_aggregate_mode(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_aggregate_mode"
|
||||
bl_label = "Disable Aggregate Mode"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_toggle_aggregate_mode_local_view(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_aggregate_mode_local_view"
|
||||
bl_label = "Toggle Aggregate Mode Local View"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMAggregateProperties
|
||||
objs = [o.obj for o in props.editing_objects]
|
||||
if props.in_aggregate_mode:
|
||||
if context.space_data.local_view:
|
||||
bpy.ops.view3d.localview()
|
||||
else:
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
bpy.ops.view3d.localview()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -32,7 +32,6 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from bonsai.bim.module.aggregate.decorator import AggregateDecorator, AggregateModeDecorator
|
||||
|
||||
|
||||
def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool:
|
||||
@@ -74,20 +73,6 @@ def poll_related_object(self: "BIMObjectAggregateProperties", related_obj: bpy.t
|
||||
return True
|
||||
|
||||
|
||||
def update_aggregate_decorator(self, context):
|
||||
if self.aggregate_decorator:
|
||||
AggregateDecorator.install(bpy.context)
|
||||
else:
|
||||
AggregateDecorator.uninstall()
|
||||
|
||||
|
||||
def update_aggregate_mode_decorator(self, context):
|
||||
if self.in_aggregate_mode:
|
||||
AggregateModeDecorator.install(bpy.context)
|
||||
else:
|
||||
AggregateModeDecorator.uninstall()
|
||||
|
||||
|
||||
class BIMObjectAggregateProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing")
|
||||
relating_object: PointerProperty(name="Relating Whole", type=bpy.types.Object, poll=poll_relating_object)
|
||||
@@ -97,21 +82,3 @@ class BIMObjectAggregateProperties(PropertyGroup):
|
||||
type=bpy.types.Object,
|
||||
poll=poll_related_object,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
editing_objects: CollectionProperty(type=Objects)
|
||||
not_editing_objects: CollectionProperty(type=Objects)
|
||||
aggregate_decorator: BoolProperty(
|
||||
name="Display Aggregate",
|
||||
default=False,
|
||||
update=update_aggregate_decorator,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -47,9 +47,6 @@ class BIM_PT_aggregate(Panel):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text="Aggregate Decorator")
|
||||
row.prop(context.scene.BIMAggregateProperties, "aggregate_decorator", icon="HIDE_OFF", text="")
|
||||
if not AggregateData.is_loaded:
|
||||
AggregateData.load()
|
||||
|
||||
@@ -66,9 +63,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:
|
||||
@@ -115,14 +112,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
|
||||
|
||||
|
||||
@@ -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,113 +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
|
||||
from typing import get_args, TYPE_CHECKING
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
bl_idname = "bim.import_alignment_csv"
|
||||
bl_label = "Import Alignment CSV"
|
||||
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))
|
||||
@@ -38,16 +38,13 @@ class AttributesData:
|
||||
def attributes(cls):
|
||||
results = []
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
assert element
|
||||
data = element.get_info()
|
||||
if "GlobalId" in data:
|
||||
if hasattr(element, "GlobalId"):
|
||||
excluded_keys = ["id", "type"]
|
||||
else:
|
||||
excluded_keys = ["type"]
|
||||
# Same types also filtered by `import_attribute`.
|
||||
exclude_value_types = (tuple, ifcopenshell.entity_instance)
|
||||
for key, value in data.items():
|
||||
if value is None or isinstance(value, exclude_value_types) or key in excluded_keys:
|
||||
if value is None or isinstance(value, ifcopenshell.entity_instance) or key in excluded_keys:
|
||||
continue
|
||||
if key == "id":
|
||||
key = "STEP ID"
|
||||
|
||||
@@ -19,42 +19,28 @@
|
||||
import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.bim.helper
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.attribute as core
|
||||
import bonsai.core.spatial
|
||||
|
||||
|
||||
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[:]
|
||||
return [context.active_object]
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
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"})
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mass_operation = event.alt
|
||||
return self.execute(context)
|
||||
|
||||
def enable_editing_attribute_on_obj(self, obj):
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects[self.obj]
|
||||
props = obj.BIMAttributeProperties
|
||||
props.attributes.clear()
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
has_inherited_predefined_type = False
|
||||
if not element.is_a("IfcTypeObject") and (element_type := ifcopenshell.util.element.get_type(element)):
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
@@ -80,65 +66,52 @@ class EnableEditingAttributes(bpy.types.Operator):
|
||||
|
||||
bonsai.bim.helper.import_attributes2(element, props.attributes, callback=callback)
|
||||
props.is_editing_attributes = True
|
||||
|
||||
def execute(self, context):
|
||||
for obj in get_objs_for_operation(self, context):
|
||||
self.enable_editing_attribute_on_obj(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
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 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
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
for obj in get_objs_for_operation(self, context):
|
||||
self.disable_editing_attributes_on_obj(obj)
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
props.is_editing_attributes = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_attributes"
|
||||
bl_label = "Edit Attributes"
|
||||
bl_description = "Edit the attributes of the active object"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
obj = tool.Blender.get_active_object(is_selected=False)
|
||||
if not (element := tool.Ifc.get_entity(obj)):
|
||||
return
|
||||
self.file = IfcStore.get_file()
|
||||
obj = bpy.data.objects.get(self.obj)
|
||||
props = obj.BIMAttributeProperties
|
||||
product = tool.Ifc.get_entity(obj)
|
||||
|
||||
def callback(attributes, prop):
|
||||
if prop.name in ("RefLatitude", "RefLongitude"):
|
||||
if not prop.is_null:
|
||||
if prop.is_null:
|
||||
attributes[prop.name] = None
|
||||
else:
|
||||
try:
|
||||
attributes[prop.name] = json.loads(prop.string_value)
|
||||
except:
|
||||
attributes[prop.name] = None
|
||||
return True
|
||||
return True
|
||||
|
||||
props = obj.BIMAttributeProperties
|
||||
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback)
|
||||
ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes)
|
||||
ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes)
|
||||
|
||||
tool.Root.set_object_name(obj, element)
|
||||
if (name := tool.Loader.get_name(product)) and obj.name != name:
|
||||
obj.name = name
|
||||
bpy.ops.bim.disable_editing_attributes(obj=obj.name)
|
||||
|
||||
if tool.Root.is_spatial_element(element):
|
||||
bonsai.core.spatial.import_spatial_decomposition(tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -187,12 +160,9 @@ class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.copy_attribute_to_selection"
|
||||
bl_label = "Copy Attribute To Selection"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
name: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
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
|
||||
)
|
||||
self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.")
|
||||
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value()
|
||||
for obj in tool.Blender.get_selected_objects():
|
||||
core.copy_attribute_to_selection(tool.Ifc, name=self.name, value=value, obj=obj)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -18,29 +18,33 @@
|
||||
|
||||
import bonsai.bim.helper
|
||||
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, layout, attributes):
|
||||
obj = context.active_object
|
||||
oprops = obj.BIMObjectProperties
|
||||
props = obj.BIMAttributeProperties
|
||||
|
||||
if props.is_editing_attributes:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
op.obj = obj.name
|
||||
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
|
||||
op.obj = obj.name
|
||||
|
||||
bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
|
||||
else:
|
||||
row = layout.row()
|
||||
op = row.operator("bim.enable_editing_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
op.obj = obj.name
|
||||
|
||||
for attribute in attributes:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
value = bonsai.bim.helper.get_display_value(attribute["value"])
|
||||
op = row.operator("bim.select_similar", text=value, icon="NONE", emboss=False)
|
||||
# row.label(text=attribute["value"])
|
||||
op = row.operator("bim.select_similar", text=attribute["value"], icon="NONE", emboss=False)
|
||||
op.key = attribute["name"]
|
||||
|
||||
# TODO: reimplement, see #1222
|
||||
@@ -58,7 +62,11 @@ class BIM_PT_object_attributes(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get_entity(context.active_object)
|
||||
if not context.active_object:
|
||||
return False
|
||||
if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id):
|
||||
return False
|
||||
return bool(context.active_object.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
def draw(self, context):
|
||||
if not AttributesData.is_loaded:
|
||||
|
||||
@@ -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,7 +389,7 @@ 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"))
|
||||
)
|
||||
@@ -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]
|
||||
|
||||
@@ -624,7 +616,7 @@ class AddBcfViewpoint(bpy.types.Operator):
|
||||
old_file_format = blender_render.image_settings.file_format
|
||||
blender_render.image_settings.file_format = "PNG"
|
||||
old_filepath = blender_render.filepath
|
||||
blender_render.filepath = tool.Blender.get_data_dir_path("snapshot.png").__str__()
|
||||
blender_render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
with open(blender_render.filepath, "rb") as f:
|
||||
snapshot = f.read()
|
||||
@@ -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:
|
||||
@@ -1315,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(
|
||||
@@ -1354,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)
|
||||
@@ -1390,14 +1378,16 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
def set_view_setup_hints(
|
||||
self, viewpoint: bcf.agnostic.visinfo.VisualizationInfoHandler, context: bpy.types.Context
|
||||
) -> None:
|
||||
# TODO: handle view_setup_hints.openings_visible
|
||||
# should we reload elements with/without opening applied here or ...?
|
||||
if view_setup_hints := tool.Bcf.get_viewpoint_view_setup_hints(viewpoint):
|
||||
pass
|
||||
if not view_setup_hints.spaces_visible:
|
||||
if viewpoint.visualization_info.components.view_setup_hints:
|
||||
if not viewpoint.visualization_info.components.view_setup_hints.spaces_visible:
|
||||
self.hide_spaces(context)
|
||||
if viewpoint.visualization_info.components.view_setup_hints.openings_visible is not None:
|
||||
self.set_openings_visibility(
|
||||
viewpoint.visualization_info.components.view_setup_hints.openings_visible, context
|
||||
)
|
||||
else:
|
||||
self.hide_spaces(context)
|
||||
self.set_openings_visibility(False, context)
|
||||
|
||||
def hide_spaces(self, context: bpy.types.Context) -> None:
|
||||
old = context.area.type
|
||||
@@ -1407,13 +1397,16 @@ class ActivateBcfViewpoint(bpy.types.Operator):
|
||||
bpy.ops.object.hide_view_set()
|
||||
context.area.type = old
|
||||
|
||||
def set_openings_visibility(self, is_visible, context):
|
||||
pass # We no longer have an openings collection
|
||||
|
||||
def set_selection(self, viewpoint: bcf.agnostic.visinfo.VisualizationInfoHandler) -> None:
|
||||
selected_global_ids = viewpoint.get_selected_guids()
|
||||
if selected_global_ids is None:
|
||||
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)
|
||||
@@ -1426,7 +1419,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)
|
||||
|
||||
@@ -1529,51 +1522,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"
|
||||
@@ -1590,8 +1590,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)
|
||||
@@ -1611,8 +1610,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
|
||||
@@ -35,7 +34,6 @@ 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
|
||||
@@ -97,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
|
||||
@@ -112,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")
|
||||
@@ -125,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")
|
||||
@@ -142,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
|
||||
@@ -207,44 +183,20 @@ 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
|
||||
props = self
|
||||
active_topic = props.active_topic
|
||||
active_related_topics = active_topic.related_topics.keys()
|
||||
RELATED_TOPICS_ENUM_ITEMS = []
|
||||
enum_items = []
|
||||
i = 0
|
||||
for t in props.topics:
|
||||
if t.name == active_topic.name:
|
||||
continue
|
||||
if t.name in active_related_topics:
|
||||
continue
|
||||
RELATED_TOPICS_ENUM_ITEMS.append((t.name, t.title, t.description))
|
||||
return RELATED_TOPICS_ENUM_ITEMS
|
||||
enum_items.append((t.name, t.title, t.description))
|
||||
return enum_items
|
||||
|
||||
|
||||
class BCFProperties(PropertyGroup):
|
||||
@@ -283,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 = ""
|
||||
@@ -321,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:
|
||||
@@ -330,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,16 @@ 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 get_boundaries_collection(blender_space):
|
||||
space_collection = blender_space.BIMObjectProperties.collection
|
||||
collection_name = f"Boundaries/{blender_space.BIMObjectProperties.ifc_definition_id}"
|
||||
boundaries_collection = space_collection.children.get(collection_name)
|
||||
if not boundaries_collection:
|
||||
boundaries_collection = bpy.data.collections.new(collection_name)
|
||||
space_collection.children.link(boundaries_collection)
|
||||
return boundaries_collection
|
||||
|
||||
|
||||
def disable_editing_boundary_geometry(context):
|
||||
@@ -60,7 +67,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 +76,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 +106,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 +117,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 +140,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)
|
||||
@@ -163,8 +164,9 @@ class Loader:
|
||||
mesh = self.create_mesh(boundary)
|
||||
obj = bpy.data.objects.new(f"{boundary.is_a()}/{boundary.Name}", mesh)
|
||||
obj.matrix_world = blender_space.matrix_world
|
||||
boundaries_collection = get_boundaries_collection(blender_space)
|
||||
boundaries_collection.objects.link(obj)
|
||||
tool.Ifc.link(boundary, obj)
|
||||
tool.Collector.assign(obj)
|
||||
return obj
|
||||
|
||||
|
||||
@@ -288,7 +290,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 +326,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 +365,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 +384,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,10 +397,8 @@ 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)
|
||||
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)
|
||||
@@ -426,7 +421,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"}
|
||||
|
||||
|
||||
@@ -535,7 +530,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:
|
||||
@@ -555,7 +549,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"}
|
||||
|
||||
|
||||
@@ -566,7 +560,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()
|
||||
@@ -592,70 +586,47 @@ 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))}
|
||||
if len(ifc_objects) not in (1, 2):
|
||||
self.report({"ERROR"}, "Please select one or two IFC elements.")
|
||||
return {"CANCELLED"}
|
||||
objs = context.selected_objects
|
||||
|
||||
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)
|
||||
|
||||
elif len(objs) == 1 and False:
|
||||
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
|
||||
|
||||
relating_space = None
|
||||
related_building_element = None
|
||||
relating_space_obj = None
|
||||
related_building_element_obj = None
|
||||
|
||||
def msg(self, context):
|
||||
self.layout.label(text="Please set an active container to detect space boundaries from.")
|
||||
|
||||
@@ -692,17 +663,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
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 = (
|
||||
@@ -722,7 +683,7 @@ 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())
|
||||
@@ -735,12 +696,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 = {}
|
||||
@@ -818,7 +779,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:
|
||||
@@ -850,7 +813,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
|
||||
@@ -859,6 +821,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
# Create shape of opening as a dissolved BMesh
|
||||
settings = ifcopenshell.geom.settings()
|
||||
shape = ifcopenshell.geom.create_shape(settings, opening)
|
||||
m = shape.transformation.matrix
|
||||
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
|
||||
mat.translation = (0, 0, 0)
|
||||
opening_bm = bmesh.new()
|
||||
@@ -896,7 +859,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(
|
||||
@@ -910,39 +875,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
|
||||
@@ -957,7 +911,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)
|
||||
|
||||
@@ -973,8 +929,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
|
||||
@@ -987,14 +942,16 @@ 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", []):
|
||||
inner_boundaries = []
|
||||
|
||||
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
|
||||
@@ -1009,11 +966,11 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
continue
|
||||
|
||||
connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix)
|
||||
boundary = tool.Ifc.run("root.create_entity", 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"
|
||||
@@ -1025,23 +982,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
|
||||
@@ -1111,7 +1065,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)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.boundary.data import SpaceBoundariesData
|
||||
|
||||
@@ -33,7 +34,7 @@ class BIM_PT_SceneBoundaries(Panel):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return tool.Ifc.get()
|
||||
return IfcStore.get_file()
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row(align=True)
|
||||
@@ -52,23 +53,21 @@ class BIM_PT_Boundary(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
|
||||
entity = tool.Ifc.get().by_id(props.ifc_definition_id)
|
||||
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = tool.Blender.get_object_bim_props(obj)
|
||||
props = context.active_object.BIMObjectProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
boundary = ifc_file.by_id(props.ifc_definition_id)
|
||||
self.bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
self.bprops = context.active_object.bim_boundary_properties
|
||||
if self.bprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
@@ -128,12 +127,12 @@ class BIM_PT_SpaceBoundaries(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
|
||||
element = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_class in ("IfcSpace", "IfcExternalSpatialElement"):
|
||||
@@ -145,9 +144,7 @@ class BIM_PT_SpaceBoundaries(Panel):
|
||||
if not SpaceBoundariesData.is_loaded:
|
||||
SpaceBoundariesData.load()
|
||||
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
self.props = tool.Blender.get_object_bim_props(obj)
|
||||
self.props = context.active_object.BIMObjectProperties
|
||||
self.ifc_file = tool.Ifc.get()
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_space_boundaries")
|
||||
|
||||
@@ -52,7 +52,7 @@ class BrickschemaData:
|
||||
def active_relations(cls):
|
||||
if BrickStore.graph is None:
|
||||
return []
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = bpy.context.scene.BIMBrickProperties
|
||||
try:
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
except:
|
||||
|
||||
@@ -22,26 +22,29 @@ import ifcopenshell.api
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.brick as core
|
||||
import bonsai.bim.handler
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.tool.brick import BrickStore
|
||||
|
||||
|
||||
class LoadBrickProject(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
class LoadBrickProject(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_brick_project"
|
||||
bl_label = "Load Brickschema Project"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Load in a Brick project from a file"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
filename_ext = ".ttl"
|
||||
|
||||
def _execute(self, context):
|
||||
if os.path.exists(self.filepath) and "ttl" in os.path.splitext(self.filepath)[1].lower():
|
||||
root = tool.Brick.get_brick_props().brick_list_root
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.load_brick_project(tool.Brick, filepath=self.filepath, brick_root=root)
|
||||
else:
|
||||
self.report({"ERROR"}, f"Failed to load {self.filepath}")
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
|
||||
class ViewBrickClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.view_brick_class"
|
||||
@@ -108,10 +111,10 @@ class AssignBrickReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Assign the selected Ifc entity to the selected Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
if not (obj := context.active_object) or not (element := tool.Ifc.get_entity(obj)):
|
||||
if not context.active_object:
|
||||
self.report({"ERROR"}, f"No Ifc selected")
|
||||
return
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
try:
|
||||
props.bricks[props.active_brick_index]
|
||||
except:
|
||||
@@ -120,7 +123,7 @@ class AssignBrickReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.assign_brick_reference(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
element=element,
|
||||
element=tool.Ifc.get_entity(context.active_object),
|
||||
library=tool.Ifc.get().by_id(int(props.libraries)),
|
||||
brick_uri=props.bricks[props.active_brick_index].uri,
|
||||
)
|
||||
@@ -133,7 +136,7 @@ class AddBrick(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Create the Brick entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.add_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
@@ -152,7 +155,7 @@ class AddBrickRelation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Create the Brick relationship"
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
if props.new_brick_relation_type == "http://www.w3.org/2000/01/rdf-schema#label":
|
||||
object = props.new_brick_relation_object
|
||||
@@ -170,7 +173,7 @@ class ConvertIfcToBrick(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Convert Ifc entities and relations to Brick entities and relations"
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
library = None
|
||||
if props.libraries:
|
||||
library = tool.Ifc.get().by_id(int(props.libraries))
|
||||
@@ -198,8 +201,7 @@ class NewBrickFile(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
root = props.brick_list_root
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.new_brick_file(tool.Brick, brick_root=root)
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -228,7 +230,7 @@ class RemoveBrick(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Delete this entity"
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
core.remove_brick(
|
||||
tool.Ifc,
|
||||
tool.Brick,
|
||||
@@ -237,20 +239,18 @@ class RemoveBrick(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
|
||||
class SerializeBrick(bpy.types.Operator, ExportHelper):
|
||||
class SerializeBrick(bpy.types.Operator):
|
||||
bl_idname = "bim.serialize_brick"
|
||||
bl_label = "Serialize Brick"
|
||||
# Prevents crash on Blender 4.4.0.
|
||||
bl_description = "Save active Brick project by the provided filepath."
|
||||
|
||||
filename_ext = ".ttl"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ttl", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
should_save_as: bpy.props.BoolProperty(name="Should Save As", default=False, options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.should_save_as or not BrickStore.path:
|
||||
return ExportHelper.invoke(self, context, event)
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
@@ -273,7 +273,7 @@ class AddBrickNamespace(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_description = "Bind a new namespace to the Brick project"
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
alias = props.new_brick_namespace_alias
|
||||
uri = props.new_brick_namespace_uri
|
||||
core.add_brick_namespace(tool.Brick, alias=alias, uri=uri)
|
||||
@@ -288,6 +288,6 @@ class RemoveBrickRelation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
object: bpy.props.StringProperty(name="Object")
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Brick.get_brick_props()
|
||||
props = context.scene.BIMBrickProperties
|
||||
brick = props.bricks[props.active_brick_index]
|
||||
core.remove_brick_relation(tool.Brick, brick_uri=brick.uri, predicate=self.predicate, object=self.object)
|
||||
|
||||
@@ -33,7 +33,6 @@ from bpy.props import (
|
||||
import bonsai.core.brick as core
|
||||
import bonsai.tool.brick as tool
|
||||
from bonsai.tool.brick import BrickStore
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
def update_active_brick_index(self, context):
|
||||
@@ -47,41 +46,34 @@ def get_libraries(self, context):
|
||||
|
||||
|
||||
def get_namespaces(self, context):
|
||||
global NAMESPACES_ENUM_ITEMS
|
||||
NAMESPACES_ENUM_ITEMS = [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
||||
return NAMESPACES_ENUM_ITEMS
|
||||
return [(uri, f"{alias}: {uri}", "") for alias, uri in BrickStore.namespaces]
|
||||
|
||||
|
||||
def get_brick_entity_classes(self, context):
|
||||
global ENTITY_CLASSES_ENUM_ITEMS
|
||||
entity = self.brick_entity_create_type
|
||||
ENTITY_CLASSES_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
||||
return ENTITY_CLASSES_ENUM_ITEMS
|
||||
return [(uri, uri.split("#")[-1], "") for uri in BrickStore.entity_classes[entity]]
|
||||
|
||||
|
||||
def get_brick_roots(self, context):
|
||||
global BRICK_ROOTS_ENUM_ITEMS
|
||||
BRICK_ROOTS_ENUM_ITEMS = [(root, root, "") for root in BrickStore.root_classes]
|
||||
return BRICK_ROOTS_ENUM_ITEMS
|
||||
return [(root, root, "") for root in BrickStore.root_classes]
|
||||
|
||||
|
||||
def get_brick_relations(self, context):
|
||||
global BRICK_RELATIONS_ENUM_ITEMS
|
||||
BRICK_RELATIONS_ENUM_ITEMS = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
||||
relations = [(uri, uri.split("#")[-1], "") for uri in BrickStore.relationships]
|
||||
for relation in BrickschemaData.data["active_relations"]:
|
||||
if relation["predicate_name"] == "label":
|
||||
return BRICK_RELATIONS_ENUM_ITEMS
|
||||
BRICK_RELATIONS_ENUM_ITEMS.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return BRICK_RELATIONS_ENUM_ITEMS
|
||||
return relations
|
||||
relations.append(("http://www.w3.org/2000/01/rdf-schema#label", "label", ""))
|
||||
return relations
|
||||
|
||||
|
||||
def update_view(self: "BIMBrickProperties", context: bpy.types.Context) -> None:
|
||||
root = self.brick_list_root
|
||||
def update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=False)
|
||||
|
||||
|
||||
def split_screen_update_view(self: "BIMBrickProperties", context: bpy.types.Context) -> None:
|
||||
root = self.split_screen_brick_list_root
|
||||
def split_screen_update_view(self, context):
|
||||
root = context.scene.BIMBrickProperties.split_screen_brick_list_root
|
||||
core.set_brick_list_root(tool.Brick, brick_root=root, split_screen=True)
|
||||
|
||||
|
||||
@@ -91,11 +83,6 @@ class Brick(PropertyGroup):
|
||||
uri: StringProperty(name="URI")
|
||||
total_items: IntProperty(name="Total Items")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
label: str
|
||||
uri: str
|
||||
total_items: int
|
||||
|
||||
|
||||
class BIMBrickProperties(PropertyGroup):
|
||||
active_brick_class: StringProperty(name="Active Brick Class")
|
||||
@@ -130,28 +117,3 @@ class BIMBrickProperties(PropertyGroup):
|
||||
split_screen_brick_list_root: EnumProperty(
|
||||
name="Split Screen Brick List Root", items=get_brick_roots, update=split_screen_update_view
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
active_brick_class: str
|
||||
brick_breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
bricks: bpy.types.bpy_prop_collection_idprop[Brick]
|
||||
active_brick_index: int
|
||||
libraries: str
|
||||
set_list_root_toggled: bool
|
||||
brick_list_root: str
|
||||
namespace: str
|
||||
new_brick_namespace_alias: str
|
||||
new_brick_namespace_uri: str
|
||||
new_brick_label: str
|
||||
brick_entity_create_type: str
|
||||
brick_entity_class: str
|
||||
brick_create_relations_toggled: bool
|
||||
brick_edit_relations_toggled: bool
|
||||
new_brick_relation_type: str
|
||||
new_brick_relation_object: str
|
||||
split_screen_toggled: bool
|
||||
split_screen_bricks: bpy.types.bpy_prop_collection_idprop[Brick]
|
||||
split_screen_active_brick_index: int
|
||||
split_screen_active_brick_class: str
|
||||
split_screen_brick_breadcrumbs: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
split_screen_brick_list_root: str
|
||||
|
||||
@@ -45,7 +45,7 @@ class BIM_PT_brickschema_project_info(Panel):
|
||||
bl_parent_id = "BIM_PT_brickschema"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = tool.Brick.get_brick_props()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaData.data["is_loaded"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -87,7 +87,7 @@ class BIM_PT_brickschema_namespaces(Panel):
|
||||
return BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = tool.Brick.get_brick_props()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="Active Namespace:")
|
||||
@@ -122,7 +122,7 @@ class BIM_PT_brickschema_create_entity(Panel):
|
||||
return BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = tool.Brick.get_brick_props()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
# TO DO: hide this if selected entity already has a reference, or something similar
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
@@ -156,7 +156,7 @@ class BIM_PT_brickschema_viewport(Panel):
|
||||
return BrickStore.graph != None
|
||||
|
||||
def draw(self, context):
|
||||
self.props = tool.Brick.get_brick_props()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.column().alignment = "RIGHT"
|
||||
@@ -178,14 +178,7 @@ class BIM_PT_brickschema_viewport(Panel):
|
||||
|
||||
row = grid_left.row()
|
||||
BIM_UL_bricks.split_screen = False
|
||||
row.template_list(
|
||||
BIM_UL_bricks.__name__,
|
||||
BIM_UL_bricks.__name__ + "_bricks",
|
||||
self.props,
|
||||
"bricks",
|
||||
self.props,
|
||||
"active_brick_index",
|
||||
)
|
||||
row.template_list("BIM_UL_bricks", "", self.props, "bricks", self.props, "active_brick_index")
|
||||
|
||||
if self.props.split_screen_toggled:
|
||||
grid_right = grid.column(align=True)
|
||||
@@ -202,12 +195,7 @@ class BIM_PT_brickschema_viewport(Panel):
|
||||
row = grid_right.row()
|
||||
BIM_UL_bricks.split_screen = True
|
||||
row.template_list(
|
||||
BIM_UL_bricks.__name__,
|
||||
BIM_UL_bricks.__name__ + "_split_screen_bricks",
|
||||
self.props,
|
||||
"split_screen_bricks",
|
||||
self.props,
|
||||
"split_screen_active_brick_index",
|
||||
"BIM_UL_bricks", "", self.props, "split_screen_bricks", self.props, "split_screen_active_brick_index"
|
||||
)
|
||||
|
||||
if BrickschemaData.data["active_relations"]:
|
||||
@@ -304,7 +292,7 @@ class BIM_PT_ifc_brickschema_references(Panel):
|
||||
def draw(self, context):
|
||||
if not BrickschemaReferencesData.is_loaded:
|
||||
BrickschemaReferencesData.load()
|
||||
self.props = tool.Brick.get_brick_props()
|
||||
self.props = context.scene.BIMBrickProperties
|
||||
|
||||
if not BrickschemaReferencesData.data["is_loaded"]:
|
||||
row = self.layout.row()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bonsai.tool as tool
|
||||
from bpy.types import Panel, UIList
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class BIM_PT_bsdd(Panel):
|
||||
|
||||
@@ -25,7 +25,6 @@ import bonsai.tool as tool
|
||||
from mathutils import Vector, Matrix
|
||||
from math import pi, radians, sin, cos, sqrt
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Union
|
||||
|
||||
|
||||
messages = {
|
||||
@@ -128,10 +127,9 @@ class CadMitre(bpy.types.Operator):
|
||||
class CadFillet(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_fillet"
|
||||
bl_label = "CAD Fillet"
|
||||
bl_description = "Add fillet to the 2 selected edges."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=0, default=1)
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -144,15 +142,18 @@ class CadFillet(bpy.types.Operator):
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
obj = bpy.context.active_object
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||
mesh = obj.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_edges = [e for e in bm.edges if e.select]
|
||||
if len(selected_edges) != 2:
|
||||
self.report({"ERROR"}, "Exactly 2 edges should be selected.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Assume the user has selected two edges sharing a vert, but merge verts
|
||||
@@ -217,7 +218,6 @@ class CadFillet(bpy.types.Operator):
|
||||
class CadArcFrom2Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_2_points"
|
||||
bl_label = "CAD Arc from 2 Points"
|
||||
bl_description = "Add an arc to the active mesh based on 2 selected vertices."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
|
||||
@@ -234,7 +234,6 @@ class CadArcFrom2Points(bpy.types.Operator):
|
||||
if bpy.context.mode != "EDIT_MESH":
|
||||
return {"CANCELLED"}
|
||||
obj = bpy.context.active_object
|
||||
assert obj
|
||||
region = bpy.context.region
|
||||
region_3d = bpy.context.area.spaces.active.region_3d
|
||||
cursor = bpy.context.scene.cursor.location
|
||||
@@ -242,12 +241,10 @@ class CadArcFrom2Points(bpy.types.Operator):
|
||||
if not center:
|
||||
return {"CANCELLED"}
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, bpy.types.Mesh)
|
||||
mw = obj.matrix_world
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
selected_verts = [v for v in bm.verts if v.select]
|
||||
if len(selected_verts) != 2:
|
||||
self.report({"ERROR"}, "Exactly 2 vertices should be selected.")
|
||||
return {"CANCELLED"}
|
||||
v1 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[0].co)
|
||||
v2 = bpy_extras.view3d_utils.location_3d_to_region_2d(region, region_3d, mw @ selected_verts[1].co)
|
||||
@@ -281,7 +278,6 @@ class CadArcFrom3Points(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_arc_from_3_points"
|
||||
bl_label = "CAD Arc from 3 Points"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Create a points based arc from 3 selected points."
|
||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||
only_recalculate_center: bpy.props.BoolProperty(name="Only Recalculate Center", default=False)
|
||||
|
||||
@@ -344,12 +340,11 @@ class CadArcFrom3Points(bpy.types.Operator):
|
||||
class CadOffset(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_offset"
|
||||
bl_label = "CAD Offset"
|
||||
bl_description = "Copy selected mesh geometry at provided offset. Mesh copied based on the current viewport angle."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE")
|
||||
distance: bpy.props.FloatProperty(name="Distance", default=0.1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
def poll(self, context):
|
||||
return context.mode == "EDIT_MESH"
|
||||
|
||||
def draw(self, context):
|
||||
@@ -372,6 +367,9 @@ class CadOffset(bpy.types.Operator):
|
||||
# effect of converting an unclosed loop into a closed loop which is also
|
||||
# not what users expect.
|
||||
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.distance = self.distance * si_conversion
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
@@ -543,8 +541,7 @@ class CadOffset(bpy.types.Operator):
|
||||
class AddIfcCircle(bpy.types.Operator):
|
||||
bl_idname = "bim.add_ifccircle"
|
||||
bl_label = "Add IfcCircle"
|
||||
bl_description = "Add IfcCircle to the currently active mesh."
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.5, subtype="DISTANCE")
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.5)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -552,19 +549,18 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
self.obj = obj
|
||||
self.mesh = mesh
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
if self.has_selected_existing_circle(context):
|
||||
self.change_radius(context)
|
||||
else:
|
||||
self.create_circle(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def has_selected_existing_circle(self, context: bpy.types.Context) -> bool:
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
def has_selected_existing_circle(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
if len(verts) != 2:
|
||||
return False
|
||||
@@ -577,32 +573,32 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
for group in groups:
|
||||
if group in verts[0][deform_layer] and group in verts[1][deform_layer]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def change_radius(self, context: bpy.types.Object) -> None:
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
def change_radius(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
center = verts[0].co.lerp(verts[1].co, 0.5)
|
||||
verts[0].co = center + ((verts[0].co - center).normalized() * self.radius)
|
||||
verts[1].co = center + ((verts[1].co - center).normalized() * self.radius)
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
def create_circle(self, context: bpy.types.Context) -> None:
|
||||
obj = self.obj
|
||||
def create_circle(self, context):
|
||||
obj = context.active_object
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# The last group may be the result of a prior run of this operator.
|
||||
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
|
||||
if len(obj.vertex_groups):
|
||||
last_group = obj.vertex_groups[-1]
|
||||
verts_in_group = [v for v in self.mesh.vertices if last_group.index in [vg.group for vg in v.groups]]
|
||||
verts_in_group = [v for v in obj.data.vertices if last_group.index in [vg.group for vg in v.groups]]
|
||||
if "IFCCIRCLE" in last_group.name and len(verts_in_group) != 2:
|
||||
obj.vertex_groups.remove(last_group)
|
||||
group = obj.vertex_groups.new(name="IFCCIRCLE")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
bm.verts.layers.deform.verify()
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
@@ -619,15 +615,14 @@ class AddIfcCircle(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
|
||||
class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
bl_idname = "bim.add_ifcarcindex_fillet"
|
||||
bl_label = "Add Arc Index Fillet"
|
||||
bl_description = "Add a fillet for the selected vertices."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE")
|
||||
radius: bpy.props.FloatProperty(name="Radius", default=0.1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -640,19 +635,18 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
layout.prop(self, prop)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj and isinstance((mesh := obj.data), bpy.types.Mesh)
|
||||
self.obj = obj
|
||||
self.mesh = mesh
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.radius = self.radius * si_conversion
|
||||
|
||||
if self.has_selected_existing_arc(context):
|
||||
self.change_radius(context)
|
||||
else:
|
||||
return self.create_arc(context)
|
||||
self.create_arc(context)
|
||||
return {"FINISHED"}
|
||||
|
||||
def has_selected_existing_arc(self, context: bpy.types.Context) -> bool:
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
def has_selected_existing_arc(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
verts = [v for v in bm.verts if v.select and not v.hide]
|
||||
if len(verts) != 3:
|
||||
return False
|
||||
@@ -668,17 +662,15 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
return True
|
||||
except:
|
||||
pass # Potentially fail if the vert has been removed in the previous operation
|
||||
return False
|
||||
|
||||
def change_radius(self, context: bpy.types.Context) -> None:
|
||||
obj = self.obj
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
def change_radius(self, context):
|
||||
obj = context.active_object
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
edges = [e for e in bm.edges if e.select and not e.hide]
|
||||
|
||||
mid = list(set(edges[0].verts) & set(edges[1].verts))[0]
|
||||
v1 = edges[0].other_vert(mid)
|
||||
v2 = edges[1].other_vert(mid)
|
||||
assert v1 and v2
|
||||
center = tool.Cad.get_center_of_arc([v1.co, mid.co, v2.co])
|
||||
|
||||
if len(v1.link_edges) != 2 or len(v2.link_edges) != 2:
|
||||
@@ -686,7 +678,6 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
v3 = v1.link_edges[1].other_vert(v1) if mid in v1.link_edges[0].verts else v1.link_edges[0].other_vert(v1)
|
||||
v4 = v2.link_edges[1].other_vert(v2) if mid in v2.link_edges[0].verts else v2.link_edges[0].other_vert(v2)
|
||||
assert v3 and v4
|
||||
dir1 = (v3.co - v1.co).normalized()
|
||||
dir2 = (v4.co - v2.co).normalized()
|
||||
|
||||
@@ -707,10 +698,11 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
def create_arc(self, context):
|
||||
obj = bpy.context.active_object
|
||||
|
||||
def create_arc(self, context: bpy.types.Context) -> set[str]:
|
||||
obj = self.obj
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
# The last group may be the result of a prior run of this operator.
|
||||
# I tried looping through all groups but Blender group indices seem to behave unpredictably.
|
||||
@@ -722,7 +714,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
group = obj.vertex_groups.new(name="IFCARCINDEX")
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
|
||||
bm = bmesh.from_edit_mesh(self.mesh)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
bm.verts.layers.deform.verify()
|
||||
deform_layer = bm.verts.layers.deform.active
|
||||
@@ -741,7 +733,6 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
shared_vert = list(set(selected_edges[0].verts) & set(selected_edges[1].verts))[0]
|
||||
v1 = selected_edges[0].other_vert(shared_vert)
|
||||
v2 = selected_edges[1].other_vert(shared_vert)
|
||||
assert v1 and v2
|
||||
dir1 = (v1.co - shared_vert.co).normalized()
|
||||
dir2 = (v2.co - shared_vert.co).normalized()
|
||||
edge_angle = dir1.angle(dir2)
|
||||
@@ -775,8 +766,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
bmesh.update_edit_mesh(self.mesh)
|
||||
return {"FINISHED"}
|
||||
bmesh.update_edit_mesh(obj.data)
|
||||
|
||||
|
||||
class AlignViewToProfile(bpy.types.Operator):
|
||||
@@ -812,9 +802,8 @@ class AlignViewToProfile(bpy.types.Operator):
|
||||
class AddRectangle(bpy.types.Operator):
|
||||
bl_idname = "bim.add_rectangle"
|
||||
bl_label = "Add Rectangle"
|
||||
bl_description = "Add rectangle shape to the active mesh."
|
||||
x: bpy.props.FloatProperty(name="X", default=1, subtype="DISTANCE")
|
||||
y: bpy.props.FloatProperty(name="Y", default=1, subtype="DISTANCE")
|
||||
x: bpy.props.FloatProperty(name="X", default=1)
|
||||
y: bpy.props.FloatProperty(name="Y", default=1)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -822,8 +811,11 @@ class AddRectangle(bpy.types.Operator):
|
||||
return bool(obj) and obj.type == "MESH"
|
||||
|
||||
def execute(self, context):
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
self.x = self.x * si_conversion
|
||||
self.y = self.y * si_conversion
|
||||
|
||||
obj = context.active_object
|
||||
assert obj and isinstance(obj.data, bpy.types.Mesh)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
cursor = obj.matrix_world.inverted() @ context.scene.cursor.location
|
||||
new_verts = [
|
||||
|
||||
@@ -20,7 +20,6 @@ import bpy
|
||||
from bonsai.bim.module.model.data import AuthoringData
|
||||
from bpy.types import PropertyGroup
|
||||
from math import pi
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class BIMCadProperties(PropertyGroup):
|
||||
@@ -32,11 +31,4 @@ class BIMCadProperties(PropertyGroup):
|
||||
gable_roof_edge_angle: bpy.props.FloatProperty(
|
||||
name="Gable Roof Edge Angle", default=pi / 2, soft_min=0, soft_max=pi / 2, subtype="ANGLE"
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
resolution: int
|
||||
radius: float
|
||||
distance: float
|
||||
x: float
|
||||
y: float
|
||||
gable_roof_edge_angle: float
|
||||
gable_roof_separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
|
||||
|
||||
@@ -23,13 +23,26 @@ import bonsai.bim.module.type.prop as type_prop
|
||||
import ifcopenshell.util.unit
|
||||
from bpy.types import WorkSpaceTool
|
||||
from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData
|
||||
from functools import partial
|
||||
|
||||
|
||||
# TODO duplicate code in cad/workspace and model/workspace
|
||||
def check_display_mode():
|
||||
global display_mode
|
||||
try:
|
||||
theme = bpy.context.preferences.themes["Default"]
|
||||
text_color = theme.user_interface.wcol_menu_item.text
|
||||
if sum(text_color) < 2.6:
|
||||
display_mode = "lm"
|
||||
else:
|
||||
display_mode = "dm"
|
||||
except:
|
||||
display_mode = "dm"
|
||||
|
||||
|
||||
def load_custom_icons():
|
||||
global custom_icon_previews, display_mode
|
||||
global custom_icon_previews
|
||||
if display_mode is None:
|
||||
display_mode = tool.Blender.detect_icon_color_mode("user_interface.wcol_tool.text")
|
||||
check_display_mode()
|
||||
|
||||
icons_dir = os.path.join(os.path.dirname(__file__), "..", "..", "data", "icons")
|
||||
custom_icon_previews = bpy.utils.previews.new()
|
||||
@@ -69,15 +82,12 @@ class CadTool(WorkSpaceTool):
|
||||
("bim.cad_hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}),
|
||||
)
|
||||
|
||||
def draw_settings(
|
||||
context: bpy.types.Context, layout: bpy.types.UILayout, workspace_tool: bpy.types.WorkSpaceTool
|
||||
) -> None:
|
||||
def draw_settings(context, layout, workspace_tool):
|
||||
ui_context = str(context.region.type)
|
||||
obj = context.active_object
|
||||
if not obj or not (data := obj.data):
|
||||
if not obj or not obj.data:
|
||||
return
|
||||
is_profile = tool.Geometry.is_profile_object_active()
|
||||
if is_profile:
|
||||
if hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
if element.is_a("IfcProfileDef"):
|
||||
@@ -112,25 +122,20 @@ class CadTool(WorkSpaceTool):
|
||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
||||
)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_V", "Fillet", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", "Offset", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Rectangle", "S_R", bpy.ops.bim.add_rectangle.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Rectangle", "S_R", "Rectangle", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Circle", "S_C", bpy.ops.bim.add_ifccircle.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Circle", "S_C", "Circle", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.set_arc_index.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", "3-Point Arc", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", "Reset Vertex", ui_context)
|
||||
|
||||
elif (
|
||||
isinstance(data, tool.Geometry.TYPES_WITH_MESH_PROPERTIES)
|
||||
and tool.Geometry.get_mesh_props(data).subshape_type == "AXIS"
|
||||
):
|
||||
add_header_apply_button(
|
||||
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
|
||||
)
|
||||
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
add_header_apply_button(layout, "Edit Axis", "bim.set_arc_index", "bim.set_arc_index", ui_context)
|
||||
row = layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Extend", "S_E", "Extends/reduces element to 3D cursor", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
@@ -138,15 +143,15 @@ class CadTool(WorkSpaceTool):
|
||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
||||
)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.cad_fillet.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_F", "Fillet", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", "Offset", ui_context)
|
||||
|
||||
else:
|
||||
if (
|
||||
(RailingData.is_loaded or not RailingData.load())
|
||||
and RailingData.data["pset_data"]
|
||||
and tool.Model.get_railing_props(obj).is_editing_path
|
||||
and context.active_object.BIMRailingProperties.is_editing_path
|
||||
):
|
||||
add_header_apply_button(
|
||||
layout,
|
||||
@@ -159,7 +164,7 @@ class CadTool(WorkSpaceTool):
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and tool.Model.get_roof_props(obj).is_editing_path
|
||||
and context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
add_header_apply_button(
|
||||
layout, "Edit Roof Path", "bim.finish_editing_roof_path", "bim.cancel_editing_roof_path", ui_context
|
||||
@@ -174,19 +179,19 @@ class CadTool(WorkSpaceTool):
|
||||
row, "Join", "S_T", "Joins two non-parallel paths at their intersection", ui_context
|
||||
)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_F", bpy.ops.bim.add_ifcarcindex_fillet.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Fillet", "S_V", "Fillet", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", bpy.ops.bim.cad_offset.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "Offset", "S_O", "Offset", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "2-Point Arc", "S_C", bpy.ops.bim.cad_arc_from_2_points.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "2-Point Arc", "S_C", "2-Point Arc", ui_context)
|
||||
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", bpy.ops.bim.cad_arc_from_3_points.__doc__, ui_context)
|
||||
add_layout_hotkey_operator(row, "3-Point Arc", "S_V", "3-Point Arc", ui_context)
|
||||
|
||||
|
||||
class CadHotkey(bpy.types.Operator):
|
||||
bl_idname = "bim.cad_hotkey"
|
||||
bl_label = ""
|
||||
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
||||
bl_label = "CAD Hotkey"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
hotkey: bpy.props.StringProperty()
|
||||
description: bpy.props.StringProperty()
|
||||
|
||||
@@ -195,21 +200,19 @@ class CadHotkey(bpy.types.Operator):
|
||||
return operator.description or ""
|
||||
|
||||
def execute(self, context):
|
||||
self.props = tool.Cad.get_cad_props()
|
||||
self.props = context.scene.BIMCadProperties
|
||||
getattr(self, f"hotkey_{self.hotkey}")()
|
||||
return {"FINISHED"}
|
||||
|
||||
def draw(self, context):
|
||||
props = tool.Cad.get_cad_props()
|
||||
obj = context.active_object
|
||||
|
||||
props = context.scene.BIMCadProperties
|
||||
if self.hotkey == "S_C":
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
if self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "radius")
|
||||
|
||||
elif self.hotkey == "S_F":
|
||||
if not tool.Geometry.is_profile_object_active():
|
||||
if not self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "resolution")
|
||||
row = self.layout.row()
|
||||
@@ -220,7 +223,7 @@ class CadHotkey(bpy.types.Operator):
|
||||
row.prop(props, "distance")
|
||||
|
||||
elif self.hotkey == "S_R":
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
if self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "x")
|
||||
row = self.layout.row()
|
||||
@@ -228,18 +231,20 @@ class CadHotkey(bpy.types.Operator):
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and tool.Model.get_roof_props(obj).is_editing_path
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
self.layout.row().prop(props, "gable_roof_edge_angle")
|
||||
self.layout.row().prop(props, "gable_roof_separate_verts")
|
||||
|
||||
elif self.hotkey == "S_V":
|
||||
if not tool.Geometry.is_profile_object_active():
|
||||
if not self.is_profile():
|
||||
row = self.layout.row()
|
||||
row.prop(props, "resolution")
|
||||
|
||||
def hotkey_S_C(self):
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_2_points()
|
||||
|
||||
@@ -247,69 +252,65 @@ class CadHotkey(bpy.types.Operator):
|
||||
bpy.ops.bim.cad_trim_extend()
|
||||
|
||||
def hotkey_S_F(self):
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.add_ifcarcindex_fillet(radius=self.props.radius / si_conversion)
|
||||
else:
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius)
|
||||
bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius / si_conversion)
|
||||
|
||||
def hotkey_S_O(self):
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance)
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.cad_offset(distance=self.props.distance / si_conversion)
|
||||
|
||||
def hotkey_S_Q(self):
|
||||
obj = bpy.context.active_object
|
||||
|
||||
if not obj:
|
||||
return
|
||||
|
||||
if not tool.Geometry.has_mesh_properties(data := obj.data):
|
||||
return
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
|
||||
mprops = tool.Geometry.get_mesh_props(data)
|
||||
if mprops.subshape_type == "PROFILE":
|
||||
element = tool.Ifc.get_entity(bpy.context.active_object)
|
||||
if bpy.context.active_object.data.BIMMeshProperties.subshape_type == "PROFILE":
|
||||
if element.is_a("IfcProfileDef"):
|
||||
bpy.ops.bim.edit_arbitrary_profile()
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
bpy.ops.bim.edit_boundary_geometry()
|
||||
else:
|
||||
bpy.ops.bim.edit_extrusion_profile()
|
||||
elif mprops.subshape_type == "AXIS":
|
||||
elif bpy.context.active_object.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
bpy.ops.bim.edit_extrusion_axis()
|
||||
|
||||
def hotkey_S_R(self):
|
||||
obj = bpy.context.active_object
|
||||
if not obj:
|
||||
return
|
||||
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x, y=self.props.y)
|
||||
if self.is_profile():
|
||||
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
bpy.ops.bim.add_rectangle(x=self.props.x / si_conversion, y=self.props.y / si_conversion)
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
and tool.Model.get_roof_props(obj).is_editing_path
|
||||
and bpy.context.active_object.BIMRoofProperties.is_editing_path
|
||||
):
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(angle=self.props.gable_roof_edge_angle)
|
||||
bpy.ops.bim.set_gable_roof_edge_angle(
|
||||
angle=self.props.gable_roof_edge_angle, separate_verts=self.props.gable_roof_separate_verts
|
||||
)
|
||||
|
||||
def hotkey_S_T(self):
|
||||
bpy.ops.bim.cad_mitre()
|
||||
|
||||
def hotkey_S_V(self):
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.set_arc_index()
|
||||
else:
|
||||
bpy.ops.bim.cad_arc_from_3_points(resolution=self.props.resolution)
|
||||
|
||||
def hotkey_S_X(self):
|
||||
if tool.Geometry.is_profile_object_active():
|
||||
if self.is_profile():
|
||||
bpy.ops.bim.reset_vertex()
|
||||
|
||||
def is_profile(self):
|
||||
obj = bpy.context.active_object
|
||||
return (
|
||||
obj
|
||||
and obj.data
|
||||
and hasattr(obj.data, "BIMMeshProperties")
|
||||
and obj.data.BIMMeshProperties.subshape_type == "PROFILE"
|
||||
)
|
||||
|
||||
def add_header_apply_button(
|
||||
layout: bpy.types.UILayout, text: str, apply_operator: str, cancel_operator: str, ui_context: str = ""
|
||||
) -> None:
|
||||
|
||||
def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_context=""):
|
||||
custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id
|
||||
row = layout.row(align=True)
|
||||
row.label(text=f"{text} Mode", icon_value=custom_icon)
|
||||
@@ -328,8 +329,32 @@ def add_header_apply_button(
|
||||
row.label(text="Tools")
|
||||
|
||||
|
||||
add_layout_hotkey_operator = partial(tool.Blender.add_layout_hotkey_operator, tool_name="cad", module_name=__name__)
|
||||
def add_layout_hotkey_operator(layout, text, hotkey, description, ui_context=""):
|
||||
parts = hotkey.split("_")
|
||||
modifier, key = parts
|
||||
op_text = "" if ui_context == "TOOL_HEADER" else text
|
||||
custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id
|
||||
modifier_icon, modifier_str = MODIFIERS.get(modifier, ("NONE", ""))
|
||||
|
||||
row = layout if ui_context == "TOOL_HEADER" else layout.row(align=True)
|
||||
op = row.operator("bim.cad_hotkey", text=op_text, icon_value=custom_icon)
|
||||
|
||||
if ui_context != "TOOL_HEADER" and len(parts) == 2:
|
||||
layout = layout.row(align=True) # Create a new line for hotkey display
|
||||
layout.label(text="", icon=modifier_icon)
|
||||
layout.label(text="", icon=f"EVENT_{key}")
|
||||
|
||||
hotkey_description = f"Hotkey: {modifier_str} {key}"
|
||||
description = "\n\n".join(filter(None, [hotkey_description if description else ""]))
|
||||
op.hotkey = hotkey
|
||||
op.description = description or hotkey_description
|
||||
|
||||
return op
|
||||
|
||||
|
||||
custom_icon_previews = None
|
||||
display_mode = None
|
||||
|
||||
MODIFIERS = {
|
||||
"S": ("EVENT_SHIFT", "⇧"),
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ class ClashDecorator:
|
||||
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)
|
||||
@@ -62,9 +60,8 @@ class ClashDecorator:
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
special_elements_color = self.addon_prefs.decorator_color_special
|
||||
|
||||
props = tool.Clash.get_clash_props()
|
||||
text = props.active_clash_text
|
||||
p = props.p1.lerp(props.p2, 0.5)
|
||||
text = context.scene.BIMClashProperties.active_clash_text
|
||||
p = context.scene.BIMClashProperties.p1.lerp(context.scene.BIMClashProperties.p2, 0.5)
|
||||
|
||||
font_id = 0
|
||||
blf.size(font_id, 12)
|
||||
@@ -95,8 +92,7 @@ class ClashDecorator:
|
||||
# general shader
|
||||
self.shader = gpu.shader.from_builtin("UNIFORM_COLOR")
|
||||
|
||||
props = tool.Clash.get_clash_props()
|
||||
selected_vertices = [props.p1, props.p2]
|
||||
selected_vertices = [context.scene.BIMClashProperties.p1, context.scene.BIMClashProperties.p2]
|
||||
selected_edges = []
|
||||
if selected_vertices[0] != selected_vertices[1]:
|
||||
selected_edges = [[0, 1]]
|
||||
|
||||
@@ -24,20 +24,26 @@ import logging
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import bonsai.tool as tool
|
||||
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
||||
from math import radians
|
||||
from mathutils import Matrix, Vector
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.clash.decorator import ClashDecorator
|
||||
|
||||
|
||||
class ExportClashSets(bpy.types.Operator, ExportHelper):
|
||||
class ExportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.export_clash_sets"
|
||||
bl_label = "Export Clash Sets"
|
||||
bl_description = "Export clash sets to a selected file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
clash_sets = tool.Clash.export_clash_sets()
|
||||
@@ -46,24 +52,26 @@ class ExportClashSets(bpy.types.Operator, ExportHelper):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ImportClashSets(bpy.types.Operator, ImportHelper):
|
||||
class ImportClashSets(bpy.types.Operator):
|
||||
bl_idname = "bim.import_clash_sets"
|
||||
bl_label = "Import Clash Sets"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Import clash sets from a selected file"
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
return ImportHelper.invoke(self, context, event)
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
tool.Clash.load_clash_sets(self.filepath)
|
||||
props = tool.Clash.get_clash_props()
|
||||
props.clash_sets.clear()
|
||||
context.scene.BIMClashProperties.clash_sets.clear()
|
||||
for clash_set in tool.Clash.get_clash_sets():
|
||||
new = props.clash_sets.add()
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = clash_set["name"]
|
||||
new.mode = clash_set["mode"]
|
||||
if new.mode == "intersection":
|
||||
@@ -98,8 +106,7 @@ class AddClashSet(bpy.types.Operator):
|
||||
bl_description = "Add a clash set"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
new = props.clash_sets.add()
|
||||
new = context.scene.BIMClashProperties.clash_sets.add()
|
||||
new.name = "New Clash Set"
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -112,8 +119,7 @@ class RemoveClashSet(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
props.clash_sets.remove(self.index)
|
||||
context.scene.BIMClashProperties.clash_sets.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -125,8 +131,7 @@ class AddClashSource(bpy.types.Operator):
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
clash_set = context.scene.BIMClashProperties.active_clash_set
|
||||
source = getattr(clash_set, self.group).add()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -140,74 +145,82 @@ class RemoveClashSource(bpy.types.Operator):
|
||||
group: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
clash_set = context.scene.BIMClashProperties.active_clash_set
|
||||
getattr(clash_set, self.group).remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectClashSource(bpy.types.Operator, ImportHelper):
|
||||
class SelectClashSource(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_source"
|
||||
bl_label = "Select Clash Source"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select an IFC file to add as a clash source"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc", options={"HIDDEN"})
|
||||
index: bpy.props.IntProperty()
|
||||
group: bpy.props.StringProperty()
|
||||
filename_ext = ".ifc"
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
clash_set = props.active_clash_set
|
||||
clash_set = context.scene.BIMClashProperties.active_clash_set
|
||||
getattr(clash_set, self.group)[self.index].name = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
class SelectClashResults(bpy.types.Operator, ImportHelper):
|
||||
|
||||
class SelectClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_clash_results"
|
||||
bl_label = "Select Clash Results"
|
||||
bl_description = "Select filepath for clash results."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
props.clash_results_path = self.filepath
|
||||
context.scene.BIMClashProperties.clash_results_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
class SelectSmartGroupedClashesPath(bpy.types.Operator, ImportHelper):
|
||||
|
||||
class SelectSmartGroupedClashesPath(bpy.types.Operator):
|
||||
bl_idname = "bim.select_smart_grouped_clashes_path"
|
||||
bl_label = "Select Smart-Grouped Clashes Path"
|
||||
bl_description = "Select filepath for smart-grouped clashes."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
props.smart_grouped_clashes_path = self.filepath
|
||||
context.scene.BIMClashProperties.smart_grouped_clashes_path = self.filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
|
||||
class ExecuteIfcClash(bpy.types.Operator):
|
||||
bl_idname = "bim.execute_ifc_clash"
|
||||
bl_label = "Execute IFC Clash"
|
||||
bl_description = "Execute clash detection and save the information to a .bcf or .json file"
|
||||
filter_glob: bpy.props.StringProperty(default="*.bcf;*.json", options={"HIDDEN"})
|
||||
format: bpy.props.EnumProperty(name="Format", items=[(i, i, "") for i in ("bcf", "json")])
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
|
||||
|
||||
@property
|
||||
def filename_ext(self) -> str:
|
||||
return f".{self.format.lower()}"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
if self.filepath:
|
||||
return self.execute(context)
|
||||
return ExportHelper.invoke(self, context, event)
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".bcf":
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".bcf")
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
from ifcclash import ifcclash
|
||||
|
||||
self.props = tool.Clash.get_clash_props()
|
||||
self.props = context.scene.BIMClashProperties
|
||||
|
||||
_, extension = os.path.splitext(self.filepath)
|
||||
if extension != ".bcf":
|
||||
@@ -216,7 +229,6 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
if extension != ".json":
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".bcf")
|
||||
|
||||
self.props.export_path = self.filepath
|
||||
settings = ifcclash.ClashSettings()
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
@@ -257,7 +269,7 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
context.scene.render.resolution_x = 480
|
||||
context.scene.render.resolution_y = 270
|
||||
context.scene.render.image_settings.file_format = "PNG"
|
||||
context.scene.render.filepath = tool.Blender.get_data_dir_path("shapshot.png").__str__()
|
||||
context.scene.render.filepath = os.path.join(context.scene.BIMProperties.data_dir, "snapshot.png")
|
||||
bpy.ops.render.opengl(write_still=True)
|
||||
with open(context.scene.render.filepath, "rb") as f:
|
||||
return ("snapshot.png", f.read())
|
||||
@@ -275,27 +287,27 @@ class ExecuteIfcClash(bpy.types.Operator, ExportHelper):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectIfcClashResults(bpy.types.Operator, ImportHelper):
|
||||
class SelectIfcClashResults(bpy.types.Operator):
|
||||
bl_idname = "bim.select_ifc_clash_results"
|
||||
bl_label = "Select IFC Clash Results"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = "Select the clashing IFC geometry stored in a file"
|
||||
filter_glob: bpy.props.StringProperty(default="*.json", options={"HIDDEN"})
|
||||
filename_ext = ".json"
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = bpy.path.ensure_ext(bpy.data.filepath, ".json")
|
||||
return ImportHelper.invoke(self, context, event)
|
||||
WindowManager = context.window_manager
|
||||
WindowManager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def execute(self, context):
|
||||
# TODO refactor into new clash results system
|
||||
self.file = tool.Ifc.get()
|
||||
self.file = IfcStore.get_file()
|
||||
self.filepath = bpy.path.ensure_ext(self.filepath, ".json")
|
||||
with open(self.filepath) as f:
|
||||
clash_sets = json.load(f)
|
||||
clash_props = tool.Clash.get_clash_props()
|
||||
assert clash_props.active_clash_set
|
||||
clash_set_name = clash_props.active_clash_set.name
|
||||
clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
|
||||
global_ids = []
|
||||
for clash_set in clash_sets:
|
||||
if clash_set["name"] != clash_set_name:
|
||||
@@ -307,15 +319,13 @@ class SelectIfcClashResults(bpy.types.Operator, ImportHelper):
|
||||
global_ids.extend([clash["a_global_id"], clash["b_global_id"]])
|
||||
|
||||
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
|
||||
|
||||
ifc_file = ""
|
||||
for scene in obj.users_scene:
|
||||
bim_props = tool.Blender.get_bim_props(scene)
|
||||
if bim_props.ifc_file:
|
||||
ifc_file = bim_props.ifc_file
|
||||
if scene.BIMProperties.ifc_file:
|
||||
ifc_file = scene.BIMProperties.ifc_file
|
||||
if scene.library:
|
||||
break
|
||||
|
||||
@@ -327,7 +337,7 @@ class SelectIfcClashResults(bpy.types.Operator, ImportHelper):
|
||||
element_file = self.file
|
||||
|
||||
try:
|
||||
element = element_file.by_id(props.ifc_definition_id)
|
||||
element = element_file.by_id(obj.BIMObjectProperties.ifc_definition_id)
|
||||
except:
|
||||
continue
|
||||
|
||||
@@ -347,7 +357,7 @@ class SelectClash(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
self.props = tool.Clash.get_clash_props()
|
||||
self.props = context.scene.BIMClashProperties
|
||||
clash_set = tool.Clash.get_clash_set(self.props.active_clash_set.name)
|
||||
active_clash = self.props.active_clash
|
||||
clash = tool.Clash.get_clash(clash_set, active_clash.a_global_id, active_clash.b_global_id)
|
||||
@@ -381,15 +391,13 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
return bool(props.clash_results_path)
|
||||
return context.scene.BIMClashProperties.clash_results_path
|
||||
|
||||
def execute(self, context):
|
||||
from ifcclash import ifcclash
|
||||
|
||||
settings = ifcclash.ClashSettings()
|
||||
props = tool.Clash.get_clash_props()
|
||||
self.filepath = bpy.path.ensure_ext(props.clash_results_path, ".json")
|
||||
self.filepath = bpy.path.ensure_ext(context.scene.BIMClashProperties.clash_results_path, ".json")
|
||||
settings.output = self.filepath
|
||||
settings.logger = logging.getLogger("Clash")
|
||||
settings.logger.setLevel(logging.DEBUG)
|
||||
@@ -399,18 +407,19 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
clash_sets = json.load(f)
|
||||
|
||||
# execute the smart grouping
|
||||
save_path = bpy.path.ensure_ext(props.smart_grouped_clashes_path, ".json")
|
||||
smart_grouped_clashes = ifc_clasher.smart_group_clashes(clash_sets, props.smart_clash_grouping_max_distance)
|
||||
save_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
smart_grouped_clashes = ifc_clasher.smart_group_clashes(
|
||||
clash_sets, context.scene.BIMClashProperties.smart_clash_grouping_max_distance
|
||||
)
|
||||
|
||||
# save smart_groups to json
|
||||
with open(save_path, "w") as f:
|
||||
f.write(json.dumps(smart_grouped_clashes))
|
||||
|
||||
assert props.active_clash_set
|
||||
clash_set_name = props.active_clash_set.name
|
||||
clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
props.smart_clash_groups.clear()
|
||||
context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
@@ -418,7 +427,7 @@ class SmartClashGroup(bpy.types.Operator):
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = props.smart_clash_groups.add()
|
||||
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
|
||||
for pair in global_id_pairs:
|
||||
@@ -436,21 +445,18 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
return bool(props.active_clash_set)
|
||||
return context.scene.BIMClashProperties.active_clash_set
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
smart_groups_path = bpy.path.ensure_ext(props.smart_grouped_clashes_path, ".json")
|
||||
smart_groups_path = bpy.path.ensure_ext(context.scene.BIMClashProperties.smart_grouped_clashes_path, ".json")
|
||||
|
||||
assert props.active_clash_set
|
||||
clash_set_name = props.active_clash_set.name
|
||||
clash_set_name = context.scene.BIMClashProperties.active_clash_set.name
|
||||
|
||||
with open(smart_groups_path) as f:
|
||||
smart_grouped_clashes = json.load(f)
|
||||
|
||||
# Reset the list of smart_clash_groups for the UI
|
||||
props.smart_clash_groups.clear()
|
||||
context.scene.BIMClashProperties.smart_clash_groups.clear()
|
||||
|
||||
for clash_set, smart_groups in smart_grouped_clashes.items():
|
||||
# Only select the clashes that correspond to the actively selected IFC Clash Set
|
||||
@@ -458,12 +464,12 @@ class LoadSmartGroupsForActiveClashSet(bpy.types.Operator):
|
||||
continue
|
||||
else:
|
||||
for smart_group, global_id_pairs in smart_groups[0].items():
|
||||
new_group = props.smart_clash_groups.add()
|
||||
new_group = context.scene.BIMClashProperties.smart_clash_groups.add()
|
||||
new_group.number = f"{smart_group}"
|
||||
for pair in global_id_pairs:
|
||||
for guid in pair:
|
||||
new_global_id = new_group.global_ids.add()
|
||||
new_global_id.name = guid
|
||||
new_global_id.guid = guid
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -475,19 +481,15 @@ class SelectSmartGroup(bpy.types.Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = tool.Clash.get_clash_props()
|
||||
return tool.Ifc.get() and context.visible_objects and props.active_smart_group
|
||||
return IfcStore.get_file() and context.visible_objects and context.scene.BIMClashProperties.active_smart_group
|
||||
|
||||
def execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
props = tool.Clash.get_clash_props()
|
||||
selected_smart_group = props.active_smart_group
|
||||
assert selected_smart_group
|
||||
products: list[ifcopenshell.entity_instance] = []
|
||||
selected_smart_group = context.scene.BIMClashProperties.active_smart_group
|
||||
products = []
|
||||
for global_id in selected_smart_group.global_ids:
|
||||
try:
|
||||
products.append(ifc_file.by_guid(global_id.name))
|
||||
except RuntimeError:
|
||||
products.append(tool.Ifc.get().by_guid(global_id.guid))
|
||||
except:
|
||||
continue
|
||||
tool.Spatial.select_products(products, unhide=True)
|
||||
context_override = tool.Blender.get_viewport_context()
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.prop import StrProperty, Attribute, BIMFilterGroup
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
@@ -30,8 +29,6 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from mathutils import Vector
|
||||
from typing import TYPE_CHECKING, Literal, Union
|
||||
|
||||
|
||||
class ClashSource(PropertyGroup):
|
||||
@@ -46,10 +43,6 @@ class ClashSource(PropertyGroup):
|
||||
name="Mode",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
|
||||
mode: Literal["a", "i", "e"]
|
||||
|
||||
|
||||
class Clash(PropertyGroup):
|
||||
a_global_id: StringProperty(name="A")
|
||||
@@ -58,15 +51,9 @@ class Clash(PropertyGroup):
|
||||
b_name: StringProperty(name="B Name")
|
||||
status: BoolProperty(name="Status", default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
a_global_id: str
|
||||
b_global_id: str
|
||||
a_name: str
|
||||
b_name: str
|
||||
status: bool
|
||||
|
||||
|
||||
class ClashSet(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
mode: EnumProperty(
|
||||
items=[
|
||||
(
|
||||
@@ -89,25 +76,11 @@ class ClashSet(PropertyGroup):
|
||||
b: CollectionProperty(name="Group B", type=ClashSource)
|
||||
clashes: CollectionProperty(name="Clashes", type=Clash)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
mode: Literal["intersection", "collision", "clearance"]
|
||||
tolerance: float
|
||||
clearance: float
|
||||
allow_touching: bool
|
||||
check_all: bool
|
||||
a: bpy.types.bpy_prop_collection_idprop[ClashSource]
|
||||
b: bpy.types.bpy_prop_collection_idprop[ClashSource]
|
||||
clashes: bpy.types.bpy_prop_collection_idprop[Clash]
|
||||
|
||||
|
||||
class SmartClashGroup(PropertyGroup):
|
||||
number: StringProperty(name="Number")
|
||||
global_ids: CollectionProperty(name="GlobalIDs", type=StrProperty)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
number: str
|
||||
global_ids: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
|
||||
|
||||
class BIMClashProperties(PropertyGroup):
|
||||
blender_clash_set_a: CollectionProperty(name="Blender Clash Set A", type=StrProperty)
|
||||
@@ -128,39 +101,18 @@ class BIMClashProperties(PropertyGroup):
|
||||
p1: FloatVectorProperty(name="P1", default=(0.0, 0.0, 0.0), subtype="XYZ")
|
||||
p2: FloatVectorProperty(name="P2", default=(0.0, 0.0, 0.0), subtype="XYZ")
|
||||
active_clash_text: StringProperty(name="Active Clash Text")
|
||||
export_path: StringProperty(
|
||||
name="Export Path",
|
||||
description=".bcf or .json file to export the clash results to",
|
||||
subtype="FILE_PATH",
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
blender_clash_set_a: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
blender_clash_set_b: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
clash_sets: bpy.types.bpy_prop_collection_idprop[ClashSet]
|
||||
should_create_clash_snapshots: bool
|
||||
clash_results_path: str
|
||||
smart_grouped_clashes_path: str
|
||||
active_clash_set_index: int
|
||||
active_clash_index: int
|
||||
smart_clash_groups: bpy.types.bpy_prop_collection_idprop[SmartClashGroup]
|
||||
active_smart_group_index: int
|
||||
smart_clash_grouping_max_distance: int
|
||||
p1: Vector
|
||||
p2: Vector
|
||||
active_clash_text: str
|
||||
export_path: str
|
||||
|
||||
@property
|
||||
def active_clash_set(self) -> Union[ClashSet, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.clash_sets, self.active_clash_set_index)
|
||||
def active_clash_set(self):
|
||||
if self.active_clash_set_index < len(self.clash_sets):
|
||||
return self.clash_sets[self.active_clash_set_index]
|
||||
|
||||
@property
|
||||
def active_smart_group(self) -> Union[SmartClashGroup, None]:
|
||||
return tool.Blender.get_active_uilist_element(self.smart_clash_groups, self.active_smart_group_index)
|
||||
def active_smart_group(self):
|
||||
if self.active_smart_group_index < len(self.smart_clash_groups):
|
||||
return self.smart_clash_groups[self.active_smart_group_index]
|
||||
|
||||
@property
|
||||
def active_clash(self) -> Union[Clash, None]:
|
||||
if not (clash_set := self.active_clash_set):
|
||||
return None
|
||||
return tool.Blender.get_active_uilist_element(clash_set.clashes, self.active_clash_index)
|
||||
def active_clash(self):
|
||||
if self.active_clash_index < len(self.active_clash_set.clashes):
|
||||
return self.active_clash_set.clashes[self.active_clash_index]
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bpy.types import Panel
|
||||
from bonsai.bim.module.clash.data import ClashData
|
||||
@@ -37,7 +36,9 @@ class BIM_PT_ifcclash(Panel):
|
||||
ClashData.load()
|
||||
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
|
||||
scene = context.scene
|
||||
props = scene.BIMClashProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_clash_set")
|
||||
@@ -117,12 +118,8 @@ class BIM_PT_ifcclash(Panel):
|
||||
|
||||
row = layout.row()
|
||||
row.prop(props, "should_create_clash_snapshots")
|
||||
|
||||
layout.prop(props, "export_path")
|
||||
|
||||
row = layout.row()
|
||||
op = row.operator("bim.execute_ifc_clash")
|
||||
op.filepath = props.export_path
|
||||
row.operator("bim.execute_ifc_clash")
|
||||
|
||||
row = layout.row()
|
||||
row.label(text=f"{len(clash_set.clashes)} Clashes Found", icon="PIVOT_CURSOR")
|
||||
@@ -156,7 +153,7 @@ class BIM_PT_smart_clash_manager(Panel):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
props = tool.Clash.get_clash_props()
|
||||
props = context.scene.BIMClashProperties
|
||||
|
||||
row = layout.row()
|
||||
layout.label(text="Select clash results to group:")
|
||||
|
||||
@@ -118,7 +118,7 @@ class MaterialClassificationsData(ReferencesData):
|
||||
def references(cls):
|
||||
results = []
|
||||
|
||||
props = tool.Material.get_material_props()
|
||||
props = bpy.context.scene.BIMMaterialProperties
|
||||
if props.materials and props.active_material_index < len(props.materials):
|
||||
material = props.materials[props.active_material_index]
|
||||
if material.ifc_definition_id:
|
||||
@@ -145,8 +145,11 @@ class CostClassificationsData(ReferencesData):
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = []
|
||||
props = tool.Cost.get_cost_props()
|
||||
element = tool.Ifc.get().by_id(props.cost_items[props.active_cost_item_index].ifc_definition_id)
|
||||
element = tool.Ifc.get().by_id(
|
||||
bpy.context.scene.BIMCostProperties.cost_items[
|
||||
bpy.context.scene.BIMCostProperties.active_cost_item_index
|
||||
].ifc_definition_id
|
||||
)
|
||||
if element:
|
||||
for reference in ifcopenshell.util.classification.get_references(element):
|
||||
data = reference.get_info()
|
||||
|
||||
@@ -20,21 +20,23 @@ import bpy
|
||||
import json
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.classification
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
|
||||
|
||||
class LoadClassificationLibrary(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
class LoadClassificationLibrary(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.load_classification_library"
|
||||
bl_label = "Load Classification Library"
|
||||
bl_description = "Load classification library from the provided filepath."
|
||||
filename_ext = ".ifc"
|
||||
filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"})
|
||||
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def _execute(self, context):
|
||||
IfcStore.classification_file = ifcopenshell.open(self.filepath)
|
||||
@@ -198,8 +200,7 @@ class EnableEditingClassification(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
def callback(name, prop, data):
|
||||
if name == "ReferenceTokens":
|
||||
geo_props = tool.Georeference.get_georeference_props()
|
||||
new = geo_props.projected_crs.add()
|
||||
new = bpy.context.scene.BIMGeoreferenceProperties.projected_crs.add()
|
||||
new.name = name
|
||||
new.data_type = "string"
|
||||
new.is_null = data[name] is None
|
||||
@@ -237,7 +238,7 @@ class RemoveClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
classification: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
self.file = tool.Ifc.get()
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"classification.remove_classification",
|
||||
tool.Ifc.get(),
|
||||
@@ -252,18 +253,19 @@ class EditClassification(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationProperties
|
||||
|
||||
def callback(attributes, prop):
|
||||
if prop.name == "ReferenceTokens":
|
||||
attributes[prop.name] = json.loads(prop.string_value)
|
||||
return True
|
||||
|
||||
attributes = bonsai.bim.helper.export_attributes(props.classification_attributes, callback=callback)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.classification.edit_classification(
|
||||
ifc_file,
|
||||
classification=ifc_file.by_id(props.active_classification_id),
|
||||
attributes=attributes,
|
||||
attributes = {}
|
||||
for attribute in props.classification_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
elif attribute.name == "ReferenceTokens":
|
||||
attributes[attribute.name] = json.loads(attribute.string_value)
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_classification",
|
||||
self.file,
|
||||
**{"classification": self.file.by_id(props.active_classification_id), "attributes": attributes},
|
||||
)
|
||||
bpy.ops.bim.disable_editing_classification()
|
||||
|
||||
@@ -344,11 +346,17 @@ class EditClassificationReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMClassificationReferenceProperties
|
||||
attributes = bonsai.bim.helper.export_attributes(props.reference_attributes)
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifcopenshell.api.classification.edit_reference(
|
||||
ifc_file,
|
||||
reference=ifc_file.by_id(props.active_reference_id),
|
||||
attributes = {}
|
||||
for attribute in props.reference_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
attributes[attribute.name] = attribute.string_value
|
||||
self.file = IfcStore.get_file()
|
||||
ifcopenshell.api.run(
|
||||
"classification.edit_reference",
|
||||
self.file,
|
||||
reference=self.file.by_id(props.active_reference_id),
|
||||
attributes=attributes,
|
||||
)
|
||||
bpy.ops.bim.disable_editing_classification_reference()
|
||||
@@ -457,7 +465,6 @@ class AddClassificationReferenceFromBSDD(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for prop in blender_properties:
|
||||
properties[prop.name] = prop.get_value()
|
||||
|
||||
# TODO: is this still the correct approach?
|
||||
if classification_pset.name == "undefined_set":
|
||||
if "ObjectType" in properties:
|
||||
if hasattr(element, "ObjectType"):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user