Compare commits

..

2 Commits

Author SHA1 Message Date
Ryan Schultz cc90340b3b tweaked active operator 2024-10-26 14:22:18 -05:00
Ryan Schultz db075c7160 display the sum of all selected objects 2024-10-26 13:26:11 -05:00
694 changed files with 12910 additions and 30361 deletions
-20
View File
@@ -1,20 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Replace this text describing 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.
-10
View File
@@ -1,10 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
Replace this text and 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.
-138
View File
@@ -1,138 +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
brew link --overwrite python@3.12
brew link --overwrite python@3.13
# https://github.com/rust-lang/rustup/pull/3989/files
brew install --overwrite awscli
- name: Upload .zip archives to S3
run: |
aws s3 cp ~/output s3://ifcopenshell-builds/ --recursive
-54
View File
@@ -1,54 +0,0 @@
name: Build IfcOpenShell WASM / Pyodide
on:
workflow_dispatch:
jobs:
build_ifcopenshell:
runs-on: ubuntu-20.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"
-116
View File
@@ -1,116 +0,0 @@
name: Build IfcOpenShell Linux
on:
workflow_dispatch:
jobs:
build_ifcopenshell:
runs-on: ubuntu-20.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
-116
View File
@@ -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
-122
View File
@@ -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;
+12
View File
@@ -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
+20 -42
View File
@@ -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://ftp.nluug.nl/pub/graphics/blender/release/Blender4.3/blender-4.3.0-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
+3 -6
View File
@@ -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
@@ -127,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
@@ -135,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
-36
View File
@@ -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,48 +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'
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
-9
View File
@@ -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
-6
View File
@@ -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
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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"])
# 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
View File
@@ -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
View File
@@ -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']},
)
-2
View File
@@ -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 -25
View File
@@ -85,7 +85,7 @@ BLENDER_PLATFORM:=windows-x64
endif
# Current build commit hash.
OLD:=463289e
OLD:=0e5008d
.PHONY: bump
bump:
ifndef NEW
@@ -177,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
@@ -214,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
@@ -228,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
@@ -278,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,
@@ -300,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
@@ -344,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.
+2 -8
View File
@@ -317,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
@@ -325,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?")
+2
View File
@@ -0,0 +1,2 @@
# addon writes tmp stuff directly to its dir
/data/
+2 -17
View File
@@ -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:
@@ -98,25 +99,21 @@ 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.SelectDataDir,
operator.SelectCacheDir,
operator.SelectIfcFile,
operator.SelectSchemaDir,
operator.SelectURIAttribute,
@@ -138,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,
@@ -195,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():
@@ -229,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)
@@ -265,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

File diff suppressed because it is too large Load Diff
@@ -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.);
@@ -33,6 +33,6 @@ DATA;
#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.);
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.
@@ -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);
}
}
+15 -14
View File
@@ -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
@@ -137,6 +142,8 @@ class IfcExporter:
def sync_object_placement(self, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
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
@@ -144,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"):
+5 -36
View File
@@ -30,10 +30,6 @@ from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.owner.prop import get_user_person, get_user_organisation
from bonsai.bim.module.model.data import AuthoringData
from bonsai.bim.module.model.workspace import LIST_OF_TOOLS, TOOLS_TO_CLASSES_MAP
from bonsai.bim.module.aggregate.decorator import AggregateDecorator
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator
from bonsai.bim.module.nest.decorator import NestDecorator
from mathutils import Vector
from math import cos, degrees
from typing import Union, Callable
@@ -56,13 +52,9 @@ def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -
return
if isinstance(obj, bpy.types.Material):
props = obj.BIMStyleProperties
if ifc_definition_id := props.ifc_definition_id:
if props.is_renaming:
props.is_renmaing = False
return
if ifc_definition_id := obj.BIMStyleProperties.ifc_definition_id:
IfcStore.get_file().by_id(ifc_definition_id).Name = obj.name
refresh_ui_data()
refresh_ui_data()
return
if not obj.BIMObjectProperties.ifc_definition_id:
@@ -142,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
@@ -188,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:
@@ -316,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.
@@ -327,25 +318,3 @@ def load_post(scene):
if tool.Ifc.get() and bpy.data.is_saved:
bpy.context.scene.BIMProperties.has_blend_warning = True
# Bonsai overlays
georeference_props = bpy.context.scene.BIMGeoreferenceProperties
aggregate_props = bpy.context.scene.BIMAggregateProperties
nest_props = bpy.context.scene.BIMNestProperties
model_props = bpy.context.scene.BIMModelProperties
if georeference_props.should_visualise:
GeoreferenceDecorator.install(bpy.context)
if aggregate_props.aggregate_decorator:
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"}
+27 -78
View File
@@ -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
# 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,19 +62,20 @@ 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"):
elif attribute.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
propis = bpy.context.scene.BIMWorkScheduleProperties
for item in propis.durations_attributes:
if item.name == attribute.name:
@@ -92,30 +92,26 @@ 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:
@@ -126,7 +122,7 @@ def import_attributes(
# 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):
@@ -142,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:
@@ -151,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()
@@ -168,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(
@@ -256,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] = {}
@@ -271,25 +241,11 @@ 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,
):
@@ -302,7 +258,7 @@ 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
@@ -346,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
+10 -34
View File
@@ -33,7 +33,7 @@ import bonsai.bim.handler
import bonsai.tool as tool
from pathlib import Path
from bonsai.tool.brick import BrickStore
from typing import Set, Union, Optional, TypedDict, Callable, NotRequired, cast
from typing import Set, Union, Optional, TypedDict, Callable, NotRequired
IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
@@ -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,9 +101,8 @@ class IfcStore:
@staticmethod
def get_file():
if IfcStore.file is None:
IfcStore.path = cast(str, bpy.context.scene.BIMProperties.ifc_file)
# Interpret relative paths as relative to .blend file.
if IfcStore.path and not os.path.isabs(IfcStore.path):
IfcStore.path = 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:
@@ -121,8 +116,7 @@ class IfcStore:
if IfcStore.cache is None and IfcStore.path:
ifc_key = IfcStore.path + IfcStore.file.wrapped_data.header.file_name.time_stamp
ifc_hash = hashlib.md5(ifc_key.encode("utf-8")).hexdigest()
os.makedirs(bpy.context.scene.BIMProperties.cache_dir, exist_ok=True)
IfcStore.cache_path = os.path.join(bpy.context.scene.BIMProperties.cache_dir, f"{ifc_hash}.h5")
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()
@@ -162,7 +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()
new_cache_path = os.path.join(bpy.context.scene.BIMProperties.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)
@@ -301,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:
@@ -405,6 +378,9 @@ class IfcStore:
obj.BIMStyleProperties.ifc_definition_id = 0
else: # bpy.types.Object
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, is_invoke=False) -> set[str]:
+97 -70
View File
@@ -21,7 +21,6 @@ import bpy
import time
import json
import logging
import traceback
import mathutils
import numpy as np
import multiprocessing
@@ -34,6 +33,7 @@ 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
@@ -70,11 +70,8 @@ class MaterialCreator:
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"]
@@ -84,18 +81,6 @@ class MaterialCreator:
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.
context = tool.Ifc.get().by_id(self.mesh.BIMMeshProperties.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)
@@ -109,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:
@@ -209,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 = bpy.context.scene.DocProperties.classes_to_wireframe
self.classes_to_wireframe_list = [word.strip() for word in classes_to_wireframe_str.split(",")]
def profile_code(self, message: str) -> None:
if not self.time:
@@ -271,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:
@@ -348,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]
@@ -421,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,
@@ -430,6 +411,23 @@ 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:
@@ -535,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:
@@ -704,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)
@@ -808,10 +811,8 @@ class IfcImporter:
obj = bpy.data.objects.new(tool.Loader.get_name(element), mesh)
self.link_element(element, obj)
for ifcclass in self.classes_to_wireframe_list:
if element.is_a(ifcclass):
obj.display_type = "WIRE"
if getattr(element, "HasOpenings", None):
tool.Geometry.lock_scale(obj)
if shape:
# We use numpy here because Blender mathutils.Matrix is not accurate enough
@@ -835,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:
@@ -897,9 +935,9 @@ class IfcImporter:
tool.Loader.set_unit_scale(self.unit_scale)
def set_units(self):
if not (assignment := self.file.by_type("IfcProject")[0].UnitsInContext):
if not (units := self.file.by_type("IfcUnitAssignment")):
return # Geometry is optional in IFC
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"
@@ -1022,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
@@ -1074,12 +1116,7 @@ class IfcImporter:
verts = geometry.verts
mesh["has_cartesian_point_offset"] = False
return tool.Loader.convert_geometry_to_mesh(
geometry,
mesh,
verts=verts,
load_indexed_maps=self.ifc_import_settings.load_indexed_maps,
)
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
@@ -1147,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 = bpy.context.scene.DiffProperties
props = tool.Project.get_project_props()
props = bpy.context.scene.BIMProjectProperties
settings = IfcImportSettings()
settings.input_file = input_file
if logger is None:
@@ -1176,22 +1212,13 @@ class IfcImportSettings:
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,332 +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):
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},
)
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):
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):
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)
@@ -23,7 +23,6 @@ import ifcopenshell.util.element
import bonsai.tool as tool
import bonsai.core.aggregate as core
import bonsai.core.spatial
from bonsai.bim.ifc import IfcStore
class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator):
@@ -67,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:
@@ -370,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,20 +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")
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,
)
@@ -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()
@@ -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,43 +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
from bonsai.bim.ifc import IfcStore
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]
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
@@ -81,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 = IfcStore.get_file()
obj = tool.Blender.get_active_object(is_selected=False)
if not (element := tool.Ifc.get_entity(obj)):
return
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):
@@ -188,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)
+13 -6
View File
@@ -20,28 +20,31 @@ 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
@@ -59,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:
+11 -6
View File
@@ -616,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()
@@ -1378,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
@@ -1395,6 +1397,9 @@ 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:
+4 -4
View File
@@ -185,18 +185,18 @@ class BcfTopic(PropertyGroup):
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):
@@ -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
@@ -78,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
@@ -42,6 +42,16 @@ import bonsai.core
import bonsai.core.geometry
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):
ProfileDecorator.uninstall()
bpy.ops.object.mode_set(mode="OBJECT")
@@ -154,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
@@ -354,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():
@@ -375,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)
@@ -390,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)
@@ -525,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:
@@ -545,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"}
@@ -556,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()
@@ -817,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()
@@ -937,6 +942,8 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
# space.
exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords)
inner_boundaries = []
for rel in getattr(related_building_element, "HasOpenings", []):
opening = rel.RelatedOpeningElement
if not opening.HasFillings:
+4 -15
View File
@@ -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)
+2 -4
View File
@@ -64,12 +64,10 @@ class BIM_PT_Boundary(Panel):
return entity.is_a("IfcRelSpaceBoundary")
def draw(self, context):
obj = context.active_object
assert obj
props = obj.BIMObjectProperties
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")
+7 -14
View File
@@ -46,32 +46,25 @@ 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, context):
+3 -7
View File
@@ -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 = {
@@ -279,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)
@@ -623,9 +621,8 @@ class AddIfcCircle(bpy.types.Operator):
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):
@@ -647,7 +644,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator):
self.create_arc(context)
return {"FINISHED"}
def has_selected_existing_arc(self, context: bpy.types.Context) -> bool:
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]
@@ -665,9 +662,8 @@ 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:
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]
+1
View File
@@ -31,3 +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"
)
gable_roof_separate_verts: bpy.props.BoolProperty(name="Separate Verts", default=True)
+46 -26
View File
@@ -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 typing import Union
# 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()
@@ -74,8 +87,7 @@ class CadTool(WorkSpaceTool):
obj = context.active_object
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"):
@@ -110,7 +122,7 @@ 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", "Offset", ui_context)
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
@@ -118,14 +130,12 @@ class CadTool(WorkSpaceTool):
row = row if ui_context == "TOOL_HEADER" else layout.row(align=True)
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 hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
add_header_apply_button(
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", ui_context
)
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)
@@ -169,13 +179,13 @@ 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", "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", "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):
@@ -197,12 +207,12 @@ class CadHotkey(bpy.types.Operator):
def draw(self, context):
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()
@@ -213,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()
@@ -224,15 +234,16 @@ class CadHotkey(bpy.types.Operator):
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):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if tool.Geometry.is_profile_object_active():
if self.is_profile():
bpy.ops.bim.add_ifccircle(radius=self.props.radius / si_conversion)
else:
bpy.ops.bim.cad_arc_from_2_points()
@@ -242,7 +253,7 @@ class CadHotkey(bpy.types.Operator):
def hotkey_S_F(self):
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
if tool.Geometry.is_profile_object_active():
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 / si_conversion)
@@ -264,7 +275,7 @@ class CadHotkey(bpy.types.Operator):
bpy.ops.bim.edit_extrusion_axis()
def hotkey_S_R(self):
if tool.Geometry.is_profile_object_active():
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 (
@@ -272,21 +283,32 @@ class CadHotkey(bpy.types.Operator):
and RoofData.data["pset_data"]
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, text, apply_operator, cancel_operator, ui_context=""):
custom_icon = custom_icon_previews.get(text.upper().replace(" ", "_"), custom_icon_previews["IFC"]).icon_id
@@ -307,9 +329,7 @@ def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_co
row.label(text="Tools")
def add_layout_hotkey_operator(
layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None], ui_context: str = ""
) -> bpy.types.OperatorProperties:
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
@@ -205,12 +205,16 @@ class ExecuteIfcClash(bpy.types.Operator):
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"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH", options={"SKIP_SAVE"})
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def invoke(self, context, event):
if self.filepath:
return self.execute(context)
context.window_manager.fileselect_add(self)
_, 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):
@@ -225,7 +229,6 @@ class ExecuteIfcClash(bpy.types.Operator):
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")
@@ -266,7 +269,7 @@ class ExecuteIfcClash(bpy.types.Operator):
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())
@@ -101,11 +101,6 @@ 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",
)
@property
def active_clash_set(self):
+1 -5
View File
@@ -118,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")
@@ -20,7 +20,6 @@ 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
@@ -254,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()
@@ -346,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()
@@ -313,8 +313,10 @@ class BIM_PT_material_classifications(Panel, ReferenceUI):
if not tool.Ifc.get():
return False
props = context.scene.BIMMaterialProperties
if props.is_editing and (material := props.active_material) and material.ifc_definition_id:
return True
if props.materials and props.active_material_index < len(props.materials):
material = props.materials[props.active_material_index]
if material.ifc_definition_id:
return True
return False
def draw(self, context):
@@ -18,7 +18,6 @@
import bpy
import bonsai.tool as tool
from ifcopenshell.util.doc import get_entity_doc
def refresh():
@@ -32,21 +31,13 @@ class ConstraintsData:
@classmethod
def load(cls):
cls.data = {
"total_objectives": cls.total_objectives(),
"constraint_types_enum": cls.constraint_types_enum(),
}
cls.data = {"total_objectives": cls.total_objectives()}
cls.is_loaded = True
@classmethod
def total_objectives(cls):
return len(tool.Ifc.get().by_type("IfcObjective"))
@classmethod
def constraint_types_enum(cls) -> list[tuple[str, str, str]]:
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in ["IfcObjective"]]
class ObjectConstraintsData:
data = {}
@@ -19,7 +19,6 @@
import bpy
import json
import ifcopenshell.api
import ifcopenshell.api.constraint
import ifcopenshell.util.attribute
import bonsai.bim.helper
import bonsai.tool as tool
@@ -97,12 +96,19 @@ class EditObjective(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
props = context.scene.BIMConstraintProperties
attributes = bonsai.bim.helper.export_attributes(props.constraint_attributes)
ifc_file = tool.Ifc.get()
ifcopenshell.api.constraint.edit_objective(
ifc_file,
objective=ifc_file.by_id(props.active_constraint_id),
attributes=attributes,
attributes = {}
for attribute in props.constraint_attributes:
if attribute.is_null:
attributes[attribute.name] = None
elif attribute.enum_items:
attributes[attribute.name] = attribute.enum_value
else:
attributes[attribute.name] = attribute.string_value
self.file = IfcStore.get_file()
ifcopenshell.api.run(
"constraint.edit_objective",
self.file,
**{"objective": self.file.by_id(props.active_constraint_id), "attributes": attributes},
)
bpy.ops.bim.load_objectives()
return {"FINISHED"}
@@ -20,7 +20,6 @@ import bpy
from ifcopenshell.util.doc import get_entity_doc
import bonsai.tool as tool
from bonsai.bim.prop import Attribute
from bonsai.bim.module.constraint.data import ConstraintsData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
@@ -35,9 +34,8 @@ from bpy.props import (
def get_available_constraint_types(self, context):
if not ConstraintsData.is_loaded:
ConstraintsData.load()
return ConstraintsData.data["constraint_types_enum"]
version = tool.Ifc.get_schema()
return [(c, c, get_entity_doc(version, c).get("description", "")) for c in ["IfcObjective"]]
class Constraint(PropertyGroup):
@@ -45,7 +45,7 @@ class BIM_PT_constraints(Panel):
row = self.layout.row(align=True)
row.label(text=f"{ConstraintsData.data['total_objectives']} Objectives Found", icon="LIGHT")
if self.props.is_editing == "IfcObjective":
row.operator("bim.disable_constraint_editing_ui", text="", icon="CANCEL")
row.operator("bim.disable_constraint_editing_ui", text="", icon="CHECKMARK")
row.operator("bim.add_objective", text="", icon="ADD")
else:
row.operator("bim.load_objectives", text="", icon="GREASEPENCIL")
@@ -67,6 +67,7 @@ classes = (
operator.LoadCostItemTaskQuantities,
operator.LoadCostItemTypes,
operator.LoadProductCostItems,
operator.LoadScheduleOfRates,
operator.RemoveCostColumn,
operator.RemoveCostItem,
operator.RemoveCostItemQuantity,
+2 -3
View File
@@ -325,11 +325,10 @@ class CostItemQuantitiesData:
@classmethod
def process_quantity_names(cls):
active_task_index = bpy.context.scene.BIMWorkScheduleProperties.active_task_index
tprops = tool.Sequence.get_task_tree_props()
total_tasks = len(tprops.tasks)
total_tasks = len(bpy.context.scene.BIMTaskTreeProperties.tasks)
if not total_tasks or active_task_index >= total_tasks:
return []
ifc_definition_id = tprops.tasks[active_task_index].ifc_definition_id
ifc_definition_id = bpy.context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id
element = tool.Ifc.get().by_id(ifc_definition_id)
names = set()
qtos = ifcopenshell.util.element.get_psets(element, qtos_only=True)
+15 -31
View File
@@ -16,15 +16,12 @@
# 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 bpy
import ifcopenshell.api
import bonsai.tool as tool
from bpy_extras.io_utils import ImportHelper
import bonsai.tool as tool
import bonsai.core.cost as core
from typing import get_args, TYPE_CHECKING
class AddCostSchedule(bpy.types.Operator, tool.Ifc.Operator):
@@ -264,34 +261,17 @@ class AssignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Assign Cost Item Quantity"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
related_object_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
items=[(i, i, "") for i in get_args(tool.Cost.RELATED_OBJECT_TYPE)],
)
related_object_type: bpy.props.StringProperty()
prop_name: bpy.props.StringProperty()
if TYPE_CHECKING:
related_object_type: tool.Cost.RELATED_OBJECT_TYPE
@classmethod
def description(cls, context, properties) -> str:
descr = f"Assign cost item quantity to the active cost item from active {properties.related_object_type}"
if prop_name := properties.prop_name:
descr += f" property '{prop_name}'"
return descr
def _execute(self, context):
result = core.assign_cost_item_quantity(
core.assign_cost_item_quantity(
tool.Ifc,
tool.Cost,
cost_item=tool.Ifc.get().by_id(self.cost_item),
related_object_type=self.related_object_type,
prop_name=self.prop_name, # TODO: REVIEW PROP_NAME USABILITY
)
if not result:
self.report(
{"ERROR"},
f"Cost item wasn't assigned - no objects of type '{self.related_object_type}' are selected.",
)
class UnassignCostItemQuantity(bpy.types.Operator, tool.Ifc.Operator):
@@ -423,7 +403,7 @@ class AddCostValue(bpy.types.Operator, tool.Ifc.Operator):
class RemoveCostItemValue(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_cost_value"
bl_label = "Remove Cost Item Value"
bl_label = "Add Cost Item Value"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty()
cost_value: bpy.props.IntProperty()
@@ -633,7 +613,6 @@ class LoadCostItemTypes(bpy.types.Operator):
class AssignCostValue(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_cost_value"
bl_label = "Assign Cost Rate Value"
bl_description = "Assign cost rate value to active cost item"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
cost_rate: bpy.props.IntProperty()
@@ -644,6 +623,17 @@ class AssignCostValue(bpy.types.Operator, tool.Ifc.Operator):
)
class LoadScheduleOfRates(bpy.types.Operator):
bl_idname = "bim.load_schedule_of_rates_tree"
bl_label = "Load Schedule of Rates"
bl_options = {"REGISTER", "UNDO"}
cost_schedule: bpy.props.IntProperty()
def execute(self, context):
core.load_schedule_of_rates_tree(tool.Cost, schedule_of_rates=tool.Ifc.get().by_id(self.cost_schedule))
return {"FINISHED"}
class ExpandCostItemRate(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.expand_cost_item_rate"
bl_label = "Expand Cost Item Rate"
@@ -669,7 +659,6 @@ class ContractCostItemRate(bpy.types.Operator, tool.Ifc.Operator):
class CalculateCostItemResourceValue(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.calculate_cost_item_resource_value"
bl_label = "Calculate Cost Item Resource Value"
bl_description = "Calculate cost item value based on it's resources. Any previous cost values are removed"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
@@ -716,12 +705,7 @@ class ClearCostItemAssignments(bpy.types.Operator, tool.Ifc.Operator):
bl_label = "Clear Cost Item Product Assignments"
bl_options = {"REGISTER", "UNDO"}
cost_item: bpy.props.IntProperty()
related_object_type: bpy.props.EnumProperty( # type: ignore [reportRedeclaration]
items=[(i, i, "") for i in get_args(tool.Cost.RELATED_OBJECT_TYPE)],
)
if TYPE_CHECKING:
related_object_type: tool.Cost.RELATED_OBJECT_TYPE
related_object_type: bpy.props.StringProperty()
def _execute(self, context):
core.clear_cost_item_assignments(
+20 -19
View File
@@ -115,24 +115,25 @@ def get_schedule_predefined_types(self, context):
return CostSchedulesData.data["predefined_types"]
CURRENCIES_ENUM_ITEMS = (
("USD", "USD", "Dollar"),
("EUR", "EUR", "Euro"),
("GBP", "GBP", "Pound"),
("AUD", "AUD", "Australian Dollar"),
("CAD", "CAD", "Canadian Dollar"),
("CHF", "CHF", "Swiss Franc"),
("CNY", "CNY", "Chinese Yuan"),
("HKD", "HKD", "Hong Kong Dollar"),
("JPY", "JPY", "Japanese Yen"),
("NZD", "NZD", "New Zealand Dollar"),
("SEK", "SEK", "Swedish Krona"),
("KRW", "KRW", "South Korean Won"),
("SGD", "SGD", "Singapore Dollar"),
("NOK", "NOK", "Norwegian Krone"),
("MAD", "MAD", "Moroccan Dirham"),
("CUSTOM", "Custom currency", "Custom"),
)
def get_currencies(self, context):
return [
("USD", "USD", "Dollar"),
("EUR", "EUR", "Euro"),
("GBP", "GBP", "Pound"),
("AUD", "AUD", "Australian Dollar"),
("CAD", "CAD", "Canadian Dollar"),
("CHF", "CHF", "Swiss Franc"),
("CNY", "CNY", "Chinese Yuan"),
("HKD", "HKD", "Hong Kong Dollar"),
("JPY", "JPY", "Japanese Yen"),
("NZD", "NZD", "New Zealand Dollar"),
("SEK", "SEK", "Swedish Krona"),
("KRW", "KRW", "South Korean Won"),
("SGD", "SGD", "Singapore Dollar"),
("NOK", "NOK", "Norwegian Krone"),
("MAD", "MAD", "Moroccan Dirham"),
("CUSTOM", "Custom currency", "Custom"),
]
class CostItem(PropertyGroup):
@@ -246,7 +247,7 @@ class BIMCostProperties(PropertyGroup):
)
change_cost_item_parent: BoolProperty(name="Change Cost Item Parent", default=False, update=update_cost_item_parent)
show_cost_item_operators: BoolProperty(name="Show Cost Item Operators", default=False)
currency: EnumProperty(items=CURRENCIES_ENUM_ITEMS, name="Currencies")
currency: EnumProperty(items=get_currencies, name="Currencies")
custom_currency: StringProperty(
name="Custom Currency", default="USD", description="Custom Currency in ISO 4217 format"
)
+3 -27
View File
@@ -16,10 +16,8 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.bim.helper
import bonsai.bim.module.cost.prop as CostProp
import bonsai.tool as tool
from bpy.types import Panel, UIList
from bonsai.bim.ifc import IfcStore
from bonsai.bim.module.cost.data import CostSchedulesData
@@ -79,7 +77,7 @@ class BIM_PT_cost_schedules(Panel):
cost_schedule["id"]
)
row1.operator(
"bim.generate_cost_schedule_browser", text="Generate spreadsheet browser", icon="URL"
"bim.generate_cost_schedule_browser", text="Generate spreadsheet browsser", icon="URL"
).cost_schedule = cost_schedule["id"]
row2 = col.row(align=True)
row2.alignment = "RIGHT"
@@ -97,7 +95,7 @@ class BIM_PT_cost_schedules(Panel):
row1.prop(self.props, "should_show_column_ui", text="Schedule Columns", icon="SHORTDISPLAY")
if self.props.is_editing == "COST_SCHEDULE_ATTRIBUTES":
row.operator("bim.edit_cost_schedule", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_cost_schedule", text="", icon="CANCEL")
row.operator("bim.disable_editing_cost_schedule", text="Disable Editing", icon="CANCEL")
else:
row.label(
text="{}[{}]".format(cost_schedule["name"], cost_schedule["predefined_type"]), icon="LINENUMBERS_ON"
@@ -177,8 +175,6 @@ class BIM_PT_cost_schedules(Panel):
else:
op = row.operator("bim.enable_editing_cost_item_attributes", text="", icon="GREASEPENCIL")
op.cost_item = ifc_definition_id
BIM_UL_cost_items_trait.draw_header(self.layout)
self.layout.template_list(
"BIM_UL_cost_items",
"",
@@ -462,7 +458,7 @@ class BIM_PT_cost_item_quantities(Panel):
total_cost_item_processes = len(self.props.cost_item_processes)
row2.label(text="Tasks ({})".format(total_cost_item_processes))
tprops = tool.Sequence.get_task_tree_props()
tprops = context.scene.BIMTaskTreeProperties
wprops = context.scene.BIMWorkScheduleProperties
if tprops.tasks and wprops.active_task_index < len(tprops.tasks):
if has_quantity_names:
@@ -591,26 +587,6 @@ class BIM_PT_cost_item_rates(Panel):
class BIM_UL_cost_items_trait:
@classmethod
def draw_header(cls, layout: bpy.types.UILayout):
row = layout.row(align=True)
split1 = row.split(factor=0.1)
split1.label(text="ID")
split2 = split1.split(factor=0.5)
split2.alignment = "RIGHT"
split2.label(text="Name")
if CostSchedulesData.data["is_editing_rates"]:
split2.label(text="Unit")
else:
split2.label(text="Quantity")
split2.label(text="Value")
for column in bpy.context.scene.BIMCostProperties.columns:
split2.label(text=column.name)
split2.label(text="Total Cost")
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
self.props = context.scene.BIMCostProperties
@@ -27,7 +27,7 @@ classes = (
def register():
if not bpy.app.background:
bpy.utils.register_tool(workspace.CoveringTool, after={"bim.wall_tool"}, separator=False, group=False)
bpy.utils.register_tool(workspace.CoveringTool, after={"bim.structural_tool"}, separator=False, group=False)
bpy.types.Scene.BIMCoveringProperties = bpy.props.PointerProperty(type=prop.BIMCoveringProperties)
@@ -31,7 +31,7 @@ class CoveringTool(WorkSpaceTool):
bl_context_mode = "OBJECT"
bl_idname = "bim.covering_tool"
bl_label = "Covering Tool"
bl_description = "Create and edit coverings, including ceiling, flooring, cladding, roofing, moulding, skirtingboard, insulation, membrane, sleeving, and wrapping coverings"
bl_description = "Create and edit coverings"
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.covering")
bl_widget = None
ifc_element_type = "IfcCoveringType"
@@ -329,7 +329,6 @@ class ImportIfcCsv(bpy.types.Operator, tool.Ifc.Operator):
empty=props.empty_value,
bool_true=props.true_value,
bool_false=props.false_value,
concat=props.concat_value,
)
if not props.should_load_from_memory:
ifc_file.write(props.csv_ifc_file)
+13 -31
View File
@@ -39,7 +39,7 @@ import bonsai.bim.import_ifc as import_ifc
from pathlib import Path
from bonsai import get_debug_info, format_debug_info
from bonsai.bim.ifc import IfcStore
from typing import get_args, Union
from typing import get_args
class CopyDebugInformation(bpy.types.Operator):
@@ -376,30 +376,19 @@ class InspectFromObject(bpy.types.Operator):
bl_label = "Inspect From Object"
bl_description = "Inspect the Active Object's attributes and references"
@classmethod
def get_active_object_ifc_definition(cls, context: bpy.types.Context) -> Union[int, None]:
obj = context.active_object
assert obj
if ifc_id := obj.BIMObjectProperties.ifc_definition_id:
return ifc_id
if (
(data := obj.data)
and tool.Geometry.has_mesh_properties(data)
and (ifc_id := data.BIMMeshProperties.ifc_definition_id)
):
return ifc_id
@classmethod
def poll(cls, context):
if not context.active_object:
cls.poll_message_set("No Active Object")
elif not cls.get_active_object_ifc_definition(context):
cls.poll_message_set("Active Object doesn't have an IFC definition")
if bpy.app.version >= (3, 0, 0):
cls.poll_message_set("No Active Object")
elif not context.active_object.BIMObjectProperties.ifc_definition_id:
if bpy.app.version >= (3, 0, 0):
cls.poll_message_set("Active Object doesn't have an IFC definition")
else:
return True
def execute(self, context):
bpy.ops.bim.inspect_from_step_id(step_id=InspectFromObject.get_active_object_ifc_definition(context))
bpy.ops.bim.inspect_from_step_id(step_id=context.active_object.BIMObjectProperties.ifc_definition_id)
return {"FINISHED"}
@@ -724,9 +713,9 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
return {"CANCELLED"}
plural_object_type = f"{object_type.lower()}s"
if merged_data:
print(f"Merged {plural_object_type}:")
for element_type, element_names in merged_data.items():
names = ", ".join([n or "Unnamed" for n in element_names])
print(f"- {element_type}: {names}")
print(f"- {element_type}: {', '.join(element_names)}")
merged = sum(len(v) for v in merged_data.values())
msg = " See system console for details." if merged else ""
@@ -934,14 +923,7 @@ class RestartBlender(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
ms_store_app_id = tool.Blender.get_microsoft_store_app_id()
if not ms_store_app_id:
path = bpy.app.binary_path
os.execv(path, sys.argv)
else:
# Microsoft apps do not allow launching blender.exe directly
# since Blender folder is kind of private.
cmd_exe = Path(os.environ["SystemRoot"]) / "system32" / "cmd.exe"
blender_app = f"shell:AppsFolder\\BlenderFoundation.Blender_{ms_store_app_id}!BLENDER"
cmd_args = ["/c", "start", blender_app] + sys.argv[1:]
os.execv(cmd_exe, cmd_args)
import os
path = bpy.app.binary_path
os.execv(path, sys.argv)
+3 -23
View File
@@ -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
from bpy.types import PropertyGroup
from bpy.props import (
@@ -32,29 +31,10 @@ from bpy.props import (
)
def update_document_name(self: "Document", context: bpy.types.Context) -> None:
if not self.ifc_definition_id:
return
tool.Ifc.get().by_id(self.ifc_definition_id).Name = self.name
def update_document_identification(self: "Document", context: bpy.types.Context) -> None:
if not self.ifc_definition_id:
return
document = tool.Ifc.get().by_id(self.ifc_definition_id)
if document.is_a("IfcDocumentInformation"):
tool.Document.set_document_information_id(document, self.identification)
else:
tool.Document.set_external_reference_id(document, self.identification)
class Document(PropertyGroup):
name: StringProperty(name="Name", update=update_document_name)
identification: StringProperty(name="Identification", update=update_document_identification)
is_information: BoolProperty(
name="Is Information",
description="Whether element is IfcDocumentInformation, otherwise it's IfcDocumentReference.",
)
name: StringProperty(name="Name")
identification: StringProperty(name="Identification")
is_information: BoolProperty(name="Is Information")
ifc_definition_id: IntProperty(name="IFC Definition ID")
+2 -3
View File
@@ -155,7 +155,6 @@ class BIM_UL_documents(UIList):
row.label(text="", icon="FILE_HIDDEN")
split1 = row.split(factor=0.1)
# split1.label(text=item.identification)
split1.prop(item, "identification", text="", emboss=False)
split1.label(text=item.identification)
split2 = split1.split(factor=0.9)
split2.prop(item, "name", text="", emboss=False)
split2.label(text=item.name)
@@ -21,7 +21,6 @@ from . import ui, prop, operator, handler, gizmos, workspace
classes = (
operator.ActivateDrawing,
operator.ActivateDrawingFromSheet,
operator.ActivateDrawingStyle,
operator.ActivateModel,
operator.AddAnnotation,
@@ -53,7 +52,6 @@ classes = (
operator.DisableEditingSheets,
operator.DisableEditingText,
operator.DuplicateDrawing,
operator.DuplicateSheet,
operator.EditAssignedProduct,
operator.EditElementFilter,
operator.EditSheet,
@@ -70,7 +68,6 @@ classes = (
operator.LoadSchedules,
operator.LoadSheets,
operator.OpenDrawing,
operator.OpenLayout,
operator.OpenReference,
operator.OpenSchedule,
operator.OpenSheet,
@@ -89,7 +86,6 @@ classes = (
operator.SaveDrawingStyle,
operator.SaveDrawingStylesData,
operator.SelectAllDrawings,
operator.SelectAllSheets,
operator.SelectAssignedProduct,
operator.SelectDocIfcFile,
operator.OpenDocumentationWebUi,
@@ -100,7 +96,7 @@ classes = (
prop.Sheet,
prop.DocProperties,
prop.BIMCameraProperties,
prop.LiteralProps,
prop.Literal,
prop.BIMTextProperties,
prop.BIMAssignedProductProperties,
prop.BIMAnnotationProperties,
@@ -22,7 +22,6 @@ import math
import bmesh
import bonsai.tool as tool
import ifcopenshell.util.element
from pathlib import Path
from mathutils import Vector, Matrix
from typing import Optional
@@ -44,7 +43,7 @@ class Annotator:
font = bpy.data.fonts.get("OpenGost TypeB TT")
if not font:
font = bpy.data.fonts.load(
tool.Blender.get_data_dir_path(Path("fonts") / "OpenGost Type B TT.ttf").__str__()
os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", "OpenGost Type B TT.ttf")
)
font.name = "OpenGost Type B TT"
obj.data.font = font
+3 -5
View File
@@ -82,7 +82,8 @@ class SheetsData:
@classmethod
def titleblocks(cls):
files = [p.stem for p in tool.Blender.get_data_dir_paths(Path("templates") / "titleblocks", "*.svg")]
files = Path(os.path.join(bpy.context.scene.BIMProperties.data_dir, "templates", "titleblocks")).glob("*.svg")
files = [str(f.stem) for f in files]
if tool.Ifc.get():
project = tool.Ifc.get().by_type("IfcProject")[0]
@@ -320,9 +321,6 @@ class DecoratorData:
# get symbol
symbol = tool.Drawing.get_annotation_symbol(element)
# get newline_at
newline_at = pset_data.get("Newline_At", 0)
# other attributes
props_literals = props.literals
props_literals_n = len(props.literals)
@@ -340,7 +338,7 @@ class DecoratorData:
literals_data.append(literal_data)
text_data = {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol, "Newline_At": newline_at}
text_data = {"Literals": literals_data, "FontSize": font_size, "Symbol": symbol}
cls.data[obj.name] = text_data
return text_data
@@ -28,7 +28,6 @@ import ifcopenshell.util.element
import ifcopenshell.util.unit
import bonsai.tool as tool
import bonsai.bim.module.drawing.helper as helper
from pathlib import Path
from math import pi, sin, cos, tan, acos, atan, degrees, radians, ceil
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
@@ -595,7 +594,6 @@ class BaseDecorator:
text_data = text_data | props.get_text_edited_data()
literals_data = text_data["Literals"]
symbol = text_data["Symbol"]
newline_at = text_data["Newline_At"]
text_scale = 1.0
# draw asterisk symbol to indicate that there is some symbol that's not shown in viewport
@@ -607,14 +605,8 @@ class BaseDecorator:
font_size_mm = text_data["FontSize"] * text_scale
for literal_data in literals_data:
box_alignment = literal_data["BoxAlignment"]
text = literal_data["CurrentValue"]
if newline_at != 0:
text = helper.add_newline_between_words(text, newline_at)
multiple_lines = text.split("\n")
for line in multiple_lines:
for line in literal_data["CurrentValue"].split("\n"):
self.draw_label(
context,
line,
@@ -2023,9 +2015,9 @@ class DecorationsHandler:
self.decorators[object_type] = self.decorators["FALL"]
self.decorators["MULTI_SYMBOL"] = self.decorators["SYMBOL"]
if drawing_font := bpy.context.scene.DocProperties.drawing_font:
drawing_font_path = tool.Blender.get_data_dir_path(Path("fonts") / drawing_font)
if drawing_font_path.is_file():
font_id = blf.load(drawing_font_path.__str__())
drawing_font_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "fonts", drawing_font)
if os.path.exists(drawing_font_path):
font_id = blf.load(drawing_font_path)
for decorator in self.decorators.values():
decorator.font_id = font_id
@@ -414,38 +414,3 @@ def elevate_segment(bounds, segm):
return None
x = p1.x
return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))]
def add_newline_between_words(text, newline_at):
result = []
start = 0
while start < len(text):
# Find the next newline character if present
newline_index = text.find("\n", start)
if newline_index != -1 and newline_index < start + newline_at:
# If a newline is found within the current range
result.append(text[start:newline_index]) # Add text up to the newline
start = newline_index + 1 # Move past the newline
continue
# Find the end index considering the limit newline_at
end = start + newline_at
if end >= len(text): # If we're at the end of the string
result.append(text[start:])
break
# Look for the nearest space around the newline_at limit
space_index = text.rfind(" ", start, end) # Try to break before newline_at
if space_index == -1: # No space found, force a break at newline_at
space_index = text.find(" ", end) # Try to break after newline_at
if space_index == -1: # If there's still no space, take the rest of the text
result.append(text[start:])
break
# Add the chunk and update the start position
result.append(text[start:space_index])
start = space_index + 1 # Skip the space itself
return "\n".join(result)
File diff suppressed because it is too large Load Diff
+8 -97
View File
@@ -44,15 +44,17 @@ from bpy.props import (
CollectionProperty,
BoolVectorProperty,
)
from typing import TYPE_CHECKING, Literal
diagram_scales_enum = []
sheets_enum = []
def purge():
global diagram_scales_enum
global sheets_enum
diagram_scales_enum = []
sheets_enum = []
def update_target_view(self, context):
@@ -221,22 +223,6 @@ def update_has_annotation(self, context):
update_layer(self, context, "HasAnnotation", self.has_annotation)
def update_dpi(self, context):
update_layer(self, context, "DPI", self.dpi)
def update_linework_mode(self, context):
update_layer(self, context, "LineworkMode", self.linework_mode)
def update_fill_mode(self, context):
update_layer(self, context, "FillMode", self.fill_mode)
def update_cut_mode(self, context):
update_layer(self, context, "CutMode", self.cut_mode)
def update_layer(self, context, name, value):
if not self.update_props:
return
@@ -263,7 +249,7 @@ def update_titleblocks(self, context):
SheetsData.data["titleblocks"] = SheetsData.titleblocks()
def update_should_draw_decorations(self, context: bpy.types.Context) -> None:
def update_should_draw_decorations(self, context):
if self.should_draw_decorations:
# TODO: design a proper text variable templating renderer
collection = context.scene.camera.BIMObjectProperties.collection
@@ -295,44 +281,21 @@ class Drawing(PropertyGroup):
is_drawing: BoolProperty(name="Is Drawing", default=False)
is_expanded: BoolProperty(name="Is Expanded", default=True)
if TYPE_CHECKING:
ifc_definition_id: int
name: str
target_view: str
is_selected: bool
is_drawing: bool
is_expanded: bool
class Document(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
name: StringProperty(name="Name", update=update_document_name)
identification: StringProperty(name="Identification")
if TYPE_CHECKING:
ifc_definition_id: int
name: str
identification: str
class Sheet(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID")
identification: StringProperty(name="Identification")
name: StringProperty(name="Name")
is_sheet: BoolProperty(name="Is Sheet", default=False)
is_selected: BoolProperty(name="Is Selected", default=True)
reference_type: StringProperty(name="Reference Type")
is_expanded: BoolProperty(name="Is Expanded", default=False)
if TYPE_CHECKING:
ifc_definition_id: int
identification: str
name: str
is_sheet: bool
is_selected: bool
reference_type: str
is_expanded: bool
class DrawingStyle(PropertyGroup):
name: StringProperty(name="Name", get=get_drawing_style_name, set=set_drawing_style_name)
@@ -424,53 +387,6 @@ class DocProperties(PropertyGroup):
drawing_font: StringProperty(default="OpenGost Type B TT.ttf", name="Drawing Font")
magic_font_scale: bpy.props.FloatProperty(default=0.004118616, name="Font Scale Factor")
imperial_precision: StringProperty(default="1/32", name="Imperial Precision")
tolerance: bpy.props.FloatProperty(default=0.00001, name="A tolerance used when selecting objects")
classes_to_wireframe: StringProperty(
default="IfcVirtualElement",
name="Classes to Wireframe",
description="Upon import, these classes will display as wireframe.\nEx: IfcVirtualelement, IfcSpace",
)
if TYPE_CHECKING:
should_use_underlay_cache: bool
should_use_linework_cache: bool
should_use_annotation_cache: bool
is_editing_drawings: bool
is_editing_schedules: bool
is_editing_references: bool
target_view: Literal["PLAN_VIEW", "ELEVATION_VIEW", "SECTION_VIEW", "REFLECTED_PLAN_VIEW", "MODEL_VIEW"]
location_hint: str
drawings: bpy.types.bpy_prop_collection_idprop[Drawing]
active_drawing_id: int
active_drawing_index: int
current_drawing_index: int
schedules: bpy.types.bpy_prop_collection_idprop[Document]
active_schedule_index: int
references: bpy.types.bpy_prop_collection_idprop[Document]
active_reference_index: int
titleblock: str
is_editing_sheets: bool
sheets: bpy.types.bpy_prop_collection_idprop[Sheet]
active_sheet_index: int
ifc_files: bpy.types.bpy_prop_collection_idprop[StrProperty]
drawing_styles: bpy.types.bpy_prop_collection_idprop[DrawingStyle]
should_draw_decorations: bool
sheets_dir: str
layouts_dir: str
titleblocks_dir: str
drawings_dir: str
stylesheet_path: str
schedules_stylesheet_path: str
markers_path: str
symbols_path: str
patterns_path: str
shadingstyles_path: str
shadingstyle_default: str
drawing_font: str
magic_font_scale: float
imperial_precision: str
tolerance: float
classes_to_wireframe: str
class BIMCameraProperties(PropertyGroup):
@@ -481,7 +397,6 @@ class BIMCameraProperties(PropertyGroup):
],
default="OPENCASCADE",
name="Linework Mode",
update=update_linework_mode,
)
fill_mode: EnumProperty(
items=[
@@ -491,7 +406,6 @@ class BIMCameraProperties(PropertyGroup):
],
default="NONE",
name="Fill Mode",
update=update_fill_mode,
)
cut_mode: EnumProperty(
items=[
@@ -500,7 +414,6 @@ class BIMCameraProperties(PropertyGroup):
],
default="BISECT",
name="Cut Mode",
update=update_cut_mode,
)
has_underlay: BoolProperty(name="Underlay", default=False, update=update_has_underlay)
has_linework: BoolProperty(name="Linework", default=True, update=update_has_linework)
@@ -512,7 +425,7 @@ class BIMCameraProperties(PropertyGroup):
custom_scale_denominator: bpy.props.StringProperty(default="100", update=update_diagram_scale)
raster_x: IntProperty(name="Raster X", default=1000)
raster_y: IntProperty(name="Raster Y", default=1000)
dpi: IntProperty(name="DPI", default=75, update=update_dpi)
dpi: IntProperty(name="DPI", default=75)
width: FloatProperty(name="Width", default=50, subtype="DISTANCE")
height: FloatProperty(name="Height", default=50, subtype="DISTANCE")
is_nts: BoolProperty(name="Is NTS", update=update_is_nts)
@@ -554,7 +467,7 @@ BOX_ALIGNMENT_POSITIONS = [
]
class LiteralProps(PropertyGroup):
class Literal(PropertyGroup):
def set_box_alignment(self, new_value):
markers = new_value.count(True)
if not markers:
@@ -579,7 +492,7 @@ class LiteralProps(PropertyGroup):
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
attributes: CollectionProperty(name="Attributes", type=Attribute)
# Current text value with evaluated expressions stored in `value`.
# Current text value with evaluated experessions stored in `value`.
# The original (Literal) value stored in `attributes['Literal']`
# and can be accessed with `get_text()`
value: StringProperty(name="Value", default="TEXT")
@@ -599,7 +512,7 @@ class LiteralProps(PropertyGroup):
class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
literals: CollectionProperty(name="Literals", type=LiteralProps)
literals: CollectionProperty(name="Literals", type=Literal)
font_size: EnumProperty(
items=[
("1.8", "1.8 - Small", ""),
@@ -611,7 +524,6 @@ class BIMTextProperties(PropertyGroup):
default="2.5",
name="Font Size",
)
newline_at: IntProperty(name="Newline At")
def get_text_edited_data(self):
"""should be called only if `is_editing`
@@ -625,7 +537,6 @@ class BIMTextProperties(PropertyGroup):
text_data = {
"Literals": literals_data,
"FontSize": float(self.font_size),
"Newline_At": int(self.newline_at),
}
return text_data
@@ -22,7 +22,6 @@ import bpy
import string
import svgwrite
import openpyxl
import bonsai.tool as tool
from bonsai.bim.module.drawing.svgwriter import SvgWriter
from odf.opendocument import load as load_ods
@@ -78,7 +77,7 @@ class Scheduler:
ifc_file_path = os.path.dirname(IfcStore.path)
stylesheet_path = ifc_file_path + "\\" + stylesheet_rel_path
if not os.path.exists(stylesheet_path):
stylesheet_path = tool.Blender.get_data_dir_path(Path("assets") / "schedule.css")
stylesheet_path = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "schedule.css")
with open(stylesheet_path, "r") as stylesheet:
css = stylesheet.read()
@@ -26,7 +26,6 @@ import urllib.parse
import xml.etree.ElementTree as ET
import bonsai.tool as tool
import ifcopenshell.util.geolocation
from pathlib import Path
from xml.dom import minidom
from mathutils import Vector
import re
@@ -39,6 +38,7 @@ XLINK = "{http://www.w3.org/1999/xlink}"
class SheetBuilder:
def __init__(self):
self.data_dir = None
self.scale = "NTS"
def create(self, layout_path: str, titleblock_name: str) -> None:
@@ -49,9 +49,7 @@ class SheetBuilder:
root.attrib["version"] = "1.1"
sheet_dir = os.path.dirname(layout_path)
ootb_titleblock_path = tool.Blender.get_data_dir_path(
Path("templates") / "titleblocks" / (titleblock_name + ".svg")
)
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name))
os.makedirs(sheet_dir, exist_ok=True)
@@ -243,7 +241,7 @@ class SheetBuilder:
title_path = os.path.join(layout_dir, "assets", "view-title.svg")
os.makedirs(os.path.dirname(title_path), exist_ok=True)
if not os.path.exists(title_path):
ootb_title = tool.Blender.get_data_dir_path(Path("assets") / "view-title.svg")
ootb_title = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", "view-title.svg")
shutil.copy(ootb_title, title_path)
title_tree = ET.parse(title_path)
@@ -399,19 +397,7 @@ class SheetBuilder:
data.update({"Sheet" + k: v for k, v in sheet.get_info().items()})
if not data["Name"]:
data["Name"] = ntpath.basename(foreground_path)[0:-4]
# If a perspective drawing, don't add scale to view title
try:
is_perspective = (
drawing.Representation.Representations[0]
.Items[0]
.TreeRootExpression.FirstOperand.is_a("IfcRectangularPyramid")
)
except AttributeError:
is_perspective = False
if not is_perspective:
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
data["Scale"] = tool.Drawing.get_drawing_human_scale(drawing)
view.append(self.parse_embedded_svg(view_title, data))
for image in images:
@@ -494,10 +480,8 @@ class SheetBuilder:
return group
def change_titleblock(self, sheet: ifcopenshell.entity_instance, titleblock_name: str) -> None:
ootb_titleblock_path = tool.Blender.get_data_dir_path(
Path("templates") / "titleblocks" / (titleblock_name + ".svg")
)
titleblock_path = tool.Ifc.resolve_uri(tool.Drawing.get_default_titleblock_path(titleblock_name))
ootb_titleblock_path = os.path.join(self.data_dir, "templates", "titleblocks", titleblock_name + ".svg")
titleblock_path = tool.Drawing.get_default_titleblock_path(titleblock_name)
sheet_path = tool.Drawing.get_document_uri(sheet, "LAYOUT")
sheet_dir = os.path.dirname(sheet_path)
@@ -25,7 +25,6 @@ import shutil
import mathutils
import xml.etree.ElementTree as ET
import svgwrite
import svgwrite.text
import ifcopenshell
import ifcopenshell.util.element
import ifcopenshell.util.representation
@@ -38,7 +37,6 @@ from bonsai.bim.module.drawing.data import DecoratorData
from math import pi, ceil, atan, degrees, acos
from mathutils import geometry, Vector
from typing import Optional, Self
from pathlib import Path
class External(svgwrite.container.Group):
@@ -99,7 +97,7 @@ class SvgWriter:
os.makedirs(os.path.dirname(resource_path), exist_ok=True)
if not os.path.exists(resource_path):
resource_basename = os.path.basename(resource_path)
ootb_resource = tool.Blender.get_data_dir_path(Path("assets") / resource_basename)
ootb_resource = os.path.join(bpy.context.scene.BIMProperties.data_dir, "assets", resource_basename)
print(
f"WARNING. Couldn't find {resource} for the drawing by the path: {resource_path}. Default BBIM resource will be copied from {ootb_resource}"
)
@@ -776,9 +774,7 @@ class SvgWriter:
"text-anchor": text_anchor,
}
def add_fill_bg(self, element: svgwrite.text.Text, copy: bool = True) -> svgwrite.text.Text:
# Useful since tspans and texts do not support "background-color"
# so we just add a filter. Have to do it in a separate tag to avoid blurry image.
def add_fill_bg(self, element, copy=True):
if copy:
element = element.copy()
if hasattr(element, "xml"):
@@ -808,7 +804,6 @@ class SvgWriter:
fill_bg = "fill-bg" in classes
symbol = tool.Drawing.get_annotation_symbol(element)
newline_at = tool.Drawing.get_newline_at(element)
template_text_fields = []
if symbol:
symbol_transform = self.get_symbol_transform(text_position_svg_str, angle, text_obj)
@@ -843,7 +838,6 @@ class SvgWriter:
self.draw_symbol(symbol, symbol_transform)
line_number = 0
for text_literal in text_literals:
text = tool.Drawing.replace_text_literal_variables(text_literal.Literal, product or element)
text_tags = self.create_text_tag(
@@ -854,7 +848,6 @@ class SvgWriter:
classes_str,
fill_bg=fill_bg,
line_number_start=line_number,
newline_at=newline_at,
)
for tag in text_tags:
self.svg.add(tag)
@@ -1377,7 +1370,6 @@ class SvgWriter:
multiline_to_bottom=True,
fill_bg=False,
line_number_start=0,
newline_at=0,
):
"""returns list of created text tags"""
text_tags = []
@@ -1407,8 +1399,6 @@ class SvgWriter:
text_tag = self.svg.text("", **text_kwargs, **base_text_attrs)
text_tags.append(text_tag)
if isinstance(newline_at, int) and newline_at > 0:
text = helper.add_newline_between_words(text, newline_at)
text_lines = text.replace("\\n", "\n").split("\n")
text_lines = text_lines if multiline_to_bottom else text_lines[::-1]

Some files were not shown because too many files have changed in this diff Show More