mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 01:11:40 +00:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5db6738ac2 | |||
| eabdb5d281 | |||
| 59dd105e99 | |||
| a28070afef | |||
| 689e2718e5 | |||
| a2ab3972c8 | |||
| 667223da8e | |||
| 1e58a517a0 | |||
| fcee54c572 | |||
| 889a9e3891 | |||
| 7bc1992963 | |||
| 49c3a55456 | |||
| f11d79f8c1 | |||
| 2cc4f96b7f | |||
| bb46a1659d | |||
| f09002c7e2 | |||
| 9f75a14204 | |||
| c40d21d945 | |||
| 0d3245102f | |||
| 3aa4fa580b | |||
| 4c255cca43 | |||
| e603ebed04 | |||
| 38e6f09c91 | |||
| 6473fa4b67 | |||
| 38adaf4d18 | |||
| b7fb5c90bc | |||
| 23efb09ba6 | |||
| cf6280b928 | |||
| c16894cabf | |||
| 5df297c23c | |||
| b2b3a83463 | |||
| 4b0b0191ba | |||
| a92306d413 | |||
| f457ce8270 | |||
| 590c2e120c | |||
| 13746eb55e | |||
| 55b9c7d6bf | |||
| e86624873b | |||
| ae11f205f3 | |||
| 457f9c6aef | |||
| cc88bd4a9d | |||
| 8f74f4d9f1 | |||
| 56f0c19a85 | |||
| 76747bdf5a | |||
| a4de196259 | |||
| 049b157c8a | |||
| 7ec1bc5272 | |||
| d4f44cb0a1 | |||
| ecd7282150 | |||
| bd2c3a0ca1 | |||
| f0f2dda40e | |||
| 0c41f0c32c | |||
| a35ef32ee6 | |||
| 00eff23b74 | |||
| 88af3eb1a6 | |||
| a48d774337 | |||
| 734cd47ea5 | |||
| 5159a69225 | |||
| 2f2072cf7f | |||
| 8c47f2a72f | |||
| a6632167be | |||
| 12d1fd221d | |||
| 10642618e5 | |||
| be4f6ac222 | |||
| 3eaf4367ad | |||
| 305b4c27b5 | |||
| b56f2d7514 | |||
| cf026dccc2 | |||
| c5185aaf54 | |||
| 50ed756353 | |||
| 0f0578e1c5 | |||
| 0d086fda33 | |||
| f3f7510b48 | |||
| b2a4529904 | |||
| 7de50ff0c5 | |||
| 7b0a7e51bb | |||
| 507d13363d | |||
| 9713b06d75 | |||
| be6a4de6f0 | |||
| ea85a4247a | |||
| f377d794a0 | |||
| 16d077f1cb | |||
| 3f62f72f35 | |||
| dc79e9e005 | |||
| d25f57ec40 | |||
| 9f93779ac2 | |||
| 8436817408 | |||
| 1a98504328 | |||
| 1680c3135a | |||
| 79e4ba5b19 | |||
| d6d1f7714f | |||
| 68543c7f05 | |||
| 135655f8a0 | |||
| df49356104 | |||
| 04bcf8e2a9 | |||
| 9f17ec5bf5 | |||
| 67aa8f1413 | |||
| 8469d10c85 | |||
| 87cbc66c99 | |||
| c2ae3dcb08 | |||
| 089f15a5cc | |||
| 919df92888 | |||
| 553872ced2 | |||
| bc33cb6391 |
@@ -0,0 +1,20 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,116 @@
|
||||
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
|
||||
@@ -99,9 +99,9 @@ jobs:
|
||||
token: ${{ secrets.IOS_TO_BLENDER_REPO }}
|
||||
path: bonsai_unstable_repo
|
||||
|
||||
- name: Update index.json on extensions repo
|
||||
- name: Download Blender and run critical tests
|
||||
run: |
|
||||
set -x -e
|
||||
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
|
||||
|
||||
# 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
|
||||
@@ -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
|
||||
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"
|
||||
cd ..
|
||||
bonsai_zip="$(pwd)/$(ls bonsai_unstable_repo/bonsai_py311*-linux-x64.zip)"
|
||||
blender --version
|
||||
|
||||
# Install Bonsai.
|
||||
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,17 +139,41 @@ jobs:
|
||||
rm -r sverchok
|
||||
blender --command extension install-file -r user_default sverchok.zip
|
||||
|
||||
git clone https://github.com/IfcOpenShell/IfcOpenShell.git IfcOpenShell
|
||||
# Install ifcsverchok.
|
||||
cd IfcOpenShell/src/ifcsverchok
|
||||
make dist
|
||||
sverchok_zip="$(pwd)/dist/$(ls dist)"
|
||||
blender --command extension install-file -r user_default $sverchok_zip
|
||||
|
||||
- name: Update index.json on extensions repo
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
# Setup Blender.
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
cd bonsai_unstable_repo
|
||||
git config --global user.name 'IfcOpenBot'
|
||||
git config --global user.email 'IfcOpenBot@users.noreply.github.com'
|
||||
git add index.json
|
||||
git add readme.md
|
||||
git commit -m "Update index.json"
|
||||
git push
|
||||
|
||||
- name: Run bonsai tests
|
||||
run: |
|
||||
set -x -e
|
||||
BLENDER_PATH=$(find blender-*/ -maxdepth 0 -exec readlink -f {} \;)
|
||||
export PATH="$PATH:$BLENDER_PATH"
|
||||
blender --version
|
||||
|
||||
# Install Sun Position extension.
|
||||
blender --online-mode --command extension sync
|
||||
blender --online-mode --command extension install --enable --sync sun_position
|
||||
|
||||
cd ../bonsai
|
||||
cd IfcOpenShell/src/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
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
steps:
|
||||
- run: echo ok go
|
||||
|
||||
build:
|
||||
compile-and-test:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
steps:
|
||||
@@ -135,4 +135,5 @@ jobs:
|
||||
pip install deepdiff
|
||||
cd ../ifcdiff && make test
|
||||
cd ../ifcpatch && make test
|
||||
pip install -e ../ifctester --no-deps
|
||||
cd ../ifctester && make test
|
||||
|
||||
@@ -27,7 +27,9 @@ 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]
|
||||
|
||||
+8
-60
@@ -85,7 +85,7 @@ BLENDER_PLATFORM:=windows-x64
|
||||
endif
|
||||
|
||||
# Current build commit hash.
|
||||
OLD:=1d27161
|
||||
OLD:=463289e
|
||||
.PHONY: bump
|
||||
bump:
|
||||
ifndef NEW
|
||||
@@ -233,7 +233,7 @@ endif
|
||||
|
||||
# required for three-way git merging
|
||||
ifeq ($(PLATFORM), win)
|
||||
cd build/bonsai/libs/bin && wget https://github.com/brunopostle/ifcmerge/releases/download/2024-06-24/ifcmerge.zip
|
||||
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
|
||||
@@ -278,64 +278,6 @@ endif
|
||||
fi
|
||||
mv build/wheels/*.whl build/bonsai/wheels/
|
||||
|
||||
# Ugly patch to accomodate MacOS universal builds not being recognized by Blender.
|
||||
# Blender doesn't look into the wheel contents, it just checks the name.
|
||||
# So this hack works without working out some way to repack those wheels.
|
||||
# See: https://projects.blender.org/blender/blender/issues/125091
|
||||
|
||||
ifeq ($(PLATFORM), macosm1)
|
||||
# Has universal and x86_64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/lxml-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed -E "s/macosx_10_([0-9]+)_x86_64\.macosx_11_0_arm64\.macosx_10_\1_universal2/macosx_10_\1_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# Has only universal build.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/greenlet-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# has universal and x86_64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/fonttools-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
# Has only universal build.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/charset_normalizer-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_arm64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
endif
|
||||
ifeq ($(PLATFORM), macos)
|
||||
# Has universal and arm64 builds.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/MarkupSafe-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed -E "s/macosx_10_([0-9]+)_x86_64\.macosx_11_0_arm64\.macosx_10_\1_universal2/macosx_10_\1_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/greenlet-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
|
||||
prev_whl_name=$$(find build/bonsai/wheels/charset_normalizer-*.whl); \
|
||||
whl_name=$$(echo $$prev_whl_name | sed "s/_universal2/_x86_64/"); \
|
||||
mv "$$prev_whl_name" "$$whl_name";
|
||||
endif
|
||||
|
||||
# Safeguard for unhandled universal files.
|
||||
wheels=$$(find build/bonsai/wheels/*universal2*.whl); \
|
||||
if [ -n "$$wheels" ]; then \
|
||||
echo "Found universal2 wheel files:"; \
|
||||
echo "$$wheels"; \
|
||||
echo "Error: universal2 wheel files are not supported by Blender!"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Temporary workaround for Blender not handling non-3.11 wheels #5743.
|
||||
# Use '-n' as on macos x86-64 doesn't have a binary wheel and it autoincludes 'cp311'.
|
||||
prev_whl_name=$$(find build/bonsai/wheels/tzfpy-*.whl); \
|
||||
@@ -402,6 +344,12 @@ 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.
@@ -20,8 +20,7 @@ import os
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from . import handler, ui, prop, operator, helper
|
||||
from . import handler, ui, prop, operator
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -20,7 +20,6 @@ from __future__ import annotations
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import numpy as np
|
||||
import datetime
|
||||
import zipfile
|
||||
import tempfile
|
||||
|
||||
@@ -188,6 +188,9 @@ 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:
|
||||
|
||||
@@ -20,21 +20,19 @@ 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
|
||||
@@ -48,7 +46,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
def draw_attributes(
|
||||
props: list[bonsai.bim.prop.Attribute],
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
layout: bpy.types.UILayout,
|
||||
copy_operator: Optional[str] = None,
|
||||
popup_active_attribute: Optional[bonsai.bim.prop.Attribute] = None,
|
||||
@@ -117,7 +115,7 @@ def draw_attribute(
|
||||
|
||||
def import_attributes(
|
||||
ifc_class: str,
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
data: dict[str, Any],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
@@ -128,7 +126,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,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
if isinstance(element, str):
|
||||
@@ -144,7 +142,7 @@ def import_attributes2(
|
||||
|
||||
def import_attribute(
|
||||
attribute: W.attribute,
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
data: dict[str, Any],
|
||||
callback: Optional[ImportCallback] = None,
|
||||
) -> None:
|
||||
@@ -258,7 +256,7 @@ def add_attribute_description(attribute_blender: bonsai.bim.prop.Attribute, attr
|
||||
|
||||
|
||||
def export_attributes(
|
||||
props: bpy.types.bpy_prop_collection,
|
||||
props: bpy.types.bpy_prop_collection_idprop[Attribute],
|
||||
callback: Optional[ExportCallback] = None,
|
||||
) -> dict[str, Any]:
|
||||
attributes: dict[str, Any] = {}
|
||||
|
||||
@@ -34,10 +34,9 @@ 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, cast
|
||||
from typing import Dict, Union, Optional, Any
|
||||
|
||||
|
||||
class MaterialCreator:
|
||||
@@ -71,6 +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)
|
||||
@@ -83,6 +84,18 @@ 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)
|
||||
@@ -96,7 +109,7 @@ class MaterialCreator:
|
||||
return coords
|
||||
# TODO: support IfcTextureMap
|
||||
if coords.is_a("IfcTextureMap"):
|
||||
print(f"WARNING. IfcTextureMap texture coordinates is not supported.")
|
||||
print("WARNING. IfcTextureMap texture coordinates is not supported.")
|
||||
return
|
||||
|
||||
def load_texture_maps(self, shape_has_openings: bool) -> None:
|
||||
|
||||
@@ -49,9 +49,9 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
|
||||
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
|
||||
bpy.types.Object.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMBoundaryProperties
|
||||
del bpy.types.Object.bim_boundary_properties
|
||||
del bpy.types.Object.BIMBoundaryProperties
|
||||
|
||||
@@ -78,7 +78,8 @@ class BoundaryDecorator:
|
||||
unselected_edges = []
|
||||
unselected_tris = []
|
||||
|
||||
for boundary in context.scene.BIMBoundaryProperties.boundaries:
|
||||
props = tool.Boundary.get_boundary_props()
|
||||
for boundary in props.boundaries:
|
||||
obj = boundary.obj
|
||||
if not obj or not obj.data: # A boundary may not have data if it has no connection geometry
|
||||
continue
|
||||
|
||||
@@ -354,7 +354,9 @@ class EnableEditingBoundary(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
bprops.is_editing = True
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
@@ -373,7 +375,9 @@ class DisableEditingBoundary(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
bprops.is_editing = False
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
setattr(bprops, blender_property, None)
|
||||
@@ -386,8 +390,10 @@ class EditBoundaryAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
bprops = context.active_object.bim_boundary_properties
|
||||
boundary = tool.Ifc.get_entity(context.active_object)
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
boundary = tool.Ifc.get_entity(obj)
|
||||
attributes = dict()
|
||||
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
|
||||
obj = getattr(bprops, blender_property, None)
|
||||
@@ -519,6 +525,7 @@ 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:
|
||||
@@ -538,7 +545,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)
|
||||
context.scene.BIMBoundaryProperties.boundaries.clear()
|
||||
props.boundaries.clear()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -549,7 +556,7 @@ class DecorateBoundaries(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMBoundaryProperties
|
||||
props = tool.Boundary.get_boundary_props()
|
||||
# filter not decorated boundaries and add decorations for them
|
||||
decorated_boundaries = set([i.obj for i in props.boundaries])
|
||||
active_boundaries = set()
|
||||
|
||||
@@ -30,23 +30,24 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
)
|
||||
import bonsai.tool as tool
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
|
||||
def space_filter(self, object):
|
||||
def space_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
|
||||
return False
|
||||
|
||||
|
||||
def boundary_filter(self, object):
|
||||
def boundary_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
return False
|
||||
|
||||
|
||||
def element_filter(self, object):
|
||||
def element_filter(self: "BIMObjectBoundaryProperties", object: bpy.types.Object) -> bool:
|
||||
entity = tool.Ifc.get_entity(object)
|
||||
if entity:
|
||||
return entity.is_a("IfcElement")
|
||||
@@ -60,6 +61,16 @@ 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)
|
||||
boundaries: bpy.props.CollectionProperty(type=ObjProperty, description="Decorated boundaries")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
boundaries: bpy.types.bpy_prop_collection_idprop[ObjProperty]
|
||||
|
||||
@@ -64,10 +64,12 @@ class BIM_PT_Boundary(Panel):
|
||||
return entity.is_a("IfcRelSpaceBoundary")
|
||||
|
||||
def draw(self, context):
|
||||
props = context.active_object.BIMObjectProperties
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = obj.BIMObjectProperties
|
||||
ifc_file = tool.Ifc.get()
|
||||
boundary = ifc_file.by_id(props.ifc_definition_id)
|
||||
self.bprops = context.active_object.bim_boundary_properties
|
||||
self.bprops = tool.Boundary.get_object_boundary_props(obj)
|
||||
if self.bprops.is_editing:
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
|
||||
@@ -123,7 +123,9 @@ class CadTool(WorkSpaceTool):
|
||||
add_layout_hotkey_operator(row, "Reset Vertex", "S_X", bpy.ops.bim.reset_vertex.__doc__, ui_context)
|
||||
|
||||
elif hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.subshape_type == "AXIS":
|
||||
add_header_apply_button(layout, "Edit Axis", "bim.set_arc_index", "bim.set_arc_index", ui_context)
|
||||
add_header_apply_button(
|
||||
layout, "Edit Axis", "bim.edit_extrusion_axis", "bim.disable_editing_extrusion_axis", 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)
|
||||
|
||||
@@ -325,10 +325,11 @@ class CostItemQuantitiesData:
|
||||
@classmethod
|
||||
def process_quantity_names(cls):
|
||||
active_task_index = bpy.context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
total_tasks = len(bpy.context.scene.BIMTaskTreeProperties.tasks)
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
total_tasks = len(tprops.tasks)
|
||||
if not total_tasks or active_task_index >= total_tasks:
|
||||
return []
|
||||
ifc_definition_id = bpy.context.scene.BIMTaskTreeProperties.tasks[active_task_index].ifc_definition_id
|
||||
ifc_definition_id = tprops.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)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
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
|
||||
@@ -461,7 +462,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 = context.scene.BIMTaskTreeProperties
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
wprops = context.scene.BIMWorkScheduleProperties
|
||||
if tprops.tasks and wprops.active_task_index < len(tprops.tasks):
|
||||
if has_quantity_names:
|
||||
|
||||
@@ -100,7 +100,7 @@ classes = (
|
||||
prop.Sheet,
|
||||
prop.DocProperties,
|
||||
prop.BIMCameraProperties,
|
||||
prop.Literal,
|
||||
prop.LiteralProps,
|
||||
prop.BIMTextProperties,
|
||||
prop.BIMAssignedProductProperties,
|
||||
prop.BIMAnnotationProperties,
|
||||
|
||||
@@ -422,7 +422,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.svg_writer.create_blank_svg(svg_path).draw_underlay(context.scene.render.filepath).save()
|
||||
return svg_path
|
||||
|
||||
def get_linework_contexts(self, ifc, target_view) -> LineworkContexts:
|
||||
def get_linework_contexts(self, ifc: ifcopenshell.file, target_view: str) -> LineworkContexts:
|
||||
plan_body_target_contexts = []
|
||||
plan_body_model_contexts = []
|
||||
model_body_target_contexts = []
|
||||
@@ -694,8 +694,8 @@ class CreateDrawing(bpy.types.Operator):
|
||||
invalidated_elements = exporter.sync_all_objects()
|
||||
invalidated_elements += exporter.sync_edited_objects()
|
||||
invalidated_guids = [e.GlobalId for e in invalidated_elements if hasattr(e, "GlobalId")]
|
||||
cache = IfcStore.get_cache()
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
if cache := IfcStore.get_cache():
|
||||
[cache.remove(guid) for guid in invalidated_guids]
|
||||
|
||||
# If we have already calculated it in the SVG in the past, don't recalculate
|
||||
edited_guids = set()
|
||||
@@ -1686,20 +1686,13 @@ class RemoveDrawingFromSheet(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return False
|
||||
|
||||
if active_item.reference_type == "TITLEBLOCK":
|
||||
cls.poll_message_set("No effect deleting this.")
|
||||
cls.poll_message_set("No effect deleting titleblock reference.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
reference = tool.Ifc.get().by_id(self.reference)
|
||||
sheet = tool.Drawing.get_reference_document(reference)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.remove_drawing(reference, sheet)
|
||||
|
||||
tool.Ifc.run("document.remove_reference", reference=reference)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
ifc_file = tool.Ifc.get()
|
||||
tool.Drawing.remove_drawing_from_sheet(ifc_file.by_id(self.reference))
|
||||
|
||||
|
||||
class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -1759,7 +1752,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
|
||||
references = sheet_builder.build(sheet)
|
||||
raster_references = [tool.Ifc.get_relative_uri(r) for r in references["RASTER"]]
|
||||
raster_references = [tool.Ifc.get_uri(r, use_relative_path=True) for r in references["RASTER"]]
|
||||
|
||||
# These variables will be made available to the evaluated commands
|
||||
svg = references["SHEET"]
|
||||
@@ -1790,7 +1783,7 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"document.edit_reference",
|
||||
reference=reference,
|
||||
attributes=tool.Drawing.generate_reference_attributes(
|
||||
reference, Location=tool.Ifc.get_relative_uri(svg), Description="SHEET"
|
||||
reference, Location=tool.Ifc.get_uri(svg, use_relative_path=True), Description="SHEET"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1800,7 +1793,9 @@ class CreateSheets(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"document.edit_reference",
|
||||
reference=reference,
|
||||
attributes=tool.Drawing.generate_reference_attributes(
|
||||
reference, Location=tool.Ifc.get_relative_uri(raster_reference), Description="RASTER"
|
||||
reference,
|
||||
Location=tool.Ifc.get_uri(raster_reference, use_relative_path=True),
|
||||
Description="RASTER",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2133,7 +2128,7 @@ class RemoveDrawing(bpy.types.Operator, tool.Ifc.Operator):
|
||||
for drawing in drawings:
|
||||
sheet_references = tool.Drawing.get_sheet_references(drawing)
|
||||
for reference in sheet_references:
|
||||
bpy.ops.bim.remove_drawing_from_sheet(reference=reference.id())
|
||||
tool.Drawing.remove_drawing_from_sheet(reference)
|
||||
core.remove_drawing(tool.Ifc, tool.Drawing, drawing=drawing)
|
||||
|
||||
# In case we removed the active drawing.
|
||||
@@ -2462,21 +2457,8 @@ class AddSchedule(bpy.types.Operator, tool.Ifc.Operator):
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
|
||||
def _execute(self, context):
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"SCHEDULE",
|
||||
uri=filepath,
|
||||
)
|
||||
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
core.add_document(tool.Ifc, tool.Drawing, "SCHEDULE", uri=filepath)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
@@ -2660,21 +2642,8 @@ class AddReference(bpy.types.Operator, tool.Ifc.Operator):
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
|
||||
def _execute(self, context):
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
ifc_path = tool.Ifc.get_path()
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
|
||||
# taking into account different drives on windows
|
||||
if Path(filepath).drive == Path(ifc_path).drive:
|
||||
filepath = os.path.relpath(filepath, ifc_path)
|
||||
core.add_document(
|
||||
tool.Ifc,
|
||||
tool.Drawing,
|
||||
"REFERENCE",
|
||||
uri=filepath,
|
||||
)
|
||||
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
core.add_document(tool.Ifc, tool.Drawing, "REFERENCE", uri=filepath)
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
@@ -3308,11 +3277,8 @@ class AddReferenceImage(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"RUNNING_MODAL"}
|
||||
|
||||
def _execute(self, context):
|
||||
abs_path = Path(self.filepath)
|
||||
if self.use_relative_path:
|
||||
image_filepath = abs_path.relative_to(Path(tool.Ifc.get_path()).parent)
|
||||
else:
|
||||
image_filepath = abs_path
|
||||
abs_path = Path(self.filepath).absolute().resolve()
|
||||
image_filepath = Path(tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path))
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
if self.override_existing_image:
|
||||
|
||||
@@ -44,6 +44,7 @@ from bpy.props import (
|
||||
CollectionProperty,
|
||||
BoolVectorProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
|
||||
diagram_scales_enum = []
|
||||
@@ -294,12 +295,25 @@ 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")
|
||||
@@ -310,6 +324,15 @@ class Sheet(PropertyGroup):
|
||||
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)
|
||||
@@ -408,6 +431,47 @@ class DocProperties(PropertyGroup):
|
||||
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):
|
||||
linework_mode: EnumProperty(
|
||||
@@ -490,7 +554,7 @@ BOX_ALIGNMENT_POSITIONS = [
|
||||
]
|
||||
|
||||
|
||||
class Literal(PropertyGroup):
|
||||
class LiteralProps(PropertyGroup):
|
||||
def set_box_alignment(self, new_value):
|
||||
markers = new_value.count(True)
|
||||
if not markers:
|
||||
@@ -535,7 +599,7 @@ class Literal(PropertyGroup):
|
||||
|
||||
class BIMTextProperties(PropertyGroup):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
literals: CollectionProperty(name="Literals", type=Literal)
|
||||
literals: CollectionProperty(name="Literals", type=LiteralProps)
|
||||
font_size: EnumProperty(
|
||||
items=[
|
||||
("1.8", "1.8 - Small", ""),
|
||||
|
||||
@@ -402,6 +402,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Update Representation"
|
||||
bl_description = (
|
||||
"Write selected objects representations to IFC.\n"
|
||||
"Converting to tesellation will also unassign material layer/profile sets.\n"
|
||||
"A star in the operator name indicates that active object representation in IFC is not yet synced with Blender.\n"
|
||||
"ALT+CLICK to apply openings to the mesh"
|
||||
)
|
||||
@@ -478,6 +479,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if tool.Ifc.is_moved(obj) or tool.Geometry.is_scaled(obj):
|
||||
core.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
if material and material.is_a() in ["IfcMaterialProfileSet", "IfcMaterialLayerSet"]:
|
||||
if self.ifc_representation_class == "IfcTessellatedFaceSet":
|
||||
# We are explicitly casting to a tessellation, so remove all parametric materials.
|
||||
@@ -489,9 +491,14 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Material.ensure_material_unassigned([product])
|
||||
else:
|
||||
# These objects are parametrically based on an axis and should not be modified as a mesh
|
||||
return
|
||||
if extrusion := tool.Model.get_extrusion(old_representation):
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"Representation for '{obj.name}' wasn't updated because "
|
||||
"it has material set and extrusion that probably was defined parametrically.",
|
||||
)
|
||||
return
|
||||
|
||||
old_representation = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
context_of_items = old_representation.ContextOfItems
|
||||
|
||||
# TODO: remove this code a bit later
|
||||
@@ -1075,7 +1082,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
|
||||
|
||||
@staticmethod
|
||||
def duplicate_item(obj: bpy.types.Object) -> None:
|
||||
props = bpy.context.scene.BIMGeometryProperties
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
new_item = ifcopenshell.util.element.copy_deep(tool.Ifc.get(), item)
|
||||
new_obj = obj.copy()
|
||||
@@ -2671,12 +2678,13 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
|
||||
shape_aspect, representation_item
|
||||
)
|
||||
styles = tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
# TODO this looks wrong to me. In theory styles can be > 1 (e.g. curve
|
||||
# styles) and then the usecase will assign the wrong style.
|
||||
tool.Ifc.run(
|
||||
"style.assign_representation_styles",
|
||||
shape_representation=shape_aspect_representation,
|
||||
styles=styles,
|
||||
)
|
||||
tool.Geometry.get_shape_aspect_styles(element, shape_aspect, representation_item)
|
||||
tool.Geometry.reload_representation(obj)
|
||||
|
||||
# reload items ui
|
||||
@@ -2749,17 +2757,17 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
def _execute(self, context):
|
||||
obj = context.active_object
|
||||
assert obj
|
||||
props = context.scene.BIMGeometryProperties
|
||||
props = tool.Geometry.get_geometry_props()
|
||||
if previous_obj := props.representation_obj:
|
||||
previous_obj.hide_set(False)
|
||||
props.representation_obj = obj
|
||||
obj.hide_set(True)
|
||||
tool.Geometry.lock_object(obj)
|
||||
|
||||
context.scene.BIMGeometryProperties.is_changing_mode = True
|
||||
if context.scene.BIMGeometryProperties.mode != "ITEM":
|
||||
context.scene.BIMGeometryProperties.mode = "ITEM"
|
||||
context.scene.BIMGeometryProperties.is_changing_mode = False
|
||||
props.is_changing_mode = True
|
||||
if props.mode != "ITEM":
|
||||
props.mode = "ITEM"
|
||||
props.is_changing_mode = False
|
||||
|
||||
tool.Loader.load_settings()
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional, TYPE_CHECKING, Union
|
||||
|
||||
|
||||
def get_contexts(self, context):
|
||||
@@ -302,3 +302,13 @@ class BIMGeometryProperties(PropertyGroup):
|
||||
type=bpy.types.Object,
|
||||
poll=is_object_valid_for_representation_copy,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
should_use_presentation_style_assignment: bool
|
||||
should_force_faceted_brep: bool
|
||||
should_force_triangulation: bool
|
||||
is_changing_mode: bool
|
||||
mode: str
|
||||
representation_obj: Union[bpy.types.Object, None]
|
||||
item_objs: bpy.types.bpy_prop_collection_idprop[RepresentationItemObject]
|
||||
representation_from_object: Union[bpy.types.Object, None]
|
||||
|
||||
@@ -45,12 +45,12 @@ class BIMLibraryProperties(PropertyGroup):
|
||||
editing_mode: EnumProperty(
|
||||
name="Editing Mode",
|
||||
items=(
|
||||
("-", "-", ""),
|
||||
("NONE", "NONE", ""),
|
||||
("LIBRARY", "LIBRARY", ""),
|
||||
("REFERENCES", "REFERENCES", ""),
|
||||
("REFERENCE", "REFERENCE", ""),
|
||||
),
|
||||
default="-",
|
||||
default="NONE",
|
||||
)
|
||||
library_attributes: CollectionProperty(name="Library Attributes", type=Attribute)
|
||||
active_library_id: IntProperty(name="Active Library Id")
|
||||
|
||||
@@ -113,6 +113,7 @@ def update_sun_path(self):
|
||||
sun_props.sun_distance = self.sun_path_size
|
||||
sun_props.latitude = self.latitude
|
||||
sun_props.longitude = self.longitude
|
||||
sun_props.year = self.year
|
||||
sun_props.month = self.month
|
||||
sun_props.day = self.day
|
||||
sun_props.time = self.hour + (self.minute / 60)
|
||||
@@ -382,6 +383,7 @@ class BIMSolarProperties(PropertyGroup):
|
||||
longitude: FloatProperty(name="Longitude", min=-180, max=180, update=update_latlong)
|
||||
timezone: StringProperty(name="Timezone", default="Etc/GMT")
|
||||
true_north: FloatProperty(name="True North", min=-180, max=180, update=update_true_north)
|
||||
year: IntProperty(name="Year", min=1, max=9999, default=2025, update=update_date)
|
||||
month: IntProperty(name="Month", min=1, max=12, default=1, update=update_date)
|
||||
day: IntProperty(name="Date", min=1, max=31, default=1, update=update_date)
|
||||
hour: IntProperty(name="Hour", min=0, max=23, default=12, update=update_hourminute)
|
||||
|
||||
@@ -172,6 +172,8 @@ class BIM_PT_solar(bpy.types.Panel):
|
||||
if SolarData.data["true_north"] is not None:
|
||||
row.operator("bim.import_true_north", icon="IMPORT", text="")
|
||||
|
||||
row = self.layout.row()
|
||||
row.prop(props, "year")
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(
|
||||
props,
|
||||
|
||||
@@ -41,6 +41,7 @@ from . import (
|
||||
roof,
|
||||
mep,
|
||||
)
|
||||
from typing import NamedTuple
|
||||
|
||||
classes = (
|
||||
array.AddArray,
|
||||
@@ -200,37 +201,47 @@ classes = (
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
class ToolsData(NamedTuple):
|
||||
tool: type[bpy.types.WorkSpaceTool]
|
||||
after: set[str]
|
||||
separator: bool
|
||||
group: bool
|
||||
|
||||
|
||||
tools: tuple[ToolsData, ...] = (
|
||||
ToolsData(workspace.BimTool, {"bim.explore_tool"}, False, False),
|
||||
ToolsData(workspace.DuctTool, {"bim.explore_tool"}, False, True),
|
||||
ToolsData(workspace.PipeTool, {"bim.duct_tool"}, False, False),
|
||||
ToolsData(workspace.CableCarrierTool, {"bim.pipe_tool"}, False, False),
|
||||
ToolsData(workspace.CableTool, {"bim.cable_carrier_tool"}, False, False),
|
||||
ToolsData(workspace.FurnitureTool, {"bim.explore_tool"}, False, True),
|
||||
ToolsData(workspace.SanitaryTerminalTool, {"bim.furniture_tool"}, False, False),
|
||||
ToolsData(workspace.LightFixtureTool, {"bim.sanitary_terminal_tool"}, False, False),
|
||||
ToolsData(workspace.ElectricApplianceTool, {"bim.light_fixture_tool"}, False, False),
|
||||
ToolsData(workspace.GeographicElement, {"bim.electric_appliance_tool"}, False, False),
|
||||
ToolsData(workspace.ColumnTool, {"bim.explore_tool"}, False, True),
|
||||
ToolsData(workspace.BeamTool, {"bim.column_tool"}, False, False),
|
||||
ToolsData(workspace.MemberTool, {"bim.beam_tool"}, False, False),
|
||||
ToolsData(workspace.PlateTool, {"bim.member_tool"}, False, False),
|
||||
ToolsData(workspace.FootingTool, {"bim.plate_tool"}, False, False),
|
||||
ToolsData(workspace.PileTool, {"bim.footing_tool"}, False, False),
|
||||
ToolsData(workspace.DoorTool, {"bim.explore_tool"}, False, True),
|
||||
ToolsData(workspace.WindowTool, {"bim.door_tool"}, False, False),
|
||||
ToolsData(workspace.SlabTool, {"bim.explore_tool"}, False, True),
|
||||
ToolsData(workspace.RoofTool, {"bim.slab_tool"}, False, False),
|
||||
ToolsData(workspace.StairFlightTool, {"bim.roof_tool"}, False, False),
|
||||
ToolsData(workspace.RampFlightTool, {"bim.stair_flight_tool"}, False, False),
|
||||
ToolsData(workspace.WallTool, {"bim.explore_tool"}, True, True),
|
||||
ToolsData(workspace.RailingTool, {"bim.wall_tool"}, False, False),
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.BimTool, after={"bim.explore_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DuctTool, after={"bim.explore_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.PipeTool, after={"bim.duct_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableCarrierTool, after={"bim.pipe_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.CableTool, after={"bim.cable_carrier_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.FurnitureTool, after={"bim.explore_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(
|
||||
workspace.SanitaryTerminalTool, after={"bim.furniture_tool"}, separator=False, group=False
|
||||
)
|
||||
bpy.utils.register_tool(
|
||||
workspace.LightFixtureTool, after={"bim.sanitary_terminal_tool"}, separator=False, group=False
|
||||
)
|
||||
bpy.utils.register_tool(
|
||||
workspace.ElectricApplianceTool, after={"bim.light_fixture_tool"}, separator=False, group=False
|
||||
)
|
||||
bpy.utils.register_tool(workspace.ColumnTool, after={"bim.explore_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.BeamTool, after={"bim.column_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.MemberTool, after={"bim.beam_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.PlateTool, after={"bim.member_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.FootingTool, after={"bim.plate_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.PileTool, after={"bim.footing_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.DoorTool, after={"bim.explore_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.WindowTool, after={"bim.door_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.SlabTool, after={"bim.explore_tool"}, separator=False, group=True)
|
||||
bpy.utils.register_tool(workspace.RoofTool, after={"bim.slab_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.StairFlightTool, after={"bim.roof_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.RampFlightTool, after={"bim.stair_flight_tool"}, separator=False, group=False)
|
||||
bpy.utils.register_tool(workspace.WallTool, after={"bim.explore_tool"}, separator=True, group=True)
|
||||
bpy.utils.register_tool(workspace.RailingTool, after={"bim.wall_tool"}, separator=False, group=False)
|
||||
for tool_data in tools:
|
||||
bpy.utils.register_tool(
|
||||
tool_data.tool, after=tool_data.after, separator=tool_data.separator, group=tool_data.group
|
||||
)
|
||||
|
||||
bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties)
|
||||
bpy.types.Scene.BIMPolylineProperties = bpy.props.PointerProperty(type=prop.BIMPolylineProperties)
|
||||
@@ -251,25 +262,8 @@ def register():
|
||||
|
||||
def unregister():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.unregister_tool(workspace.WallTool)
|
||||
bpy.utils.unregister_tool(workspace.RailingTool)
|
||||
bpy.utils.unregister_tool(workspace.SlabTool)
|
||||
bpy.utils.unregister_tool(workspace.RoofTool)
|
||||
bpy.utils.unregister_tool(workspace.DoorTool)
|
||||
bpy.utils.unregister_tool(workspace.WindowTool)
|
||||
bpy.utils.unregister_tool(workspace.ColumnTool)
|
||||
bpy.utils.unregister_tool(workspace.BeamTool)
|
||||
bpy.utils.unregister_tool(workspace.MemberTool)
|
||||
bpy.utils.unregister_tool(workspace.FootingTool)
|
||||
bpy.utils.unregister_tool(workspace.FurnitureTool)
|
||||
bpy.utils.unregister_tool(workspace.SanitaryTerminalTool)
|
||||
bpy.utils.unregister_tool(workspace.LightFixtureTool)
|
||||
bpy.utils.unregister_tool(workspace.ElectricApplianceTool)
|
||||
bpy.utils.unregister_tool(workspace.DuctTool)
|
||||
bpy.utils.unregister_tool(workspace.PipeTool)
|
||||
bpy.utils.unregister_tool(workspace.CableCarrierTool)
|
||||
bpy.utils.unregister_tool(workspace.CableTool)
|
||||
bpy.utils.unregister_tool(workspace.BimTool)
|
||||
for tool_data in reversed(tools):
|
||||
bpy.utils.unregister_tool(tool_data.tool)
|
||||
|
||||
del bpy.types.Scene.BIMModelProperties
|
||||
del bpy.types.Scene.BIMPolylineProperties
|
||||
|
||||
@@ -76,11 +76,13 @@ class AuthoringData:
|
||||
cls.data["paginated_relating_types"] = cls.paginated_relating_types()
|
||||
|
||||
cls.data["type_thumbnail"] = cls.type_thumbnail() # Only after .relating_type_id_current()
|
||||
cls.data["materials"] = cls.materials()
|
||||
cls.data["is_voidable_element"] = cls.is_voidable_element()
|
||||
cls.data["has_visible_openings"] = cls.has_visible_openings()
|
||||
cls.data["has_visible_boundaries"] = cls.has_visible_boundaries()
|
||||
cls.data["active_class"] = cls.active_class()
|
||||
cls.data["active_material_usage"] = cls.active_material_usage()
|
||||
cls.data["has_extrusion"] = cls.has_extrusion()
|
||||
cls.data["is_representation_item_active"] = cls.is_representation_item_active()
|
||||
# After is_representation_item_active.
|
||||
cls.data["is_representation_item_swept_solid"] = cls.is_representation_item_swept_solid()
|
||||
@@ -114,6 +116,12 @@ class AuthoringData:
|
||||
def type_thumbnail(cls):
|
||||
return cls.type_thumbnails.get(int(cls.data["relating_type_id_current"] or 0), 0)
|
||||
|
||||
@classmethod
|
||||
def materials(cls):
|
||||
results = [("0", "None", "No material")]
|
||||
results.extend([(str(m.id()), m.Name or "Unnamed", "") for m in tool.Ifc.get().by_type("IfcMaterial")])
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def total_types(cls):
|
||||
return len(cls.data["type_elements_filtered"])
|
||||
@@ -240,6 +248,19 @@ class AuthoringData:
|
||||
if (obj := tool.Blender.get_active_object()) and (element := tool.Ifc.get_entity(obj)):
|
||||
return tool.Model.get_usage_type(element)
|
||||
|
||||
@classmethod
|
||||
def has_extrusion(cls) -> bool:
|
||||
if not (obj := tool.Blender.get_active_object()) or not (
|
||||
representation := tool.Geometry.get_active_representation(obj)
|
||||
):
|
||||
return False
|
||||
|
||||
# Skip representation items.
|
||||
if not representation.is_a("IfcShapeRepresentation"):
|
||||
return False
|
||||
|
||||
return bool(tool.Model.get_extrusion(representation))
|
||||
|
||||
@classmethod
|
||||
def is_representation_item_active(cls) -> bool:
|
||||
if not (obj := tool.Blender.get_active_object()):
|
||||
|
||||
@@ -88,7 +88,49 @@ def update_door_modifier_representation(obj: bpy.types.Object) -> None:
|
||||
# (Model/Body defined only BEFORE Plan/Body to prevent #2744)
|
||||
body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
representation_data["context"] = body
|
||||
representation_data["part_of_product"] = ifcopenshell.util.representation.get_part_of_product(element, body)
|
||||
model_representation = ifcopenshell.api.run("geometry.add_door_representation", ifc_file, **representation_data)
|
||||
representation_data["part_of_product"] = None
|
||||
if fallback_material := (int(props.lining_material) or int(props.panel_material)):
|
||||
lining_material = tool.Ifc.get().by_id(int(props.lining_material) or fallback_material)
|
||||
panel_material = tool.Ifc.get().by_id(int(props.panel_material) or fallback_material)
|
||||
should_create_new_material_set = False
|
||||
if material := ifcopenshell.util.element.get_material(element):
|
||||
if (
|
||||
material.is_a("IfcMaterialConstituent")
|
||||
and len(names := [c.Name for c in material.MaterialConstituents]) == 2
|
||||
and set(names) == {"Lining", "Framing"}
|
||||
):
|
||||
should_create_new_material_set = False
|
||||
else:
|
||||
should_create_new_material_set = True
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
if not material.is_a("IfcMaterial") and not ifc_file.get_total_inverses(material):
|
||||
ifcopenshell.api.material.remove_material_set(ifc_file, material=material)
|
||||
else:
|
||||
should_create_new_material_set = True
|
||||
if should_create_new_material_set:
|
||||
material_set = ifcopenshell.api.material.add_material_set(ifc_file, set_type="IfcMaterialConstituentSet")
|
||||
ifcopenshell.api.material.add_constituent(
|
||||
ifc_file, constituent_set=material_set, material=lining_material, name="Lining"
|
||||
)
|
||||
ifcopenshell.api.material.add_constituent(
|
||||
ifc_file, constituent_set=material_set, material=panel_material, name="Framing"
|
||||
)
|
||||
ifcopenshell.api.material.assign_material(ifc_file, products=[element], material=material_set)
|
||||
|
||||
styles = {
|
||||
"Lining": ifcopenshell.util.representation.get_material_style(lining_material, body),
|
||||
"Framing": ifcopenshell.util.representation.get_material_style(panel_material, body),
|
||||
}
|
||||
for item in model_representation.Items:
|
||||
if aspect := ifcopenshell.util.representation.get_item_shape_aspect(model_representation, item):
|
||||
if style := styles.get(aspect.Name, None):
|
||||
ifcopenshell.api.style.assign_item_style(ifc_file, item=item, style=style)
|
||||
elif material := ifcopenshell.util.element.get_material(element):
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
if not material.is_a("IfcMaterial") and not ifc_file.get_total_inverses(material):
|
||||
ifcopenshell.api.material.remove_material_set(ifc_file, material=material)
|
||||
tool.Model.replace_object_ifc_representation(body, obj, model_representation)
|
||||
|
||||
# Body/PLAN_VIEW representation
|
||||
|
||||
@@ -114,7 +114,7 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
props.ifc_predefined_type = "FOOTING_BEAM"
|
||||
props.representation_template = "PROFILESET"
|
||||
elif self.ifc_element_type == "IfcPileType":
|
||||
props.ifc_predefined_type = "BORED"
|
||||
props.ifc_predefined_type = "COHESION"
|
||||
props.representation_template = "PROFILESET"
|
||||
|
||||
elif self.ifc_element_type == "IfcDuctSegmentType":
|
||||
@@ -143,6 +143,13 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif self.ifc_element_type == "IfcElectricApplianceType":
|
||||
props.ifc_predefined_type = "WASHINGMACHINE"
|
||||
props.representation_template = "MESH"
|
||||
elif self.ifc_element_type == "IfcGeographicElementType":
|
||||
if tool.Ifc.get().schema == "IFC4":
|
||||
props.ifc_predefined_type = "USERDEFINED"
|
||||
props.ifc_userdefined_type = "VEGETATION"
|
||||
elif tool.Ifc.get().schema == "IFC4X3":
|
||||
props.ifc_predefined_type = "VEGETATION"
|
||||
props.representation_template = "MESH"
|
||||
|
||||
bpy.ops.bim.add_element()
|
||||
|
||||
@@ -319,6 +326,12 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
|
||||
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, tool.Ifc.get().schema)[0]
|
||||
material = ifcopenshell.util.element.get_material(relating_type)
|
||||
|
||||
existing_context = None
|
||||
for existing_occurrence in ifcopenshell.util.element.get_types(relating_type):
|
||||
if existing_obj := tool.Ifc.get_object(existing_occurrence):
|
||||
existing_context = tool.Geometry.get_active_representation_context(existing_obj)
|
||||
break
|
||||
|
||||
if material and material.is_a("IfcMaterialProfileSet"):
|
||||
if obj := profile.DumbProfileGenerator(relating_type).generate():
|
||||
tool.Blender.select_and_activate_single_object(context, obj)
|
||||
@@ -408,7 +421,10 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
||||
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if existing_context:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, existing_context)
|
||||
else:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation and element.Representation:
|
||||
representation = element.Representation.Representations[0]
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ def get_relating_type_id(self, context):
|
||||
return AuthoringData.data["relating_type_id"]
|
||||
|
||||
|
||||
def get_materials(self, context):
|
||||
if not AuthoringData.is_loaded:
|
||||
AuthoringData.load()
|
||||
return AuthoringData.data["materials"]
|
||||
|
||||
|
||||
def update_ifc_class(self, context):
|
||||
bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class)
|
||||
AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id()
|
||||
@@ -621,6 +627,10 @@ class BIMDoorProperties(PropertyGroup):
|
||||
frame_thickness: bpy.props.FloatProperty(name="Window Frame Thickness", default=0.035, subtype="DISTANCE")
|
||||
frame_depth: bpy.props.FloatProperty(name="Window Frame Depth", default=0.035, subtype="DISTANCE")
|
||||
|
||||
# Material properties
|
||||
panel_material: bpy.props.EnumProperty(name="Panel Material", items=get_materials, options=set())
|
||||
lining_material: bpy.props.EnumProperty(name="Lining Material", items=get_materials, options=set())
|
||||
|
||||
def get_general_kwargs(self, convert_to_project_units=False):
|
||||
kwargs = {
|
||||
"door_type": self.door_type,
|
||||
|
||||
@@ -94,18 +94,18 @@ class GenerateSpacesFromWalls(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.generate_spaces_from_walls(tool.Ifc, tool.Spatial, tool.Collector)
|
||||
|
||||
|
||||
class ToggleSpaceVisibility(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ToggleSpaceVisibility(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_space_visibility"
|
||||
bl_label = "Toggle Space Visibility"
|
||||
bl_description = "Toggle spaces visilibility types between WIRED and TEXTURED."
|
||||
bl_options = {"REGISTER"}
|
||||
bl_description = "Change the space visibility"
|
||||
|
||||
def execute(self, context):
|
||||
core.toggle_space_visibility(tool.Ifc, tool.Spatial)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleHideSpaces(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class ToggleHideSpaces(bpy.types.Operator):
|
||||
bl_idname = "bim.toggle_hide_spaces"
|
||||
bl_label = "Toggle Hide Spaces"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@@ -534,15 +534,20 @@ class BIM_PT_door(bpy.types.Panel):
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
lining_props = props.get_lining_kwargs()
|
||||
self.layout.label(text="Lining properties")
|
||||
self.layout.label(text="Lining Properties")
|
||||
for prop in lining_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
panel_props = props.get_panel_kwargs()
|
||||
self.layout.label(text="Panel properties")
|
||||
self.layout.label(text="Panel Properties")
|
||||
for prop in panel_props:
|
||||
self.layout.prop(props, prop)
|
||||
|
||||
self.layout.use_property_split = True
|
||||
self.layout.label(text="Material Properties")
|
||||
self.layout.prop(props, "lining_material")
|
||||
self.layout.prop(props, "panel_material")
|
||||
|
||||
update_door_modifier_bmesh(context)
|
||||
|
||||
else:
|
||||
|
||||
@@ -238,6 +238,17 @@ class ElectricApplianceTool(BimTool):
|
||||
ifc_element_type = "IfcElectricApplianceType"
|
||||
|
||||
|
||||
class GeographicElement(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "bim.geographic_element_tool"
|
||||
bl_label = "Geographic Element Tool"
|
||||
bl_description = "Create and edit terrain and vegetation landscape geographic elements"
|
||||
bl_icon = os.path.join(os.path.dirname(__file__), "ops.authoring.geographicelement")
|
||||
bl_widget = None
|
||||
ifc_element_type = "IfcGeographicElementType"
|
||||
|
||||
|
||||
class ColumnTool(BimTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
@@ -1042,7 +1053,7 @@ class EditObjectUI:
|
||||
row.label(text="Mode") if ui_context != "TOOL_HEADER" else row
|
||||
|
||||
if AuthoringData.data["active_material_usage"] == "LAYER3":
|
||||
if len(context.selected_objects) == 1:
|
||||
if len(context.selected_objects) == 1 and AuthoringData.data["has_extrusion"]:
|
||||
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||
add_layout_hotkey_operator(row, "Edit Profile", "S_E", "", ui_context)
|
||||
elif (
|
||||
@@ -1231,7 +1242,11 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
if self.active_material_usage == "LAYER3":
|
||||
# Edit LAYER3 profile
|
||||
if bpy.context.active_object and bpy.context.active_object.mode == "OBJECT":
|
||||
if (
|
||||
active_object.mode == "OBJECT"
|
||||
and (representation := tool.Geometry.get_active_representation(active_object))
|
||||
and tool.Model.get_extrusion(representation)
|
||||
):
|
||||
bpy.ops.bim.enable_editing_extrusion_profile()
|
||||
elif self.active_material_usage == "LAYER2":
|
||||
# Extend LAYER2 to cursor
|
||||
|
||||
@@ -217,17 +217,16 @@ class ActorData:
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"the_actor": cls.the_actor(),
|
||||
"actors": cls.actors(),
|
||||
"actor_class_enum": cls.actor_class_enum(),
|
||||
"get_actor_type_enum": cls.get_actor_type_enum(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
cls.data["actor_class"] = cls.actor_class()
|
||||
cls.data["actor_type"] = cls.actor_type()
|
||||
cls.data["the_actor"] = cls.the_actor()
|
||||
cls.data["actors"] = cls.actors()
|
||||
|
||||
@classmethod
|
||||
def the_actor(cls):
|
||||
ifc_class = bpy.context.scene.BIMOwnerProperties.actor_type
|
||||
if not (ifc_class := bpy.context.scene.BIMOwnerProperties.actor_type):
|
||||
ifc_class = cls.actor_type()[0][0]
|
||||
return [(str(p.id()), p[0] or "Unnamed", "") for p in tool.Ifc.get().by_type(ifc_class)]
|
||||
|
||||
@classmethod
|
||||
@@ -249,7 +248,7 @@ class ActorData:
|
||||
return actors
|
||||
|
||||
@classmethod
|
||||
def actor_class_enum(cls) -> list[tuple[str, str, str]]:
|
||||
def actor_class(cls) -> list[tuple[str, str, str]]:
|
||||
version = tool.Ifc.get_schema()
|
||||
return [
|
||||
("IfcActor", "Actor", get_entity_doc(version, "IfcActor").get("description", "")),
|
||||
@@ -257,7 +256,7 @@ class ActorData:
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_actor_type_enum(cls) -> list[tuple[str, str, str]]:
|
||||
def actor_type(cls) -> list[tuple[str, str, str]]:
|
||||
version = tool.Ifc.get_schema()
|
||||
return [
|
||||
("IfcPerson", "Person", get_entity_doc(version, "IfcPerson").get("description", "")),
|
||||
|
||||
@@ -65,16 +65,16 @@ def update_actor_class(self, context):
|
||||
ActorData.data["actors"] = ActorData.actors()
|
||||
|
||||
|
||||
def get_actor_class_enum(self, context):
|
||||
def get_actor_class(self, context):
|
||||
if not ActorData.is_loaded:
|
||||
ActorData.load()
|
||||
return ActorData.data["actor_class_enum"]
|
||||
return ActorData.data["actor_class"]
|
||||
|
||||
|
||||
def get_actor_type_enum(self, context):
|
||||
def get_actor_type(self, context):
|
||||
if not ActorData.is_loaded:
|
||||
ActorData.load()
|
||||
return ActorData.data["actor_type_enum"]
|
||||
return ActorData.data["actor_type"]
|
||||
|
||||
|
||||
class BIMOwnerProperties(PropertyGroup):
|
||||
@@ -106,12 +106,12 @@ class BIMOwnerProperties(PropertyGroup):
|
||||
active_actor_id: IntProperty(name="Active Actor Id")
|
||||
actor_attributes: CollectionProperty(name="Actor Attributes", type=Attribute)
|
||||
actor_class: EnumProperty(
|
||||
items=get_actor_class_enum,
|
||||
items=get_actor_class,
|
||||
name="Actor Type",
|
||||
update=update_actor_class,
|
||||
)
|
||||
actor_type: EnumProperty(
|
||||
items=get_actor_type_enum,
|
||||
items=get_actor_type,
|
||||
name="Actor Type",
|
||||
update=update_actor_type,
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
from . import ui, prop, operator, workspace, gizmo, decorator
|
||||
|
||||
classes = (
|
||||
operator.AddProjectLibrary,
|
||||
operator.AppendEntireLibrary,
|
||||
operator.AppendInspectedLinkedElement,
|
||||
operator.AppendLibraryElement,
|
||||
@@ -35,6 +36,7 @@ classes = (
|
||||
operator.DisableCulling,
|
||||
operator.DisableEditingHeader,
|
||||
operator.EditHeader,
|
||||
operator.EditProjectLibrary,
|
||||
operator.EnableCulling,
|
||||
operator.EnableEditingHeader,
|
||||
operator.ExportIFC,
|
||||
|
||||
@@ -118,6 +118,7 @@ class ProjectLibraryData:
|
||||
cls.data["project_libraries"] = cls.project_libraries()
|
||||
# After .project_libraries().
|
||||
cls.data["project_libraries_enum"] = cls.project_libraries_enum()
|
||||
cls.data["parent_libraries_enum"] = cls.parent_libraries_enum()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -132,13 +133,30 @@ class ProjectLibraryData:
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def project_libraries_enum(cls) -> list[tuple[str, str, str]]:
|
||||
def project_libraries_enum(cls) -> list[tuple[str, str, str, str, int]]:
|
||||
results = [
|
||||
("*", "All Libraries", "Show all elements"),
|
||||
("-", "No Library", "Show elements without library assigned"),
|
||||
("*", "All Libraries", "Show all elements", "", 0),
|
||||
("-", "No Library", "Show elements without library assigned", "", 1),
|
||||
]
|
||||
for data in cls.data["project_libraries"].values():
|
||||
results.append((str(data["id"]), data["Name"] or "", data["Description"] or ""))
|
||||
props = tool.Project.get_project_props()
|
||||
libs = []
|
||||
for i, data in enumerate(cls.data["project_libraries"].values(), len(results)):
|
||||
icon = "GREASEPENCIL" if props.editing_project_library_id == data["id"] else ""
|
||||
libs.append((str(data["id"]), data["Name"] or "Unnamed", data["Description"] or "", icon, i))
|
||||
libs.sort(key=lambda x: x[1])
|
||||
results += libs
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
def parent_libraries_enum(cls) -> list[tuple[str, str, str]]:
|
||||
results: list[tuple[str, str, str]] = []
|
||||
library_file = IfcStore.library_file
|
||||
if library_file is None or library_file.schema == "IFC2X3":
|
||||
return results
|
||||
project = library_file.by_type("IfcProject")[0]
|
||||
results.append((str(project.id()), f"IfcProject {project.Name or 'Unnamed'}", project.Description or ""))
|
||||
for library_id, data in cls.data["project_libraries"].items():
|
||||
results.append((str(library_id), data["Name"] or "Unnamed", data["Description"] or ""))
|
||||
return results
|
||||
|
||||
|
||||
|
||||
@@ -25,10 +25,13 @@ import tempfile
|
||||
import traceback
|
||||
import subprocess
|
||||
import datetime
|
||||
import ifcopenshell.api.attribute
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.file
|
||||
@@ -40,6 +43,7 @@ import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.unit
|
||||
import bonsai.bim.handler
|
||||
import bonsai.bim.helper
|
||||
import bonsai.bim.schema
|
||||
import bonsai.tool as tool
|
||||
import bonsai.core.project as core
|
||||
@@ -54,7 +58,7 @@ from collections import defaultdict
|
||||
from mathutils import Vector, Matrix
|
||||
from bpy.app.handlers import persistent
|
||||
from ifcopenshell.geom import ShapeElementType
|
||||
from bonsai.bim.module.project.data import LinksData
|
||||
from bonsai.bim.module.project.data import LinksData, ProjectLibraryData
|
||||
from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator, MeasureDecorator
|
||||
from bonsai.bim.module.model.decorator import PolylineDecorator
|
||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||
@@ -288,11 +292,17 @@ class ChangeLibraryElement(bpy.types.Operator):
|
||||
is_declarable = project_libraries_exist and element.is_a("IfcObjectDefinition")
|
||||
new.is_declarable = is_declarable
|
||||
|
||||
selected_library = self.props.selected_project_library
|
||||
# is_declared.
|
||||
if not is_declarable:
|
||||
new.is_declared = False
|
||||
elif (has_context := element.HasContext) and has_context[0].RelatingContext.is_a("IfcProjectLibrary"):
|
||||
new.is_declared = True
|
||||
elif has_context := element.HasContext:
|
||||
relating_context: ifcopenshell.entity_instance
|
||||
relating_context = has_context[0].RelatingContext
|
||||
if selected_library in ("-", "*"):
|
||||
new.is_declared = relating_context.is_a("IfcProjectLibrary")
|
||||
else:
|
||||
new.is_declared = relating_context == self.library_file.by_id(int(selected_library))
|
||||
|
||||
# is_appended.
|
||||
try:
|
||||
@@ -313,7 +323,7 @@ class RewindLibrary(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.props = tool.Project.get_project_props()
|
||||
total_breadcrumbs = len(self.props.library_breadcrumb)
|
||||
if total_breadcrumbs < 2:
|
||||
bpy.ops.bim.refresh_library()
|
||||
@@ -328,9 +338,13 @@ class RewindLibrary(bpy.types.Operator):
|
||||
class AssignLibraryDeclaration(bpy.types.Operator):
|
||||
bl_idname = "bim.assign_library_declaration"
|
||||
bl_label = "Assign Library Declaration"
|
||||
bl_description = "Assign element to the active library. If no specific library selected, will assign it to the first library in the file."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
definition: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
definition: int
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
IfcStore.library_file.begin_transaction()
|
||||
@@ -341,17 +355,22 @@ class AssignLibraryDeclaration(bpy.types.Operator):
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.file = IfcStore.library_file
|
||||
ifcopenshell.api.run(
|
||||
"project.assign_declaration",
|
||||
self.file,
|
||||
definitions=[self.file.by_id(self.definition)],
|
||||
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||
props = tool.Project.get_project_props()
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
|
||||
if props.selected_project_library in ("*", "-"):
|
||||
project_library = library_file.by_type("IfcProjectLibrary")[0]
|
||||
else:
|
||||
project_library = library_file.by_id(int(props.selected_project_library))
|
||||
|
||||
ifcopenshell.api.project.assign_declaration(
|
||||
library_file,
|
||||
definitions=[library_file.by_id(self.definition)],
|
||||
relating_context=project_library,
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
bpy.ops.bim.rewind_library()
|
||||
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||
|
||||
tool.Project.update_current_library_page()
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -367,6 +386,9 @@ class UnassignLibraryDeclaration(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
definition: bpy.props.IntProperty()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
definition: int
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
IfcStore.library_file.begin_transaction()
|
||||
@@ -377,17 +399,18 @@ class UnassignLibraryDeclaration(bpy.types.Operator):
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.file = IfcStore.library_file
|
||||
ifcopenshell.api.run(
|
||||
"project.unassign_declaration",
|
||||
self.file,
|
||||
definitions=[self.file.by_id(self.definition)],
|
||||
relating_context=self.file.by_type("IfcProjectLibrary")[0],
|
||||
props = tool.Project.get_project_props()
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
|
||||
element = library_file.by_id(self.definition)
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
library_file,
|
||||
definitions=[library_file.by_id(self.definition)],
|
||||
relating_context=element.HasContext[0].RelatingContext,
|
||||
)
|
||||
element_name = self.props.active_library_element
|
||||
bpy.ops.bim.rewind_library()
|
||||
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||
|
||||
tool.Project.update_current_library_page()
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
@@ -585,6 +608,90 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_importer.create_style(element)
|
||||
|
||||
|
||||
class EditProjectLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.edit_project_library"
|
||||
bl_label = "Edit Project Library"
|
||||
bl_description = "Apply changes for currently edited library."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
library_file.begin_transaction()
|
||||
result = self._execute(context)
|
||||
library_file.end_transaction()
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Project.get_project_props()
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
project_library = library_file.by_id(props.editing_project_library_id)
|
||||
attributes = bonsai.bim.helper.export_attributes(props.project_library_attributes)
|
||||
ifcopenshell.api.attribute.edit_attributes(library_file, project_library, attributes)
|
||||
|
||||
# Update parent library.
|
||||
previous_parent_library = tool.Project.get_parent_library(project_library)
|
||||
new_parent_library = library_file.by_id(int(props.parent_library))
|
||||
if previous_parent_library != new_parent_library:
|
||||
if previous_parent_library.is_a("IfcProject"):
|
||||
# Then new one is IfcProjectLibrary.
|
||||
ifcopenshell.api.nest.assign_object(library_file, [project_library], new_parent_library)
|
||||
else: # Previous is IfcProjectLibrary.
|
||||
ifcopenshell.api.nest.unassign_object(library_file, [project_library])
|
||||
# If new one is IfcProject, then it's already assigned by default.
|
||||
if new_parent_library.is_a("IfcProjectLibrary"):
|
||||
ifcopenshell.api.nest.assign_object(library_file, [project_library], new_parent_library)
|
||||
|
||||
props.is_editing_project_library = False
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.library_file.undo()
|
||||
|
||||
def commit(self, data):
|
||||
IfcStore.library_file.redo()
|
||||
|
||||
|
||||
class AddProjectLibrary(bpy.types.Operator):
|
||||
bl_idname = "bim.add_project_library"
|
||||
bl_label = "Add Project Library"
|
||||
bl_description = "Add new IfcProjectLibrary to the currently selected library."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
IfcStore.begin_transaction(self)
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
library_file.begin_transaction()
|
||||
result = self._execute(context)
|
||||
library_file.end_transaction()
|
||||
IfcStore.add_transaction_operation(self)
|
||||
IfcStore.end_transaction(self)
|
||||
return result
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Project.get_project_props()
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
project = library_file.by_type("IfcProject")[0]
|
||||
project_library = ifcopenshell.api.root.create_entity(library_file, "IfcProjectLibrary")
|
||||
ifcopenshell.api.project.assign_declaration(library_file, [project_library], project)
|
||||
ProjectLibraryData.load() # Update enum.
|
||||
props.selected_project_library = str(project_library.id())
|
||||
props.is_editing_project_library = True
|
||||
return {"FINISHED"}
|
||||
|
||||
def rollback(self, data):
|
||||
IfcStore.library_file.undo()
|
||||
|
||||
def commit(self, data):
|
||||
IfcStore.library_file.redo()
|
||||
|
||||
|
||||
class EnableEditingHeader(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_header"
|
||||
bl_label = "Enable Editing Header"
|
||||
@@ -1028,17 +1135,12 @@ class LinkIfc(bpy.types.Operator):
|
||||
self.report({"INFO"}, "Can't link the current .blend file")
|
||||
continue
|
||||
new = context.scene.BIMProjectProperties.links.add()
|
||||
if self.use_relative_path and (ifc_filepath := tool.Ifc.get_path()):
|
||||
try:
|
||||
filepath = filepath.relative_to(Path(ifc_filepath).parent)
|
||||
except:
|
||||
pass # Perhaps on another drive or something
|
||||
# Store link paths as posix for cross-platform.
|
||||
new.name = filepath.as_posix()
|
||||
status = bpy.ops.bim.load_link(filepath=filepath.as_posix(), use_cache=self.use_cache)
|
||||
filepath = tool.Ifc.get_uri(filepath, use_relative_path=self.use_relative_path)
|
||||
new.name = filepath
|
||||
status = bpy.ops.bim.load_link(filepath=filepath, use_cache=self.use_cache)
|
||||
if status == {"CANCELLED"}:
|
||||
error_msg = (
|
||||
f'Error processing IFC file "{filepath.as_posix()}" '
|
||||
f'Error processing IFC file "{filepath}" '
|
||||
"was critical and blend file either wasn't saved or wasn't updated. "
|
||||
"See logs above in system console for details."
|
||||
)
|
||||
@@ -1152,7 +1254,7 @@ class LoadLink(bpy.types.Operator):
|
||||
os.remove(blend_filepath)
|
||||
|
||||
if not blend_filepath.exists():
|
||||
pprops = bpy.context.scene.BIMProjectProperties
|
||||
pprops = tool.Project.get_project_props()
|
||||
gprops = bpy.context.scene.BIMGeoreferenceProperties
|
||||
|
||||
code = f"""
|
||||
@@ -1191,7 +1293,20 @@ except Exception as e:
|
||||
t = time.time()
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as temp_file:
|
||||
temp_file.write(code)
|
||||
run = subprocess.run([bpy.app.binary_path, "-b", "--python", temp_file.name, "--python-exit-code", "1"])
|
||||
run = subprocess.run(
|
||||
[
|
||||
bpy.app.binary_path,
|
||||
"-b",
|
||||
# Explicitly ask to enable Bonsai for this Blender instance
|
||||
# as Bonsai may be just enabled and user preferences are not saved.
|
||||
"--addons",
|
||||
tool.Blender.get_blender_addon_package_name(),
|
||||
"--python",
|
||||
temp_file.name,
|
||||
"--python-exit-code",
|
||||
"1",
|
||||
]
|
||||
)
|
||||
if run.returncode == 1:
|
||||
print("An error occurred while processing your IFC.")
|
||||
if not blend_filepath.exists() or blend_filepath.stat().st_mtime < t:
|
||||
|
||||
@@ -21,9 +21,10 @@ import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.helper
|
||||
from bonsai.bim.module.project.data import ProjectData, ProjectLibraryData
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.prop import StrProperty, ObjProperty
|
||||
from bonsai.bim.prop import StrProperty, ObjProperty, Attribute
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -64,10 +65,17 @@ def update_library_file(self: "BIMProjectProperties", context: bpy.types.Context
|
||||
if self.library_file != "0":
|
||||
filepath = next(p for p in tool.Blender.get_data_dir_paths("libraries", "*.ifc") if p.name == self.library_file)
|
||||
bpy.ops.bim.select_library_file(filepath=filepath.__str__())
|
||||
ProjectLibraryData.load()
|
||||
props = tool.Project.get_project_props()
|
||||
props.selected_project_library = "*"
|
||||
|
||||
|
||||
def update_selected_project_library(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
||||
bpy.ops.bim.refresh_library()
|
||||
if self.filter_by_library:
|
||||
bpy.ops.bim.refresh_library()
|
||||
else:
|
||||
# Ensure `.is_declared` up to date.
|
||||
tool.Project.update_current_library_page()
|
||||
|
||||
|
||||
def get_project_libaries(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
@@ -76,6 +84,41 @@ def get_project_libaries(self: "BIMProjectProperties", context: bpy.types.Contex
|
||||
return ProjectLibraryData.data["project_libraries_enum"]
|
||||
|
||||
|
||||
def filter_by_library_update(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
||||
if self.filter_by_library and self.selected_project_library == "*":
|
||||
# Filter is toggled from OFF to ON, so it was showing all elements previously either way.
|
||||
return
|
||||
bpy.ops.bim.refresh_library()
|
||||
|
||||
|
||||
def is_editing_project_library_update(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
||||
if self.is_editing_project_library:
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
self.editing_project_library_id = int(self.selected_project_library)
|
||||
project_library = library_file.by_id(int(self.selected_project_library))
|
||||
self.project_library_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(project_library, self.project_library_attributes)
|
||||
self.parent_library = str(tool.Project.get_parent_library(project_library).id())
|
||||
ProjectLibraryData.load() # Show edit icon in enum.
|
||||
return
|
||||
|
||||
del self["is_editing_project_library"]
|
||||
del self["editing_project_library_id"]
|
||||
self.project_library_attributes.clear()
|
||||
del self["parent_library"]
|
||||
ProjectLibraryData.load() # Hide edit icon in enum.
|
||||
|
||||
|
||||
def get_parent_libaries(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
|
||||
if not ProjectLibraryData.is_loaded:
|
||||
ProjectLibraryData.load()
|
||||
edited_library = str(self.editing_project_library_id)
|
||||
# Prevent assigning library to itself.
|
||||
enum_items = [i for i in ProjectLibraryData.data["parent_libraries_enum"] if i[0] != edited_library]
|
||||
return enum_items
|
||||
|
||||
|
||||
def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context) -> None:
|
||||
self.filter_categories.clear()
|
||||
if self.filter_mode == "NONE":
|
||||
@@ -282,6 +325,26 @@ class BIMProjectProperties(PropertyGroup):
|
||||
description="Project library to display elements from",
|
||||
update=update_selected_project_library,
|
||||
)
|
||||
filter_by_library: BoolProperty(
|
||||
name="Filter by Library",
|
||||
description="Filter library elements based on selected library. If unselected can be used to assign selected library to library elements.",
|
||||
default=True,
|
||||
update=filter_by_library_update,
|
||||
)
|
||||
is_editing_project_library: BoolProperty(
|
||||
name="Is Editing Project Library",
|
||||
description="Toggle editing for currently selected project library",
|
||||
update=is_editing_project_library_update,
|
||||
)
|
||||
editing_project_library_id: IntProperty(
|
||||
description="Needed to keep track of currently edited library when user changes currently selected library in dropdown."
|
||||
)
|
||||
project_library_attributes: CollectionProperty(name="Project Library Attributes", type=Attribute)
|
||||
parent_library: EnumProperty(
|
||||
name="Parent Library",
|
||||
description="Parent library that library is assigned to (either IfcProject or IfcProjectLibrary).",
|
||||
items=get_parent_libaries,
|
||||
)
|
||||
|
||||
use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False)
|
||||
queried_obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
@@ -348,6 +411,11 @@ class BIMProjectProperties(PropertyGroup):
|
||||
|
||||
library_file: str
|
||||
selected_project_library: Union[Literal["*", "-"], str]
|
||||
filter_by_library: bool
|
||||
is_editing_project_library: bool
|
||||
editing_project_library_id: int
|
||||
project_library_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
parent_library: str
|
||||
|
||||
use_relative_project_path: bool
|
||||
queried_obj: Union[bpy.types.Object, None]
|
||||
|
||||
@@ -21,7 +21,7 @@ import bpy
|
||||
import os
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.helper import prop_with_search
|
||||
from bonsai.bim.helper import prop_with_search, draw_attributes
|
||||
from bpy.types import Panel, Menu, UIList
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.module.project.data import ProjectData, LinksData
|
||||
@@ -54,16 +54,19 @@ class BIM_MT_recent_projects(Menu):
|
||||
bl_label = "Open Recent IFC Project"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
paths = tool.Project.get_recent_ifc_projects()
|
||||
if not paths:
|
||||
self.layout.label(text="No Recent IFC Projects")
|
||||
return
|
||||
|
||||
for path in paths:
|
||||
op = self.layout.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons["IFC"].icon_id)
|
||||
row = layout.row()
|
||||
op = row.operator("bim.load_project", text=path.name, icon_value=bonsai.bim.icons["IFC"].icon_id)
|
||||
op.filepath = str(path)
|
||||
op.should_start_fresh_session = True
|
||||
op.use_detailed_tooltip = True
|
||||
row.enabled = path.is_file()
|
||||
|
||||
self.layout.separator()
|
||||
self.layout.operator("bim.clear_recent_ifc_projects", icon="TRASH")
|
||||
@@ -128,7 +131,7 @@ class BIM_PT_project(Panel):
|
||||
self.layout.use_property_decorate = False
|
||||
self.layout.use_property_split = True
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
pprops = self.props = tool.Project.get_project_props()
|
||||
self.file = IfcStore.get_file()
|
||||
if pprops.is_loading:
|
||||
self.draw_advanced_loading_ui(context)
|
||||
@@ -148,7 +151,7 @@ class BIM_PT_project(Panel):
|
||||
self.draw_unsaved_project_ui(context)
|
||||
|
||||
def draw_advanced_loading_ui(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
pprops = self.props
|
||||
prop_with_search(self.layout, pprops, "filter_mode")
|
||||
if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -222,7 +225,7 @@ class BIM_PT_project(Panel):
|
||||
row.operator("bim.load_project_elements")
|
||||
|
||||
def draw_editing_buttons(self, context, row):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
pprops = self.props
|
||||
if IfcStore.get_file():
|
||||
if pprops.is_editing:
|
||||
row.operator("bim.edit_header", icon="CHECKMARK", text="")
|
||||
@@ -231,7 +234,7 @@ class BIM_PT_project(Panel):
|
||||
row.operator("bim.enable_editing_header", icon="GREASEPENCIL", text="")
|
||||
|
||||
def draw_editable_file_info(self, context):
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
pprops = self.props
|
||||
|
||||
if IfcStore.get_file():
|
||||
row = self.layout.row(align=True)
|
||||
@@ -316,7 +319,7 @@ class BIM_PT_new_project_wizard(Panel):
|
||||
self.layout.use_property_split = True
|
||||
|
||||
props = context.scene.BIMProperties
|
||||
pprops = context.scene.BIMProjectProperties
|
||||
pprops = tool.Project.get_project_props()
|
||||
prop_with_search(self.layout, pprops, "export_schema")
|
||||
row = self.layout.row()
|
||||
row.prop(context.scene.unit_settings, "system")
|
||||
@@ -344,7 +347,7 @@ class BIM_PT_project_library(Panel):
|
||||
def draw(self, context):
|
||||
self.layout.use_property_decorate = False
|
||||
self.layout.use_property_split = True
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.props = tool.Project.get_project_props()
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "library_file", text="")
|
||||
if self.props.library_file == "0":
|
||||
@@ -363,11 +366,38 @@ class BIM_PT_project_library(Panel):
|
||||
row.label(text="No Library Loaded", icon="ASSET_MANAGER")
|
||||
|
||||
def draw_library_ul(self):
|
||||
layout = self.layout
|
||||
props = self.props
|
||||
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
library_is_selected = props.selected_project_library not in ("*", "-")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(self.props, "selected_project_library", text="")
|
||||
|
||||
if library_file.schema != "IFC2X3":
|
||||
row.operator("bim.add_project_library", text="", icon="ADD")
|
||||
|
||||
if library_is_selected and not props.is_editing_project_library:
|
||||
row.prop(props, "is_editing_project_library", text="", icon="GREASEPENCIL")
|
||||
|
||||
row.prop(self.props, "filter_by_library", text="", icon="FILTER")
|
||||
|
||||
if props.is_editing_project_library:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.edit_project_library", text="Save Attributes", icon="GREASEPENCIL")
|
||||
row.prop(props, "is_editing_project_library", text="", icon="CANCEL")
|
||||
draw_attributes(props.project_library_attributes, layout)
|
||||
row = layout.row()
|
||||
row.prop(props, "parent_library", text="Parent Library")
|
||||
layout.separator()
|
||||
|
||||
if not self.props.library_elements:
|
||||
row = self.layout.row()
|
||||
row.label(text="No Assets Found", icon="ERROR")
|
||||
return
|
||||
self.layout.prop(self.props, "selected_project_library", text="")
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=self.props.active_library_element or "Top Level Assets")
|
||||
if self.props.active_library_element:
|
||||
@@ -393,7 +423,7 @@ class BIM_PT_links(Panel):
|
||||
bl_parent_id = "BIM_PT_tab_project_setup"
|
||||
|
||||
def draw(self, context):
|
||||
self.props = context.scene.BIMProjectProperties
|
||||
self.props = tool.Project.get_project_props()
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.link_ifc")
|
||||
if self.props.links:
|
||||
|
||||
@@ -55,7 +55,11 @@ class Data:
|
||||
for name, data in sorted(psetqtos.items()):
|
||||
pset = ifc_file.by_id(data["id"])
|
||||
pset_uses = ifcopenshell.util.element.get_elements_by_pset(pset)
|
||||
has_template = bool(tool.Pset.get_pset_template(name))
|
||||
pset_template = tool.Pset.get_pset_template(name)
|
||||
if has_template := bool(pset_template):
|
||||
template_available_in_ui = pset_template
|
||||
else:
|
||||
template_available_in_ui = False
|
||||
results.append(
|
||||
{
|
||||
"id": data["id"],
|
||||
@@ -113,7 +117,10 @@ class ObjectPsetsData(Data):
|
||||
if not element:
|
||||
return []
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(
|
||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element), pset_only=True
|
||||
element.is_a(),
|
||||
ifcopenshell.util.element.get_predefined_type(element),
|
||||
pset_only=True,
|
||||
schema=tool.Ifc.get_schema(),
|
||||
)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(element, psets_only=True, should_inherit=False).keys()
|
||||
@@ -126,7 +133,10 @@ class ObjectPsetsData(Data):
|
||||
if not element:
|
||||
return []
|
||||
qtos = bonsai.bim.schema.ifc.psetqto.get_applicable(
|
||||
element.is_a(), ifcopenshell.util.element.get_predefined_type(element), qto_only=True
|
||||
element.is_a(),
|
||||
ifcopenshell.util.element.get_predefined_type(element),
|
||||
qto_only=True,
|
||||
schema=tool.Ifc.get_schema(),
|
||||
)
|
||||
return cls.format_pset_enum(qtos)
|
||||
|
||||
@@ -184,7 +194,9 @@ class MaterialPsetsData(Data):
|
||||
if material.ifc_definition_id:
|
||||
material = tool.Ifc.get().by_id(material.ifc_definition_id)
|
||||
category = getattr(material, "Category", None) or None
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(props.material_type, category, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(
|
||||
props.material_type, category, pset_only=True, schema=tool.Ifc.get_schema()
|
||||
)
|
||||
psetnames = cls.format_pset_enum(psets)
|
||||
assigned_names = ifcopenshell.util.element.get_psets(
|
||||
material, psets_only=True, should_inherit=False
|
||||
@@ -214,7 +226,7 @@ class TaskQtosData(Data):
|
||||
@classmethod
|
||||
def load(cls):
|
||||
wprops = bpy.context.scene.BIMWorkScheduleProperties
|
||||
tprops = bpy.context.scene.BIMTaskTreeProperties
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
ifc_definition_id = tprops.tasks[wprops.active_task_index].ifc_definition_id
|
||||
cls.data = {"qtos": cls.psetqtos(tool.Ifc.get().by_id(ifc_definition_id), qtos_only=True)}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -180,15 +180,16 @@ class RemovePset(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
|
||||
class AddPset(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class AddPset(bpy.types.Operator):
|
||||
bl_idname = "bim.add_pset"
|
||||
bl_label = "Add Pset"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
obj: bpy.props.StringProperty()
|
||||
obj_type: bpy.props.StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.add_pset(tool.Ifc, tool.Pset, tool.Blender, obj_name=self.obj, obj_type=self.obj_type)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnsharePset(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -490,14 +491,14 @@ class SavePsetAsTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOpera
|
||||
pset_id: bpy.props.IntProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
if tool.Blender.get_enum_safe(props, "pset_template_files") is None:
|
||||
self.report({"ERROR"}, "No template files found. You can create one in Property Set Templates UI.")
|
||||
return {"CANCELLED"}
|
||||
return context.window_manager.invoke_props_dialog(self, width=250)
|
||||
|
||||
def draw(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
self.layout.prop(props, "pset_template_files", text="Template File")
|
||||
|
||||
def _execute(self, context):
|
||||
|
||||
@@ -38,7 +38,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import Union, Literal, TYPE_CHECKING, get_args
|
||||
from typing import Union, Literal, TYPE_CHECKING, get_args, Literal
|
||||
|
||||
psetnames = {}
|
||||
qtonames = {}
|
||||
@@ -105,7 +105,7 @@ def get_material_set_pset_names(self, context):
|
||||
if not ifc_class or "Set" not in ifc_class:
|
||||
return []
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -117,7 +117,7 @@ def get_material_set_item_pset_names(self, context):
|
||||
return []
|
||||
ifc_class = tool.Ifc.get().by_id(ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -126,7 +126,7 @@ def get_task_qto_names(self, context):
|
||||
global qtonames
|
||||
ifc_class = "IfcTask"
|
||||
if ifc_class not in qtonames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
|
||||
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return qtonames[ifc_class]
|
||||
|
||||
@@ -137,7 +137,7 @@ def get_resource_pset_names(self, context):
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -148,7 +148,7 @@ def get_resource_qto_names(self, context):
|
||||
rtprops = context.scene.BIMResourceTreeProperties
|
||||
ifc_class = IfcStore.get_file().by_id(rtprops.resources[rprops.active_resource_index].ifc_definition_id).is_a()
|
||||
if ifc_class not in qtonames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
|
||||
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return qtonames[ifc_class]
|
||||
|
||||
@@ -157,7 +157,7 @@ def get_group_pset_names(self, context):
|
||||
global psetnames
|
||||
ifc_class = "IfcGroup"
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -166,7 +166,7 @@ def get_group_qto_names(self, context):
|
||||
global qtonames
|
||||
ifc_class = "IfcGroup"
|
||||
if ifc_class not in qtonames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, qto_only=True, schema=tool.Ifc.get_schema())
|
||||
qtonames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return qtonames[ifc_class]
|
||||
|
||||
@@ -176,7 +176,7 @@ def get_profile_pset_names(self, context):
|
||||
pprops = context.scene.BIMProfileProperties
|
||||
ifc_class = IfcStore.get_file().by_id(pprops.profiles[pprops.active_profile_index].ifc_definition_id).is_a()
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -185,7 +185,7 @@ def get_work_schedule_pset_names(self, context):
|
||||
global psetnames
|
||||
ifc_class = "IfcWorkSchedule"
|
||||
if ifc_class not in psetnames:
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True)
|
||||
psets = bonsai.bim.schema.ifc.psetqto.get_applicable(ifc_class, pset_only=True, schema=tool.Ifc.get_schema())
|
||||
psetnames[ifc_class] = blender_formatted_enum_from_psets(psets)
|
||||
return psetnames[ifc_class]
|
||||
|
||||
@@ -241,6 +241,7 @@ class IfcProperty(PropertyGroup):
|
||||
if TYPE_CHECKING:
|
||||
metadata: Attribute
|
||||
value_type: IfcPropertyValueType
|
||||
enumerated_value: IfcPropertyEnumeratedValue
|
||||
|
||||
|
||||
class PsetProperties(PropertyGroup):
|
||||
@@ -259,6 +260,17 @@ class PsetProperties(PropertyGroup):
|
||||
prop_name: StringProperty(name="Property Name", default="MyProperty")
|
||||
prop_value: StringProperty(name="Property Value", default="Some Value")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
active_pset_id: int
|
||||
active_pset_has_template: bool
|
||||
active_pset_name: str
|
||||
active_pset_type: Literal["-", "PSET", "QTO"]
|
||||
properties: bpy.types.bpy_prop_collection_idprop[IfcProperty]
|
||||
pset_name: str
|
||||
qto_name: str
|
||||
prop_name: str
|
||||
prop_value: str
|
||||
|
||||
|
||||
class RenameProperties(PropertyGroup):
|
||||
pset_name: StringProperty(name="Pset")
|
||||
|
||||
@@ -40,7 +40,7 @@ from typing import Any, Optional, TYPE_CHECKING
|
||||
from typing_extensions import assert_never
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.pset.prop import IfcProperty
|
||||
from bonsai.bim.module.pset.prop import IfcProperty, PsetProperties
|
||||
|
||||
|
||||
def draw_property(prop: IfcProperty, layout: bpy.types.UILayout, copy_operator: Optional[str] = None) -> None:
|
||||
@@ -73,7 +73,7 @@ def draw_single_property(prop: IfcProperty, layout: bpy.types.UILayout, copy_ope
|
||||
|
||||
|
||||
def draw_enumerated_property(
|
||||
prop: bpy.types.PropertyGroup, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
prop: IfcProperty, layout: bpy.types.UILayout, copy_operator: Optional[str] = None
|
||||
) -> None:
|
||||
value_name = prop.metadata.get_value_name()
|
||||
if not value_name:
|
||||
@@ -99,7 +99,7 @@ def draw_psetqto_ui(
|
||||
context: bpy.types.Context,
|
||||
pset_id: int,
|
||||
pset: dict[str, Any],
|
||||
props: bpy.types.PropertyGroup,
|
||||
props: PsetProperties,
|
||||
layout: bpy.types.UILayout,
|
||||
obj_type: tool.Ifc.OBJECT_TYPE,
|
||||
allow_removing: bool = True,
|
||||
@@ -143,6 +143,8 @@ def draw_psetqto_ui(
|
||||
op.obj_type = obj_type
|
||||
if pset["has_template"]:
|
||||
row.label(text="", icon="ASSET_MANAGER")
|
||||
op = row.operator("bim.pset_templates_ui_select", text="", icon="ZOOM_SELECTED")
|
||||
op.pset_id = pset_id
|
||||
else:
|
||||
op = row.operator("bim.save_pset_as_template", icon="ASSET_MANAGER", text="")
|
||||
op.pset_id = pset_id
|
||||
@@ -219,9 +221,7 @@ def draw_psetqto_ui(
|
||||
row.label(text="No Properties")
|
||||
|
||||
|
||||
def draw_psetqto_editable_ui(
|
||||
box: bpy.types.UILayout, props: bpy.types.PropertyGroup, prop: bpy.types.PropertyGroup
|
||||
) -> None:
|
||||
def draw_psetqto_editable_ui(box: bpy.types.UILayout, props: PsetProperties, prop: IfcProperty) -> None:
|
||||
row = box.row(align=True)
|
||||
draw_property(prop, row, copy_operator="bim.copy_property_to_selection")
|
||||
|
||||
@@ -493,7 +493,8 @@ class BIM_PT_task_qtos(Panel):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.active_work_schedule_id:
|
||||
return False
|
||||
total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
total_tasks = len(tprops.tasks)
|
||||
if total_tasks > 0 and props.active_task_index < total_tasks:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -35,6 +35,7 @@ classes = (
|
||||
operator.RemovePsetTemplate,
|
||||
operator.RemovePsetTemplateFile,
|
||||
operator.SavePsetTemplateFile,
|
||||
operator.SelectPsetTemplatesInUI,
|
||||
prop.PsetTemplate,
|
||||
prop.EnumerationValues,
|
||||
prop.PropTemplate,
|
||||
|
||||
@@ -111,14 +111,8 @@ class PsetTemplatesData:
|
||||
@classmethod
|
||||
def pset_template_files(cls) -> list[tuple[str, str, str]]:
|
||||
results: list[tuple[str, str, str]] = []
|
||||
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
|
||||
results.append((f.__str__(), f.stem, "Global Pset Template"))
|
||||
|
||||
pset_dir = tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir)
|
||||
if os.path.isdir(pset_dir):
|
||||
for path in pathlib.Path(pset_dir).glob("*.ifc"):
|
||||
results.append((str(path), path.stem, "Project Pset Template"))
|
||||
|
||||
for path, pset_location in tool.PsetTemplate.get_pset_template_files():
|
||||
results.append((str(path), path.stem, pset_location))
|
||||
return sorted(results, key=lambda x: x[1])
|
||||
|
||||
@classmethod
|
||||
@@ -126,13 +120,14 @@ class PsetTemplatesData:
|
||||
if not cls.data["pset_template_files"]:
|
||||
return []
|
||||
if not IfcStore.pset_template_file:
|
||||
IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
IfcStore.pset_template_path = props.pset_template_files
|
||||
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
|
||||
return [(str(t.id()), t.Name, "") for t in IfcStore.pset_template_file.by_type("IfcPropertySetTemplate")]
|
||||
|
||||
@classmethod
|
||||
def pset_template(cls) -> dict[str, Any]:
|
||||
props = bpy.context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
template_id = props.pset_templates
|
||||
if not template_id:
|
||||
return {}
|
||||
@@ -145,7 +140,7 @@ class PsetTemplatesData:
|
||||
|
||||
@classmethod
|
||||
def prop_templates(cls) -> list[dict[str, Any]]:
|
||||
props = bpy.context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
template_id = props.pset_templates
|
||||
if not template_id:
|
||||
return []
|
||||
|
||||
@@ -49,7 +49,8 @@ class AddPsetTemplateFile(bpy.types.Operator):
|
||||
template.write(filepath)
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
bonsai.bim.schema.reload(tool.Ifc.get().schema)
|
||||
context.scene.BIMPsetTemplateProperties.pset_template_files = filepath
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
props.pset_template_files = filepath
|
||||
tool.PsetTemplate.enable_editing_pset_template()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -67,7 +68,8 @@ class AddPsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator
|
||||
self.template_file.write(IfcStore.pset_template_path)
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
bonsai.bim.schema.reload(tool.Ifc.get().schema)
|
||||
context.scene.BIMPsetTemplateProperties.pset_templates = str(template.id())
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
props.pset_templates = str(template.id())
|
||||
|
||||
|
||||
class RemovePsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator):
|
||||
@@ -76,7 +78,7 @@ class RemovePsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOpera
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
current_pset_template_id = int(props.pset_templates)
|
||||
if props.active_pset_template_id == current_pset_template_id:
|
||||
bpy.ops.bim.disable_editing_pset_template()
|
||||
@@ -107,7 +109,7 @@ class DisableEditingPsetTemplate(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
props.active_pset_template_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -119,7 +121,7 @@ class EnableEditingPropTemplate(bpy.types.Operator):
|
||||
prop_template: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
tool.PsetTemplate.enable_editing_prop_template(tool.Ifc.get().by_id(self.prop_template))
|
||||
tool.PsetTemplate.enable_editing_prop_template(IfcStore.pset_template_file.by_id(self.prop_template))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -130,7 +132,8 @@ class DeletePropEnum(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
active_prop = props.active_prop_template
|
||||
active_prop.enum_values.remove(self.index)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -142,7 +145,8 @@ class AddPropEnum(bpy.types.Operator):
|
||||
index: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
active_prop = context.scene.BIMPsetTemplateProperties.active_prop_template
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
active_prop = props.active_prop_template
|
||||
active_prop.enum_values.add()
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -153,7 +157,7 @@ class DisableEditingPropTemplate(bpy.types.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
props.active_prop_template_id = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -164,7 +168,7 @@ class EditPsetTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperato
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
ifcopenshell.api.run(
|
||||
"pset_template.edit_pset_template",
|
||||
IfcStore.pset_template_file,
|
||||
@@ -209,7 +213,7 @@ class RemovePsetTemplateFile(bpy.types.Operator):
|
||||
bonsai.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
# Ensure enum is valid after deletion.
|
||||
self.props = context.scene.BIMPsetTemplateProperties
|
||||
self.props = tool.PsetTemplate.get_pset_template_props()
|
||||
if not tool.Blender.ensure_enum_is_valid(self.props, "pset_template_files"):
|
||||
self.update_template_files_prop(context)
|
||||
return {"FINISHED"}
|
||||
@@ -226,7 +230,7 @@ class AddPropTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperator
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
pset_template_id = props.active_pset_template_id or int(props.pset_templates)
|
||||
prop_template = ifcopenshell.api.run(
|
||||
"pset_template.add_prop_template",
|
||||
@@ -264,7 +268,7 @@ class EditPropTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperato
|
||||
|
||||
def _execute(self, context):
|
||||
assert IfcStore.pset_template_file
|
||||
props = context.scene.BIMPsetTemplateProperties
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
active_prop_template = props.active_prop_template
|
||||
if props.active_prop_template.template_type == "P_ENUMERATEDVALUE":
|
||||
data_type = props.active_prop_template.get_value_name()
|
||||
@@ -296,3 +300,38 @@ class EditPropTemplate(bpy.types.Operator, tool.PsetTemplate.PsetTemplateOperato
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
if tool.Ifc.get():
|
||||
bonsai.bim.schema.reload(tool.Ifc.get().schema)
|
||||
|
||||
|
||||
class SelectPsetTemplatesInUI(bpy.types.Operator):
|
||||
bl_idname = "bim.pset_templates_ui_select"
|
||||
bl_label = "Select Pset Template in UI"
|
||||
bl_description = "Select pset template for the provided pset in Pset Template UI."
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
pset_id: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
pset = ifc_file.by_id(self.pset_id)
|
||||
|
||||
pset_template_file: ifcopenshell.file
|
||||
pset_template = None
|
||||
path = None
|
||||
for path, _ in tool.PsetTemplate.get_pset_template_files():
|
||||
pset_template_file = ifcopenshell.open(path)
|
||||
for pset_template in pset_template_file.by_type("IfcPropertySetTemplate"):
|
||||
if pset_template.Name == pset.Name:
|
||||
break
|
||||
|
||||
if pset_template is None or path is None:
|
||||
self.report({"INFO"}, f"Couldn't find pset template for the provided pset: '{pset.Name}'.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
props.pset_template_files = str(path)
|
||||
props.pset_templates = str(pset_template.id())
|
||||
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"Pset Template '{pset.Name}' is selected in Pset Templates UI. See Property Set Templates in Project tab.",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -36,7 +36,7 @@ class BIM_PT_pset_template(Panel):
|
||||
if not PsetTemplatesData.is_loaded:
|
||||
PsetTemplatesData.load()
|
||||
|
||||
self.props = context.scene.BIMPsetTemplateProperties
|
||||
self.props = tool.PsetTemplate.get_pset_template_props()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
if PsetTemplatesData.data["pset_template_files"]:
|
||||
|
||||
@@ -43,7 +43,7 @@ class EnableReassignClass(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.active_object
|
||||
self.file = IfcStore.get_file()
|
||||
self.file = tool.Ifc.get()
|
||||
ifc_class = obj.name.split("/")[0]
|
||||
context.active_object.BIMObjectProperties.is_reassigning_class = True
|
||||
ifc_products = [
|
||||
@@ -93,11 +93,11 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
objects = [bpy.data.objects.get(self.obj)]
|
||||
else:
|
||||
objects = set(context.selected_objects + [context.active_object])
|
||||
self.file = IfcStore.get_file()
|
||||
self.file = tool.Ifc.get()
|
||||
root_props = context.scene.BIMRootProperties
|
||||
ifc_product: str = root_props.ifc_product
|
||||
ifc_class: str = root_props.ifc_class
|
||||
type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class)), None)
|
||||
type_ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class, self.file.schema)), None)
|
||||
|
||||
predefined_type = root_props.ifc_predefined_type
|
||||
if predefined_type == "USERDEFINED":
|
||||
@@ -157,7 +157,7 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
reassigned_elements.add(element)
|
||||
|
||||
for obj in objects_to_update:
|
||||
obj.name = tool.Loader.get_name(tool.Ifc.get_entity(obj))
|
||||
tool.Root.set_object_name(obj, tool.Ifc.get_entity(obj))
|
||||
tool.Collector.assign(obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -211,31 +211,28 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
continue
|
||||
|
||||
should_add_representation = self.should_add_representation
|
||||
export_mesh_to_tesselation = False
|
||||
if self.should_add_representation and isinstance(obj.data, bpy.types.Mesh) and obj.data.polygons:
|
||||
should_add_representation = False
|
||||
export_mesh_to_tesselation = True
|
||||
|
||||
if obj.scale != (1, 1, 1):
|
||||
if obj.data.users > 1:
|
||||
bpy.ops.object.make_single_user(
|
||||
object=True, obdata=True, material=False, animation=False, obdata_animation=False
|
||||
)
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True, properties=False)
|
||||
if tool.Geometry.mesh_has_loose_geometry(obj.data):
|
||||
self.report(
|
||||
{"WARNING"},
|
||||
f"Mesh '{obj.data.name}' has loose geometry, loose geometry will be ignored to save mesh to IFC as a tessellation.",
|
||||
)
|
||||
|
||||
element = core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=should_add_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
)
|
||||
|
||||
if export_mesh_to_tesselation:
|
||||
element = core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=False,
|
||||
)
|
||||
representation = tool.Geometry.export_mesh_to_tessellation(obj, ifc_context)
|
||||
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
@@ -247,6 +244,18 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
else:
|
||||
element = core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=self.should_add_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
)
|
||||
|
||||
context.view_layer.objects.active = active_object
|
||||
|
||||
@@ -412,7 +421,8 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if representation_template == "EMTPY" or not ifc_context:
|
||||
pass
|
||||
elif representation_template == "OBJ" and props.representation_obj:
|
||||
obj.matrix_world = props.representation_obj.matrix_world
|
||||
obj.matrix_world = props.representation_obj.matrix_world.copy()
|
||||
obj.scale = (1, 1, 1)
|
||||
representation = tool.Geometry.export_mesh_to_tessellation(props.representation_obj, ifc_context)
|
||||
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
|
||||
@@ -20,6 +20,7 @@ import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.type
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.root.data import IfcClassData
|
||||
from bpy.types import PropertyGroup
|
||||
@@ -66,11 +67,12 @@ def refresh_classes(self, context):
|
||||
self.representation_template = "OBJ"
|
||||
self.representation_obj = obj
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
# When switching between ElementType and Element, keep the same class and predefined type if possible
|
||||
if self.ifc_product == "IfcElement":
|
||||
ifc_class = next(iter(ifcopenshell.util.type.get_applicable_entities(old_class)), None)
|
||||
ifc_class = next(iter(ifcopenshell.util.type.get_applicable_entities(old_class, ifc_file.schema)), None)
|
||||
elif self.ifc_product == "IfcElementType":
|
||||
ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class)), None)
|
||||
ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class, ifc_file.schema)), None)
|
||||
else:
|
||||
return
|
||||
if ifc_class:
|
||||
|
||||
@@ -40,6 +40,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
def getTaskColumns(self, context):
|
||||
@@ -343,6 +344,26 @@ class Task(PropertyGroup):
|
||||
is_predecessor: BoolProperty(name="Is Predecessor")
|
||||
is_successor: BoolProperty(name="Is Successor")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
identification: str
|
||||
ifc_definition_id: int
|
||||
has_children: bool
|
||||
is_selected: bool
|
||||
is_expanded: bool
|
||||
has_bar_visual: bool
|
||||
level_index: int
|
||||
duration: str
|
||||
start: str
|
||||
finish: str
|
||||
calendar: str
|
||||
derived_start: str
|
||||
derived_finish: str
|
||||
derived_duration: str
|
||||
derived_calendar: str
|
||||
is_predecessor: bool
|
||||
is_successor: bool
|
||||
|
||||
|
||||
class WorkPlan(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
@@ -479,6 +500,9 @@ class BIMTaskTreeProperties(PropertyGroup):
|
||||
# In Blender if you add many collection items it makes other property access in the same group really slow.
|
||||
tasks: CollectionProperty(name="Tasks", type=Task)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
tasks: bpy.types.bpy_prop_collection_idprop[Task]
|
||||
|
||||
|
||||
class WorkCalendar(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
|
||||
@@ -156,7 +156,7 @@ class BIM_PT_work_schedules(Panel):
|
||||
if not WorkScheduleData.is_loaded:
|
||||
WorkScheduleData.load()
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
self.tprops = tool.Sequence.get_task_tree_props()
|
||||
|
||||
if not self.props.active_work_schedule_id:
|
||||
row = self.layout.row(align=True)
|
||||
@@ -649,7 +649,8 @@ class BIM_PT_task_icom(Panel):
|
||||
props = context.scene.BIMWorkScheduleProperties
|
||||
if not props.active_work_schedule_id:
|
||||
return False
|
||||
total_tasks = len(context.scene.BIMTaskTreeProperties.tasks)
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
total_tasks = len(tprops.tasks)
|
||||
if total_tasks > 0 and props.active_task_index < total_tasks:
|
||||
return True
|
||||
return False
|
||||
@@ -659,7 +660,7 @@ class BIM_PT_task_icom(Panel):
|
||||
TaskICOMData.load()
|
||||
|
||||
self.props = context.scene.BIMWorkScheduleProperties
|
||||
self.tprops = context.scene.BIMTaskTreeProperties
|
||||
self.tprops = tool.Sequence.get_task_tree_props()
|
||||
task = self.tprops.tasks[self.props.active_task_index]
|
||||
|
||||
grid = self.layout.grid_flow(columns=3, even_columns=True)
|
||||
|
||||
@@ -55,6 +55,8 @@ class GridDecorator:
|
||||
batch.draw(shader)
|
||||
|
||||
def draw_text(self, context):
|
||||
if not tool.Blender.is_addon_enabled():
|
||||
return
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
@@ -94,6 +96,8 @@ class GridDecorator:
|
||||
blf.disable(font_id, blf.SHADOW)
|
||||
|
||||
def draw(self, context):
|
||||
if not tool.Blender.is_addon_enabled():
|
||||
return
|
||||
self.addon_prefs = tool.Blender.get_addon_preferences()
|
||||
selected_elements_color = self.addon_prefs.decorator_color_selected
|
||||
unselected_elements_color = self.addon_prefs.decorator_color_unselected
|
||||
|
||||
@@ -92,12 +92,18 @@ class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
props = context.active_object.BIMObjectSpatialProperties
|
||||
if self.container:
|
||||
container = tool.Ifc.get().by_id(self.container)
|
||||
elif (container_obj := props.container_obj) and (container := tool.Ifc.get_entity(container_obj)):
|
||||
elif (
|
||||
(obj := tool.Blender.get_active_object())
|
||||
and (props := obj.BIMObjectSpatialProperties)
|
||||
and (container_obj := props.container_obj)
|
||||
and (container := tool.Ifc.get_entity(container_obj))
|
||||
):
|
||||
pass
|
||||
for element_obj in context.selected_objects:
|
||||
else:
|
||||
return
|
||||
for element_obj in tool.Blender.get_selected_objects():
|
||||
if self.remove_from_other_containers:
|
||||
for col in element_obj.users_collection[:]:
|
||||
col.objects.unlink(element_obj)
|
||||
@@ -311,14 +317,14 @@ class SelectDecomposedElement(bpy.types.Operator):
|
||||
|
||||
class SelectDecomposedElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_decomposed_elements"
|
||||
bl_label = "Select Children"
|
||||
bl_label = "Select Elements"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
|
||||
container: bpy.props.IntProperty()
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, operator):
|
||||
return "Select all contained elements filtered by this type" + "\nALT+CLICK to select all contained elements"
|
||||
return "Select elements in the viewport based on the active item in the list" + "\nALT+CLICK to select all listed elements"
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
@@ -326,61 +332,7 @@ class SelectDecomposedElements(bpy.types.Operator):
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
ifc_file = tool.Ifc.get()
|
||||
container = ifc_file.by_id(self.container)
|
||||
props = context.scene.BIMSpatialDecompositionProperties
|
||||
element_filter = props.element_filter
|
||||
active_element = props.active_element
|
||||
|
||||
if not self.should_filter and not element_filter:
|
||||
tool.Spatial.select_products(tool.Spatial.get_decomposed_elements(container))
|
||||
return {"CANCELLED"}
|
||||
|
||||
if props.element_mode == "TYPE":
|
||||
if active_element.type == "OCCURRENCE":
|
||||
if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)):
|
||||
tool.Blender.set_active_object(obj)
|
||||
return {"CANCELLED"}
|
||||
|
||||
ifc_class = relating_type = None
|
||||
is_untyped = False
|
||||
|
||||
if self.should_filter:
|
||||
if active_element.type == "CLASS":
|
||||
ifc_class = active_element.name
|
||||
elif active_element.type == "TYPE":
|
||||
ifc_class = active_element.ifc_class
|
||||
if ifc_id := active_element.ifc_definition_id:
|
||||
relating_type = ifc_file.by_id(ifc_id)
|
||||
|
||||
elements = tool.Spatial.get_decomposed_elements(container)
|
||||
elements = tool.Spatial.filter_elements(elements, ifc_class, relating_type, is_untyped, element_filter)
|
||||
tool.Spatial.select_products(elements)
|
||||
elif props.element_mode == "DECOMPOSITION":
|
||||
occurrence = ifc_file.by_id(active_element.ifc_definition_id)
|
||||
elements = ifcopenshell.util.element.get_decomposition(occurrence)
|
||||
elements.add(occurrence)
|
||||
tool.Spatial.select_products(elements)
|
||||
elif props.element_mode == "CLASSIFICATION":
|
||||
if active_element.type == "OCCURRENCE":
|
||||
if obj := tool.Ifc.get_object(ifc_file.by_id(active_element.ifc_definition_id)):
|
||||
tool.Blender.set_active_object(obj)
|
||||
return {"CANCELLED"}
|
||||
|
||||
if active_element.type == "CLASSIFICATION":
|
||||
identification = active_element.identification
|
||||
elements = tool.Spatial.get_decomposed_elements(container)
|
||||
|
||||
def filter_element(element: ifcopenshell.entity_instance) -> bool:
|
||||
references = ifcopenshell.util.classification.get_references(element)
|
||||
if identification == "Unclassified":
|
||||
if not references:
|
||||
return True
|
||||
elif any([r for r in references if r[1].startswith(identification)]):
|
||||
return True
|
||||
return False
|
||||
|
||||
tool.Spatial.select_products(filter(filter_element, elements))
|
||||
tool.Spatial.select_products(tool.Spatial.get_filtered_elements(self.should_filter))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -401,7 +353,7 @@ class SetContainerVisibility(bpy.types.Operator):
|
||||
bl_label = "Set Container Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
container: bpy.props.IntProperty()
|
||||
should_include_children: bpy.props.BoolProperty(name="Should Include Children", default=True, options={"SKIP_SAVE"})
|
||||
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
|
||||
mode: bpy.props.StringProperty(name="Mode")
|
||||
|
||||
@classmethod
|
||||
@@ -414,7 +366,7 @@ class SetContainerVisibility(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
self.should_include_children = False
|
||||
self.should_filter = False
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -427,9 +379,7 @@ class SetContainerVisibility(bpy.types.Operator):
|
||||
else:
|
||||
should_hide = self.mode == "HIDE"
|
||||
|
||||
container = tool.Ifc.get().by_id(self.container)
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=self.should_include_children)
|
||||
for element in elements:
|
||||
for element in tool.Spatial.get_filtered_elements(self.should_filter):
|
||||
if obj := tool.Ifc.get_object(element):
|
||||
obj.hide_set(should_hide)
|
||||
for collection in obj.users_collection:
|
||||
|
||||
@@ -143,24 +143,6 @@ class BIM_PT_spatial_decomposition(Panel):
|
||||
op = col.operator("bim.set_default_container", icon="OUTLINER_COLLECTION", text="Set Default")
|
||||
op.container = ifc_definition_id
|
||||
|
||||
col = row.column(align=True)
|
||||
col.enabled = non_ifc_project_active
|
||||
op = col.operator("bim.set_container_visibility", icon="FULLSCREEN_EXIT", text="Isolate")
|
||||
op.mode = "ISOLATE"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
col = row.column(align=True)
|
||||
col.enabled = non_ifc_project_active
|
||||
op = col.operator("bim.set_container_visibility", icon="HIDE_OFF", text="")
|
||||
op.mode = "SHOW"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
col = row.column(align=True)
|
||||
col.enabled = non_ifc_project_active
|
||||
op = col.operator("bim.set_container_visibility", icon="HIDE_ON", text="")
|
||||
op.mode = "HIDE"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
# The only operator that's enabled for IfcProject.
|
||||
col = row.column(align=True)
|
||||
col.operator("bim.select_container", icon="OBJECT_DATA", text="").container = ifc_definition_id
|
||||
@@ -193,11 +175,26 @@ class BIM_PT_spatial_decomposition(Panel):
|
||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row = self.layout.row()
|
||||
row.label(
|
||||
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Elements",
|
||||
icon="FILE_3D",
|
||||
)
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
|
||||
op = row.operator("bim.set_container_visibility", icon="FULLSCREEN_EXIT", text="Isolate")
|
||||
op.mode = "ISOLATE"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
op = row.operator("bim.set_container_visibility", icon="HIDE_OFF", text="")
|
||||
op.mode = "SHOW"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
op = row.operator("bim.set_container_visibility", icon="HIDE_ON", text="")
|
||||
op.mode = "HIDE"
|
||||
op.container = ifc_definition_id
|
||||
|
||||
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
|
||||
op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
|
||||
if active_element := self.props.active_element:
|
||||
|
||||
@@ -387,12 +387,7 @@ class BrowseExternalStyle(bpy.types.Operator):
|
||||
|
||||
bpy.data.materials.remove(db["data_block"])
|
||||
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(self.filepath, Path(tool.Ifc.get_path()).parent)
|
||||
else:
|
||||
filepath = self.filepath
|
||||
filepath = Path(filepath).as_posix()
|
||||
|
||||
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
attributes = context.scene.BIMStylesProperties.external_style_attributes
|
||||
attributes["Location"].string_value = filepath
|
||||
attributes["Identification"].string_value = f"{self.data_block_type}/{self.data_block}"
|
||||
@@ -565,19 +560,9 @@ class ChooseTextureMapPath(bpy.types.Operator):
|
||||
self.report({"ERROR"}, "Provide a texture map index")
|
||||
return {"CANCELLED"}
|
||||
|
||||
abs_path = Path(self.filepath)
|
||||
if self.use_relative_path:
|
||||
parent = Path(tool.Ifc.get_path()).parent
|
||||
if abs_path.is_relative_to(parent):
|
||||
image_filepath = abs_path.relative_to(parent)
|
||||
else:
|
||||
self.report({"INFO"}, "Path is not relative to the .ifc file, it will be saved as absolute.")
|
||||
image_filepath = abs_path
|
||||
else:
|
||||
image_filepath = abs_path
|
||||
|
||||
filepath = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
texture = context.scene.BIMStylesProperties.textures[self.texture_map_index]
|
||||
texture.path = image_filepath.as_posix()
|
||||
texture.path = filepath
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -26,22 +26,24 @@ from bonsai.bim.module.system.data import PortData
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
class LoadSystems(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class LoadSystems(bpy.types.Operator):
|
||||
bl_idname = "bim.load_systems"
|
||||
bl_label = "Load Systems"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.load_systems(tool.System)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableSystemEditingUI(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableSystemEditingUI(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_system_editing_ui"
|
||||
bl_label = "Disable System Editing UI"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.disable_system_editing_ui(tool.System)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -74,23 +76,25 @@ class RemoveSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.remove_system(tool.Ifc, tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
|
||||
|
||||
class EnableEditingSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingSystem(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_system"
|
||||
bl_label = "Enable Editing System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.enable_editing_system(tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class DisableEditingSystem(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_system"
|
||||
bl_label = "Disable Editing System"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
core.disable_editing_system(tool.System)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -119,14 +123,15 @@ class UnassignSystem(bpy.types.Operator, tool.Ifc.Operator):
|
||||
core.unassign_system(tool.Ifc, system=tool.Ifc.get().by_id(self.system), product=element)
|
||||
|
||||
|
||||
class SelectSystemProducts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class SelectSystemProducts(bpy.types.Operator):
|
||||
bl_idname = "bim.select_system_products"
|
||||
bl_label = "Select System Products"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
system: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
def execute(self, context):
|
||||
core.select_system_products(tool.System, system=tool.Ifc.get().by_id(self.system))
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ShowPorts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -144,6 +149,7 @@ class ShowPorts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
# Ifc.Operator - as operator will sync object's position with IFC.
|
||||
core.show_ports(tool.Ifc, tool.System, tool.Spatial, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
@@ -157,6 +163,7 @@ class HidePorts(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return ShowPorts.poll(context)
|
||||
|
||||
def _execute(self, context):
|
||||
# Ifc.Operator - as operator will sync object and ports positions with IFC.
|
||||
core.hide_ports(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object))
|
||||
|
||||
|
||||
@@ -324,13 +331,13 @@ class SetFlowDirection(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
class LoadZones(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class LoadZones(bpy.types.Operator):
|
||||
bl_idname = "bim.load_zones"
|
||||
bl_label = "Load Zones"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMZoneProperties
|
||||
props = tool.System.get_zone_props()
|
||||
props.zones.clear()
|
||||
for zone in tool.Ifc.get().by_type("IfcZone"):
|
||||
new = props.zones.add()
|
||||
@@ -338,16 +345,18 @@ class LoadZones(bpy.types.Operator, tool.Ifc.Operator):
|
||||
new.name = zone.Name or "Unnamed"
|
||||
props.is_loaded = True
|
||||
props.is_editing = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class UnloadZones(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class UnloadZones(bpy.types.Operator):
|
||||
bl_idname = "bim.unload_zones"
|
||||
bl_label = "Unload Zones"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMZoneProperties
|
||||
props = tool.System.get_zone_props()
|
||||
props.is_loaded = False
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AddZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -370,17 +379,18 @@ class AddZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.load_zones()
|
||||
|
||||
|
||||
class EnableEditingZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class EnableEditingZone(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_zone"
|
||||
bl_label = "Enable Editing Zone"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
zone: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMZoneProperties
|
||||
def execute(self, context):
|
||||
props = tool.System.get_zone_props()
|
||||
props.attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(tool.Ifc.get().by_id(self.zone), props.attributes)
|
||||
props.is_editing = self.zone
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class DisableEditingZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -388,9 +398,10 @@ class DisableEditingZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_label = "Disable Editing Zone"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMZoneProperties
|
||||
def execute(self, context):
|
||||
props = tool.System.get_zone_props()
|
||||
props.is_editing = 0
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -399,7 +410,7 @@ class EditZone(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
props = bpy.context.scene.BIMZoneProperties
|
||||
props = tool.System.get_zone_props()
|
||||
zone = tool.Ifc.get().by_id(props.is_editing)
|
||||
attributes = bonsai.bim.helper.export_attributes(props.attributes)
|
||||
ifcopenshell.api.run("system.edit_system", tool.Ifc.get(), system=zone, attributes=attributes)
|
||||
|
||||
@@ -31,6 +31,7 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
def get_system_class(self, context):
|
||||
@@ -71,6 +72,17 @@ class BIMSystemProperties(PropertyGroup):
|
||||
name="Should Draw Decorations", description="Toggle system decorations", update=toggle_decorations
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
system_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_editing: bool
|
||||
is_adding: bool
|
||||
systems: bpy.types.bpy_prop_collection_idprop[System]
|
||||
active_system_index: int
|
||||
active_system_id: int
|
||||
edited_system_id: int
|
||||
system_class: str
|
||||
should_draw_decorations: bool
|
||||
|
||||
|
||||
class BIMZoneProperties(PropertyGroup):
|
||||
attributes: CollectionProperty(name="Attributes", type=Attribute)
|
||||
@@ -78,3 +90,10 @@ class BIMZoneProperties(PropertyGroup):
|
||||
is_editing: IntProperty(name="Is Editing", default=0)
|
||||
zones: CollectionProperty(name="Zones", type=Zone)
|
||||
active_zone_index: IntProperty(name="Active Zone Index")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
is_loaded: bool
|
||||
is_editing: int
|
||||
zones: bpy.types.bpy_prop_collection_idprop[Zone]
|
||||
active_zone_index: int
|
||||
|
||||
@@ -350,7 +350,7 @@ class BIM_PT_zones(Panel):
|
||||
def draw(self, context):
|
||||
if not ZonesData.is_loaded:
|
||||
ZonesData.load()
|
||||
self.props = context.scene.BIMZoneProperties
|
||||
self.props = tool.System.get_zone_props()
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text="{} Zones Found".format(ZonesData.data["total_zones"]), icon="SEQ_STRIP_META")
|
||||
@@ -394,7 +394,7 @@ class BIM_PT_active_object_zones(Panel):
|
||||
def draw(self, context):
|
||||
if not ActiveObjectZonesData.is_loaded:
|
||||
ActiveObjectZonesData.load()
|
||||
self.props = context.scene.BIMZoneProperties
|
||||
self.props = tool.System.get_zone_props()
|
||||
|
||||
for zone in ActiveObjectZonesData.data["zones"]:
|
||||
row = self.layout.row()
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
import os
|
||||
import bpy
|
||||
import bmesh
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import textwrap
|
||||
@@ -32,11 +31,10 @@ import ifcopenshell
|
||||
import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim import import_ifc
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
from bonsai.bim.prop import StrProperty
|
||||
from bonsai.bim.ui import IFCFileSelector
|
||||
from bonsai.bim.helper import get_enum_items
|
||||
from mathutils import Vector, Matrix, Euler
|
||||
from mathutils import Vector, Euler
|
||||
from math import radians
|
||||
from pathlib import Path
|
||||
from collections import namedtuple
|
||||
@@ -168,10 +166,7 @@ class SelectURIAttribute(bpy.types.Operator):
|
||||
# Do you know a better way?
|
||||
pass
|
||||
if attribute:
|
||||
filepath = self.filepath
|
||||
if self.use_relative_path:
|
||||
filepath = os.path.relpath(filepath, os.path.dirname(tool.Ifc.get_path()))
|
||||
attribute.string_value = filepath
|
||||
attribute.string_value = tool.Ifc.get_uri(self.filepath, use_relative_path=self.use_relative_path)
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import bpy
|
||||
import json
|
||||
import importlib
|
||||
import platformdirs
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.pset
|
||||
@@ -36,7 +34,6 @@ import bonsai.bim
|
||||
import bonsai.bim.schema
|
||||
import bonsai.bim.handler
|
||||
import bonsai.tool as tool
|
||||
from collections import defaultdict
|
||||
from bpy.types import PropertyGroup
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
@@ -172,6 +169,9 @@ class StrProperty(PropertyGroup):
|
||||
class ObjProperty(PropertyGroup):
|
||||
obj: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
obj: Union[bpy.types.Object, None]
|
||||
|
||||
|
||||
def update_single_file(self: "MultipleFileSelect", context: bpy.types.Context) -> None:
|
||||
self.file_list.clear()
|
||||
@@ -183,6 +183,10 @@ class MultipleFileSelect(PropertyGroup):
|
||||
single_file: bpy.props.StringProperty(name="Single File Path", description="", update=update_single_file)
|
||||
file_list: bpy.props.CollectionProperty(type=StrProperty)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
single_file: str
|
||||
file_list: bpy.types.bpy_prop_collection_idprop[StrProperty]
|
||||
|
||||
def set_file_list(self, dirname: str, files: list[str]) -> None:
|
||||
self.file_list.clear()
|
||||
|
||||
|
||||
@@ -403,8 +403,11 @@ class BIM_PT_tabs(Panel):
|
||||
bl_options = {"HIDE_HEADER"}
|
||||
|
||||
def draw(self, context):
|
||||
if not UIData.is_loaded:
|
||||
UIData.load()
|
||||
is_ifc_project = bool(tool.Ifc.get())
|
||||
aprops = tool.Blender.get_area_props(context)
|
||||
ifc_icon = f"{UIData.data['icon_color_mode']}_ifc"
|
||||
|
||||
row = self.layout.row()
|
||||
row.alignment = "CENTER"
|
||||
@@ -412,8 +415,7 @@ class BIM_PT_tabs(Panel):
|
||||
"bim.set_tab",
|
||||
text="",
|
||||
emboss=aprops.tab == "PROJECT",
|
||||
depress=True,
|
||||
icon_value=bonsai.bim.icons["IFC"].icon_id,
|
||||
icon_value=bonsai.bim.icons[ifc_icon].icon_id,
|
||||
).tab = "PROJECT"
|
||||
self.draw_tab_entry(row, "FILE_3D", "OBJECT", is_ifc_project, aprops.tab == "OBJECT")
|
||||
self.draw_tab_entry(row, "MATERIAL", "GEOMETRY", is_ifc_project, aprops.tab == "GEOMETRY")
|
||||
@@ -505,9 +507,9 @@ class BIM_PT_tabs(Panel):
|
||||
op = row.operator("bim.open_uri", text="", icon="QUESTION")
|
||||
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#incompatible-blender-features"
|
||||
|
||||
def draw_tab_entry(self, row, icon, tab_name, enabled=True, highlight=False):
|
||||
def draw_tab_entry(self, row, icon, tab_name, enabled=True, highlight=True):
|
||||
tab_entry = row.row(align=True)
|
||||
tab_entry.operator("bim.set_tab", text="", emboss=highlight, icon=icon, depress=True).tab = tab_name
|
||||
tab_entry.operator("bim.set_tab", text="", emboss=highlight, icon=icon).tab = tab_name
|
||||
tab_entry.enabled = enabled
|
||||
|
||||
|
||||
@@ -1106,19 +1108,27 @@ class BIM_PT_tab_operations(Panel):
|
||||
pass
|
||||
|
||||
|
||||
def refresh():
|
||||
UIData.is_loaded = False
|
||||
|
||||
|
||||
class UIData:
|
||||
data = {}
|
||||
is_loaded = False
|
||||
|
||||
@classmethod
|
||||
def load(cls):
|
||||
cls.data = {"version": cls.version()}
|
||||
cls.data = {"version": cls.version(), "icon_color_mode": cls.icon_color_mode()}
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
def version(cls):
|
||||
return tool.Blender.get_bonsai_version()
|
||||
|
||||
@classmethod
|
||||
def icon_color_mode(cls):
|
||||
return tool.Blender.detect_icon_color_mode()
|
||||
|
||||
|
||||
def draw_statusbar(self, context):
|
||||
if not UIData.is_loaded:
|
||||
|
||||
@@ -157,6 +157,7 @@ def remove_representation(
|
||||
assert element
|
||||
element_type = geometry.get_element_type(element)
|
||||
data = None
|
||||
has_switched_from_data = False
|
||||
if element_type and (geometry.is_mapped_representation(representation) or geometry.is_type_product(element)):
|
||||
representation = geometry.resolve_mapped_representation(representation)
|
||||
data = geometry.get_representation_data(representation)
|
||||
@@ -164,6 +165,7 @@ def remove_representation(
|
||||
for element in geometry.get_elements_of_type(element_type):
|
||||
obj = ifc.get_object(element)
|
||||
if obj:
|
||||
has_switched_from_data = True
|
||||
geometry.switch_from_representation(obj, representation)
|
||||
obj = ifc.get_object(element_type)
|
||||
if obj:
|
||||
@@ -172,11 +174,12 @@ def remove_representation(
|
||||
else:
|
||||
data = geometry.get_representation_data(representation)
|
||||
if data and geometry.has_data_users(data):
|
||||
has_switched_from_data = True
|
||||
geometry.switch_from_representation(obj, representation)
|
||||
ifc.run("geometry.unassign_representation", product=element, representation=representation)
|
||||
|
||||
ifc.run("geometry.remove_representation", representation=representation)
|
||||
if data:
|
||||
if data and not has_switched_from_data:
|
||||
geometry.delete_data(data)
|
||||
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ def edit_style(ifc: tool.Ifc, style: tool.Style) -> None:
|
||||
style.disable_editing()
|
||||
load_styles(style, style.get_active_style_type())
|
||||
if is_style_side_attribute_edited:
|
||||
style.reload_repersentations(style_element)
|
||||
style.reload_representations(style_element)
|
||||
|
||||
|
||||
def load_styles(style: tool.Style, style_type: str) -> None:
|
||||
|
||||
@@ -16,58 +16,68 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
def load_systems(system):
|
||||
if TYPE_CHECKING:
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import bonsai.tool as tool
|
||||
|
||||
|
||||
def load_systems(system: tool.System) -> None:
|
||||
system.import_systems()
|
||||
system.enable_system_editing_ui()
|
||||
system.disable_editing_system()
|
||||
|
||||
|
||||
def disable_system_editing_ui(system):
|
||||
def disable_system_editing_ui(system: tool.System) -> None:
|
||||
system.disable_editing_system()
|
||||
system.disable_system_editing_ui()
|
||||
|
||||
|
||||
def add_system(ifc, system, ifc_class=None):
|
||||
def add_system(ifc: tool.Ifc, system: tool.System, ifc_class: str) -> None:
|
||||
ifc.run("system.add_system", ifc_class=ifc_class)
|
||||
system.import_systems()
|
||||
|
||||
|
||||
def edit_system(ifc, system_tool, system=None):
|
||||
def edit_system(ifc: tool.Ifc, system_tool: tool.System, system: ifcopenshell.entity_instance) -> None:
|
||||
attributes = system_tool.export_system_attributes()
|
||||
ifc.run("system.edit_system", system=system, attributes=attributes)
|
||||
system_tool.disable_editing_system()
|
||||
system_tool.import_systems()
|
||||
|
||||
|
||||
def remove_system(ifc, system_tool, system=None):
|
||||
def remove_system(ifc: tool.Ifc, system_tool: tool.System, system: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("system.remove_system", system=system)
|
||||
system_tool.import_systems()
|
||||
|
||||
|
||||
def enable_editing_system(system_tool, system=None):
|
||||
def enable_editing_system(system_tool: tool.System, system: ifcopenshell.entity_instance) -> None:
|
||||
system_tool.import_system_attributes(system)
|
||||
system_tool.set_active_edited_system(system)
|
||||
|
||||
|
||||
def disable_editing_system(system):
|
||||
def disable_editing_system(system: tool.System) -> None:
|
||||
system.disable_editing_system()
|
||||
|
||||
|
||||
def assign_system(ifc, system=None, product=None):
|
||||
def assign_system(ifc: tool.Ifc, system: ifcopenshell.entity_instance, product: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("system.assign_system", products=[product], system=system)
|
||||
|
||||
|
||||
def unassign_system(ifc, system=None, product=None):
|
||||
def unassign_system(ifc: tool.Ifc, system: ifcopenshell.entity_instance, product: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("system.unassign_system", products=[product], system=system)
|
||||
|
||||
|
||||
def select_system_products(system_tool, system=None):
|
||||
def select_system_products(system_tool: tool.System, system: ifcopenshell.entity_instance) -> None:
|
||||
system_tool.select_system_products(system)
|
||||
system_tool.set_active_system(system)
|
||||
|
||||
|
||||
def show_ports(ifc, system, spatial, element=None):
|
||||
def show_ports(
|
||||
ifc: tool.Ifc, system: tool.System, spatial: tool.Spatial, element: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
obj = ifc.get_object(element)
|
||||
if obj and ifc.is_moved(obj):
|
||||
system.run_geometry_edit_object_placement(obj=obj)
|
||||
@@ -77,7 +87,7 @@ def show_ports(ifc, system, spatial, element=None):
|
||||
spatial.select_products(ports)
|
||||
|
||||
|
||||
def hide_ports(ifc, system, element=None):
|
||||
def hide_ports(ifc: tool.Ifc, system: tool.System, element: ifcopenshell.entity_instance) -> None:
|
||||
obj = ifc.get_object(element)
|
||||
if obj and ifc.is_moved(obj):
|
||||
system.run_geometry_edit_object_placement(obj=obj)
|
||||
@@ -91,27 +101,27 @@ def hide_ports(ifc, system, element=None):
|
||||
system.delete_element_objects(ports)
|
||||
|
||||
|
||||
def add_port(ifc, system, element=None):
|
||||
def add_port(ifc: tool.Ifc, system: tool.System, element: ifcopenshell.entity_instance) -> None:
|
||||
system.load_ports(element, system.get_ports(element))
|
||||
obj = system.create_empty_at_cursor_with_element_orientation(element)
|
||||
port = system.run_root_assign_class(obj=obj, ifc_class="IfcDistributionPort", should_add_representation=False)
|
||||
ifc.run("system.assign_port", element=element, port=port)
|
||||
|
||||
|
||||
def remove_port(ifc, system, port=None):
|
||||
def remove_port(ifc: tool.Ifc, system: tool.System, port: ifcopenshell.entity_instance) -> None:
|
||||
system.delete_element_objects([port])
|
||||
ifc.run("root.remove_product", product=port)
|
||||
|
||||
|
||||
def connect_port(ifc, port1=None, port2=None):
|
||||
def connect_port(ifc: tool.Ifc, port1: ifcopenshell.entity_instance, port2: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("system.connect_port", port1=port1, port2=port2)
|
||||
|
||||
|
||||
def disconnect_port(ifc, port=None):
|
||||
def disconnect_port(ifc: tool.Ifc, port: ifcopenshell.entity_instance) -> None:
|
||||
ifc.run("system.disconnect_port", port=port)
|
||||
|
||||
|
||||
def set_flow_direction(ifc, system, port=None, direction=None):
|
||||
def set_flow_direction(ifc: tool.Ifc, system: tool.System, port: ifcopenshell.entity_instance, direction: str) -> None:
|
||||
port2 = system.get_connected_port(port)
|
||||
if not port2:
|
||||
return
|
||||
|
||||
@@ -90,7 +90,7 @@ class Blender:
|
||||
def get_name(cls, ifc_class, name): pass
|
||||
def get_obj_ifc_definition_id(cls, obj=None, obj_type=None, context=None): pass
|
||||
def get_object_bounding_box(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_selected_objects(cls, include_active=False): pass
|
||||
def get_viewport_context(cls): pass
|
||||
def is_ifc_class_active(cls, ifc_class): pass
|
||||
def is_ifc_object(cls, obj): pass
|
||||
@@ -183,7 +183,7 @@ class Classification:
|
||||
|
||||
@interface
|
||||
class Collector:
|
||||
def assign(cls, obj): pass
|
||||
def assign(cls, obj, should_clean_users_collection=False): pass
|
||||
|
||||
|
||||
@interface
|
||||
@@ -323,8 +323,10 @@ class Drawing:
|
||||
def export_text_literal_attributes(cls, obj): pass
|
||||
def generate_drawing_matrix(cls, target_view, location_hint): pass
|
||||
def generate_drawing_name(cls, target_view, location_hint): pass
|
||||
def generate_reference_attributes(cls, reference, **attributes): pass
|
||||
def generate_sheet_identification(cls): pass
|
||||
def get_annotation_context(cls, target_view, object_type=None): pass
|
||||
def get_annotation_representation(cls, element_type): pass
|
||||
def get_assigned_product(cls, element): pass
|
||||
def get_body_context(cls): pass
|
||||
def get_default_drawing_path(cls, name): pass
|
||||
@@ -345,10 +347,10 @@ class Drawing:
|
||||
def get_name(cls, element): pass
|
||||
def get_path_filename(cls, uri): pass
|
||||
def get_reference_description(cls, reference): pass
|
||||
def generate_reference_attributes(cls, reference, **attributes): pass
|
||||
def get_reference_document(cls, reference): pass
|
||||
def get_reference_location(cls, reference): pass
|
||||
def get_references_with_location(cls, location): pass
|
||||
def get_representation(cls, element, context): pass
|
||||
def get_text_literal(cls, obj): pass
|
||||
def get_unit_system(cls): pass
|
||||
def import_assigned_product(cls, obj): pass
|
||||
@@ -365,9 +367,11 @@ class Drawing:
|
||||
def open_layout_svg(cls, uri): pass
|
||||
def open_spreadsheet(cls, uri): pass
|
||||
def open_svg(cls, filepath): pass
|
||||
def reload_representation(cls, obj, representation): pass
|
||||
def remove_literal_from_annotation(cls, obj, literal): pass
|
||||
def run_drawing_activate_model(cls): pass
|
||||
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
|
||||
def run_type_assign_type(cls, element=None, relating_type=None): pass
|
||||
def select_assigned_product(cls, drawing): pass
|
||||
def set_drawing_collection_name(cls, drawing, collection): pass
|
||||
def set_name(cls, element, name): pass
|
||||
@@ -377,6 +381,7 @@ class Drawing:
|
||||
def sync_object_placement(cls, obj): pass
|
||||
def synchronise_ifc_and_text_attributes(cls, obj): pass
|
||||
def update_embedded_svg_location(cls, uri, old_location, new_location): pass
|
||||
def update_newline_at(cls, obj): pass
|
||||
def update_text_size_pset(cls, obj): pass
|
||||
def update_text_value(cls, obj): pass
|
||||
|
||||
@@ -412,12 +417,10 @@ class Geometry:
|
||||
def get_total_representation_items(cls, obj): pass
|
||||
def has_data_users(cls, data): pass
|
||||
def has_material_style_override(cls, obj): pass
|
||||
def import_representation(cls, obj, representation, apply_openings=True): pass
|
||||
def import_representation_parameters(cls, data): pass
|
||||
def is_body_representation(cls, representation): pass
|
||||
def is_box_representation(cls, representation): pass
|
||||
def is_data_supported_for_adding_representation(cls, data): pass
|
||||
def is_edited(cls, obj): pass
|
||||
def is_mapped_representation(cls, representation): pass
|
||||
def is_type_product(cls, element): pass
|
||||
def link(cls, element, obj): pass
|
||||
@@ -756,6 +759,8 @@ class Root:
|
||||
def is_containable(cls, element): pass
|
||||
def is_drawing_annotation(cls, element): pass
|
||||
def is_element_a(cls, element, ifc_class): pass
|
||||
def is_in_aggregate_mode(cls, element): pass
|
||||
def is_in_nest_mode(cls, element): pass
|
||||
def is_spatial_element(cls, element): pass
|
||||
def link_object_data(cls, source_obj, destination_obj): pass
|
||||
def recreate_decompositions(cls, relationships, old_to_new): pass
|
||||
@@ -1010,7 +1015,6 @@ class Style:
|
||||
def get_elements_by_style(cls, style): pass
|
||||
def get_currently_edited_material(cls): pass
|
||||
def get_name(cls, obj): pass
|
||||
def get_style(cls, obj): pass
|
||||
def get_style_elements(cls, blender_material): pass
|
||||
def get_surface_rendering_attributes(cls, obj, verbose=True): pass
|
||||
def get_surface_rendering_style(cls, obj): pass
|
||||
@@ -1026,7 +1030,7 @@ class Style:
|
||||
def is_editing_styles(cls): pass
|
||||
def reload_material_from_ifc(cls, obj): pass
|
||||
def is_style_side_attribute_edited(cls, style, new_attributes): pass
|
||||
def reload_repersentations(cls, style): pass
|
||||
def reload_representations(cls, style): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -194,9 +194,8 @@ class Blender(bonsai.core.tool.Blender):
|
||||
elif obj_type == "MaterialSetItem":
|
||||
return bpy.data.objects.get(obj).BIMObjectMaterialProperties.active_material_set_item_id
|
||||
elif obj_type == "Task":
|
||||
return context.scene.BIMTaskTreeProperties.tasks[
|
||||
context.scene.BIMWorkScheduleProperties.active_task_index
|
||||
].ifc_definition_id
|
||||
tprops = tool.Sequence.get_task_tree_props()
|
||||
return tprops.tasks[context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id
|
||||
elif obj_type == "Cost":
|
||||
return context.scene.BIMCostProperties.cost_items[
|
||||
context.scene.BIMCostProperties.active_cost_item_index
|
||||
@@ -1323,6 +1322,10 @@ class Blender(bonsai.core.tool.Blender):
|
||||
name = cls.get_blender_addon_package_name()
|
||||
return importlib.import_module(name)
|
||||
|
||||
@classmethod
|
||||
def is_addon_enabled(cls) -> bool:
|
||||
return cls.get_blender_addon_package_name() in bpy.context.preferences.addons
|
||||
|
||||
@classmethod
|
||||
def get_addon_preferences(cls) -> bonsai.bim.ui.BIM_ADDON_preferences:
|
||||
blender_package_name = cls.get_blender_addon_package_name()
|
||||
|
||||
@@ -16,15 +16,27 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import mathutils
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from mathutils import Matrix, Vector
|
||||
from typing import Any, Sequence
|
||||
from typing import Any, Sequence, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.boundary.prop import BIMBoundaryProperties, BIMObjectBoundaryProperties
|
||||
|
||||
|
||||
class Boundary(bonsai.core.tool.Boundary):
|
||||
@classmethod
|
||||
def get_boundary_props(cls) -> BIMBoundaryProperties:
|
||||
return bpy.context.scene.BIMBoundaryProperties
|
||||
|
||||
@classmethod
|
||||
def get_object_boundary_props(cls, obj: bpy.types.Object) -> BIMObjectBoundaryProperties:
|
||||
return obj.BIMBoundaryProperties
|
||||
|
||||
@classmethod
|
||||
def get_assign_connection_geometry_settings(cls, obj: bpy.types.Object) -> dict[str, Any]:
|
||||
from bonsai.bim.module.geometry.helper import Helper
|
||||
@@ -76,10 +88,16 @@ class Boundary(bonsai.core.tool.Boundary):
|
||||
|
||||
@classmethod
|
||||
def decorate_boundary(cls, obj: bpy.types.Object) -> None:
|
||||
new = bpy.context.scene.BIMBoundaryProperties.boundaries.add()
|
||||
props = cls.get_boundary_props()
|
||||
new = props.boundaries.add()
|
||||
new.obj = obj
|
||||
obj.show_in_front = True
|
||||
|
||||
@classmethod
|
||||
def undecorate_boundary(cls, obj: bpy.types.Object) -> None:
|
||||
obj.show_in_front = False
|
||||
props = cls.get_boundary_props()
|
||||
for i in reversed(range(len(props.boundaries))):
|
||||
if props.boundaries[i].obj == obj:
|
||||
props.boundaries.remove(i)
|
||||
break
|
||||
|
||||
@@ -248,7 +248,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
cls, cost_item: ifcopenshell.entity_instance, related_object_type: RELATED_OBJECT_TYPE
|
||||
) -> None:
|
||||
def create_list_items(
|
||||
collection: bpy.types.bpy_prop_collection, cost_item: ifcopenshell.entity_instance, is_deep: bool
|
||||
collection: bpy.types.CollectionProperty, cost_item: ifcopenshell.entity_instance, is_deep: bool
|
||||
) -> None:
|
||||
products = cls.get_cost_item_assignments(cost_item, filter_by_type=related_object_type, is_deep=False)
|
||||
for product in products:
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
import os
|
||||
import json
|
||||
import bpy
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.express
|
||||
import ifcopenshell.express.schema
|
||||
import ifcopenshell.express.schema_class
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
import bonsai.core.style
|
||||
@@ -36,14 +35,14 @@ from typing import Iterable, Literal
|
||||
|
||||
class Debug(bonsai.core.tool.Debug):
|
||||
@classmethod
|
||||
def add_schema_identifier(cls, schema: ifcopenshell.express.schema_class.SchemaClass) -> None:
|
||||
IfcStore.schema_identifiers.append(schema.schema_name)
|
||||
def add_schema_identifier(cls, schema: W.schema_definition) -> None:
|
||||
IfcStore.schema_identifiers.append(schema.name())
|
||||
|
||||
@classmethod
|
||||
def load_express(cls, filename: str) -> ifcopenshell.express.schema_class.SchemaClass:
|
||||
def load_express(cls, filename: str) -> W.schema_definition:
|
||||
schema = ifcopenshell.express.parse(filename)
|
||||
ifcopenshell.register_schema(schema)
|
||||
return schema
|
||||
return schema.schema
|
||||
|
||||
@classmethod
|
||||
def purge_hdf5_cache(cls) -> None:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import collections
|
||||
@@ -34,8 +35,10 @@ import subprocess
|
||||
import numpy as np
|
||||
import bonsai.core.tool
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.type
|
||||
import bonsai.tool as tool
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.document
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.element
|
||||
@@ -48,9 +51,13 @@ from shapely.ops import unary_union
|
||||
from lxml import etree
|
||||
from mathutils import Vector, Matrix
|
||||
from fractions import Fraction
|
||||
from typing import Optional, Union, Iterable, Any, Literal, Sequence
|
||||
from typing import Optional, Union, Iterable, Any, Literal, Sequence, TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.drawing.prop import DocProperties, Sheet
|
||||
from bonsai.bim.module.drawing.prop import Drawing as DrawingProperties
|
||||
|
||||
|
||||
class Drawing(bonsai.core.tool.Drawing):
|
||||
ANNOTATION_DATA_TYPE = Literal["empty", "curve", "mesh"]
|
||||
@@ -80,6 +87,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
@classmethod
|
||||
def get_document_props(cls) -> DocProperties:
|
||||
return bpy.context.scene.DocProperties
|
||||
|
||||
@classmethod
|
||||
def canonicalise_class_name(cls, name: str) -> str:
|
||||
return re.sub("[^0-9a-zA-Z]+", "", name)
|
||||
@@ -819,7 +830,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def import_documents(cls, document_type: DOCUMENT_TYPE) -> None:
|
||||
dprops = bpy.context.scene.DocProperties
|
||||
dprops = cls.get_document_props()
|
||||
if document_type == "SCHEDULE":
|
||||
documents_collection = dprops.schedules
|
||||
elif document_type == "REFERENCE":
|
||||
@@ -838,7 +849,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def import_sheets(cls) -> None:
|
||||
props = bpy.context.scene.DocProperties
|
||||
props = cls.get_document_props()
|
||||
expanded_sheets = {s.ifc_definition_id for s in props.sheets if s.is_expanded}
|
||||
if not hasattr(cls, "sheet_selected_states"):
|
||||
cls.sheet_selected_states = {}
|
||||
@@ -878,13 +889,13 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
new.reference_type = reference_description
|
||||
|
||||
@classmethod
|
||||
def get_active_sheet(cls, context: bpy.types.Context) -> bpy.types.PropertyGroup:
|
||||
props = context.scene.DocProperties
|
||||
def get_active_sheet(cls, context: bpy.types.Context) -> Sheet:
|
||||
props = cls.get_document_props()
|
||||
return next(s for s in props.sheets[: props.active_sheet_index + 1][::-1] if s.is_sheet)
|
||||
|
||||
@classmethod
|
||||
def get_active_drawing_item(cls) -> Union[bpy.types.PropertyGroup, None]:
|
||||
props = bpy.context.scene.DocProperties
|
||||
def get_active_drawing_item(cls) -> Union[DrawingProperties, None]:
|
||||
props = cls.get_document_props()
|
||||
drawing_index = props.active_drawing_index
|
||||
if len(props.drawings) > drawing_index >= 0:
|
||||
item = props.drawings[drawing_index]
|
||||
@@ -892,10 +903,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
return item
|
||||
|
||||
@classmethod
|
||||
def get_active_sheet_item(
|
||||
cls, *, is_sheet: bool = False, reference_type: str = ""
|
||||
) -> Union[bpy.types.PropertyGroup, None]:
|
||||
props = bpy.context.scene.DocProperties
|
||||
def get_active_sheet_item(cls, *, is_sheet: bool = False, reference_type: str = "") -> Union[Sheet, None]:
|
||||
props = cls.get_document_props()
|
||||
sheet_index = props.active_sheet_index
|
||||
if len(props.sheets) > sheet_index >= 0:
|
||||
item = props.sheets[sheet_index]
|
||||
@@ -1320,6 +1329,12 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
context=context,
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
bpy.data.curves.remove(data)
|
||||
|
||||
assert element
|
||||
representation = ifcopenshell.util.representation.get_representation(element, context)
|
||||
assert representation
|
||||
obj.data["ios_edges_item_ids"] = (representation.Items[0].id(),)
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
@@ -2252,3 +2267,16 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
msp.add_line(*points)
|
||||
|
||||
finalize_dxf()
|
||||
|
||||
@classmethod
|
||||
def remove_drawing_from_sheet(cls, reference: ifcopenshell.entity_instance) -> None:
|
||||
import bonsai.bim.module.drawing.sheeter as sheeter
|
||||
|
||||
sheet = tool.Drawing.get_reference_document(reference)
|
||||
|
||||
sheet_builder = sheeter.SheetBuilder()
|
||||
sheet_builder.remove_drawing(reference, sheet)
|
||||
|
||||
ifcopenshell.api.document.remove_reference(tool.Ifc.get(), reference=reference)
|
||||
|
||||
tool.Drawing.import_sheets()
|
||||
|
||||
@@ -57,10 +57,14 @@ from typing_extensions import TypeIs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import Attribute
|
||||
from bonsai.bim.module.geometry.prop import BIMObjectGeometryProperties
|
||||
from bonsai.bim.module.geometry.prop import BIMObjectGeometryProperties, BIMGeometryProperties
|
||||
|
||||
|
||||
class Geometry(bonsai.core.tool.Geometry):
|
||||
@classmethod
|
||||
def get_geometry_props(cls) -> BIMGeometryProperties:
|
||||
return bpy.context.scene.BIMGeometryProperties
|
||||
|
||||
@classmethod
|
||||
def get_object_geometry_props(cls, object: bpy.types.Object) -> BIMObjectGeometryProperties:
|
||||
return object.BIMGeometryProperties
|
||||
@@ -191,6 +195,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
return bonsai.core.drawing.remove_drawing(tool.Ifc, tool.Drawing, drawing=element)
|
||||
elif element.is_a("IfcRelSpaceBoundary"):
|
||||
ifcopenshell.api.run("boundary.remove_boundary", tool.Ifc.get(), boundary=element)
|
||||
tool.Boundary.undecorate_boundary(obj)
|
||||
return bpy.data.objects.remove(obj)
|
||||
elif element.is_a("IfcGridAxis"):
|
||||
is_last_axis = False
|
||||
@@ -479,7 +484,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
|
||||
@classmethod
|
||||
def get_active_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""< IfcShapeRepresentation or None"""
|
||||
""":return: IfcRepresentation/IfcRepresentationItem or None"""
|
||||
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
|
||||
@@ -842,13 +847,13 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
representation = tool.Ifc.get().by_id(int(geometry.id.split("-")[0]))
|
||||
if geometry:
|
||||
mesh = ifc_importer.create_mesh(element, geometry)
|
||||
tool.Loader.link_mesh(geometry, mesh)
|
||||
ifc_importer.material_creator.load_existing_materials()
|
||||
shape_has_openings = False
|
||||
ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings)
|
||||
mesh.BIMMeshProperties.has_openings_applied = apply_openings
|
||||
if not shape_has_openings:
|
||||
tool.Loader.load_indexed_colour_map(representation, mesh)
|
||||
tool.Loader.link_mesh(geometry, mesh)
|
||||
meshes[mesh_name] = mesh
|
||||
|
||||
change_data(obj, element, mesh)
|
||||
@@ -1837,6 +1842,7 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
|
||||
tool.Blender.select_and_activate_single_object(bpy.context, dup_obj)
|
||||
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True, properties=False)
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.mesh.select_all(action="SELECT")
|
||||
bpy.ops.mesh.separate(type="LOOSE")
|
||||
|
||||
@@ -26,6 +26,7 @@ import ifcopenshell.util.schema
|
||||
import bonsai.core.tool
|
||||
import bonsai.bim.handler
|
||||
import bonsai.tool as tool
|
||||
from pathlib import Path
|
||||
from bonsai.bim.ifc import IfcStore, IFC_CONNECTED_TYPE
|
||||
from typing import Optional, Union, Any, final, Literal
|
||||
|
||||
@@ -247,13 +248,17 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
return (uri if not uri else os.path.join(ifc_path, uri)).replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def get_relative_uri(cls, uri: str) -> str:
|
||||
if not os.path.isabs(uri):
|
||||
return uri
|
||||
ifc_path = cls.get_path()
|
||||
def get_uri(cls, uri: str | Path, use_relative_path: bool = False) -> str:
|
||||
if not use_relative_path:
|
||||
return Path(uri).absolute().resolve().as_posix()
|
||||
uri = Path(uri)
|
||||
if not os.path.isabs(uri) or not (ifc_path := cls.get_path()):
|
||||
return uri.as_posix().replace("\\", "/")
|
||||
if Path(uri).drive != Path(ifc_path).drive:
|
||||
return uri.as_posix().replace("\\", "/")
|
||||
if os.path.isfile(ifc_path):
|
||||
ifc_path = os.path.dirname(ifc_path)
|
||||
return os.path.relpath(uri, ifc_path).replace("\\", "/")
|
||||
return Path(os.path.relpath(uri, ifc_path)).as_posix().replace("\\", "/")
|
||||
|
||||
@classmethod
|
||||
def unlink(
|
||||
|
||||
@@ -27,7 +27,7 @@ from typing import Literal, Any, Union
|
||||
class Library(bonsai.core.tool.Library):
|
||||
@classmethod
|
||||
def clear_editing_mode(cls) -> None:
|
||||
bpy.context.scene.BIMLibraryProperties.editing_mode = "-"
|
||||
bpy.context.scene.BIMLibraryProperties.editing_mode = "NONE"
|
||||
|
||||
@classmethod
|
||||
def export_library_attributes(cls) -> dict[str, Any]:
|
||||
|
||||
@@ -95,7 +95,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
def get_name(cls, element: ifcopenshell.entity_instance) -> str:
|
||||
if element.is_a("IfcGridAxis"):
|
||||
return "{}/{}".format(element.is_a(), element.AxisTag)
|
||||
return "{}/{}".format(element.is_a(), getattr(element, "Name", "None"))
|
||||
return "{}/{}".format(element.is_a(), getattr(element, "Name", "Unnamed") or "Unnamed")
|
||||
|
||||
@classmethod
|
||||
def link_mesh(
|
||||
@@ -298,6 +298,7 @@ class Loader(bonsai.core.tool.Loader):
|
||||
if is_relative:
|
||||
ifc_path = Path(tool.Ifc.get_path())
|
||||
image_url = ifc_path.parent / image_url
|
||||
image_url = image_url.absolute().resolve()
|
||||
|
||||
if not image_url.exists():
|
||||
print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.")
|
||||
@@ -592,11 +593,11 @@ class Loader(bonsai.core.tool.Loader):
|
||||
|
||||
faces_tex_coord_data = {}
|
||||
for tex_coord_index, face_remap in zip(texture_map, faces_remap, strict=True):
|
||||
faces_tex_coord_data[frozenset(face_remap)] = (tex_coord_index, face_remap)
|
||||
faces_tex_coord_data[tuple(face_remap)] = (tex_coord_index, face_remap)
|
||||
|
||||
# Apply attribute to each face
|
||||
for bface in bm.faces:
|
||||
face = frozenset(loop.vert.index for loop in bface.loops)
|
||||
face = tuple(loop.vert.index for loop in bface.loops)
|
||||
# Find the corresponding index in data list by matching ifc faceset with blender face.
|
||||
data_index = None
|
||||
if tex_coord_data := faces_tex_coord_data.get(face):
|
||||
|
||||
+12
-119
@@ -207,7 +207,6 @@ class Model(bonsai.core.tool.Model):
|
||||
)
|
||||
|
||||
surface.OuterBoundary = tool.Ifc.get().add(profile_def.OuterCurve)
|
||||
surface.InnerBoundaries = results
|
||||
if profile_def.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||
surface.InnerBoundaries = [tool.Ifc.get().add(c) for c in profile_def.InnerCurves]
|
||||
|
||||
@@ -1456,71 +1455,20 @@ class Model(bonsai.core.tool.Model):
|
||||
`assigned_material` argument is there just to indicate whether we apply material changes
|
||||
after material assignment or material unassignment.
|
||||
"""
|
||||
|
||||
if assigned_material:
|
||||
# NOTE: currently only IfcMaterials are supported
|
||||
# for anyone else we just switch representation.
|
||||
if not assigned_material.is_a("IfcMaterial"):
|
||||
tool.Geometry.reload_representation([tool.Ifc.get_object(e) for e in elements])
|
||||
return
|
||||
|
||||
# Since different elements can share meshes (e.g. occurrences without openings)
|
||||
# we need to make sure not to process them multiple times.
|
||||
meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict()
|
||||
for obj in bpy.data.objects:
|
||||
if not obj.data:
|
||||
continue
|
||||
meshes_users.setdefault(obj.data, set()).add(obj)
|
||||
|
||||
objects: set[bpy.types.Object] = set()
|
||||
for element in elements:
|
||||
obj: bpy.types.Object = tool.Ifc.get_object(element)
|
||||
if not obj or not obj.data:
|
||||
if not (obj := tool.Ifc.get_object(element)) or not obj.data:
|
||||
continue
|
||||
objects.add(obj)
|
||||
|
||||
meshes: set[bpy.types.Mesh] = {obj.data for obj in objects}
|
||||
|
||||
for mesh in meshes:
|
||||
mesh_users = meshes_users[mesh]
|
||||
|
||||
if not mesh_users.issubset(objects):
|
||||
# It's unsafe to make changes to the mesh
|
||||
# as it's used by objects unrelated to the current change.
|
||||
# E.g. material with a style was assigned to a particular occurrence
|
||||
# and this change shouldn't be applied to other occurrences and type.
|
||||
objs_to_reload = mesh_users.intersection(objects)
|
||||
for obj in objs_to_reload:
|
||||
tool.Geometry._reload_representation(obj)
|
||||
continue
|
||||
|
||||
obj = next(iter(mesh_users))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element # Type checker.
|
||||
own_material = next(iter(ifcopenshell.util.element.get_materials(element, should_inherit=False)), None)
|
||||
inherited_mstyle = tool.Geometry.get_inherited_material_style(element)
|
||||
|
||||
if assigned_material:
|
||||
if own_material:
|
||||
ms2 = tool.Material.get_style(own_material)
|
||||
ms1 = inherited_mstyle
|
||||
else:
|
||||
ms1 = None
|
||||
ms2 = inherited_mstyle
|
||||
|
||||
cls.replace_material_style(mesh, obj, ms1, ms2)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
|
||||
else: # Material unnassignment.
|
||||
if not tool.Geometry.has_geometry_without_styles(mesh):
|
||||
tool.Geometry._reload_representation(obj)
|
||||
continue
|
||||
|
||||
if not inherited_mstyle:
|
||||
continue
|
||||
|
||||
cls.replace_material_style(mesh, obj, None, inherited_mstyle)
|
||||
tool.Geometry.record_object_materials(obj)
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_occurrences_without_material_override(
|
||||
@@ -1533,61 +1481,6 @@ class Model(bonsai.core.tool.Model):
|
||||
]
|
||||
return occurrences
|
||||
|
||||
@classmethod
|
||||
def replace_material_style(
|
||||
cls,
|
||||
mesh: bpy.types.Mesh,
|
||||
obj: bpy.types.Object,
|
||||
mstyle1: Union[ifcopenshell.entity_instance, None],
|
||||
mstyle2: Union[ifcopenshell.entity_instance, None],
|
||||
) -> None:
|
||||
if mstyle1 == mstyle2:
|
||||
return
|
||||
|
||||
# Get Blender materials.
|
||||
mbstyle1, mbstyle2 = None, None
|
||||
if mstyle1:
|
||||
mbstyle1 = tool.Ifc.get_object(mstyle1)
|
||||
assert isinstance(mbstyle1, bpy.types.Material)
|
||||
if mstyle2:
|
||||
mbstyle2 = tool.Ifc.get_object(mstyle2)
|
||||
assert isinstance(mbstyle2, bpy.types.Material)
|
||||
|
||||
# Copy data to the list as mesh.materials doesn't allow to search for None.
|
||||
materials: list[Union[bpy.types.Material, None]] = mesh.materials[:]
|
||||
# Material style is overridden by representation item, nothing to change.
|
||||
if mesh.materials and mbstyle1 not in materials:
|
||||
return
|
||||
|
||||
i1 = None
|
||||
if mbstyle1 is None:
|
||||
if not materials:
|
||||
mesh.materials.append(None)
|
||||
i1 = 0
|
||||
else:
|
||||
i1 = materials.index(None)
|
||||
else:
|
||||
rep = tool.Geometry.get_active_representation(obj)
|
||||
assert rep # Type checker.
|
||||
rep_styles = tool.Geometry.get_representation_styles(rep)
|
||||
if mbstyle1 in rep_styles:
|
||||
tool.Geometry._reload_representation(obj)
|
||||
return
|
||||
else:
|
||||
i1 = materials.index(mbstyle1)
|
||||
|
||||
if mbstyle2 in materials:
|
||||
i2 = materials.index(mbstyle2)
|
||||
# Reassign faces.
|
||||
buffer = np.empty(len(mesh.polygons), dtype=np.int32)
|
||||
mesh.polygons.foreach_get("material_index", buffer)
|
||||
buffer[buffer == i1] = i2
|
||||
mesh.polygons.foreach_set("material_index", buffer)
|
||||
|
||||
mesh.materials.pop(index=i1)
|
||||
else:
|
||||
mesh.materials[i1] = mbstyle2
|
||||
|
||||
@classmethod
|
||||
def add_body_representation(cls, obj: bpy.types.Object) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
@@ -303,12 +303,14 @@ class Project(bonsai.core.tool.Project):
|
||||
library_file = IfcStore.library_file
|
||||
assert library_file
|
||||
|
||||
if props.selected_project_library == "*":
|
||||
selected_project_library = props.selected_project_library if props.filter_by_library else "*"
|
||||
|
||||
if selected_project_library == "*":
|
||||
|
||||
def condition(elements: list[ifcopenshell.entity_instance]):
|
||||
yield from elements
|
||||
|
||||
elif props.selected_project_library == "-":
|
||||
elif selected_project_library == "-":
|
||||
|
||||
def condition(elements: list[ifcopenshell.entity_instance]):
|
||||
for element in elements:
|
||||
@@ -316,7 +318,7 @@ class Project(bonsai.core.tool.Project):
|
||||
yield element
|
||||
|
||||
else:
|
||||
project_library = library_file.by_id(int(props.selected_project_library))
|
||||
project_library = library_file.by_id(int(selected_project_library))
|
||||
project_library_rels = set(project_library.Declares)
|
||||
if project_library_rels:
|
||||
|
||||
@@ -330,6 +332,23 @@ class Project(bonsai.core.tool.Project):
|
||||
else:
|
||||
|
||||
def condition(elements: list[ifcopenshell.entity_instance]):
|
||||
pass
|
||||
# Hacky way to create empty generator.
|
||||
return
|
||||
yield
|
||||
|
||||
return condition
|
||||
|
||||
@classmethod
|
||||
def update_current_library_page(cls):
|
||||
props = cls.get_project_props()
|
||||
element_name = props.active_library_element
|
||||
bpy.ops.bim.rewind_library()
|
||||
bpy.ops.bim.change_library_element(element_name=element_name)
|
||||
|
||||
@classmethod
|
||||
def get_parent_library(cls, project_library: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
if nests := project_library.Nests:
|
||||
# IfcProjectLibrary.
|
||||
return nests[0].RelatingObject
|
||||
# IfcProject.
|
||||
return project_library.HasContext[0].RelatingContext
|
||||
|
||||
@@ -16,18 +16,24 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.bim.helper
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.schema
|
||||
from typing import Union, Literal, Any
|
||||
from typing import Union, Literal, Any, TYPE_CHECKING
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.pset.prop import PsetProperties
|
||||
|
||||
|
||||
class Pset(bonsai.core.tool.Pset):
|
||||
PSET_TYPE = Literal["PSET", "QTO"]
|
||||
|
||||
@@ -40,7 +46,7 @@ class Pset(bonsai.core.tool.Pset):
|
||||
return tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
@classmethod
|
||||
def get_pset_props(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE) -> bpy.types.PropertyGroup:
|
||||
def get_pset_props(cls, obj: str, obj_type: tool.Ifc.OBJECT_TYPE) -> PsetProperties:
|
||||
if obj_type == "Object":
|
||||
return bpy.data.objects.get(obj).PsetProperties
|
||||
elif obj_type == "Material":
|
||||
@@ -75,7 +81,9 @@ class Pset(bonsai.core.tool.Pset):
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
return bool(
|
||||
pset_name
|
||||
in bonsai.bim.schema.ifc.psetqto.get_applicable_names(element.is_a(), predefined_type, pset_only=True)
|
||||
in bonsai.bim.schema.ifc.psetqto.get_applicable_names(
|
||||
element.is_a(), predefined_type, pset_only=True, schema=tool.Ifc.get_schema()
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -130,7 +138,7 @@ class Pset(bonsai.core.tool.Pset):
|
||||
return special_type
|
||||
|
||||
@classmethod
|
||||
def import_pset_from_existing(cls, pset: ifcopenshell.entity_instance, props: bpy.types.PropertyGroup) -> None:
|
||||
def import_pset_from_existing(cls, pset: ifcopenshell.entity_instance, props: PsetProperties) -> None:
|
||||
pset_props = []
|
||||
if pset.is_a("IfcElementQuantity"):
|
||||
pset_props = pset.Quantities
|
||||
@@ -295,7 +303,10 @@ class Pset(bonsai.core.tool.Pset):
|
||||
# to prevent data loss and error casting data.
|
||||
# Property will be added later by import_pset_from_existing.
|
||||
for prop_template in sorted(pset_template.HasPropertyTemplates, key=lambda p: p.Name):
|
||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||
if (
|
||||
not prop_template.is_a("IfcSimplePropertyTemplate")
|
||||
or prop_template.PrimaryMeasureType == "IfcComplexNumber"
|
||||
):
|
||||
continue # Other types not yet supported
|
||||
prop_data = data.get(prop_template.Name)
|
||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||
|
||||
@@ -24,6 +24,7 @@ import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from pathlib import Path
|
||||
from typing import Union, Literal, Any, final
|
||||
from typing_extensions import assert_never, TYPE_CHECKING
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
@@ -119,3 +120,21 @@ class PsetTemplate(bonsai.core.tool.PsetTemplate):
|
||||
|
||||
# Disable because of the intersecting enums in data.py.
|
||||
props.active_pset_template_id = 0
|
||||
|
||||
PSET_TEMPLATE_LOCATION = Literal["Global Pset Template", "Project Pset Template"]
|
||||
|
||||
@classmethod
|
||||
def get_pset_template_files(cls) -> list[tuple[Path, "PSET_TEMPLATE_LOCATION"]]:
|
||||
"""
|
||||
:return: List of pset template files. Each template file is represented
|
||||
by a tuple of the filepath and pset template location source.
|
||||
"""
|
||||
paths: list[tuple[Path, tool.PsetTemplate.PSET_TEMPLATE_LOCATION]] = []
|
||||
for f in tool.Blender.get_data_dir_paths("pset", "*.ifc"):
|
||||
paths.append((f, "Global Pset Template"))
|
||||
|
||||
pset_dir = Path(tool.Ifc.resolve_uri(bpy.context.scene.BIMProperties.pset_dir))
|
||||
if pset_dir.is_dir():
|
||||
for path in Path(pset_dir).glob("*.ifc"):
|
||||
paths.append((path, "Project Pset Template"))
|
||||
return paths
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import json
|
||||
import lark
|
||||
@@ -26,7 +26,10 @@ import ifcopenshell.guid
|
||||
import ifcopenshell.util.selector
|
||||
from itertools import cycle
|
||||
from bonsai.bim.prop import BIMFacet
|
||||
from typing import Union, Literal
|
||||
from typing import Union, Literal, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.prop import BIMFilterGroup
|
||||
|
||||
|
||||
class Search(bonsai.core.tool.Search):
|
||||
@@ -35,7 +38,7 @@ class Search(bonsai.core.tool.Search):
|
||||
return json.loads(group.Description)["query"]
|
||||
|
||||
@classmethod
|
||||
def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection:
|
||||
def get_filter_groups(cls, module: str) -> bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]:
|
||||
if module == "search":
|
||||
return bpy.context.scene.BIMSearchProperties.filter_groups
|
||||
elif module == "csv":
|
||||
@@ -54,13 +57,15 @@ class Search(bonsai.core.tool.Search):
|
||||
assert False, f"Unsupported module: {module}"
|
||||
|
||||
@classmethod
|
||||
def import_filter_query(cls, query: str, filter_groups: bpy.types.bpy_prop_collection) -> None:
|
||||
def import_filter_query(
|
||||
cls, query: str, filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]
|
||||
) -> None:
|
||||
filter_groups.clear()
|
||||
transformer = ImportFilterQueryTransformer(filter_groups)
|
||||
transformer.transform(ifcopenshell.util.selector.filter_elements_grammar.parse(query))
|
||||
|
||||
@classmethod
|
||||
def export_filter_query(cls, filter_groups: bpy.types.bpy_prop_collection) -> str:
|
||||
def export_filter_query(cls, filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]) -> str:
|
||||
query = []
|
||||
for filter_group in filter_groups:
|
||||
filter_group_query = []
|
||||
@@ -300,7 +305,7 @@ class Search(bonsai.core.tool.Search):
|
||||
|
||||
|
||||
class ImportFilterQueryTransformer(lark.Transformer):
|
||||
def __init__(self, filter_groups):
|
||||
def __init__(self, filter_groups: bpy.types.bpy_prop_collection_idprop[BIMFilterGroup]):
|
||||
self.filter_groups = filter_groups
|
||||
|
||||
def get_results(self):
|
||||
|
||||
@@ -42,12 +42,17 @@ from typing import Optional, Any, Union, Literal, TYPE_CHECKING, Iterable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bonsai.bim.prop
|
||||
from bonsai.bim.module.sequence.prop import BIMTaskTreeProperties
|
||||
|
||||
|
||||
class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
RELATED_OBJECT_TYPE = Literal["RESOURCE", "PRODUCT", "CONTROL"]
|
||||
|
||||
@classmethod
|
||||
def get_task_tree_props(cls) -> BIMTaskTreeProperties:
|
||||
return bpy.context.scene.BIMTaskTreeProperties
|
||||
|
||||
@classmethod
|
||||
def get_work_plan_attributes(cls) -> dict[str, Any]:
|
||||
import bonsai.bim.module.sequence.helper as helper
|
||||
@@ -146,7 +151,8 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
@classmethod
|
||||
def load_task_tree(cls, work_schedule: ifcopenshell.entity_instance) -> None:
|
||||
bpy.context.scene.BIMTaskTreeProperties.tasks.clear()
|
||||
props = cls.get_task_tree_props()
|
||||
props.tasks.clear()
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
cls.contracted_tasks = json.loads(props.contracted_tasks)
|
||||
|
||||
@@ -185,7 +191,8 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def create_new_task_li(cls, related_object_id: int, level_index: int) -> None:
|
||||
task = tool.Ifc.get().by_id(related_object_id)
|
||||
new = bpy.context.scene.BIMTaskTreeProperties.tasks.add()
|
||||
props = cls.get_task_tree_props()
|
||||
new = props.tasks.add()
|
||||
new.ifc_definition_id = related_object_id
|
||||
new.is_expanded = related_object_id not in cls.contracted_tasks
|
||||
new.level_index = level_index
|
||||
@@ -199,7 +206,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def load_task_properties(cls, task: Optional[ifcopenshell.entity_instance] = None) -> None:
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
task_props = bpy.context.scene.BIMTaskTreeProperties
|
||||
task_props = cls.get_task_tree_props()
|
||||
tasks_with_visual_bar = cls.get_task_bar_list()
|
||||
props.is_task_update_enabled = False
|
||||
|
||||
@@ -281,8 +288,9 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def contract_all_tasks(cls) -> None:
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
tprops = cls.get_task_tree_props()
|
||||
contracted_tasks = json.loads(props.contracted_tasks)
|
||||
for task_item in bpy.context.scene.BIMTaskTreeProperties.tasks:
|
||||
for task_item in tprops.tasks:
|
||||
if task_item.is_expanded:
|
||||
contracted_tasks.append(task_item.ifc_definition_id)
|
||||
props.contracted_tasks = json.dumps(contracted_tasks)
|
||||
@@ -302,7 +310,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
def disable_selecting_deleted_task(cls) -> None:
|
||||
props = bpy.context.scene.BIMWorkScheduleProperties
|
||||
if props.active_task_id not in [
|
||||
task.ifc_definition_id for task in bpy.context.scene.BIMTaskTreeProperties.tasks
|
||||
task.ifc_definition_id for task in cls.get_task_tree_props().tasks
|
||||
]: # Task was deleted
|
||||
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
|
||||
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
|
||||
@@ -310,9 +318,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
@classmethod
|
||||
def get_checked_tasks(cls) -> list[ifcopenshell.entity_instance]:
|
||||
return [
|
||||
tool.Ifc.get().by_id(task.ifc_definition_id)
|
||||
for task in bpy.context.scene.BIMTaskTreeProperties.tasks
|
||||
if task.is_selected
|
||||
tool.Ifc.get().by_id(task.ifc_definition_id) for task in cls.get_task_tree_props().tasks if task.is_selected
|
||||
] or []
|
||||
|
||||
@classmethod
|
||||
@@ -486,7 +492,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
|
||||
@classmethod
|
||||
def get_highlighted_task(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
tasks = bpy.context.scene.BIMTaskTreeProperties.tasks
|
||||
tasks = cls.get_task_tree_props().tasks
|
||||
if len(tasks) and len(tasks) > bpy.context.scene.BIMWorkScheduleProperties.active_task_index:
|
||||
return tool.Ifc.get().by_id(
|
||||
tasks[bpy.context.scene.BIMWorkScheduleProperties.active_task_index].ifc_definition_id
|
||||
@@ -789,7 +795,7 @@ class Sequence(bonsai.core.tool.Sequence):
|
||||
cls.load_task_tree(work_schedule)
|
||||
cls.load_task_properties()
|
||||
|
||||
task_props = bpy.context.scene.BIMTaskTreeProperties
|
||||
task_props = cls.get_task_tree_props()
|
||||
expanded_tasks = [item.ifc_definition_id for item in task_props.tasks]
|
||||
bpy.context.scene.BIMWorkScheduleProperties.active_task_index = expanded_tasks.index(task.id()) or 0
|
||||
|
||||
|
||||
@@ -212,7 +212,9 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
if props.should_include_children:
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
|
||||
else:
|
||||
elements = ifcopenshell.util.element.get_contained(container)
|
||||
elements = set(ifcopenshell.util.element.get_contained(container))
|
||||
for e in elements:
|
||||
elements.update(ifcopenshell.util.element.get_decomposition(e))
|
||||
flat_results: dict[str, Any] = {}
|
||||
reference_names: dict[str, str] = {}
|
||||
for element in elements:
|
||||
@@ -257,14 +259,20 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
if props.should_include_children:
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
|
||||
else:
|
||||
elements = ifcopenshell.util.element.get_contained(container)
|
||||
elements = set(ifcopenshell.util.element.get_contained(container))
|
||||
for e in elements:
|
||||
elements.update(ifcopenshell.util.element.get_decomposition(e))
|
||||
for element in elements:
|
||||
if element.is_a("IfcOpeningElement") or tool.Root.is_spatial_element(element):
|
||||
continue
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
ifc_class = element.is_a()
|
||||
ifc_definition_id = element_type.id() if element_type else 0
|
||||
type_name = element_type.Name or "Unnamed" if element_type else f"Untyped {element.is_a()}"
|
||||
type_name = (
|
||||
element_type.is_a() + "/" + element_type.Name or "Unnamed"
|
||||
if element_type
|
||||
else f"Untyped {element.is_a()}"
|
||||
)
|
||||
class_data = results.setdefault(ifc_class, {})
|
||||
type_data = class_data.setdefault(ifc_definition_id, {"type_name": type_name, "elements": []})
|
||||
type_data["elements"].append(element)
|
||||
@@ -348,6 +356,8 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
|
||||
def add_elements(elements, level=0):
|
||||
for element in sorted(elements, key=lambda x: f"{x.is_a()}/{x.Name or 'Unnamed'}"):
|
||||
if not props.should_include_children and tool.Root.is_spatial_element(element):
|
||||
continue
|
||||
ifc_definition_id = element.id()
|
||||
new = props.elements.add()
|
||||
new.name = f"{element.is_a()}/{element.Name or 'Unnamed'}"
|
||||
@@ -406,7 +416,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
@classmethod
|
||||
def filter_elements(
|
||||
cls,
|
||||
elements: List[ifcopenshell.entity_instance],
|
||||
elements: Iterable[ifcopenshell.entity_instance],
|
||||
ifc_class: str | None,
|
||||
relating_type: ifcopenshell.entity_instance | None,
|
||||
is_untyped: bool,
|
||||
@@ -1133,23 +1143,27 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
@classmethod
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces: list[ifcopenshell.entity_instance]) -> None:
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
if bpy.data.objects[first_obj.name].display_type == "TEXTURED":
|
||||
assert isinstance(first_obj, bpy.types.Object)
|
||||
obj: bpy.types.Object
|
||||
if first_obj.display_type == "TEXTURED":
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = True
|
||||
bpy.data.objects[obj.name].display_type = "WIRE"
|
||||
obj.show_wire = True
|
||||
obj.display_type = "WIRE"
|
||||
return
|
||||
|
||||
elif bpy.data.objects[first_obj.name].display_type == "WIRE":
|
||||
elif first_obj.display_type == "WIRE":
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
bpy.data.objects[obj.name].show_wire = False
|
||||
bpy.data.objects[obj.name].display_type = "TEXTURED"
|
||||
obj.show_wire = False
|
||||
obj.display_type = "TEXTURED"
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def toggle_hide_spaces(cls, spaces: list[ifcopenshell.entity_instance]) -> None:
|
||||
first_obj = tool.Ifc.get_object(spaces[0])
|
||||
assert isinstance(first_obj, bpy.types.Object)
|
||||
obj: bpy.types.Object
|
||||
if first_obj.hide_get() == False:
|
||||
for space in spaces:
|
||||
obj = tool.Ifc.get_object(space)
|
||||
@@ -1228,3 +1242,117 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
):
|
||||
props = bpy.context.active_object.BIMObjectSpatialProperties
|
||||
props.container_obj = obj
|
||||
|
||||
@classmethod
|
||||
def get_filtered_elements(cls, should_filter: bool = True) -> Iterable[ifcopenshell.entity_instance]:
|
||||
ifc_file = tool.Ifc.get()
|
||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||
container = ifc_file.by_id(props.active_container.ifc_definition_id)
|
||||
element_filter = props.element_filter
|
||||
active_element = props.active_element
|
||||
|
||||
if not should_filter:
|
||||
if props.should_include_children:
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
|
||||
else:
|
||||
elements = set(ifcopenshell.util.element.get_contained(container))
|
||||
for e in elements:
|
||||
elements.update(ifcopenshell.util.element.get_decomposition(e))
|
||||
if not element_filter:
|
||||
return elements
|
||||
|
||||
keyword = element_filter.lower()
|
||||
if props.element_mode == "TYPE":
|
||||
filtered_occurrences = set()
|
||||
filtered_classes = set()
|
||||
filtered_types = set()
|
||||
for item in props.elements:
|
||||
if item.type == "CLASS" and not item.is_expanded and keyword in item.name.lower():
|
||||
filtered_classes.add(item.name)
|
||||
elif item.type == "TYPE" and not item.is_expanded and keyword in item.name.lower():
|
||||
if item.ifc_definition_id:
|
||||
filtered_types.add(ifc_file.by_id(item.ifc_definition_id))
|
||||
else:
|
||||
filtered_types.add(item.name.split(" ")[1])
|
||||
elif item.type == "OCCURRENCE" and keyword in item.name.lower():
|
||||
filtered_occurrences.add(ifc_file.by_id(item.ifc_definition_id))
|
||||
return {
|
||||
e
|
||||
for e in elements
|
||||
if e.is_a() in filtered_classes
|
||||
or ((e_type := ifcopenshell.util.element.get_type(e)) and e_type in filtered_types)
|
||||
or (not e_type and e.is_a() in filtered_types)
|
||||
} | filtered_occurrences
|
||||
elif props.element_mode == "DECOMPOSITION":
|
||||
return [ifc_file.by_id(i.ifc_definition_id) for i in props.elements if keyword in i.name.lower()]
|
||||
elif props.element_mode == "CLASSIFICATION":
|
||||
filtered_classifications = set()
|
||||
filtered_occurrences = set()
|
||||
for item in props.elements:
|
||||
if item.type == "CLASSIFICATION" and not item.is_expanded and keyword in item.name.lower():
|
||||
filtered_classifications.add(item.identification)
|
||||
elif item.type == "OCCURRENCE" and keyword in item.name.lower():
|
||||
filtered_occurrences.add(ifc_file.by_id(item.ifc_definition_id))
|
||||
for element in elements:
|
||||
if refs := ifcopenshell.util.classification.get_references(element):
|
||||
for ref in refs:
|
||||
for filtered_classification in filtered_classifications:
|
||||
if ref[1].startswith(filtered_classification):
|
||||
filtered_occurrences.add(element)
|
||||
elif "Unclassified" in filtered_classifications:
|
||||
filtered_occurrences.add(element)
|
||||
return filtered_occurrences
|
||||
return elements
|
||||
|
||||
if not active_element:
|
||||
return []
|
||||
|
||||
if props.element_mode == "TYPE":
|
||||
if active_element.type == "OCCURRENCE":
|
||||
return {ifc_file.by_id(active_element.ifc_definition_id)}
|
||||
|
||||
ifc_class = relating_type = None
|
||||
is_untyped = False
|
||||
|
||||
if active_element.type == "CLASS":
|
||||
ifc_class = active_element.name
|
||||
elif active_element.type == "TYPE":
|
||||
ifc_class = active_element.ifc_class
|
||||
if ifc_id := active_element.ifc_definition_id:
|
||||
relating_type = ifc_file.by_id(ifc_id)
|
||||
|
||||
if props.should_include_children:
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
|
||||
else:
|
||||
elements = set(ifcopenshell.util.element.get_contained(container))
|
||||
for e in elements:
|
||||
elements.update(ifcopenshell.util.element.get_decomposition(e))
|
||||
return cls.filter_elements(elements, ifc_class, relating_type, is_untyped, element_filter)
|
||||
elif props.element_mode == "DECOMPOSITION":
|
||||
occurrence = ifc_file.by_id(active_element.ifc_definition_id)
|
||||
elements = ifcopenshell.util.element.get_decomposition(occurrence, is_recursive=True)
|
||||
elements.add(occurrence)
|
||||
return elements
|
||||
elif props.element_mode == "CLASSIFICATION":
|
||||
if active_element.type == "OCCURRENCE":
|
||||
return {ifc_file.by_id(active_element.ifc_definition_id)}
|
||||
|
||||
if active_element.type == "CLASSIFICATION":
|
||||
identification = active_element.identification
|
||||
if props.should_include_children:
|
||||
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=True)
|
||||
else:
|
||||
elements = set(ifcopenshell.util.element.get_contained(container))
|
||||
for e in elements:
|
||||
elements.update(ifcopenshell.util.element.get_decomposition(e))
|
||||
|
||||
def filter_element(element: ifcopenshell.entity_instance) -> bool:
|
||||
references = ifcopenshell.util.classification.get_references(element)
|
||||
if identification == "Unclassified":
|
||||
if not references:
|
||||
return True
|
||||
elif any([r for r in references if r[1].startswith(identification)]):
|
||||
return True
|
||||
return False
|
||||
|
||||
return filter(filter_element, elements)
|
||||
|
||||
@@ -679,7 +679,7 @@ class Style(bonsai.core.tool.Style):
|
||||
return old_value != new_value and "NEGATIVE" in (old_value, new_value)
|
||||
|
||||
@classmethod
|
||||
def reload_repersentations(cls, style: ifcopenshell.entity_instance) -> None:
|
||||
def reload_representations(cls, style: ifcopenshell.entity_instance) -> None:
|
||||
elements = ifcopenshell.util.element.get_elements_by_style(tool.Ifc.get(), style)
|
||||
objects = [tool.Ifc.get_object(e) for e in elements]
|
||||
tool.Geometry.reload_representation(objects)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import bpy
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.system
|
||||
@@ -31,11 +32,30 @@ from mathutils import Matrix, Vector
|
||||
from bonsai.bim.module.system.data import ObjectSystemData, SystemDecorationData
|
||||
from bonsai.bim.module.drawing.decoration import profile_consequential
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Optional, Any, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bonsai.bim.module.system.prop import BIMSystemProperties, BIMZoneProperties
|
||||
|
||||
|
||||
class System(bonsai.core.tool.System):
|
||||
@classmethod
|
||||
def add_ports(cls, obj, add_start_port=True, add_end_port=True, end_port_pos=None, offset_end_port=None):
|
||||
def get_system_props(cls) -> BIMSystemProperties:
|
||||
return bpy.context.scene.BIMSystemProperties
|
||||
|
||||
@classmethod
|
||||
def get_zone_props(cls) -> BIMZoneProperties:
|
||||
return bpy.context.scene.BIMZoneProperties
|
||||
|
||||
@classmethod
|
||||
def add_ports(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
add_start_port: bool = True,
|
||||
add_end_port: bool = True,
|
||||
end_port_pos: Optional[Vector] = None,
|
||||
offset_end_port: Optional[Vector] = None,
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
def add_port(mep_element, matrix):
|
||||
port = tool.Ifc.run("system.add_port", element=mep_element)
|
||||
port.FlowDirection = "NOTDEFINED"
|
||||
@@ -66,7 +86,7 @@ class System(bonsai.core.tool.System):
|
||||
return ports
|
||||
|
||||
@classmethod
|
||||
def create_empty_at_cursor_with_element_orientation(cls, element):
|
||||
def create_empty_at_cursor_with_element_orientation(cls, element: ifcopenshell.entity_instance) -> bpy.types.Object:
|
||||
element_obj = tool.Ifc.get_object(element)
|
||||
obj = bpy.data.objects.new("Port", None)
|
||||
obj.matrix_world = element_obj.matrix_world
|
||||
@@ -75,38 +95,38 @@ class System(bonsai.core.tool.System):
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def delete_element_objects(cls, elements):
|
||||
def delete_element_objects(cls, elements: list[ifcopenshell.entity_instance]) -> None:
|
||||
for element in elements:
|
||||
obj = tool.Ifc.get_object(element)
|
||||
if obj:
|
||||
bpy.data.objects.remove(obj)
|
||||
|
||||
@classmethod
|
||||
def disable_editing_system(cls):
|
||||
bpy.context.scene.BIMSystemProperties.edited_system_id = 0
|
||||
def disable_editing_system(cls) -> None:
|
||||
cls.get_system_props().edited_system_id = 0
|
||||
|
||||
@classmethod
|
||||
def disable_system_editing_ui(cls):
|
||||
bpy.context.scene.BIMSystemProperties.is_editing = False
|
||||
def disable_system_editing_ui(cls) -> None:
|
||||
cls.get_system_props().is_editing = False
|
||||
|
||||
@classmethod
|
||||
def enable_system_editing_ui(cls):
|
||||
bpy.context.scene.BIMSystemProperties.is_editing = True
|
||||
def enable_system_editing_ui(cls) -> None:
|
||||
cls.get_system_props().is_editing = True
|
||||
|
||||
@classmethod
|
||||
def export_system_attributes(cls):
|
||||
return bonsai.bim.helper.export_attributes(bpy.context.scene.BIMSystemProperties.system_attributes)
|
||||
def export_system_attributes(cls) -> dict[str, Any]:
|
||||
return bonsai.bim.helper.export_attributes(cls.get_system_props().system_attributes)
|
||||
|
||||
@classmethod
|
||||
def get_connected_port(cls, port):
|
||||
def get_connected_port(cls, port: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
return ifcopenshell.util.system.get_connected_port(port)
|
||||
|
||||
@classmethod
|
||||
def get_ports(cls, element):
|
||||
def get_ports(cls, element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
return ifcopenshell.util.system.get_ports(element)
|
||||
|
||||
@classmethod
|
||||
def get_port_relating_element(cls, port):
|
||||
def get_port_relating_element(cls, port: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
element = port.ContainedIn[0].RelatedElement
|
||||
else:
|
||||
@@ -114,7 +134,7 @@ class System(bonsai.core.tool.System):
|
||||
return element
|
||||
|
||||
@classmethod
|
||||
def get_port_predefined_type(cls, mep_element):
|
||||
def get_port_predefined_type(cls, mep_element: ifcopenshell.entity_instance) -> str:
|
||||
split_camel_case = lambda x: re.findall("[A-Z][^A-Z]*", x)
|
||||
mep_class = mep_element.is_a()
|
||||
if mep_class.endswith("Type"):
|
||||
@@ -125,20 +145,20 @@ class System(bonsai.core.tool.System):
|
||||
return class_name
|
||||
|
||||
@classmethod
|
||||
def import_system_attributes(cls, system):
|
||||
props = bpy.context.scene.BIMSystemProperties
|
||||
def import_system_attributes(cls, system: ifcopenshell.entity_instance) -> None:
|
||||
props = cls.get_system_props()
|
||||
props.system_attributes.clear()
|
||||
bonsai.bim.helper.import_attributes2(system, props.system_attributes)
|
||||
|
||||
@classmethod
|
||||
def get_systems(cls):
|
||||
def get_systems(cls) -> list[ifcopenshell.entity_instance]:
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
return tool.Ifc.get().by_type("IfcSystem") + tool.Ifc.get().by_type("IfcZone")
|
||||
return tool.Ifc.get().by_type("IfcSystem")
|
||||
|
||||
@classmethod
|
||||
def import_systems(cls):
|
||||
props = bpy.context.scene.BIMSystemProperties
|
||||
def import_systems(cls) -> None:
|
||||
props = cls.get_system_props()
|
||||
props.systems.clear()
|
||||
for system in cls.get_systems():
|
||||
if system.is_a() in ["IfcStructuralAnalysisModel"]:
|
||||
@@ -152,13 +172,20 @@ class System(bonsai.core.tool.System):
|
||||
def load_ports(cls, element: ifcopenshell.entity_instance, ports: list[ifcopenshell.entity_instance]) -> None:
|
||||
if not ports:
|
||||
return
|
||||
|
||||
ports_to_create = ports.copy()
|
||||
for port in ports:
|
||||
if tool.Ifc.get_object(port):
|
||||
ports_to_create.remove(port)
|
||||
|
||||
obj = tool.Ifc.get_object(element)
|
||||
assert isinstance(obj, bpy.types.Object)
|
||||
ifc_import_settings = import_ifc.IfcImportSettings.factory()
|
||||
ifc_importer = import_ifc.IfcImporter(ifc_import_settings)
|
||||
ifc_importer.file = tool.Ifc.get()
|
||||
ifc_importer.calculate_unit_scale()
|
||||
ifc_importer.process_context_filter()
|
||||
ifc_importer.create_generic_elements(set(ports))
|
||||
ifc_importer.create_generic_elements(set(ports_to_create))
|
||||
|
||||
container = ifcopenshell.util.element.get_container(element)
|
||||
if container:
|
||||
@@ -167,11 +194,12 @@ class System(bonsai.core.tool.System):
|
||||
ifc_importer.place_objects_in_collections()
|
||||
|
||||
for port_obj in ifc_importer.added_data.values():
|
||||
assert isinstance(port_obj, bpy.types.Object)
|
||||
port_obj.parent = obj
|
||||
port_obj.matrix_parent_inverse = obj.matrix_world.inverted()
|
||||
|
||||
@classmethod
|
||||
def run_geometry_edit_object_placement(cls, obj=None):
|
||||
def run_geometry_edit_object_placement(cls, obj: bpy.types.Object) -> None:
|
||||
return bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
|
||||
|
||||
@classmethod
|
||||
@@ -197,24 +225,24 @@ class System(bonsai.core.tool.System):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def select_system_products(cls, system):
|
||||
def select_system_products(cls, system: ifcopenshell.entity_instance) -> None:
|
||||
tool.Spatial.select_products(ifcopenshell.util.system.get_system_elements(system))
|
||||
|
||||
@classmethod
|
||||
def set_active_edited_system(cls, system):
|
||||
bpy.context.scene.BIMSystemProperties.edited_system_id = system.id()
|
||||
def set_active_edited_system(cls, system: ifcopenshell.entity_instance) -> None:
|
||||
cls.get_system_props().edited_system_id = system.id()
|
||||
|
||||
@classmethod
|
||||
def set_active_system(cls, system):
|
||||
bpy.context.scene.BIMSystemProperties.active_system_id = system.id()
|
||||
def set_active_system(cls, system: ifcopenshell.entity_instance) -> None:
|
||||
cls.get_system_props().active_system_id = system.id()
|
||||
|
||||
@classmethod
|
||||
def get_active_system(cls):
|
||||
system_props = bpy.context.scene.BIMSystemProperties
|
||||
def get_active_system(cls) -> Union[ifcopenshell.entity_instance, None]:
|
||||
system_props = cls.get_system_props()
|
||||
return tool.Ifc.get_entity_by_id(system_props.active_system_id)
|
||||
|
||||
@classmethod
|
||||
def get_decoration_data(cls):
|
||||
def get_decoration_data(cls) -> dict[str, Any]:
|
||||
all_vertices = []
|
||||
preview_edges = []
|
||||
special_vertices = []
|
||||
@@ -359,7 +387,11 @@ class System(bonsai.core.tool.System):
|
||||
return decoration_data
|
||||
|
||||
@classmethod
|
||||
def get_connected_elements(cls, element, traversed_elements=None):
|
||||
def get_connected_elements(
|
||||
cls,
|
||||
element: ifcopenshell.entity_instance,
|
||||
traversed_elements: Optional[set[ifcopenshell.entity_instance]] = None,
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
"""Recursively retrieves all connected elements to the given `element`.
|
||||
|
||||
`traversed_elements` is a set to store connected elements fetched recursively,
|
||||
@@ -379,17 +411,19 @@ class System(bonsai.core.tool.System):
|
||||
return traversed_elements
|
||||
|
||||
@classmethod
|
||||
def is_mep_element(cls, element):
|
||||
def is_mep_element(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting")
|
||||
|
||||
@classmethod
|
||||
def get_flow_element_controls(cls, element):
|
||||
def get_flow_element_controls(cls, element: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
if not element.HasControlElements:
|
||||
return []
|
||||
return [control for control in element.HasControlElements[0].RelatedControlElements]
|
||||
|
||||
@classmethod
|
||||
def get_flow_control_flow_element(cls, element):
|
||||
def get_flow_control_flow_element(
|
||||
cls, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if not element.AssignedToFlowElement:
|
||||
return
|
||||
return element.AssignedToFlowElement[0].RelatingFlowElement
|
||||
|
||||
@@ -52,13 +52,13 @@ sections on the same page, like
|
||||
|
||||
.. code-block:: restructuredtext
|
||||
|
||||
:ref:`contribute/writing_docs:Writing technical documentation`
|
||||
:ref:`guides/development/writing_docs:Writing technical documentation`
|
||||
|
||||
or with
|
||||
|
||||
.. code-block:: restructuredtext
|
||||
|
||||
:ref:`custom text<contribute/writing_docs:writing technical documentation>`.
|
||||
:ref:`custom text<guides/development/writing_docs:writing technical documentation>`.
|
||||
|
||||
Traditional references like
|
||||
|
||||
@@ -70,13 +70,13 @@ work too but are discouraged. You can link to other pages, like this:
|
||||
|
||||
.. code-block:: restructuredtext
|
||||
|
||||
:doc:`Hello World<hello_world>`
|
||||
:doc:`Hello World<guides/development/writing_docs>`
|
||||
|
||||
or sections within other pages, like this:
|
||||
|
||||
.. code-block:: restructuredtext
|
||||
|
||||
:ref:`devs/installation:unstable installation`
|
||||
:ref:`guides/development/installation:unstable installation`
|
||||
|
||||
We have ``autosectionlabel`` enabled so it is not necessary to manually create labels. The depth of sections
|
||||
with automatic labels is set to 2, so the third level of titles
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Nathan Hild <nathan.hild@gmail.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/>.
|
||||
|
||||
"""
|
||||
Disabling addon without issues is crucial for extension update to work.
|
||||
|
||||
Use operators instead of `blender --command extension remove`
|
||||
to ensure disable and enable occur in the same Blender session.
|
||||
"""
|
||||
|
||||
|
||||
import bpy
|
||||
import bonsai.tool as tool
|
||||
|
||||
bonsai_name = tool.Blender.get_blender_addon_package_name()
|
||||
bpy.ops.preferences.addon_disable(module=bonsai_name)
|
||||
bpy.ops.preferences.addon_enable(module=bonsai_name)
|
||||
@@ -8,8 +8,12 @@ Scenario: Duplicate drawing
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
When I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
When I press "bim.duplicate_drawing(drawing={drawing})"
|
||||
Then nothing happens
|
||||
@@ -21,12 +25,14 @@ Scenario: Create drawing
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
And I press "bim.activate_drawing(drawing={drawing})"
|
||||
When I press "bim.create_drawing"
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
When I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
And I click "OUTLINER_OB_CAMERA"
|
||||
And I click "OUTPUT"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Create drawing after deleting a duplicated object
|
||||
@@ -36,19 +42,18 @@ Scenario: Create drawing after deleting a duplicated object
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.copy_class(obj='IfcWall/Cube')"
|
||||
And the variable "wall2" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.load_drawings"
|
||||
# And I set "scene.DocProperties.location_hint" to "('My Storey', 'My Storey', '')"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
And I press "bim.activate_drawing(drawing={drawing})"
|
||||
And I press "bim.create_drawing"
|
||||
And I duplicate the selected objects
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
And I click "OUTLINER_OB_CAMERA"
|
||||
And I click "OUTPUT"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "object.delete(use_global=False)"
|
||||
When I press "bim.create_drawing"
|
||||
And I delete the selected objects
|
||||
When I click "OUTPUT"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Activate drawing preserves visibility for non-ifc objects
|
||||
@@ -57,10 +62,13 @@ Scenario: Activate drawing preserves visibility for non-ifc objects
|
||||
And I add a cube
|
||||
And the object "Cube" is visible
|
||||
And the object "Cube.001" is not visible
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
When I press "bim.activate_drawing(drawing={drawing})"
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
And I click "OUTLINER_OB_CAMERA"
|
||||
Then the object "Cube" is visible
|
||||
And the object "Cube.001" is not visible
|
||||
|
||||
@@ -68,10 +76,13 @@ Scenario: Activate drawing preserves selection
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
When I press "bim.activate_drawing(drawing={drawing})"
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
When I click "OUTLINER_OB_CAMERA"
|
||||
Then the object "Cube" is selected
|
||||
|
||||
Scenario: Remove drawing
|
||||
@@ -81,10 +92,14 @@ Scenario: Remove drawing
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
When I click "OUTLINER_OB_CAMERA"
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
When I press "bim.remove_drawing(drawing={drawing})"
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
@@ -95,7 +110,6 @@ Scenario: Remove drawing - via object deletion
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
@@ -110,12 +124,13 @@ Scenario: Remove drawing - deleting active drawing
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "wall1" is "IfcStore.get_file().by_type('IfcWall')[-1].id()"
|
||||
And I press "bim.add_drawing"
|
||||
And the variable "drawing" is "IfcStore.get_file().by_type('IfcAnnotation')[0].id()"
|
||||
And the collection "IfcAnnotation/PLAN_VIEW" exists
|
||||
And I set "scene.DocProperties.active_drawing_index" to "0"
|
||||
And I press "bim.activate_drawing(drawing={drawing})"
|
||||
And I save sample test files
|
||||
And I look at the "Drawings" panel
|
||||
And I click "IMPORT"
|
||||
And I click "ADD"
|
||||
And I press "bim.expand_target_view(target_view='PLAN_VIEW')"
|
||||
And I select the "PLAN_VIEW" item in the "BIM_UL_drawinglist" list
|
||||
And I click "OUTLINER_OB_CAMERA"
|
||||
When the object "IfcAnnotation/PLAN_VIEW" is selected
|
||||
And I press "bim.override_object_delete"
|
||||
And I delete the selected objects
|
||||
Then the collection "IfcAnnotation/PLAN_VIEW" does not exist
|
||||
|
||||
@@ -55,7 +55,7 @@ Scenario: Add representation - add a representation with a scale factor applied
|
||||
Then the object "IfcWall/Cube" has no scale
|
||||
And the object "IfcWall/Cube" dimensions are "4,4,4"
|
||||
|
||||
Scenario: Add representation - add a representation with a scale factor removed due to multiple users
|
||||
Scenario: Add representation - add a representation with a scale factor still applied regardless of multiple users
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And I press "object.duplicate_move_linked"
|
||||
@@ -65,7 +65,7 @@ Scenario: Add representation - add a representation with a scale factor removed
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
Then the object "IfcWall/Cube" has no scale
|
||||
And the object "IfcWall/Cube" dimensions are "2,2,2"
|
||||
And the object "IfcWall/Cube" dimensions are "4,4,4"
|
||||
|
||||
Scenario: Switch representation
|
||||
Given an empty IFC project
|
||||
@@ -78,39 +78,6 @@ Scenario: Switch representation
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Switch representation - current edited representation is updated prior to switch
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.ContextType == 'Plan' and c.ContextIdentifier=='Annotation'][0].id()"
|
||||
And I set "active_object.BIMGeometryProperties.contexts" to "{context}"
|
||||
And I press "bim.add_representation"
|
||||
When the object "IfcWall/Cube" is scaled to "2"
|
||||
And the variable "representation" is "[r for r in {ifc}.by_type('IfcShapeRepresentation') if r.RepresentationType=='Tessellation'][0].id()"
|
||||
And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "[r for r in {ifc}.by_type('IfcShapeRepresentation') if r.RepresentationType=='Annotation2D'][0].id()"
|
||||
And I press "bim.switch_representation(ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "4,4,0"
|
||||
|
||||
Scenario: Switch representation - current edited representation is discarded if switching to a box
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When the object "IfcWall/Cube" is scaled to "2"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[-1].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "2,2,2"
|
||||
|
||||
Scenario: Switch representation - existing Blender modifiers must be purged
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
@@ -132,9 +99,7 @@ Scenario: Remove representation - remove an active representation
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcShapeRepresentation')[1].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWall/Cube" has no data
|
||||
|
||||
Scenario: Remove representation - remove an unloaded representation
|
||||
@@ -145,6 +110,9 @@ Scenario: Remove representation - remove an unloaded representation
|
||||
And I set "scene.BIMRootProperties.ifc_product" to "IfcElement"
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When the variable "context" is "[c for c in {ifc}.by_type('IfcGeometricRepresentationSubContext') if c.ContextType == 'Plan' and c.ContextIdentifier == 'Body' and c.TargetView == 'PLAN_VIEW'][0].id()"
|
||||
And I set "active_object.BIMGeometryProperties.contexts" to "{context}"
|
||||
And I press "bim.add_representation"
|
||||
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[1].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation})"
|
||||
Then the object "IfcWall/Cube" has data which is an IFC representation
|
||||
@@ -162,10 +130,8 @@ Scenario: Remove representation - remove an instanced representation from an act
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWallType/Cube" is selected
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[0].MappedRepresentation.id()"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[0].MappedRepresentation.id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWallType/Cube" has no data
|
||||
Then the object "IfcWall/Wall" has no data
|
||||
Then the object "IfcWall/Wall.001" has no data
|
||||
@@ -183,10 +149,8 @@ Scenario: Remove representation - remove an instanced representation from an act
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And I press "bim.add_constr_type_instance"
|
||||
And the object "IfcWall/Wall" is selected
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
|
||||
And the variable "representation_bbox" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[0].id()"
|
||||
When the variable "representation_body" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[0].id()"
|
||||
And I press "bim.remove_representation(representation_id={representation_body})"
|
||||
And I press "bim.remove_representation(representation_id={representation_bbox})"
|
||||
Then the object "IfcWallType/Cube" has no data
|
||||
Then the object "IfcWall/Wall" has no data
|
||||
Then the object "IfcWall/Wall.001" has no data
|
||||
@@ -320,6 +284,7 @@ Scenario: Override duplicate move - with active IFC data
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And additionally the object "IfcBuildingStorey/My Storey" is selected
|
||||
And I set "scene.BIMSpatialDecompositionProperties.is_locked" to "False"
|
||||
When I duplicate the selected objects
|
||||
Then the object "IfcWall/Cube" exists
|
||||
And the object "IfcWall/Cube" is an "IfcWall"
|
||||
|
||||
@@ -9,14 +9,16 @@ Scenario: Viewing default solar settings
|
||||
Scenario: Changing the month
|
||||
Given an empty IFC project
|
||||
And I look at the "Solar Access / Shadow" panel
|
||||
When I set the "January" property to "3"
|
||||
When I set the "Year" property to "2024"
|
||||
And I set the "January" property to "3"
|
||||
Then I see "March"
|
||||
And I see "Sunrise: 06:08:54"
|
||||
|
||||
Scenario: Changing the date
|
||||
Given an empty IFC project
|
||||
And I look at the "Solar Access / Shadow" panel
|
||||
When I set the "Date" property to "3"
|
||||
When I set the "Year" property to "2024"
|
||||
And I set the "Date" property to "3"
|
||||
Then I see "Sunrise: 06:00:31"
|
||||
|
||||
Scenario: Changing the time
|
||||
@@ -29,6 +31,7 @@ Scenario: Changing the time
|
||||
Scenario: Automatic timezone detection based on lat / long
|
||||
Given an empty IFC project
|
||||
And I look at the "Solar Access / Shadow" panel
|
||||
And I set the "Year" property to "2024"
|
||||
When I set the "Latitude" property to "10.0"
|
||||
And I set the "Longitude" property to "20.0"
|
||||
Then I see "Africa/Ndjamena"
|
||||
|
||||
@@ -802,10 +802,10 @@ Scenario: Toggle link visibility - visible mode
|
||||
And I press "bim.link_ifc(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='VISIBLE')"
|
||||
Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_hidden" is "True"
|
||||
And the collection "IfcProject/basic.ifc" exclude status is "True"
|
||||
And the object "IfcProject/basic.ifc" is not visible
|
||||
When I press "bim.toggle_link_visibility(link='{cwd}/test/files/basic.ifc', mode='VISIBLE')"
|
||||
Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_hidden" is "False"
|
||||
And the collection "IfcProject/basic.ifc" exclude status is "False"
|
||||
And the object "IfcProject/basic.ifc" is visible
|
||||
|
||||
Scenario: Unload link
|
||||
Given an empty Blender session
|
||||
@@ -820,7 +820,7 @@ Scenario: Load link
|
||||
And I press "bim.unload_link(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "bim.load_link(filepath='{cwd}/test/files/basic.ifc')"
|
||||
Then "scene.BIMProjectProperties.links['{cwd}/test/files/basic.ifc'].is_loaded" is "True"
|
||||
And the collection "IfcProject/basic.ifc" exists in viewlayer
|
||||
And the object "IfcProject/basic.ifc" exists
|
||||
|
||||
Scenario: Unlink IFC
|
||||
Given an empty Blender session
|
||||
@@ -867,12 +867,13 @@ Scenario: Export IFC - with basic contents and saving as a relative path
|
||||
Given an empty Blender session
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/basic.ifc')"
|
||||
When I press "wm.save_mainfile(filepath='{cwd}/test/files/temp/export.blend')"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True, save_as_invoked=True)"
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc', use_relative_path=True, should_save_as=True)"
|
||||
Then "scene.BIMProperties.ifc_file" is "export.ifc"
|
||||
|
||||
Scenario: Export IFC - with deleted objects synchronised
|
||||
Given an empty IFC project
|
||||
When the object "IfcBuildingStorey/My Storey" is selected
|
||||
And I set "scene.BIMSpatialDecompositionProperties.is_locked" to "False"
|
||||
And I delete the selected objects
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
@@ -896,7 +897,7 @@ Scenario: Export IFC - with moved grid axis location synchronised
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcGridAxis/01" bottom left corner is at "1,-2,0"
|
||||
|
||||
Scenario: Export IFC - with changed object scale synchronised
|
||||
Scenario: Export IFC - with changed object scale ignored
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
@@ -908,4 +909,4 @@ Scenario: Export IFC - with changed object scale synchronised
|
||||
And I press "bim.save_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
And an empty Blender session is started
|
||||
And I press "bim.load_project(filepath='{cwd}/test/files/temp/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "4,4,4"
|
||||
Then the object "IfcWall/Cube" dimensions are "2,2,2"
|
||||
|
||||
@@ -70,7 +70,7 @@ class PanelSpy:
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self.spied_attr in ("row", "column", "box"):
|
||||
if self.spied_attr in ("row", "column", "box", "separator"):
|
||||
return self
|
||||
elif self.spied_attr == "template_list":
|
||||
listtype_name, list_id, dataptr, propname, active_dataptr, active_propname = args
|
||||
@@ -274,8 +274,8 @@ def i_see_the_prop_property(prop):
|
||||
@then(parsers.parse('I don\'t see the "{prop}" property'))
|
||||
def i_dont_see_the_prop_property(prop):
|
||||
panel_spy.refresh_spy()
|
||||
assert [
|
||||
p for p in panel_spy.spied_props if prop not in (p["name"], p["text"], p["icon"])
|
||||
assert not [
|
||||
p for p in panel_spy.spied_props if prop in (p["name"], p["text"], p["icon"])
|
||||
], f"Property {prop} not found in {panel_spy.spied_props}"
|
||||
|
||||
|
||||
@@ -334,6 +334,7 @@ def the_name_list_has_total_items(name, total):
|
||||
assert False, f"List {name} not found in {panel_spy.spied_lists}"
|
||||
|
||||
|
||||
@given(parsers.parse('I select the "{item_name}" item in the "{list_name}" list'))
|
||||
@when(parsers.parse('I select the "{item_name}" item in the "{list_name}" list'))
|
||||
def i_select_the_item_name_item_in_the_list_name_list(item_name, list_name):
|
||||
panel_spy.refresh_spy()
|
||||
@@ -352,7 +353,8 @@ def i_select_the_item_name_item_in_the_list_name_list(item_name, list_name):
|
||||
|
||||
@when("I load a new pset template file")
|
||||
def i_load_a_new_pset_template_file():
|
||||
IfcStore.pset_template_path = bpy.context.scene.BIMPsetTemplateProperties.pset_template_files
|
||||
props = tool.PsetTemplate.get_pset_template_props()
|
||||
IfcStore.pset_template_path = props.pset_template_files
|
||||
IfcStore.pset_template_file = ifcopenshell.open(IfcStore.pset_template_path)
|
||||
|
||||
|
||||
@@ -510,6 +512,7 @@ def i_evaluate_expression(expression):
|
||||
exec(expression)
|
||||
|
||||
|
||||
@given("I duplicate the selected objects")
|
||||
@when("I duplicate the selected objects")
|
||||
def i_duplicate_the_selected_objects():
|
||||
bpy.ops.bim.override_object_duplicate_move()
|
||||
@@ -634,6 +637,7 @@ def i_am_on_frame_number(number):
|
||||
bpy.context.scene.frame_set(int(number))
|
||||
|
||||
|
||||
@given("I delete the selected objects")
|
||||
@when("I delete the selected objects")
|
||||
def i_delete_the_selected_objects():
|
||||
bpy.ops.bim.override_object_delete()
|
||||
@@ -730,7 +734,7 @@ def the_object_name_has_a_representation_type_of_context(name, type, context):
|
||||
context, subcontext, target_view = context.split("/")
|
||||
rep = ifcopenshell.util.representation.get_representation(element, context, subcontext or None, target_view or None)
|
||||
assert rep
|
||||
assert rep.RepresentationType == type
|
||||
assert rep.RepresentationType == type, f"The object {name} does not have a {type} representation"
|
||||
|
||||
|
||||
@given(parsers.parse('the object "{name}" data is a "{type}" representation of "{context}"'))
|
||||
@@ -740,7 +744,7 @@ def the_object_name_data_is_a_type_representation_of_context(name, type, context
|
||||
context, subcontext, target_view = context.split("/")
|
||||
rep = ifc.by_id(the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id)
|
||||
assert rep
|
||||
assert rep.RepresentationType == type
|
||||
assert rep.RepresentationType == type, f"The object {name} is not a {type} representation"
|
||||
assert rep.ContextOfItems.ContextType == context
|
||||
assert rep.ContextOfItems.ContextIdentifier == subcontext
|
||||
assert rep.ContextOfItems.TargetView == target_view
|
||||
@@ -1348,7 +1352,6 @@ def run_test_code():
|
||||
@then(parsers.parse("I save sample test files"))
|
||||
def saving_sample_test_files(and_open_in_blender=None):
|
||||
filepath = f"{variables['cwd']}/test/files/temp/sample_test_file"
|
||||
blend_filepath = f"{filepath}.blend"
|
||||
bpy.ops.bim.save_project(filepath=f"{filepath}.ifc", should_save_as=True)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=f"{filepath}.blend")
|
||||
|
||||
|
||||
@@ -257,13 +257,10 @@ class Prophecy:
|
||||
call = {"name": attr, "args": args, "kwargs": kwargs}
|
||||
# Ensure that signature is valid
|
||||
getattr(self.subject, attr)(*args, **kwargs)
|
||||
try:
|
||||
key = json.dumps(call, sort_keys=True)
|
||||
self.calls.append(call)
|
||||
if key in self.return_values:
|
||||
return self.return_values[key]
|
||||
except:
|
||||
pass
|
||||
key = json.dumps(call, sort_keys=True)
|
||||
self.calls.append(call)
|
||||
if key in self.return_values:
|
||||
return self.return_values[key]
|
||||
return self
|
||||
|
||||
return decorate
|
||||
|
||||
@@ -17,15 +17,34 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bonsai.core.attribute as subject
|
||||
from test.core.bootstrap import ifc
|
||||
from test.core.bootstrap import ifc, blender, root, spatial
|
||||
|
||||
|
||||
class TestCopyAttributeToSelection:
|
||||
def test_run(self, ifc):
|
||||
def test_run(self, ifc, blender, root, spatial):
|
||||
blender.get_selected_objects(include_active=False).should_be_called().will_return(["obj"])
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
ifc.run("attribute.edit_attributes", product="element", attributes={"name": "value"}).should_be_called()
|
||||
subject.copy_attribute_to_selection(ifc, name="name", value="value", obj="obj")
|
||||
assert subject.copy_attribute_to_selection(ifc, blender, root, spatial, name="name", value="value") == 1
|
||||
|
||||
def test_do_nothing_if_object_is_not_an_element(self, ifc):
|
||||
def test_do_nothing_if_object_is_not_an_element(self, ifc, blender, root, spatial):
|
||||
blender.get_selected_objects(include_active=False).should_be_called().will_return(["obj"])
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
subject.copy_attribute_to_selection(ifc, name="name", value="value", obj="obj")
|
||||
assert subject.copy_attribute_to_selection(ifc, blender, root, spatial, name="name", value="value") == 0
|
||||
|
||||
def test_changing_object_name_in_blender_if_attribute_changed(self, ifc, blender, root, spatial):
|
||||
blender.get_selected_objects(include_active=False).should_be_called().will_return(["obj"])
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
ifc.run("attribute.edit_attributes", product="element", attributes={"Name": "value"}).should_be_called()
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
root.is_spatial_element("element").should_be_called().will_return(False)
|
||||
assert subject.copy_attribute_to_selection(ifc, blender, root, spatial, name="Name", value="value") == 1
|
||||
|
||||
def test_refreshing_spatial_decomposition_if_identification_changed(self, ifc, blender, root, spatial):
|
||||
blender.get_selected_objects(include_active=False).should_be_called().will_return(["obj"])
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
ifc.run("attribute.edit_attributes", product="element", attributes={"Name": "value"}).should_be_called()
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
root.is_spatial_element("element").should_be_called().will_return(True)
|
||||
spatial.import_spatial_decomposition().should_be_called()
|
||||
assert subject.copy_attribute_to_selection(ifc, blender, root, spatial, name="Name", value="value") == 1
|
||||
|
||||
@@ -34,9 +34,10 @@ class TestDisableEditingText:
|
||||
|
||||
|
||||
class TestEditText:
|
||||
def test_run(self, ifc, drawing):
|
||||
def test_run(self, drawing):
|
||||
drawing.synchronise_ifc_and_text_attributes("obj").should_be_called()
|
||||
drawing.update_text_size_pset("obj").should_be_called()
|
||||
drawing.update_newline_at("obj").should_be_called()
|
||||
drawing.update_text_value("obj").should_be_called()
|
||||
drawing.disable_editing_text("obj").should_be_called()
|
||||
subject.edit_text(drawing, obj="obj")
|
||||
@@ -161,11 +162,11 @@ class TestAddSheet:
|
||||
subject.add_sheet(ifc, drawing, titleblock="titleblock")
|
||||
|
||||
|
||||
class TestOpenSheet:
|
||||
class TestOpenLayout:
|
||||
def test_run(self, drawing):
|
||||
drawing.get_document_uri("sheet", "LAYOUT").should_be_called().will_return("uri")
|
||||
drawing.open_layout_svg("uri").should_be_called()
|
||||
subject.open_sheet(drawing, sheet="sheet")
|
||||
subject.open_layout(drawing, sheet="sheet")
|
||||
|
||||
|
||||
class TestRemoveSheet:
|
||||
@@ -532,19 +533,30 @@ class TestAddAnnotation:
|
||||
drawing.create_annotation_object("drawing", "object_type").should_be_called().will_return("obj")
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
drawing.get_ifc_representation_class("object_type").should_be_called().will_return("ifc_representation_class")
|
||||
drawing.get_annotation_representation("element_type").should_be_called().will_return("type_rep")
|
||||
drawing.run_root_assign_class(
|
||||
obj="obj",
|
||||
ifc_class="IfcAnnotation",
|
||||
predefined_type="object_type",
|
||||
should_add_representation=True,
|
||||
should_add_representation=False,
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
).should_be_called().will_return("element")
|
||||
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
|
||||
drawing.run_type_assign_type(element="element", relating_type="element_type").should_be_called()
|
||||
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
|
||||
collector.assign("obj").should_be_called()
|
||||
drawing.enable_editing("obj").should_be_called()
|
||||
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
|
||||
drawing.get_representation("element", "context").should_be_called().will_return("rep")
|
||||
drawing.reload_representation(obj="obj", representation="rep").should_be_called()
|
||||
collector.assign("obj", should_clean_users_collection=True).should_be_called()
|
||||
subject.add_annotation(
|
||||
ifc,
|
||||
collector,
|
||||
drawing,
|
||||
drawing="drawing",
|
||||
object_type="object_type",
|
||||
relating_type="element_type",
|
||||
enable_editing=False,
|
||||
)
|
||||
|
||||
def test_create_a_missing_annotation_context_on_the_fly(self, ifc, collector, drawing):
|
||||
drawing.get_drawing_target_view("drawing").should_be_called().will_return("target_view")
|
||||
@@ -554,6 +566,7 @@ class TestAddAnnotation:
|
||||
drawing.create_annotation_object("drawing", "object_type").should_be_called().will_return("obj")
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
drawing.get_ifc_representation_class("object_type").should_be_called().will_return("ifc_representation_class")
|
||||
drawing.get_annotation_representation("element_type").should_be_called().will_return(None)
|
||||
drawing.run_root_assign_class(
|
||||
obj="obj",
|
||||
ifc_class="IfcAnnotation",
|
||||
@@ -562,8 +575,19 @@ class TestAddAnnotation:
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
).should_be_called().will_return("element")
|
||||
drawing.run_type_assign_type(element="element", relating_type="element_type").should_be_called()
|
||||
drawing.get_drawing_group("drawing").should_be_called().will_return("group")
|
||||
ifc.run("group.assign_group", group="group", products=["element"]).should_be_called()
|
||||
collector.assign("obj").should_be_called()
|
||||
drawing.get_representation("element", "context").should_be_called().will_return("rep")
|
||||
drawing.reload_representation(obj="obj", representation="rep").should_be_called()
|
||||
collector.assign("obj", should_clean_users_collection=True).should_be_called()
|
||||
drawing.enable_editing("obj").should_be_called()
|
||||
subject.add_annotation(ifc, collector, drawing, drawing="drawing", object_type="object_type")
|
||||
subject.add_annotation(
|
||||
ifc,
|
||||
collector,
|
||||
drawing,
|
||||
drawing="drawing",
|
||||
object_type="object_type",
|
||||
relating_type="element_type",
|
||||
enable_editing=True,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user