Compare commits

..

2 Commits

Author SHA1 Message Date
Ryan Schultz 5285df13ec Resolve mapped/boolean items in unassign_representation_item_style #6931
Use ifcopenshell.util.representation.resolve_base_items on the active
representation to reach the geometry items that actually carry styles,
unwrapping IfcMappedItem (Revit families) and IfcBooleanResult (openings)
instead of the previous shallow, all-representations walk.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:10:17 -05:00
Ryan Schultz f5609e2095 proposed fix for #6931 2026-07-15 15:10:16 -05:00
356 changed files with 2237 additions and 8098 deletions
+2 -2
View File
@@ -21,12 +21,12 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ./build
+2 -2
View File
@@ -9,13 +9,13 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: IfcOpenShell
- name: Checkout Build Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ifcopenshell_build
+2 -2
View File
@@ -35,12 +35,12 @@ jobs:
aws --version
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ./build
+2 -2
View File
@@ -35,12 +35,12 @@ jobs:
aws --version
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ./build
+2 -2
View File
@@ -27,12 +27,12 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Checkout Build Repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/build-outputs
path: ${{ matrix.deps_dir }}
+2 -2
View File
@@ -19,8 +19,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7 # https://github.com/actions/checkout
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6 # https://github.com/actions/checkout
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
fetch-tags: true
fetch-depth: 0
+6 -5
View File
@@ -65,8 +65,8 @@ jobs:
config:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
@@ -98,7 +98,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout bonsai_unstable_repo repository
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/bonsai_unstable_repo
token: ${{ secrets.IFCOPENBOT_TOKEN }}
@@ -109,7 +109,7 @@ jobs:
# Ensure Bonsai and ifcsverchok enable/disable works before uploading to extensions repo.
# Download Blender.
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.2/blender-5.2.0-linux-x64.tar.xz
wget -q -O blender.tar.xz https://download.blender.org/release/Blender5.1/blender-5.1.0-linux-x64.tar.xz
tar -xf blender.tar.xz
# Setup Blender.
@@ -179,7 +179,8 @@ jobs:
blender --online-mode --command extension install --enable --sync sun_position
cd IfcOpenShell/src/bonsai
pip install -r requirements-dev.txt
pip install pytest-blender
pip install pytest-bdd
blender --background --python scripts/setup_pytest.py
blender --python-expr "import bonsai; print(bonsai.bbim_semver); import ifcopenshell; print(ifcopenshell.version)" --background
make test
+2 -2
View File
@@ -48,8 +48,8 @@ jobs:
config:
short_name: macos
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -37,8 +37,8 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
@@ -21,7 +21,7 @@ jobs:
date: ${{ steps.date.outputs.date }}
verdate: ${{ steps.verdate.outputs.verdate }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Set env
run: echo ok go
@@ -75,7 +75,7 @@ jobs:
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
fi
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-22.04
needs: activate
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -86,7 +86,7 @@ jobs:
name: Docker Build, Tag, Push
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
lfs: true
@@ -47,10 +47,10 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -38,10 +38,10 @@ jobs:
short_name: macosm164
}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Compile
+2 -2
View File
@@ -25,8 +25,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -19,8 +19,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
python-version: '3.11'
+2 -2
View File
@@ -10,9 +10,9 @@ jobs:
publish_website:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Checkout ifctester_org_static_html
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/ifctester_org_static_html
token: ${{ secrets.IFCOPENBOT_TOKEN }}
+2 -2
View File
@@ -18,8 +18,8 @@ jobs:
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7 # https://github.com/actions/setup-python
- uses: actions/checkout@v6
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
with:
python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- name: Compile
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -85,7 +85,7 @@ jobs:
cmake --build build-ifcopenshell --target install -j "$(nproc)"
- name: Set up Python 3.11
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.11
@@ -120,7 +120,7 @@ jobs:
PY
- name: Set up Python 3.12
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.12
+14 -20
View File
@@ -12,22 +12,25 @@ jobs:
MIN_BLENDER_PY_VERSION: "3.11"
steps:
- name: Action - checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Action - install python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: ${{ env.MIN_IOS_PY_VERSION }}
- name: Action - install python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: ${{ env.MIN_BLENDER_PY_VERSION }}
- name: Install dependencies
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
cat requirements-tools.txt | xargs -L1 uv tool install
uv tool install ruff
uv tool install black
uv tool install poethepoet
uv tool install ty==0.0.34
# black doesn't catch all syntax errors, so we check them explicitly.
- name: Check syntax errors
@@ -55,17 +58,11 @@ jobs:
black --diff --check . | black-codeclimate | python .github/workflows/black_to_github_annotations.py
continue-on-error: true
- name: ty check (venv setup)
run: poe ty-venv
- name: ty check (bonsai)
id: ty-bonsai
run: poe ty-bonsai
continue-on-error: true
- name: ty check (ios)
id: ty-ios
run: poe ty-ios
- name: ty check
id: ty
run: |
poe ty-venv
poe ty
continue-on-error: true
- name: Ruff check
@@ -115,10 +112,7 @@ jobs:
if [ "${{ steps.ruff.outcome }}" != "success" ]; then
echo "::error::Ruff check failed, see Summary or 'ruff' step for the details." && ERROR=1
fi
if [ "${{ steps.ty-bonsai.outcome }}" != "success" ]; then
echo "::error::ty check (bonsai) failed, see 'ty check (bonsai)' step for the details." && ERROR=1
fi
if [ "${{ steps.ty-ios.outcome }}" != "success" ]; then
echo "::error::ty check (ios) failed, see 'ty check (ios)' step for the details." && ERROR=1
if [ "${{ steps.ty.outcome }}" != "success" ]; then
echo "::error::ty check failed, see 'ty check' step for the details." && ERROR=1
fi
exit $ERROR
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout IfcOpenShell
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -25,7 +25,7 @@ jobs:
echo "name=$(basename $WHEEL)" >> $GITHUB_OUTPUT
- name: Checkout wasm-wheels
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/wasm-wheels
path: wasm-wheels
+4 -6
View File
@@ -43,12 +43,12 @@ jobs:
CLICOLOR_FORCE: "1"
CMAKE_COLOR_DIAGNOSTICS: "ON"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.11
@@ -257,15 +257,13 @@ jobs:
cd ../ifcdiff && make test || ERROR=1
cd ../ifcpatch && make test || ERROR=1
pip install -e ../ifc5d --no-deps
pip install odfpy openpyxl
pip install odfpy xlsxwriter
cd ../ifc5d && make test || ERROR=1
pip install -e ../ifcquery --no-deps
cd ../ifcquery && make test || ERROR=1
pip install -e ../ifcedit --no-deps
cd ../ifcedit && make test || ERROR=1
# Pinned <2: mcp 2.0.0 renamed mcp.server.fastmcp.FastMCP to
# mcp.server.mcpserver.MCPServer, which ifcmcp doesn't support yet.
pip install "mcp>=1.0,<2"
pip install mcp
pip install -e ../ifcmcp --no-deps
cd ../ifcmcp && make test || ERROR=1
pip install -e ../ifctester --no-deps
@@ -11,10 +11,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: '3.x'
+3 -3
View File
@@ -27,12 +27,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout (recursive)
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/aichat_ifcopenshell_org_static_html
ref: gh-pages
@@ -42,7 +42,7 @@ jobs:
run: |
rsync -av --delete --exclude='.git/' src/ifcchat/ output/
- name: Setup Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: "3.x"
- name: Download wheels
@@ -7,7 +7,7 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v7
@@ -27,12 +27,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout (recursive)
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Checkout intermediate Pages repo
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
repository: IfcOpenShell/wasm_ifcopenshell_org_static_html
ref: gh-pages
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install C++ dependencies
-1
View File
@@ -24,7 +24,6 @@
__pycache__
*.py.bak
venv
uv.lock
# Visual Studio Code files
.vscode
+1 -1
View File
@@ -18,7 +18,7 @@ and many other libraries, CLI apps, and more. Support is also provided for auxil
For more information, see:
* [IfcOpenShell Website](https://ifcopenshell.org)
* [IfcOpenShell Website](http://ifcopenshell.org)
* [IfcOpenShell Documentation](https://docs.ifcopenshell.org)
* [IfcOpenShell C++ Installation](https://docs.ifcopenshell.org/ifcopenshell/installation.html)
* [IfcOpenShell Python Installation](https://docs.ifcopenshell.org/ifcopenshell-python/installation.html)
+1 -1
View File
@@ -3,7 +3,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>blenderbim-nightly</id>
<version>blenderbim_build_version</version>
<version>blenderbim_build_version-alpha</version>
<packageSourceUrl>https://github.com/IfcOpenShell/IfcOpenShell</packageSourceUrl>
<owners>fbpyr</owners>
<!-- == SOFTWARE SPECIFIC SECTION == -->
+11 -14
View File
@@ -3,12 +3,11 @@
apt update && apt install git wget curl ptpython mono-devel micro
mkdir -p /home/runner/work/IfcOpenShell && cd /home/runner/work/IfcOpenShell
git clone https://github.com/IfcOpenShell/IfcOpenShell
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/bonsai/
cd /home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/
micro choco_release.py # paste this script, comment out push command
export CHOCO_TOKEN="secret_choco_release_token"
python3 choco_release.py
"""
import datetime
import hashlib
import os
@@ -29,7 +28,7 @@ def get_repo_tag_names() -> list[str]:
def request_repo_info(url: str):
req = request.Request(url)
req = request.Request(url)
resp = request.urlopen(req)
if not resp.status == 200:
print(f"[ERROR] could not contact server: {url}")
@@ -86,15 +85,13 @@ def run(command: str) -> None:
start = datetime.datetime.now()
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
URL_BLENDER_CMAKE = (
"https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
)
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
URL_CHOCO_PACKAGE = "https://community.chocolatey.org/packages/blender"
URL_BLENDER_CMAKE = "https://raw.githubusercontent.com/blender/blender/{}/build_files/cmake/Modules/FindPythonLibsUnix.cmake"
RE_BLENDER_VERSION_MIN_MAJ = r"Latest Version.+<span>Blender (\d+\.\d+)\..+</span>"
RE_BLENDER_VERSION_MIN_MAJ_PAT = r"Latest Version.+<span>Blender (\d+\.\d+\.\d+)</span>"
RE_BLENDER_PYTHON_VERSION_MAJ_MIN = r"\(_PYTHON_VERSION_SUPPORTED (\d+\.\d+)\)"
BLENDERBIM_DIR = pathlib.Path("/home/runner/work/IfcOpenShell/IfcOpenShell/choco/bonsai/")
BLENDERBIM_DIR = pathlib.Path("/home/runner/work/IfcOpenShell/IfcOpenShell/choco/blenderbim/")
print("_____ check choco release needed?")
@@ -151,7 +148,7 @@ print(f"{blender_python_version_maj_min=}")
python_version = f"py{found[0].replace('.', '')}"
print(f"{python_version=}")
blenderbim_build_version = target_release_tag.replace("bonsai-", "")
blenderbim_build_version = target_release_tag.replace("blenderbim-", "")
# url_blenderbim_py3x_win_zip
release_zip_file_name, url_blenderbim_py3x_win_zip = get_release_zip(target_release_tag)
@@ -169,15 +166,15 @@ topics = {
"path": HERE_DIR / "blenderbim.nuspec",
"key_values": {
"latest_blender_version_maj_min_pat": latest_blender_release_maj_min_pat,
"blenderbim_build_version": blenderbim_build_version,
"blenderbim_build_version" : blenderbim_build_version,
},
},
"install": {
"path": HERE_DIR / "tools" / "chocolateyinstall.ps1",
"key_values": {
"url_blenderbim_py3x_win_zip": url_blenderbim_py3x_win_zip,
"url_blenderbim_py3x_win_zip" : url_blenderbim_py3x_win_zip,
"sha256sum_blenderbim_py3x_win_zip": sha256sum_blenderbim_py3x_win_zip,
"latest_blender_version_maj_min": blender_version_min_maj,
"latest_blender_version_maj_min" : blender_version_min_maj,
},
},
"uninstall": {
+3 -3
View File
@@ -563,8 +563,8 @@ if(COMPILE_SCHEMA)
# Bootstrap the parser
message(STATUS "Compiling schema, this will take a while...")
execute_process(
COMMAND ${PYTHON_EXECUTABLE} bootstrap.py
WORKING_DIRECTORY ../src/ifcopenshell-python/ifcopenshell/express
COMMAND ${PYTHON_EXECUTABLE} bootstrap.py express.bnf
WORKING_DIRECTORY ../src/ifcexpressparser
OUTPUT_FILE express_parser.py
RESULT_VARIABLE SUCCESS
)
@@ -575,7 +575,7 @@ if(COMPILE_SCHEMA)
# Generate code
execute_process(
COMMAND ${PYTHON_EXECUTABLE} ../ifcopenshell-python/ifcopenshell/express/express_parser.py ../../${COMPILE_SCHEMA}
COMMAND ${PYTHON_EXECUTABLE} ../ifcexpressparser/express_parser.py ../../${COMPILE_SCHEMA}
WORKING_DIRECTORY ../src/ifcparse
OUTPUT_VARIABLE COMPILED_SCHEMA_NAME
)
+7 -20
View File
@@ -50,7 +50,7 @@ Used environment variables:
- ``NO_CLEAN`` - do not clean `ifcopenshell` build directories but continue working on current build
(installed dependencies are never cleared).
By default option is disabled, to enable pass any value from `1`, `on`, `true`.
- ``IFCOS_SCHEMAS`` - schemas to be built; defaults to cmake default (8 schemas), to be supplied as `2x3;4;4x3_add2`
- ``IFCOS_SCHEMAS`` - schemas to be built; defaults to cmake default (IFC2X3; IFC4; IFC4X3_ADD2) - to be supplied as `2x3;4`
- ``USE_OCCT`` - whether to use official Open CASCADE instead of Community Edition
(`true` by default, any other value is considered `false`)
- ``WASM_PYTHON_PATH`` - path to WASM Python installation,
@@ -121,7 +121,7 @@ import tarfile
import threading
from datetime import datetime
ssl._create_default_https_context = ssl._create_unverified_context # ty:ignore[invalid-assignment]
ssl._create_default_https_context = ssl._create_unverified_context
import time
from collections.abc import Generator, Sequence
@@ -155,7 +155,7 @@ MPFR_VERSION = "3.1.6" # latest is 4.1.0
CGAL_VERSION = "v5.6.3"
USD_VERSION = "23.05"
TBB_VERSION = "2021.9.0"
ROCKSDB_VERSION = "10.4.2"
ROCKSDB_VERSION = "9.11.2"
ZSTD_VERSION = "1.5.7"
# binaries
cp = "cp"
@@ -627,10 +627,9 @@ def build_dependency(
build_tool_args: "list[str]",
download_url: str,
download_name: str,
*,
download_tool: Literal["py", "git"] = download_tool_default,
revision: "Union[str, None]" = None,
patch: list[str] | None = None,
patch: "Union[str, list[str], None]" = None,
shell=None,
pre_compile_subs: "Sequence[tuple[str, str, str]]" = (),
additional_files: "Union[dict[str, str], None]" = None,
@@ -697,10 +696,7 @@ def build_dependency(
compr = "xz"
else:
raise RuntimeError("fix source for new download type")
# ty: false positive bug upstream.
download_tarfile = tarfile.open(
name=download_tarfile_path, mode=f"r:{compr}"
) # ty:ignore[no-matching-overload]
download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
# tarfile seriously doesn't have a function to retrieve the root directory more easily
extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
# run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
@@ -718,6 +714,8 @@ def build_dependency(
urlretrieve(url, os.path.join(extract_dir, path))
if patch is not None:
if isinstance(patch, str):
patch = [patch]
for p in patch:
patch_abs = (SCRIPT_PATH / p).absolute().__str__()
if os.path.exists(patch_abs):
@@ -726,8 +724,6 @@ def build_dependency(
except Exception as e:
# Assert that the patch has already been applied
run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)
else:
raise FileNotFoundError(patch_abs)
if shell is not None:
sp.run(shell, shell=True, check=True, cwd=extract_dir)
@@ -1175,14 +1171,6 @@ if "cgal" in targets:
os.environ["CC"] = MAC_CROSS_COMPILE_INTEL_CC
gmp_args.extend(MAC_CROSS_COMPILE_INTEL_AUTOCONF_HOST_ARGS)
# Fixes configure failing to find a working compiler under GCC 15's default -std=gnu23.
# Issue presumably will be resolved in any next gmp version, but currently the last one is 6.3.0.
# Patch is just applying fix from upstream meantion below:
# https://gmplib.org/list-archives/gmp-bugs/2025-February/005561.html
gmp_patches = ["./patches/gmp/001-fix-std23.patch"]
if GMP_VERSION != "6.3.0":
raise Exception(f"GMP_VERSION changed to {GMP_VERSION}, check whether {gmp_patches} is still needed.")
build_dependency(
name=f"gmp-{GMP_VERSION}",
mode="autoconf",
@@ -1190,7 +1178,6 @@ if "cgal" in targets:
pre_compile_subs=(
[("build/config.h", "HAVE_OBSTACK_VPRINTF 1", "HAVE_OBSTACK_VPRINTF 0")] if "wasm" in flags else []
),
patch=gmp_patches,
# Sometimes ftp.gnu.org is very slow, use ftpmirror.gnu.org as a workaround.
download_url="https://ftpmirror.gnu.org/gnu/gmp/",
download_name=f"gmp-{GMP_VERSION}.tar.bz2",
-1
View File
@@ -68,7 +68,6 @@ def unpack_dependencies(install_dir: Path) -> None:
if __name__ == "__main__":
action = None
if len(sys.argv) != 2 or (action := sys.argv[1].lower()) not in ("pack", "unpack"):
print(__doc__)
sys.exit(1)
-27
View File
@@ -1,27 +0,0 @@
Fixes configure failing to find a working compiler under GCC 15's default
-std=gnu23 (upstream fix: https://gmplib.org/repo/gmp/rev/8e7bb4ae7a18).
Upstream fix is patching `acinclude.m4`, but since in the release tarball
all macros are already expanded to `configure` script, so we're patching
all occurrences of that macro.
--- a/configure
+++ b/configure
@@ -6568,7 +6568,7 @@
#if defined (__GNUC__) && ! defined (__cplusplus)
typedef unsigned long long t1;typedef t1*t2;
-void g(){}
+void g(int,t1 const*,t1,t2,t1 const*,int){}
void h(){}
static __inline__ t1 e(t2 rp,t2 up,int n,t1 v0)
{t1 c,x,r;int i;if(v0){c=1;for(i=1;i<n;i++){x=up[i];r=x+1;rp[i]=r;}}return c;}
@@ -8187,7 +8187,7 @@
#if defined (__GNUC__) && ! defined (__cplusplus)
typedef unsigned long long t1;typedef t1*t2;
-void g(){}
+void g(int,t1 const*,t1,t2,t1 const*,int){}
void h(){}
static __inline__ t1 e(t2 rp,t2 up,int n,t1 v0)
{t1 c,x,r;int i;if(v0){c=1;for(i=1;i<n;i++){x=up[i];r=x+1;rp[i]=r;}}return c;}
+32
View File
@@ -0,0 +1,32 @@
http://git.dev.opencascade.org/gitweb/?p=occt.git;a=commitdiff;h=0ab4e621833f4eae945a3762c9a29ee12e2eec53#patch1
diff --git a/src/HLRBRep/HLRBRep_InternalAlgo.cxx b/src/HLRBRep/HLRBRep_InternalAlgo.cxx
index ca885ca..c13cb06 100644 (file)
--- a/src/HLRBRep/HLRBRep_InternalAlgo.cxx
+++ b/src/HLRBRep/HLRBRep_InternalAlgo.cxx
@@ -165,7 +165,7 @@ void HLRBRep_InternalAlgo::Update ()
SB.Bounds(v1,v2,e1,e2,f1,f2);
for (Standard_Integer e = e1; e <= e2; e++) {
- HLRBRep_EdgeData ed = aEDataArray.ChangeValue(e);
+ HLRBRep_EdgeData& ed = aEDataArray.ChangeValue(e);
HLRAlgo::DecodeMinMax(ed.MinMax(), TheMin, TheMax);
if (FirstTime) {
FirstTime = Standard_False;
@@ -307,7 +307,7 @@ void HLRBRep_InternalAlgo::InitEdgeStatus ()
Standard_Integer nf = myDS->NbFaces();
for (Standard_Integer e = 1; e <= ne; e++) {
- HLRBRep_EdgeData ed = aEDataArray.ChangeValue(e);
+ HLRBRep_EdgeData& ed = aEDataArray.ChangeValue(e);
if (ed.Selected()) ed.Status().ShowAll();
}
// for (Standard_Integer f = 1; f <= nf; f++) {
@@ -368,7 +368,7 @@ void HLRBRep_InternalAlgo::Select ()
Standard_Integer nf = myDS->NbFaces();
for (Standard_Integer e = 1; e <= ne; e++) {
- HLRBRep_EdgeData ed = aEDataArray.ChangeValue(e);
+ HLRBRep_EdgeData& ed = aEDataArray.ChangeValue(e);
ed.Selected(Standard_True);
}
+22
View File
@@ -0,0 +1,22 @@
From a0deb4ce8b43cf3c8b8c0a4225c6be5296446dbd Mon Sep 17 00:00:00 2001
From: Adam Eri <adam.eri@blackmirror.media>
Date: Tue, 3 Sep 2019 23:30:20 +0200
Subject: [PATCH] Resolves compile error on macOS
Resolves "no member named 'isnan' in namespace 'std'" on macOS
---
GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
index 1f9a3eef..dd6f5c59 100644
--- a/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
+++ b/GeneratedSaxParser/src/GeneratedSaxParserUtils.cpp
@@ -10,6 +10,7 @@
#include "GeneratedSaxParserUtils.h"
#include <math.h>
+#include <cmath>
#include <memory>
#include <string.h>
#include <limits>
+89 -41
View File
@@ -1,8 +1,13 @@
[project]
name = "IfcOpenShell"
version = "0.0.0"
# Don't provide requires-python explicitly
# allowing pyprojects to set their own (e.g. bonsai and general ifcopenshell version differ).
dependencies = [
"black==26.3.1",
"ruff==0.15.12",
"poethepoet",
"ty==0.0.32",
"gersemi==0.26.1",
]
[tool.black]
line-length = 120
@@ -38,7 +43,6 @@ exclude = [
# then they will be inherited by projects' .toml files.
# This allows using assuming different Python version for different projects.
[tool.ruff]
line-length = 120
exclude = [
# Submodules.
"src/ifcopenshell-python/ifcopenshell/express",
@@ -79,36 +83,92 @@ ignore = [
]
[tool.ty.rules]
all = "error"
all = "ignore"
# Structural rules (no deep type inference needed, easier to adapt).
# Maybe later, requires to specify element types for all generics.
missing-type-argument = "ignore"
# Conflicts with `bpy` props defined using annotations.
invalid-type-form = "ignore"
abstract-method-in-final-class = "error"
ambiguous-protocol-member = "error"
conflicting-declarations = "error"
conflicting-metaclass = "error"
cyclic-class-definition = "error"
cyclic-type-alias-definition = "error"
dataclass-field-order = "error"
duplicate-base = "error"
duplicate-kw-only = "error"
empty-body = "error"
escape-character-in-forward-annotation = "error"
final-on-non-method = "error"
final-without-value = "error"
ignore-comment-unknown-rule = "error"
implicit-concatenated-string-type-annotation = "error"
inconsistent-mro = "error"
ineffective-final = "error"
instance-layout-conflict = "error"
invalid-dataclass = "error"
invalid-dataclass-override = "error"
invalid-enum-member-annotation = "error"
invalid-explicit-override = "error"
invalid-frozen-dataclass-subclass = "error"
invalid-generic-class = "error"
invalid-generic-enum = "error"
invalid-ignore-comment = "error"
invalid-legacy-positional-parameter = "error"
invalid-legacy-type-variable = "error"
invalid-named-tuple = "error"
invalid-newtype = "error"
invalid-overload = "error"
invalid-paramspec = "error"
invalid-protocol = "error"
invalid-syntax-in-forward-annotation = "error"
invalid-total-ordering = "error"
invalid-type-alias-type = "error"
invalid-type-checking-constant = "error"
invalid-type-guard-definition = "error"
invalid-type-variable-bound = "error"
invalid-type-variable-constraints = "error"
invalid-typed-dict-header = "error"
invalid-typed-dict-statement = "error"
override-of-final-method = "error"
override-of-final-variable = "error"
possibly-missing-import = "error"
possibly-missing-submodule = "error"
# Has false positives due to ty walrus operator bug.
# possibly-unresolved-reference = "error"
raw-string-type-annotation = "error"
redundant-final-classvar = "error"
shadowed-type-variable = "error"
subclass-of-final-class = "error"
super-call-in-named-tuple-method = "error"
unavailable-implicit-super-arguments = "error"
unbound-type-variable = "error"
undefined-reveal = "error"
unresolved-global = "error"
unresolved-import = "error"
unresolved-reference = "error"
unused-ignore-comment = "error"
unused-type-ignore-comment = "error"
useless-overload-body = "error"
# Non-structural rules:
deprecated = "error"
zero-stepsize-in-slice = "error"
possibly-missing-implicit-call = "error"
unused-awaitable = "error"
# Function argument rules:
# Conflicts with `ifcopenshell.api.geometry.add_representation` type of callables we have, confusing them with a module.
call-non-callable = "ignore"
# bpy is missing some context manager implementations.
invalid-context-manager = "ignore"
# Doesn't go well with `bpy.ops.xxx.yyy`.
unresolved-attribute = "ignore"
# call-non-callable = "error"
conflicting-argument-forms = "error"
# Too many false positives.
invalid-argument-type = "ignore"
invalid-method-override = "ignore"
invalid-assignment = "ignore"
invalid-parameter-default = "ignore"
missing-override-decorator = "ignore"
invalid-yield = "ignore"
invalid-return-type = "ignore"
non-callable-init-subclass = "ignore"
not-iterable = "ignore"
possibly-missing-attribute = "ignore"
no-matching-overload = "ignore"
not-subscriptable = "ignore"
unsupported-dynamic-base = "ignore"
unsupported-operator = "ignore"
# invalid-argument-type = "error"
missing-argument = "error"
parameter-already-assigned = "error"
positional-only-parameter-as-kwarg = "error"
too-many-positional-arguments = "error"
unknown-argument = "error"
# Has a lot of warnings due to current ty walrus operator issues.
# index-out-of-bounds = "error"
# unresolved-attribute = "error"
[tool.ty.environment]
extra-paths = [
@@ -155,19 +215,6 @@ exclude = [
[tool.poe.tasks]
dev-setup.sequence = [
# 3.13 is chosen because it's the version used in the latest Bonsai.
{cmd = "uv sync --python 3.13"},
{cmd = "uv pip install -e ./src/bsdd/"},
{cmd = "uv pip install -e ./src/ifcopenshell-python/[advanced,dev]"},
{cmd = "uv pip install -e ./src/ifcedit/"},
{cmd = "uv pip install -e ./src/ifcpatch/"},
{cmd = "uv pip install -e ./src/ifcquery/"},
{cmd = "uv pip install -e './src/ifcmcp/[mcp]'"},
{cmd = "uv pip install -r src/bonsai/requirements-dev.txt"},
]
dev-setup.help = "Install repo packages in editable mode"
ruff = "ruff check"
black = "black ."
@@ -193,9 +240,9 @@ format.sequence = ["black", "ruff"]
cmake-format = "gersemi . --in-place"
[tool.poe.tasks.ty-ios]
# --ignore unresolved-reference: walrus operator false positives in ty.
cmd = """
ty check
nix/
src/bcf
src/bsdd
src/ifc2ca
@@ -210,6 +257,7 @@ cmd = """
src/ifcpatch
src/ifctester
--python=src/ifcopenshell-python/.venv
--ignore unresolved-reference
"""
[tool.poe.tasks.bonsai-deps]
-5
View File
@@ -1,5 +0,0 @@
black==26.3.1
ruff==0.16.0
poethepoet
ty==0.0.63
gersemi==0.28.0
-6
View File
@@ -188,8 +188,6 @@ class BcfClient:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
response = errh.response
assert response is not None
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
@@ -208,8 +206,6 @@ class BcfClient:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
response = errh.response
assert response is not None
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
@@ -226,8 +222,6 @@ class BcfClient:
response.raise_for_status()
return response.status_code, response.text
except requests.exceptions.HTTPError as errh:
response = errh.response
assert response is not None
print(f"message: {response.reason}' '{response.status_code}, {errh}")
return response.status_code, response.reason
+4 -22
View File
@@ -24,28 +24,10 @@ a text, a tspan { fill: blue !important; text-decoration: underline;}
a:hover { cursor: pointer; }
.cut { fill: black; stroke: black; stroke-linecap: 'round'; stroke-width: 0.35; fill-rule: evenodd; }
.projection { fill: white; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
/* SVG edge classification (issue #3668): see edge-classification.md. These select directly on
the <path> element (each classified projection edge carries its own class), so they win over
the inherited .projection rule above regardless of specificity. */
path.outline { stroke: black; stroke-width: 0.35; stroke-opacity: 1; }
path.boundary { stroke: black; stroke-width: 0.3; stroke-opacity: 0.9; }
path.crease { stroke: black; stroke-width: 0.25; stroke-opacity: 0.85; }
path.sharp { stroke: black; stroke-width: 0.18; stroke-opacity: 0.7; }
path.flush { stroke: black; stroke-width: 0.1; stroke-opacity: 0.4; }
/* Debug CSS for troubleshooting edge classification */
/*
path.outline { stroke: black; stroke-width: 0.35; stroke-opacity: 1; }
path.boundary { stroke: orange; stroke-width: 0.3; stroke-opacity: 0.9; }
path.crease { stroke: green; stroke-width: 0.25; stroke-opacity: 0.85; }
path.sharp { stroke: red; stroke-width: 0.18; stroke-opacity: 0.7; }
path.flush { stroke: blue; stroke-width: 0.1; stroke-opacity: 0.4; }
*/
.surface {fill: white; stroke-width: 0.1;}
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.3; }
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.3; }
/* .IfcGeographicElement { fill: none; stroke: rgb(150, 150, 150); stroke-linecap: 'round'; stroke-dasharray: 1, 2;} */
.surface { stroke: none; fill: #fff; fill-rule: evenodd; }
.annotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.IfcAnnotation { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 0.25; }
.IfcGeographicElement { fill: none; stroke: black; stroke-linecap: 'round'; stroke-width: 1; }
.PredefinedType-LINEWORK { stroke: black; stroke-width: 0.25; }
.PredefinedType-LINEWORK.dashed { stroke-dasharray: 3, 2; }
.PredefinedType-LINEWORK.fine { stroke-width: 0.18; stroke: #777777; }
@@ -5,7 +5,7 @@ FILE_NAME('EPset_Drawing.ifc','2020-01-01T00:00:00',$,$,'EPset_Drawing','EPset_D
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#29,#30,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2,#31,#32,#33,#34,#35,#36,#37));
#1=IFCPROPERTYSETTEMPLATE('2JhNIvqZrFnAgxfhK0XVQX',$,'EPset_Drawing','',.PSET_OCCURRENCEDRIVEN.,'IfcAnnotation/DRAWING',(#23,#22,#27,#24,#29,#30,#19,#12,#26,#9,#8,#7,#6,#4,#18,#11,#5,#20,#25,#14,#10,#17,#28,#16,#3,#21,#13,#15,#2));
#2=IFCSIMPLEPROPERTYTEMPLATE('23JavTMk98ZxXhrUEnjAcf',$,'TargetView','',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#3=IFCSIMPLEPROPERTYTEMPLATE('1yVWUt5H9DAOuu0OaMMLpe',$,'Scale','The scale of this drawing represented as a numerator and denominator, such as 1/100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
#4=IFCSIMPLEPROPERTYTEMPLATE('3gsuPBtU93b8f0gg1pjkq6',$,'HumanScale','The scale of this drawing in human readable format, such as 1:100',.P_SINGLEVALUE.,'IfcLabel',$,$,$,$,$,.READWRITE.);
@@ -35,12 +35,5 @@ DATA;
#28=IFCSIMPLEPROPERTYTEMPLATE('1YSnFzurrEyRNtoLdmmddP',$,'BringToFront','The objects with these SVG classes will render in front of all other objects.Ex: IfcBeam, IfcColumn',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
#29=IFCSIMPLEPROPERTYTEMPLATE('0lP6Y8q9v2QhDnR4sT7uVx',$,'PerspectiveShiftX','Horizontal perspective camera shift stored as drawing metadata using Blender camera shift units.',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#30=IFCSIMPLEPROPERTYTEMPLATE('2mR8b1NcW5EoFyG7hJ9kLp',$,'PerspectiveShiftY','Vertical perspective camera shift stored as drawing metadata using Blender camera shift units.',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#31=IFCSIMPLEPROPERTYTEMPLATE('1cFVJnqT13m8ItkMHaI1tp',$,'UseEdgeClassification','Enable the boundary/outline/sharp/crease/flush SVG edge classification scheme (issue #3668). When false, drawings use the original unclassified linework.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#32=IFCSIMPLEPROPERTYTEMPLATE('2kB$mxBgnBUvhjh0Ti0c4P',$,'RenderCreases','Whether to render ''crease'' (concave) edges. Only relevant when UseEdgeClassification is enabled.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#33=IFCSIMPLEPROPERTYTEMPLATE('3MSIJNW$T8r9Hl12kk0BY$',$,'ValleyAngleMinDegrees','Minimum concave dihedral deviation from flat, in degrees, for a projection edge to be classified as ''crease'' rather than ''flush''.',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#34=IFCSIMPLEPROPERTYTEMPLATE('2epSGfC4bFM9gb1X7zBIp4',$,'RenderSharp','Whether to render ''sharp'' (convex) edges. Only relevant when UseEdgeClassification is enabled.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#35=IFCSIMPLEPROPERTYTEMPLATE('3TZwsEjkr5WRDKcgrYzSIA',$,'RidgeAngleMinDegrees','Minimum convex dihedral deviation from flat, in degrees, for a projection edge to be classified as ''sharp'' rather than ''flush''.',.P_SINGLEVALUE.,'IfcReal',$,$,$,$,$,.READWRITE.);
#36=IFCSIMPLEPROPERTYTEMPLATE('2Jua$lO754vgZOkBoHM2gA',$,'RenderFlush','Whether to render ''flush'' edges (dihedral deviation below both ridge/valley thresholds). Only relevant when UseEdgeClassification is enabled.',.P_SINGLEVALUE.,'IfcBoolean',$,$,$,$,$,.READWRITE.);
#37=IFCSIMPLEPROPERTYTEMPLATE('1zM9sia2L8RQDnWZxgUwlZ',$,'JoinClasses','Comma separated list of IFC classes whose cut linework will be joined together when they meet (e.g. mitred at a corner).\X2\000A\X0\Defaults to ''IfcWall,IfcSlab'' if not set. Override to also join other classes, such as ''IfcWall,IfcSlab,IfcCovering''.',.P_SINGLEVALUE.,'IfcText',$,$,$,$,$,.READWRITE.);
ENDSEC;
END-ISO-10303-21;
+6 -52
View File
@@ -10,7 +10,6 @@ if bonsai_lib_path:
import argparse
import base64
import json
import urllib.parse
import xml.etree.ElementTree as ET
import pystache
@@ -19,53 +18,8 @@ from aiohttp import web
sio_port = 8080 # default port
def get_asset_version() -> str:
"""A cache-busting token appended to locally served static asset URLs.
Browsers otherwise keep serving a stale cached copy of static/js and
static/css after Bonsai ships a code change, until the user does a hard
refresh. Using the Bonsai version (which includes the build's commit
hash) means the token changes on every shipped update.
"""
if bonsai_version:
return urllib.parse.quote(bonsai_version, safe="")
# Fallback for standalone runs without BONSAI_VERSION set (e.g. running
# sioserver.py directly outside of Blender): derive a token from the
# newest mtime among the static assets, so it still changes whenever the
# shipped files change.
static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
latest_mtime = 0
for root, _dirs, files in os.walk(static_dir):
for name in files:
latest_mtime = max(latest_mtime, int(os.path.getmtime(os.path.join(root, name))))
return f"dev-{latest_mtime}"
asset_version = get_asset_version()
@web.middleware
async def no_cache_static_middleware(request: web.Request, handler):
"""Force revalidation of locally served static assets.
Query-string version stamping (see `asset_version`) busts the cache for
the HTML-referenced entry points, but JS files that statically import
other local modules (e.g. cost.js/gantt.js importing utilities/costui.js)
reference those modules by an un-stamped relative path. Marking all
/static/ and /jsgantt/ responses as no-cache makes browsers always
revalidate with the server (a cheap conditional GET / 304 when nothing
changed), so nested imports also pick up shipped changes without
requiring a hard refresh.
"""
response = await handler(request)
if request.path.startswith("/static/") or request.path.startswith("/jsgantt/"):
response.headers["Cache-Control"] = "no-cache, must-revalidate"
return response
sio = socketio.AsyncServer(cors_allowed_origins="*", async_mode="aiohttp", max_http_buffer_size=10000000)
app = web.Application(middlewares=[no_cache_static_middleware])
app = web.Application()
sio.attach(app)
@@ -245,28 +199,28 @@ class BlenderNamespace(socketio.AsyncNamespace):
async def schedules(request):
with open("templates/index.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version, "v": asset_version})
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
return web.Response(text=html_content, content_type="text/html")
async def costing(request):
with open("templates/costing.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version, "v": asset_version})
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
return web.Response(text=html_content, content_type="text/html")
async def sequencing(request):
with open("templates/gantt.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version, "v": asset_version})
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
return web.Response(text=html_content, content_type="text/html")
async def documentation(request):
with open("templates/drawings.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version, "v": asset_version})
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
return web.Response(text=html_content, content_type="text/html")
@@ -275,7 +229,7 @@ async def documentation(request):
async def demo(request):
with open("templates/demo.html", "r") as f:
template = f.read()
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version, "v": asset_version})
html_content = pystache.render(template, {"port": sio_port, "version": bonsai_version})
return web.Response(text=html_content, content_type="text/html")
@@ -244,7 +244,7 @@ function addGanttElement(blenderId, tasks, workSched, filename) {
vShowTaskInfoLink: 1, // Show link in tool tip (0/1)
vShowEndWeekDate: 0, // Show/Hide the date for the last day of the week in header for daily
vUseSingleCell: 10000, // Set the threshold cell per table row (Helps performance for large data.
vFormatArr: ["Hour", "Day", "Week", "Month", "Quarter"], // vUseSingleCell keeps Hour usable on large charts.
vFormatArr: ["Day", "Week", "Month", "Quarter"], // Even with setUseSingleCell using Hour format on such a large chart can cause issues in some browsers,
vShowRes: true, // Disable the resource column.
vShowComp: false, // Disable the completion column.
vShowDur: false, // Disable the duration column, because jsgantt doesn't calculate durations the way we want.
@@ -250,14 +250,6 @@ export class CostUI {
},
});
CostUI.addRibbonButton({
text: "Download CSV",
icon: "fa-solid fa-file-csv",
callback: () => {
CostUI.downloadCsv();
},
});
CostUI.addRibbonButton({
text: "Hide Schedules",
icon: "fa-regular fa-eye-slash",
@@ -531,81 +523,6 @@ export class CostUI {
}
}
static downloadCsv() {
const tables = document.querySelectorAll("table[id^='cost-items-']");
if (tables.length === 0) {
alert("No cost schedule loaded to export!");
return;
}
tables.forEach((table) => {
const scheduleId = table.id.split("-").pop();
const csv = CostUI.tableToCsv(table);
if (csv === null) {
return;
}
const nameEl = document.querySelector(
"#cost-schedule-container-" + scheduleId + " .form-header span"
);
const scheduleName = nameEl
? nameEl.textContent
: "cost_schedule_" + scheduleId;
CostUI.triggerCsvDownload(csv, scheduleName + ".csv");
});
}
static tableToCsv(table) {
const escapeCsvCell = (value) => {
const text = (value === null || value === undefined ? "" : value)
.toString()
.trim();
if (/[",\n]/.test(text)) {
return '"' + text.replace(/"/g, '""') + '"';
}
return text;
};
const cellText = (cell) => {
const input = cell.querySelector("input");
return input ? input.value : cell.innerText;
};
// The Actions column only holds buttons (edit/delete/etc), not data.
const isDataColumn = (column) => column && column !== "Actions";
const headerCells = Array.from(table.querySelectorAll("thead th")).filter(
(th) => isDataColumn(th.getAttribute("data-column"))
);
if (headerCells.length === 0) {
return null;
}
const rows = [headerCells.map((th) => escapeCsvCell(th.textContent)).join(",")];
table.querySelectorAll("tbody tr").forEach((row) => {
const cells = Array.from(row.children).filter((cell) =>
isDataColumn(cell.getAttribute("data-column"))
);
if (cells.length === 0) {
return; // e.g. the "No cost items found" placeholder row.
}
rows.push(cells.map((cell) => escapeCsvCell(cellText(cell))).join(","));
});
return rows.join("\n");
}
static triggerCsvDownload(csvContent, filename) {
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
static createCostTable({ costSchedule, currency, callbacks }) {
const preferences = CostUI.getColumnPreferences();
const isScheduleOfRates = costSchedule.PredefinedType === "SCHEDULEOFRATES";
@@ -7,7 +7,7 @@
<link
rel="stylesheet"
type="text/css"
href="/static/css/cost.css?v={{v}}"
href="/static/css/cost.css"
id="index-stylesheet"
/>
<link
@@ -21,7 +21,7 @@
/>
<script
type="text/javascript"
src="/static/js/jquery.min.js?v={{v}}"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -34,7 +34,7 @@
<script>
var SOCKET_PORT = {{port}};
</script>
<script type="module" defer src="./static/js/cost.js?v={{v}}"></script>
<script type="module" defer src="./static/js/cost.js"></script>
</head>
<body>
<nav>
@@ -12,14 +12,14 @@
/>
<!-- here we request the CSS file from the server, -->
<!-- using registered static path in the server -->
<link rel="stylesheet" href="/static/css/demo.css?v={{v}}" id="demo-stylesheet" />
<link rel="stylesheet" href="/static/css/demo.css" id="demo-stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
/>
<script
type="text/javascript"
src="/static/js/jquery.min.js?v={{v}}"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -33,7 +33,7 @@
</script>
<!-- here we request the JS file from the server-->
<!-- using registered static path in the server -->
<script defer src="./static/js/demo.js?v={{v}}"></script>
<script defer src="./static/js/demo.js"></script>
</head>
<body>
<!-- the navigation bar at the top of the page. -->
@@ -15,7 +15,7 @@
/>
<link
rel="stylesheet"
href="/static/css/drawings.css?v={{v}}"
href="/static/css/drawings.css"
id="drawings-stylesheet"
/>
<link
@@ -24,7 +24,7 @@
/>
<script
type="text/javascript"
src="/static/js/jquery.min.js?v={{v}}"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -45,7 +45,7 @@
<script>
var SOCKET_PORT = {{port}};
</script>
<script defer src="./static/js/drawings.js?v={{v}}"></script>
<script defer src="./static/js/drawings.js"></script>
</head>
<body>
<nav>
@@ -9,25 +9,25 @@
type="image/x-icon"
href="https://bonsaibim.org/assets/images/favicon-blender.png"
/>
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css?v={{v}}" />
<link rel="stylesheet" href="/static/css/gantt.css?v={{v}}" id="gantt-stylesheet" />
<link rel="stylesheet" type="text/css" href="/jsgantt/jsgantt.css" />
<link rel="stylesheet" href="/static/css/gantt.css" id="gantt-stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"
/>
<script
type="text/javascript"
src="/static/js/jquery.min.js?v={{v}}"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
src="https://cdn.socket.io/4.0.0/socket.io.min.js"
></script>
<script type="text/javascript" src="./jsgantt/jsgantt.js?v={{v}}"></script>
<script type="text/javascript" src="./jsgantt/jsgantt.js"></script>
<script>
var SOCKET_PORT = {{port}};
</script>
<script type="module" defer src="./static/js/gantt.js?v={{v}}"></script>
<script type="module" defer src="./static/js/gantt.js"></script>
</head>
<body>
<nav class="no-print">
@@ -9,7 +9,7 @@
type="image/x-icon"
href="https://bonsaibim.org/assets/images/favicon-blender.png"
/>
<link rel="stylesheet" href="/static/css/index.css?v={{v}}" id="index-stylesheet" />
<link rel="stylesheet" href="/static/css/index.css" id="index-stylesheet" />
<link
rel="stylesheet"
id="tabulator-stylesheet"
@@ -21,7 +21,7 @@
/>
<script
type="text/javascript"
src="/static/js/jquery.min.js?v={{v}}"
src="/static/js/jquery.min.js"
></script>
<script
type="text/javascript"
@@ -34,7 +34,7 @@
<script>
var SOCKET_PORT = {{port}};
</script>
<script defer src="./static/js/index.js?v={{v}}"></script>
<script defer src="./static/js/index.js"></script>
</head>
<body>
<nav>
+3 -4
View File
@@ -418,10 +418,9 @@ def get_user(ifc: ifcopenshell.file) -> Union[ifcopenshell.entity_instance, None
def viewport_shading_changed_callback(area: bpy.types.Area) -> None:
shading_type = area.spaces.active.shading.type
tool.Style.restore_material_style_types(shading_type)
if shading_type == "SOLID":
area.spaces.active.shading.color_type = "MATERIAL"
shading = area.spaces.active.shading.type
if shading == "RENDERED":
tool.Style.get_style_props().active_style_type = "External"
def subscribe_to_viewport_shading_changes():
+1 -1
View File
@@ -46,7 +46,7 @@ IFC_CONNECTED_TYPE = Union[bpy.types.Material, bpy.types.Object]
class OperationData(TypedDict):
id: int
guid: NotRequired[str]
obj: NotRequired[str]
obj: str
class EditObjectOperationData(TypedDict):
+4 -2
View File
@@ -133,6 +133,10 @@ class MaterialCreator:
if shape_has_openings and coords.is_a("IfcIndexedTextureMap"):
continue
tool.Loader.load_indexed_map(coords, self.mesh)
elif tool.Style.get_texture_style(material):
# No explicit coordinate mapping (e.g. IFC2X3 has no IsMappedBy,
# and IFC4 COORD uses generated UVs). Bake XY→UV as fallback.
tool.Loader.load_generated_uv_map(self.mesh)
def assign_material_slots_to_faces(self) -> None:
if not self.mesh["ios_materials"]:
@@ -1099,14 +1103,12 @@ class IfcImporter:
vertices = [[v[i], v[i + 1], v[i + 2], 1] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
v2 = None
polyline = None
for edge in edges:
v1 = vertices[edge[0]]
if v1 != v2:
polyline = curve.splines.new("POLY")
polyline.points[-1].co = mathutils.Vector(v1)
v2 = vertices[edge[1]]
assert polyline is not None
polyline.points.add(1)
polyline.points[-1].co = mathutils.Vector(v2)
edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(geometry).tolist()
@@ -843,6 +843,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, opening)
mat = Matrix(ifcopenshell.util.shape.get_shape_matrix(shape))
mat.translation = (0, 0, 0)
opening_bm = bmesh.new()
verts = ifcopenshell.util.shape.get_vertices(shape.geometry)
for vert in verts:
@@ -1059,7 +1060,6 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
return tool.Ifc.get().createIfcConnectionSurfaceGeometry(surface)
def export_surface(self, polygon, target_face_matrix):
ifc_file = tool.Ifc.get()
x_axis = target_face_matrix.col[0][:3]
z_axis = target_face_matrix.col[2][:3]
p1 = target_face_matrix.translation
@@ -1072,20 +1072,18 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator):
placement = builder.create_axis2_placement_3d([o / self.unit_scale for o in p1], z_axis, x_axis)
surface.BasisSurface = tool.Ifc.get().create_entity("IfcPlane", placement)
schema = ifc_file.schema
if schema != "IFC2X3":
if tool.Ifc.get().schema != "IFC2X3":
points = [tool.Model.convert_si_to_unit(list(co)) for co in polygon.exterior.coords]
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
outer_boundary = tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False)
inner_boundaries: list[ifcopenshell.entity_instance] = []
inner_boundaries = []
for interior in polygon.interiors:
points = [tool.Model.convert_si_to_unit(list(co)) for co in interior.coords]
point_list = tool.Ifc.get().createIfcCartesianPointList2D(points)
inner_boundaries.append(tool.Ifc.get().createIfcIndexedPolyCurve(point_list, None, False))
else:
# TODO:
raise NotImplementedError(schema)
pass # TODO
surface.OuterBoundary = outer_boundary
surface.InnerBoundaries = inner_boundaries
+6 -3
View File
@@ -156,13 +156,16 @@ class BrickschemaReferencesData:
for rel in getattr(tool.Ifc.get_entity(bpy.context.active_object), "HasAssociations", []):
if rel.is_a("IfcRelAssociatesLibrary"):
reference = rel.RelatingLibrary
identification = tool.Document.get_external_reference_id(reference)
if not identification or "#" not in identification:
if tool.Ifc.get_schema() == "IFC2X3" and "#" not in reference.ItemReference:
continue
if tool.Ifc.get_schema() != "IFC2X3" and "#" not in reference.Identification:
continue
results.append(
{
"id": reference.id(),
"identification": identification,
"identification": (
reference.ItemReference if tool.Ifc.get_schema() == "IFC2X3" else reference.Identification
),
"name": reference.Name or "Unnamed",
}
)
@@ -400,7 +400,6 @@ class CadOffset(bpy.types.Operator):
[verts.update(e.verts) for e in edges]
# Use the viewport angle to determine the offset direction
wp = None
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
# Don't ask me, I don't know.
@@ -410,7 +409,6 @@ class CadOffset(bpy.types.Operator):
z = area.spaces.active.region_3d.view_rotation @ Vector((0, 0, 1))
wp = Matrix([x, y, z, Vector((0, 0, 0))]).to_4x4().transposed()
break
assert wp is not None
rotation = Matrix.Rotation(pi / 2, 2, "Z")
rotation_i = Matrix.Rotation(-pi / 2, 2, "Z")
@@ -478,7 +478,6 @@ class ChangeClassificationLevel(bpy.types.Operator):
def execute(self, context):
props = tool.Classification.get_classification_props()
props.available_library_references.clear()
reference = None
for reference in IfcStore.classification_file.by_id(self.parent_id).HasReferences:
new = props.available_library_references.add()
new.identification = reference.Identification or ""
@@ -486,7 +485,6 @@ class ChangeClassificationLevel(bpy.types.Operator):
new.ifc_definition_id = reference.id()
new.has_references = bool(reference.HasReferences)
new.referenced_source
assert reference
if reference.ReferencedSource.is_a("IfcClassificationReference"):
props.active_library_referenced_source = reference.ReferencedSource.ReferencedSource.id()
else:
@@ -156,8 +156,6 @@ class CostSchedulesData:
values = root_element.CostValues
elif root_element.is_a("IfcConstructionResource"):
values = root_element.BaseCosts
else:
assert False, root_element
for cost_value in values or []:
cls._load_cost_value(root_element, data, cost_value)
# data["CostValues"].append(cost_value.id())
+22 -11
View File
@@ -127,25 +127,36 @@ class ObjectDocumentData:
identification = None
if is_information:
identification = tool.Document.get_document_information_id(relating_document)
if tool.Ifc.get_schema() == "IFC2X3":
identification = relating_document.DocumentId
else:
identification = relating_document.Identification
location = getattr(relating_document, "Location", None)
description = getattr(relating_document, "Description", "No description")
else:
description = relating_document.Description
referenced_document = tool.Document.get_reference_document(relating_document)
if tool.Ifc.get_schema() == "IFC2X3":
reference_to_document = relating_document.ReferenceToDocument
if not name and reference_to_document:
name = reference_to_document[0].Name
if not name and referenced_document:
name = referenced_document.Name
identification = relating_document.ItemReference
if not identification and reference_to_document:
identification = reference_to_document[0].DocumentId
location = relating_document.Location
else:
referenced_document = relating_document.ReferencedDocument
if not name and referenced_document:
name = referenced_document.Name
identification = tool.Document.get_external_reference_id(relating_document)
if not identification and referenced_document:
identification = tool.Document.get_document_information_id(referenced_document)
identification = relating_document.Identification
if not identification and referenced_document:
identification = referenced_document.Identification
location = relating_document.Location
# IFC2X3 IfcDocumentInformation has no Location to fall back to.
if location is None and referenced_document and tool.Ifc.get_schema() != "IFC2X3":
location = referenced_document.Location
location = relating_document.Location
if location is None and referenced_document:
location = referenced_document.Location
location = cls.convert_to_file_uri(location) if location else None
@@ -47,7 +47,6 @@ classes = (
operator.CleanWireframes,
operator.ContractSheet,
operator.ConvertSVGToDXF,
operator.CopyAnnotationToDrawing,
operator.CopyTextToSelection,
operator.CreateDrawing,
operator.CreateSheets,
@@ -108,7 +107,6 @@ classes = (
operator.SelectAssignedProduct,
operator.SelectSimilarTextLiteralValue,
operator.ToggleTargetView,
operator.ToggleDrawingCategorySelection,
operator.OpenDocumentationWebUi,
operator.FilterSelectedObjectsIfIntersectedByCamera,
prop.Variable,
@@ -425,12 +425,10 @@ class BaseDecorator:
blf.size(font_id, font_size_px)
w, h = None, None
if box_alignment or center or vcenter:
w, h = blf.dimensions(font_id, text)
if box_alignment:
assert w is not None and h is not None
box_alignment_offset = Vector((0, 0))
if "bottom" in box_alignment:
pass
@@ -452,12 +450,10 @@ class BaseDecorator:
else:
# horizontal centering
if center:
assert w is not None
pos -= Vector((cos, sin)) * w * 0.5
# vertical centering
if vcenter:
assert h is not None
pos -= Vector((-sin, cos)) * h * 0.5
# side-shifting
@@ -1005,8 +1001,6 @@ class FallDecorator(BaseDecorator):
O = A.copy()
O.z = B.z
run = (B - O).length
angle_tg = None
if run != 0:
angle_tg = rise / run
angle = round(degrees(atan(angle_tg)))
@@ -1024,7 +1018,6 @@ class FallDecorator(BaseDecorator):
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
assert angle_tg is not None
return f"{round(angle_tg * 100)} %"
return "NO DATA"
@@ -1256,7 +1249,6 @@ class SectionLevelDecorator(BaseDecorator):
}
# process edges
text_position, text_dir = None, None
for edge in edges_original:
v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]]
start_i = len(output_verts)
@@ -1562,39 +1554,32 @@ class SectionDecorator(BaseDecorator):
v0, v1 = winspace_verts[edge[0]], winspace_verts[edge[1]]
start_i = len(output_verts)
circle_head = None
if display_start_circle or display_end_circle:
circle_head = get_circle_head(circle_size)
triangle_head, divider_offset, edge_dir_circle = None, None, None
display_symbol = display_start_symbol or display_end_symbol
if display_symbol or connect_markers:
if display_start_symbol or display_end_symbol or connect_markers:
edge_dir = (v1 - v0).normalized()
side = (edge_dir.yx * Vector((1, -1))).to_3d()
edge_dir_circle = edge_dir * circle_size
if display_symbol:
triangle_head = get_triangle_head(edge_dir, -side, triangle_length, triangle_width)
divider_offset = []
divider_offset.append(edge_dir_circle if connect_markers else edge_dir_circle * 3)
divider_offset.append(edge_dir_circle)
if display_start_symbol or display_end_symbol:
triangle_head = get_triangle_head(edge_dir, -side, triangle_length, triangle_width)
divider_offset = []
divider_offset.append(edge_dir_circle if connect_markers else edge_dir_circle * 3)
divider_offset.append(edge_dir_circle)
if display_start_circle:
assert circle_head is not None
start_i = add_verts_sequence([v + v0 for v in circle_head], start_i, **out_kwargs, closed=True)
# circle middle divider
if not display_start_symbol:
assert divider_offset is not None
start_i = add_verts_sequence(
[v0 + divider_offset[0], v0 - divider_offset[1]], start_i, **out_kwargs
)
if display_start_symbol:
assert triangle_head is not None
start_i = add_verts_sequence([v + v0 for v in triangle_head], start_i, **out_kwargs, closed=True)
if display_end_circle:
assert circle_head is not None
start_i = add_verts_sequence([v + v1 for v in circle_head], start_i, **out_kwargs, closed=True)
# circle middle divider
if not display_end_symbol:
@@ -1603,11 +1588,9 @@ class SectionDecorator(BaseDecorator):
)
if display_end_symbol:
assert triangle_head is not None
start_i = add_verts_sequence([v + v1 for v in triangle_head], start_i, **out_kwargs, closed=True)
if connect_markers:
assert edge_dir_circle is not None
gap = []
gap.append(edge_dir_circle if display_start_symbol else Vector((0, 0, 0)))
gap.append(edge_dir_circle if display_end_symbol else Vector((0, 0, 0)))
@@ -1694,12 +1677,6 @@ class CutDecorator:
selected_elements_color = self.addon_prefs.decorator_color_selected
self.fallback_colour = (0.3, 0.3, 0.3, 1)
# Evaluate camera movement once per redraw rather than twice per object: is_camera_moved()
# runs eval()/numpy on the camera matrix and, as a side effect, refreshes the stored
# checksum on the first True result - so calling it per object also made the second call
# (fill) see an already-updated checksum and skip recalculating when it shouldn't.
self.camera_moved = self.is_camera_moved()
all_vertices = []
all_edges = []
selected_vertices = []
@@ -1825,35 +1802,23 @@ class CutDecorator:
# Currently selected objects must be recalculated as they may be being moved / edited.
# If the camera is selected, we also recalculate as the user may be moving the camera.
is_selected = obj.select_get()
recalc_cut = not has_cut_cache or is_selected or self.camera_moved
recalc_fill = not has_fill_cache or is_selected or self.camera_moved
if not (recalc_cut or recalc_fill):
return
# The intersection test builds a bmesh and scans every vertex; both recalculations need
# the same answer, so compute it once here rather than once in each.
is_intersecting = tool.Drawing.is_intersecting_camera(obj, context.scene.camera)
if recalc_cut:
self.recalculate_cut(context, obj, element, is_intersecting)
if recalc_fill:
self.recalculate_fill(context, obj, element, is_intersecting)
if not has_cut_cache or obj.select_get() or self.is_camera_moved():
self.recalculate_cut(context, obj, element)
if not has_fill_cache or obj.select_get() or self.is_camera_moved():
self.recalculate_fill(context, obj, element)
def recalculate_cut(
self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance, is_intersecting: bool
) -> None:
if is_intersecting:
def recalculate_cut(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
if tool.Drawing.is_intersecting_camera(obj, context.scene.camera):
verts, edges = tool.Drawing.bisect_mesh(obj, context.scene.camera)
DecoratorData.cut_cache[element.id()] = (verts, edges)
else:
DecoratorData.cut_cache[element.id()] = (False, False)
def recalculate_fill(
self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance, is_intersecting: bool
) -> None:
def recalculate_fill(self, context, obj: bpy.types.Object, element: ifcopenshell.entity_instance) -> None:
element_id = element.id()
if not is_intersecting:
if not tool.Drawing.is_intersecting_camera(obj, context.scene.camera):
DecoratorData.fill_cache[element_id] = {}
return
@@ -1906,8 +1871,6 @@ class CutDecorator:
layer_set = material
offset = 0
sense_factor = 1
else:
assert False, material
if len(layer_set.MaterialLayers) == 1:
material = layer_set.MaterialLayers[0].Material
@@ -1934,8 +1897,6 @@ class CutDecorator:
co = Vector((0.0, 0.0, offset))
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
else:
assert False, usage
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
@@ -225,11 +225,9 @@ def format_distance(
unit_system, unit_length, unit_fraction = unit_mapping[custom_unit]
value *= unit_scale
tx_dist = None
# Imperial Formatting
if unit_system == "IMPERIAL":
toInches = None
if in_unit_length:
if unit_length == "INCHES":
toInches = 1
@@ -243,7 +241,6 @@ def format_distance(
toInches = 1550
inPerFoot = 144
assert toInches is not None
decInches = value * toInches
decFeet = decInches / 12
@@ -386,7 +383,6 @@ def format_distance(
if precision and isinstance(precision, float):
value = precision * round(float(value) / precision)
fmt = None
if decimal_places is not None:
fmt = "%1." + str(decimal_places) + "f"
@@ -469,7 +465,6 @@ def format_distance(
assert f"Unexpected unit_system - '{unit_system}'."
# tx_dist = fmt % value
assert tx_dist is not None
return tx_dist
@@ -228,90 +228,6 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator):
)
def get_copy_annotation_target_drawings(self, context):
global COPY_ANNOTATION_TARGET_DRAWINGS_ENUM
drawings = [e for e in tool.Ifc.get().by_type("IfcAnnotation") if e.ObjectType == "DRAWING"]
drawings.sort(key=lambda d: d.Name or "")
COPY_ANNOTATION_TARGET_DRAWINGS_ENUM = [(str(d.id()), d.Name or "Unnamed", "") for d in drawings]
return COPY_ANNOTATION_TARGET_DRAWINGS_ENUM
COPY_ANNOTATION_TARGET_DRAWINGS_ENUM = []
class CopyAnnotationToDrawing(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.copy_annotation_to_drawing"
bl_label = "Copy Annotation To Drawing"
bl_description = (
"Copy the selected annotations to another drawing.\n\n"
"The copies become independent annotations assigned to the chosen drawing, "
"placed in its view plane. The originals stay in their current drawing"
)
bl_options = {"REGISTER", "UNDO"}
target_drawing: bpy.props.EnumProperty(name="Target Drawing", items=get_copy_annotation_target_drawings)
if TYPE_CHECKING:
target_drawing: str
@classmethod
def poll(cls, context):
if not tool.Ifc.get():
cls.poll_message_set("No IFC project loaded.")
return False
if not cls.get_selected_annotations(context):
cls.poll_message_set("No annotation selected.")
return False
return True
@classmethod
def get_selected_annotations(cls, context) -> list[ifcopenshell.entity_instance]:
return [
element
for obj in context.selected_objects
if (element := tool.Ifc.get_entity(obj))
and element.is_a("IfcAnnotation")
and element.ObjectType != "DRAWING"
]
def invoke(self, context, event):
assert context.window_manager
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
assert self.layout
row = self.layout.row()
row.prop(self, "target_drawing")
def _execute(self, context):
if not self.target_drawing:
self.report({"ERROR"}, "No target drawing selected.")
return {"CANCELLED"}
target_drawing = tool.Ifc.get().by_id(int(self.target_drawing))
annotations = self.get_selected_annotations(context)
previous_selection = [obj for a in annotations if (obj := tool.Ifc.get_object(a))]
previous_active = context.view_layer.objects.active
copied = core.copy_annotations_to_drawing(
tool.Ifc,
tool.Collector,
tool.Drawing,
tool.Geometry,
annotations=annotations,
target_drawing=target_drawing,
)
for obj in context.selected_objects:
obj.select_set(False)
for obj in previous_selection:
if obj.name in context.view_layer.objects:
obj.select_set(True)
if previous_active and previous_active.name in context.view_layer.objects:
context.view_layer.objects.active = previous_active
skipped = len(annotations) - len(copied)
message = f"Copied {len(copied)} annotations to {target_drawing.Name or 'Unnamed'}."
if skipped:
message += f" Skipped {skipped} already in that drawing."
self.report({"INFO"}, message)
class CreateDrawing(bpy.types.Operator):
"""Creates/refreshes a .svg drawing
@@ -782,8 +698,6 @@ class CreateDrawing(bpy.types.Operator):
layer_set = material
offset = 0
sense_factor = 1
else:
assert False, material
camera_matrix_i = context.scene.camera.matrix_world.inverted()
@@ -808,6 +722,7 @@ class CreateDrawing(bpy.types.Operator):
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.000001)
bmesh.ops.triangle_fill(bm, use_dissolve=True, edges=bm.edges)
prev_co = None
if not usage:
sense_factor = 1 # Assume the extrusion vector points in the direction sense
no = tool.Drawing.get_extrusion_vector(element).normalized()
@@ -824,8 +739,6 @@ class CreateDrawing(bpy.types.Operator):
co = Vector((0.0, 0.0, offset))
no = tool.Drawing.get_extrusion_vector(element).normalized()
no = Vector([1.0, 0.0, 0.0])
else:
assert False, usage
no *= sense_factor
last_i = len(layer_set.MaterialLayers) - 1
for i, layer in enumerate(layer_set.MaterialLayers):
@@ -993,10 +906,6 @@ class CreateDrawing(bpy.types.Operator):
if os.path.isfile(svg_path) and self.props.should_use_linework_cache:
return svg_path
ifc = tool.Ifc.get()
semantics = None
pairs = None
# in case of printing multiple drawings we need to sync just once
if self.sync and self.drawing_index == 0:
with profile("sync"):
@@ -1400,18 +1309,6 @@ class CreateDrawing(bpy.types.Operator):
self.svg_settings = ifcopenshell.geom.settings()
self.svg_settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
self.svg_settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
# SVG edge classification (issue #3668). See edge-classification.md. Settings are
# per-drawing, stored in EPset_Drawing and read into self.cprops by import_camera_props.
try:
self.svg_settings.set("svg-use-edge-classification", self.cprops.use_edge_classification)
self.svg_settings.set("svg-render-crease-edges", self.cprops.render_creases)
self.svg_settings.set("svg-valley-angle-min-degrees", self.cprops.valley_angle_min_degrees)
self.svg_settings.set("svg-render-sharp-edges", self.cprops.render_sharp)
self.svg_settings.set("svg-ridge-angle-min-degrees", self.cprops.ridge_angle_min_degrees)
self.svg_settings.set("svg-emit-flush-edges", self.cprops.render_flush)
except Exception:
# Backwards compatibility with older ifcopenshell builds that don't expose these keys.
pass
self.svg_buffer = ifcopenshell.geom.serializers.buffer()
self.serialiser_settings = ifcopenshell.geom.serializer_settings()
self.serialiser = ifcopenshell.geom.serializers.svg(
@@ -1555,15 +1452,6 @@ class CreateDrawing(bpy.types.Operator):
"Material.Name",
]
join_classes = ifcopenshell.util.element.get_pset(self.camera_element, "EPset_Drawing", "JoinClasses")
if join_classes:
join_classes = tuple(c.strip() for c in join_classes.split(",") if c.strip())
else:
# Architectural convention only merges these objects by default. E.g. pipe
# segments and fittings shouldn't merge. Users may override this per-drawing
# via the EPset_Drawing.JoinClasses property (e.g. to also join IfcCovering).
join_classes = ("IfcWall", "IfcSlab")
group = root.find("{http://www.w3.org/2000/svg}g")
joined_paths = {}
self.is_manifold_cache = {}
@@ -1665,7 +1553,8 @@ class CreateDrawing(bpy.types.Operator):
)
path.attrib["d"] = d
if not any(element.is_a(c) for c in join_classes):
# Architectural convention only merges these objects. E.g. pipe segments and fittings shouldn't merge.
if not element.is_a("IfcWall") and not element.is_a("IfcSlab"):
continue
keys = []
@@ -2310,11 +2199,7 @@ class SelectAllDrawings(bpy.types.Operator):
def execute(self, context):
props = tool.Drawing.get_document_props()
# When filtering to sheeted drawings only, act on the visible drawings only.
sheeted_ids = tool.Drawing.get_sheeted_drawing_ids() if props.show_drawings_on_sheets_only else None
for drawing in props.drawings:
if sheeted_ids is not None and drawing.is_drawing and drawing.ifc_definition_id not in sheeted_ids:
continue
if drawing.is_selected != self.select_all:
drawing.is_selected = self.select_all
return {"FINISHED"}
@@ -3701,7 +3586,7 @@ class EditSheet(bpy.types.Operator, tool.Ifc.Operator):
if sheet.is_a("IfcDocumentInformation"):
self.document_type = "SHEET"
self.name = sheet.Name
self.identification = tool.Document.get_document_information_id(sheet)
self.identification = sheet.DocumentId if tool.Ifc.get_schema() == "IFC2X3" else sheet.Identification
elif sheet.is_a("IfcDocumentReference") and tool.Drawing.get_reference_description(sheet) == "TITLEBLOCK":
self.document_type = "TITLEBLOCK"
else:
@@ -3874,26 +3759,6 @@ class ToggleTargetView(bpy.types.Operator):
return {"FINISHED"}
class ToggleDrawingCategorySelection(bpy.types.Operator):
bl_idname = "bim.toggle_drawing_category_selection"
bl_label = "Toggle Category Selection"
bl_description = "Select or deselect all drawings in this view category"
bl_options = {"REGISTER", "UNDO"}
target_view: bpy.props.StringProperty()
if TYPE_CHECKING:
target_view: str
def execute(self, context):
drawings = tool.Drawing.get_visible_drawings_in_category(self.target_view)
# If everything visible in the category is already selected, deselect all; otherwise select all.
new_state = not all(d.is_selected for d in drawings)
for drawing in drawings:
drawing.is_selected = new_state
return {"FINISHED"}
class ExpandSheet(bpy.types.Operator):
bl_idname = "bim.expand_sheet"
bl_label = "Expand Sheet"
@@ -409,12 +409,6 @@ class DocProperties(PropertyGroup):
options=set(),
)
is_editing_drawings: BoolProperty(name="Is Editing Drawings", default=False)
show_drawings_on_sheets_only: BoolProperty(
name="Show Only Drawings on Sheets",
description="Only show drawings that are placed on a sheet",
default=False,
options=set(),
)
is_editing_schedules: BoolProperty(name="Is Editing Schedules", default=False)
is_editing_references: BoolProperty(name="Is Editing References", default=False)
target_view: EnumProperty(
@@ -445,7 +439,6 @@ class DocProperties(PropertyGroup):
should_use_annotation_cache: bool
should_draw_linked_projects: bool
is_editing_drawings: bool
show_drawings_on_sheets_only: bool
is_editing_schedules: bool
is_editing_references: bool
target_view: Literal["PLAN_VIEW", "ELEVATION_VIEW", "SECTION_VIEW", "REFLECTED_PLAN_VIEW", "MODEL_VIEW"]
@@ -543,50 +536,6 @@ class BIMCameraProperties(PropertyGroup):
default=True,
update=get_update_layer_callback("has_annotation", "HasAnnotation"),
)
use_edge_classification: BoolProperty(
name="Use Edge Classification",
description="Classify projection edges into boundary/outline/sharp/crease/flush "
"instead of drawing all linework identically. See edge-classification.md",
default=False,
update=get_update_layer_callback("use_edge_classification", "UseEdgeClassification"),
)
render_creases: BoolProperty(
name="Render Creases",
description="Render 'crease' (concave) projection edges",
default=True,
update=get_update_layer_callback("render_creases", "RenderCreases"),
)
valley_angle_min_degrees: FloatProperty(
name="Valley Angle Minimum",
description="Minimum concave dihedral deviation from flat, in degrees, for a projection "
"edge to be classified as 'crease' rather than 'flush'",
default=12.0,
min=0.0,
max=180.0,
update=get_update_layer_callback("valley_angle_min_degrees", "ValleyAngleMinDegrees"),
)
render_sharp: BoolProperty(
name="Render Sharp",
description="Render 'sharp' (convex) projection edges",
default=True,
update=get_update_layer_callback("render_sharp", "RenderSharp"),
)
ridge_angle_min_degrees: FloatProperty(
name="Ridge Angle Minimum",
description="Minimum convex dihedral deviation from flat, in degrees, for a projection "
"edge to be classified as 'sharp' rather than 'flush'",
default=45.0,
min=0.0,
max=180.0,
update=get_update_layer_callback("ridge_angle_min_degrees", "RidgeAngleMinDegrees"),
)
render_flush: BoolProperty(
name="Render Flush",
description="Render 'flush' projection edges (dihedral deviation below both ridge/valley "
"thresholds). Omitted by default",
default=False,
update=get_update_layer_callback("render_flush", "RenderFlush"),
)
target_view: EnumProperty(
name="Target View",
default="PLAN_VIEW",
@@ -110,14 +110,12 @@ class Scheduler:
y = self.margin
rows = list(sheet.iter_rows())
total_rows = len(rows)
x = None
for i, row in enumerate(rows):
# The last row may contain only null values
if i == (total_rows - 1) and not [c for c in row if c.value is not None]:
continue
x = self.margin
unmerged_height = None
for cell in row:
if isinstance(cell, openpyxl.cell.cell.MergedCell):
column_letter = openpyxl.utils.get_column_letter(cell.column)
@@ -232,11 +230,8 @@ class Scheduler:
)
x += unmerged_width
assert unmerged_height is not None
y += unmerged_height
assert x is not None
total_width = x + self.margin
total_height = y + self.margin
self.svg["width"] = "{}mm".format(total_width)
@@ -380,7 +375,6 @@ class Scheduler:
tri = 0
stop_iterating_over_rows = False
# TODO: row spans support?
x = None
for tr in table.getElementsByType(TableRow):
if stop_iterating_over_rows:
break
@@ -497,7 +491,6 @@ class Scheduler:
tri += 1
y += height
assert x is not None
total_width = x + self.margin
total_height = y + self.margin
self.svg["width"] = "{}mm".format(total_width)
@@ -102,16 +102,16 @@ void angle_circle_head(
in vec4 circle_start, in float circle_angle,
in bool counterclockwise,
out vec4 head[CIRCLE_SEGS+1], out float angle_segs) {
// 1 added to CIRCLE_SEGS because we're number of vertices
// for n segments is n+1
float angle_d;
angle_d = PI * 2 / CIRCLE_SEGS; // 30d
// need to bottom clamp it to 1, otherwise it causes Blender crash at extruding the curve
angle_segs = max(1, ceil(circle_angle / angle_d));
angle_d = circle_angle / angle_segs;
for(int i = 0; i < (angle_segs + 1); i++) {
float angle = angle_d * i;
if (counterclockwise) {
@@ -143,7 +143,7 @@ void cross_head(in vec4 dir, in float size, out vec4 head[3]) {
#define do_vertex(pos, e) (do_vertex_util(pos, vec2(-(e).y, (e).x) / winsize.xy))
#define do_vertex_win(pos, e) ( do_vertex( WIN2CLIP( pos ), e ) )
// if vertex is shared by two segments of the line still need to emit it twice
// if vertex is shared by two segments of the line still need to emit it twice
// to avoid smoothing artifacts
// don't forget to initialize `vec2 EDGE_DIR` for macro to work
// `pos0` / `pos1` - vertex position in clip space
@@ -197,13 +197,10 @@ void do_circle_head(vec4 pos_w, vec4 head[CIRCLE_SEGS]) {
def add_verts_sequence(verts, start_i, output_verts, output_edges, closed=False):
"""Add sequence of verts to output lists, returns next vertex index"""
i = None
for i, v in enumerate(verts[:-1], start_i):
output_verts.append(v)
output_edges.append((i, i + 1))
output_verts.append(verts[-1])
assert i is not None
if closed:
output_edges.append((i + 1, start_i))
return i + 2
@@ -276,7 +273,7 @@ class BaseShader:
FRAG_GLSL = """
uniform vec4 color;
uniform float lineWidth;
in float smoothline;
out vec4 fragColor;
void main() {
@@ -903,8 +903,12 @@ class SvgWriter:
continue
sheet = tool.Drawing.get_reference_document(sheet_reference)
if sheet:
reference_id = tool.Document.get_external_reference_id(sheet_reference) or "-"
sheet_id = tool.Document.get_document_information_id(sheet) or "-"
if tool.Ifc.get_schema() == "IFC2X3":
reference_id = sheet_reference.ItemReference or "-"
sheet_id = sheet.DocumentId or "-"
else:
reference_id = sheet_reference.Identification or "-"
sheet_id = sheet.Identification or "-"
return (reference_id, sheet_id)
break
return ("-", "-")
@@ -1449,7 +1453,6 @@ class SvgWriter:
angle_tg = rise / run
angle = round(degrees(atan(angle_tg)))
else:
angle_tg = None
angle = 90
# ues SLOPE_ANGLE as default
@@ -1463,7 +1466,6 @@ class SvgWriter:
elif object_type == "SLOPE_PERCENT":
if angle == 90:
return "-"
assert angle_tg is not None
return f"{round(angle_tg * 100)} %"
tag = element.Description or get_label_text()
+4 -71
View File
@@ -113,19 +113,6 @@ class BIM_PT_camera(Panel):
row.prop(props, "fill_mode")
row = self.layout.row()
row.prop(props, "cut_mode")
row = self.layout.row()
row.prop(props, "use_edge_classification")
if props.use_edge_classification:
row = self.layout.row()
row.prop(props, "render_creases")
row.prop(props, "valley_angle_min_degrees")
row = self.layout.row()
row.prop(props, "render_sharp")
row.prop(props, "ridge_angle_min_degrees")
row = self.layout.row()
row.prop(props, "render_flush")
row = self.layout.row()
row.prop(props, "width")
row = self.layout.row()
@@ -332,8 +319,6 @@ class BIM_PT_drawings(Panel):
row3.separator(factor=0.5, type="SPACE")
row3.operator("bim.copy_annotation_to_drawing", icon="PASTEDOWN", text="")
row3.operator("bim.select_all_drawings", icon="CHECKBOX_HLT", text="")
row3.operator("bim.create_drawing", text="", icon="OUTPUT")
row3.operator("bim.convert_svg_to_dxf", text="", icon="SEQ_PREVIEW").view = active_drawing.name
@@ -341,7 +326,6 @@ class BIM_PT_drawings(Panel):
self.layout.template_list(
"BIM_UL_drawinglist", "", self.props, "drawings", self.props, "active_drawing_index"
)
self.layout.prop(self.props, "show_drawings_on_sheets_only")
class BIM_PT_schedules(Panel):
@@ -874,8 +858,8 @@ class BIM_UL_drawinglist(bpy.types.UIList):
layout.label(text="", translate=False)
return
row = layout.row(align=True)
if item.is_drawing:
row = layout.row(align=True)
row.label(text="", icon="BLANK1")
selected_icon = "CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT"
row.prop(item, "is_selected", text="", icon=selected_icon, emboss=False)
@@ -896,9 +880,6 @@ class BIM_UL_drawinglist(bpy.types.UIList):
item.ifc_definition_id
)
else:
# Give category headers a distinct inset background so they stand out from drawing rows.
box = layout.box()
row = box.row(align=True)
if item.target_view == "PLAN_VIEW":
icon = "UV_FACESEL"
elif item.target_view == "ELEVATION_VIEW":
@@ -919,55 +900,7 @@ class BIM_UL_drawinglist(bpy.types.UIList):
op = row.operator("bim.toggle_target_view", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT")
op.target_view = item.target_view
op.option = "EXPAND"
group = tool.Drawing.get_visible_drawings_in_category(item.target_view)
all_selected = bool(group) and all(d.is_selected for d in group)
row.operator(
"bim.toggle_drawing_category_selection",
text="",
icon="CHECKBOX_HLT" if all_selected else "CHECKBOX_DEHLT",
emboss=False,
).target_view = item.target_view
row.separator(factor=0.5, type="SPACE")
# Clicking the header name toggles expand/contract, same as the disclosure triangle.
op = row.operator("bim.toggle_target_view", text=item.name, icon=icon, emboss=False)
op.target_view = item.target_view
op.option = "CONTRACT" if item.is_expanded else "EXPAND"
def filter_items(self, context, data: DocProperties, propname: str):
drawings = getattr(data, propname)
helper_funcs = bpy.types.UI_UL_list
flt_flags = []
flt_neworder = []
if self.filter_name:
flt_flags = helper_funcs.filter_items_by_name(
self.filter_name,
self.bitflag_filter_item,
drawings,
"name",
reverse=self.use_filter_sort_reverse,
)
if not flt_flags:
flt_flags = [self.bitflag_filter_item] * len(drawings)
props = tool.Drawing.get_document_props()
if props.show_drawings_on_sheets_only:
ifc_file = tool.Ifc.get()
sheeted_ids = tool.Drawing.get_sheeted_drawing_ids()
# Target view headers are only shown if they contain a sheeted drawing.
sheeted_target_views = {
tool.Drawing.get_drawing_target_view(ifc_file.by_id(drawing_id)) for drawing_id in sheeted_ids
}
for i, item in enumerate(drawings):
if item.is_drawing:
is_visible = item.ifc_definition_id in sheeted_ids
else:
is_visible = item.target_view in sheeted_target_views
if not is_visible:
flt_flags[i] &= ~self.bitflag_filter_item
return flt_flags, flt_neworder
row.prop(item, "name", text="", icon=icon, emboss=False)
class BIM_UL_sheets(bpy.types.UIList):
@@ -1031,14 +964,14 @@ class BIM_UL_sheets(bpy.types.UIList):
if self.filter_name:
filter_name = self.filter_name.lower()
active_sheet_index = None
active_sheet = None
for sheet in data.sheets:
if sheet.is_sheet:
active_sheet = sheet
active_sheet_index = len(flt_flags)
if filter_name in sheet.name.lower() or filter_name in sheet.identification.lower():
flt_flags.append(self.bitflag_filter_item)
if not sheet.is_sheet:
assert active_sheet_index is not None
flt_flags[active_sheet_index] = self.bitflag_filter_item
else:
flt_flags.append(0)
@@ -225,9 +225,6 @@ class AnnotationToolUI:
def draw_edit_object_interface(cls, context):
if DecoratorData.get_text_data(bpy.context.active_object):
add_layout_hotkey_operator(cls.layout, "Edit Text", "S_E", "")
if bpy.ops.bim.copy_annotation_to_drawing.poll():
row = cls.layout.row(align=True)
row.operator("bim.copy_annotation_to_drawing", icon="PASTEDOWN", text="Copy To Drawing")
@classmethod
def draw_type_selection_interface(cls):
@@ -75,13 +75,9 @@ class Helper:
for face in bm.faces:
if len(face.verts) > 4:
potential_faces.append(face)
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
@@ -112,12 +108,10 @@ class Helper:
if not potential_faces:
potential_faces = bm.faces
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
profile = [l.vert.index for l in face.loops]
extrusion = self.detect_extrusion_edge(bm, face)
@@ -151,12 +145,9 @@ class Helper:
if total_verts > 4:
potential_faces.append(face)
# TODO: replace with next(..., None)
face = None
for face in potential_faces:
if face.normal.z < -0.1:
break
assert face is not None
end_faces = []
end_face_normal = face.normal
@@ -581,11 +581,7 @@ class UpdateRepresentation(bpy.types.Operator, tool.Ifc.Operator):
if has_openings and not self.apply_openings:
# Meshlike things with openings can only be updated without openings applied.
if self.from_ui:
self.report(
{"ERROR"},
f"Object '{obj.name}' has openings. "
"ALT+click the button to bake the openings into the new representation.",
)
self.report({"ERROR"}, f"Object '{obj.name}' has openings - representation cannot be updated.")
return
if not product.is_a("IfcGridAxis"):
@@ -3450,43 +3446,40 @@ class UnassignRepresentationItemStyle(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Couldn't find any styles associated with the active representation item.")
return {"CANCELLED"}
# Unassign matching styles from the active object itself
for style in active_styles:
tool.Style.assign_style_to_representation_item(active_representation_item, None)
tool.Geometry.reload_representation(active_obj)
break # No need to check further if one matching style is found
# Resolve an object's active representation down to its base geometry items,
# unwrapping mapped items (Revit families) and boolean results (openings/cuts)
# so we reach the items that actually carry styles.
def get_base_representation_items(obj):
representation = tool.Geometry.get_active_representation(obj)
if not representation or not representation.is_a("IfcRepresentation"):
return
yield from ifcopenshell.util.representation.resolve_base_items(representation)
# Iterate over selected objects and unassign matching styles
# Unassign the style from the active representation item.
tool.Style.assign_style_to_representation_item(active_representation_item, None)
tool.Geometry.reload_representation(active_obj)
# Iterate over other selected objects and unassign matching styles.
for obj in context.selected_objects:
if obj == active_obj:
continue # Skip the active object itself
continue
# Get the IFC entity directly from the object
element = tool.Ifc.get_entity(obj)
if not element:
continue # Skip if no IFC entity is found
representation_item_id = element.Representation.Representations[0].Items[0].id()
representation_item = tool.Ifc.get_entity_by_id(representation_item_id)
if representation_item:
# Retrieve styles for the current representation item
styles = set()
if hasattr(representation_item, "StyledByItem"):
for styled_by_item in representation_item.StyledByItem:
for item in get_base_representation_items(obj):
item_styles = set()
if hasattr(item, "StyledByItem"):
for styled_by_item in item.StyledByItem:
if hasattr(styled_by_item, "Styles"):
styles.update(styled_by_item.Styles)
item_styles.update(styled_by_item.Styles)
# Unassign matching styles
for style in styles:
if style in active_styles:
tool.Style.assign_style_to_representation_item(representation_item, None)
tool.Geometry.reload_representation(obj)
break # No need to check further if one matching style is found
if item_styles & active_styles:
tool.Style.assign_style_to_representation_item(item, None)
tool.Geometry.reload_representation(obj)
break # Only remove one matching style per object
# Reload UI items
bpy.ops.bim.disable_editing_representation_items()
bpy.ops.bim.enable_editing_representation_items()
return {"FINISHED"}
class EnableEditingRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
@@ -3527,15 +3520,12 @@ class EditRepresentationItemShapeAspect(bpy.types.Operator, tool.Ifc.Operator):
if props.representation_item_shape_aspect == "NEW":
active_representation = tool.Geometry.get_active_representation(obj)
# find IfcProductRepresentationSelect based on current representation
product_shape = None
if hasattr(element, "Representation"): # IfcProduct
product_shape = element.Representation
else: # IfcTypeProduct
for representation_map in element.RepresentationMaps:
if representation_map.MappedRepresentation == active_representation:
product_shape = representation_map
assert product_shape is not None
previous_shape_aspect_id = props.active_item.shape_aspect_id
# will be None if item didn't had a shape aspect
previous_shape_aspect = tool.Ifc.get_entity_by_id(previous_shape_aspect_id)
@@ -3885,8 +3875,6 @@ class AddSweptAreaSolidItem(bpy.types.Operator, tool.Ifc.Operator):
curve = builder.rectangle(size=Vector((0.5, 0.5)) / unit_scale)
elif self.shape == "CYLINDER":
curve = builder.circle(radius=0.25 / unit_scale)
else:
assert False, self.shape
item = builder.extrude(
curve,
magnitude=0.5 / unit_scale,
@@ -4124,31 +4112,6 @@ class OverrideMoveSelect(bpy.types.Operator):
self.new_active_obj = obj
return {"FINISHED"}
# Get arrays
ifc_file = tool.Ifc.get()
array_parents_to_move: list[bpy.types.Object] = []
for obj in list(context.selected_objects):
element = tool.Ifc.get_entity(obj)
if not element:
continue
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
continue
parent_element = ifc_file.by_guid(pset["Parent"])
parent_obj = tool.Ifc.get_object(parent_element)
if parent_obj not in array_parents_to_move:
array_parents_to_move.append(parent_obj)
if element.GlobalId != pset["Parent"]:
obj.select_set(False)
if array_parents_to_move:
for parent_obj in array_parents_to_move:
parent_element = tool.Ifc.get_entity(parent_obj)
for array_obj in tool.Array.get_all_objects(parent_element):
array_obj.select_set(True)
self.new_active_obj = parent_obj
return {"FINISHED"}
# Get nests
props = tool.Nest.get_nest_props()
not_editing_objs = [o.obj for o in props.not_editing_objects]
+1 -39
View File
@@ -163,52 +163,14 @@ class AssignGroup(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
if not self.is_assigning:
return bpy.ops.bim.unassign_group(group=self.group)
ifc_file = tool.Ifc.get()
group = ifc_file.by_id(self.group)
products = [
element
for o in tool.Blender.get_selected_objects(include_active=False)
if (element := tool.Ifc.get_entity(o))
]
relocated_annotations = self.unassign_from_previous_drawing(ifc_file, group, products)
ifcopenshell.api.group.assign_group(ifc_file, products=products, group=group)
self.relocate_annotations_to_drawing(relocated_annotations, group)
ifcopenshell.api.group.assign_group(tool.Ifc.get(), products=products, group=tool.Ifc.get().by_id(self.group))
self.report({"INFO"}, f"Assigned {len(products)} objects to group.")
def unassign_from_previous_drawing(self, ifc_file, group, products) -> list[ifcopenshell.entity_instance]:
"""Assigning an annotation to a group that represents a drawing means the
annotation should belong to that drawing only, so it needs to leave
whichever drawing it was previously part of, instead of ending up
visible in both at once.
"""
new_drawing = tool.Drawing.get_group_drawing(group)
if not new_drawing:
return []
relocated = []
for product in products:
if not product.is_a("IfcAnnotation") or product.ObjectType == "DRAWING":
continue
old_drawing = tool.Drawing.get_annotation_drawing(product)
if not old_drawing or old_drawing.id() == new_drawing.id():
continue
if old_group := tool.Drawing.get_drawing_group(old_drawing):
ifcopenshell.api.group.unassign_group(ifc_file, products=[product], group=old_group)
relocated.append(product)
return relocated
def relocate_annotations_to_drawing(self, products, group) -> None:
"""Move the relocated annotations into the new drawing's collection and
depth, now that they have actually been assigned to its group.
"""
if not products:
return
new_drawing = tool.Drawing.get_group_drawing(group)
new_camera = tool.Ifc.get_object(new_drawing) or tool.Drawing.import_drawing(new_drawing)
for product in products:
if obj := tool.Ifc.get_object(product):
tool.Drawing.ensure_annotation_in_drawing_plane(obj, camera=new_camera)
tool.Collector.assign(obj)
class UnassignGroup(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unassign_group"
+3 -1
View File
@@ -103,7 +103,9 @@ class LibraryReferencesData:
results.append(
{
"id": library.id(),
"identification": tool.Document.get_external_reference_id(library),
"identification": (
library.ItemReference if tool.Ifc.get_schema() == "IFC2X3" else library.Identification
),
"name": library.Name or "Unnamed",
}
)
@@ -156,7 +156,6 @@ class RadianceRender(bpy.types.Operator):
print(f"Quality: {quality}, Detail: {detail}, Variability: {variability}")
print(f"Output directory: {output_dir}")
hdr_image_path, hdr_mask_path, sky_map_cal_path = None, None
if use_hdr:
hdr_image = "noon_grass_2k.hdr"
hdr_mask = "noon_grass_2k_mask.hdr"
@@ -255,9 +254,6 @@ class RadianceRender(bpy.types.Operator):
# 4 0 0 -1 180
if use_hdr and choose_hdr_image == "Noon":
assert hdr_image_path is not None
assert hdr_mask_path is not None
assert sky_map_cal_path is not None
with open(sky_file_path, "w") as f:
f.write(sky_description_str)
@@ -56,7 +56,6 @@ classes = (
operator.RemoveMaterial,
operator.RemoveMaterialSet,
operator.RemoveProfile,
operator.RenameMaterial,
operator.ReorderMaterialSetItem,
operator.SelectByMaterial,
operator.SelectMaterialInMaterialsUI,
@@ -102,26 +102,6 @@ class EditMaterial(bpy.types.Operator, tool.Ifc.Operator):
core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material))
class RenameMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.rename_material"
bl_label = "Rename Material"
bl_description = "Rename an IfcMaterial"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
name: bpy.props.StringProperty(name="Name")
def invoke(self, context, event):
material = tool.Ifc.get().by_id(self.material)
self.name = material.Name or ""
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
self.layout.prop(self, "name")
def _execute(self, context):
core.rename_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material), name=self.name)
class DisableEditingMaterial(bpy.types.Operator):
bl_idname = "bim.disable_editing_material"
bl_label = "Disable Editing Material"
@@ -418,13 +418,6 @@ class ImportQuickFavorites(bpy.types.Operator):
bl_description = "Import operators from Blender's Quick Favorites menu, including their configured properties"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
if bpy.app.version[:2] not in tool.Misc.QuickFavorites.OFFSET_USER_MENUS:
cls.poll_message_set(f"Blender version {bpy.app.version_string} is not supported.")
return False
return True
def execute(self, context) -> set["rna_enums.OperatorReturnItems"]:
props = tool.Misc.get_misc_props()
props.quick_favorites.clear()
@@ -564,7 +564,6 @@ class SelectAllArrayObjects(bpy.types.Operator):
except RuntimeError:
self.report({"ERROR"}, f"Objects that don't have an array parent, were deselected.")
object.select_set(False)
continue
array_objects = tool.Array.get_all_objects(parent_element)
tool.Blender.set_objects_selection(
@@ -93,30 +93,6 @@ def _stroke_lines_alpha(
gpu.state.blend_set("NONE")
def _connected_components(
vertex_groups: dict[int, list[bmesh.types.BMVert]],
) -> list[list[bmesh.types.BMVert]]:
"""Split each vertex group's members into their connected components,
since a duplicated arc/circle loop shares its source loop's group index."""
components = []
for verts in vertex_groups.values():
remaining = set(verts)
while remaining:
seed = remaining.pop()
stack = [seed]
component = [seed]
while stack:
v = stack.pop()
for edge in v.link_edges:
other = edge.other_vert(v)
if other in remaining:
remaining.discard(other)
stack.append(other)
component.append(other)
components.append(component)
return components
class ProfileDecorator:
installed = None
@@ -289,7 +265,7 @@ class ProfileDecorator:
# Draw arcs
arc_centroids = []
arc_segments = []
for arc in _connected_components(arcs):
for arc in arcs.values():
if len(arc) != 3:
continue
sorted_arc = [None, None, None]
@@ -316,7 +292,7 @@ class ProfileDecorator:
# Draw circles
circle_centroids = []
circle_segments = []
for circle in _connected_components(circles):
for circle in circles.values():
if len(circle) != 2:
continue
p1 = obj.matrix_world @ circle[0].co
@@ -408,8 +408,6 @@ class MEPGenerator:
compare = tool.Cad.is_x(requested_value, fitting_value, compare_precision)
elif isinstance(fitting_value, list):
compare = tool.Cad.are_vectors_equal(requested_value, Vector(fitting_value), precision)
else:
assert False, f"{key} {second_key}"
return compare
ignore_keys = []
@@ -478,13 +476,11 @@ class MEPGenerator:
if predefined_type == "OBSTRUCTION":
return packed_data
start_port = None
for port in ports:
port_local_position = V(*port.ObjectPlacement.RelativePlacement.Location.Coordinates)
if tool.Cad.is_x(port_local_position.length, 0.0):
start_port = port
break
assert start_port is not None
connected_port = tool.System.get_connected_port(start_port)
connected_element = tool.System.get_port_relating_element(connected_port)
@@ -325,7 +325,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
if self.from_invoke and str(self.relating_type_id) in AuthoringData.data["relating_type_id"]:
props.relating_type_id = str(self.relating_type_id)
building_obj, building_element = None, None
building_obj = None
if len(context.selected_objects) == 1 and context.active_object:
building_obj = context.active_object
building_element = tool.Ifc.get_entity(building_obj)
@@ -593,8 +593,6 @@ class DumbProfileJoiner:
axisl = (profile2.matrix_world.inverted() @ axis1[1]) - (profile2.matrix_world.inverted() @ axis1[0])
elif connection1 == "ATSTART":
axisl = (profile2.matrix_world.inverted() @ axis1[0]) - (profile2.matrix_world.inverted() @ axis1[1])
else:
assert False, connection1
xy_angle = degrees(Vector((1, 0)).angle_signed(axisl.normalized().to_2d()))
if xy_angle >= -135 and xy_angle <= -45:
closest_plane = "bottom"
@@ -619,8 +617,6 @@ class DumbProfileJoiner:
axisl = (profile1.matrix_world.inverted() @ axis2[1]) - (profile1.matrix_world.inverted() @ axis2[0])
elif connection2 == "ATSTART":
axisl = (profile1.matrix_world.inverted() @ axis2[0]) - (profile1.matrix_world.inverted() @ axis2[1])
else:
assert False, connection2
xy_angle2 = degrees(Vector((1, 0)).angle_signed(axisl.normalized().to_2d()))
if xy_angle2 >= -135 and xy_angle2 <= -45:
closest_plane2 = "bottom"
@@ -848,8 +844,6 @@ class DumbProfileJoiner:
else:
y_axis = obj.matrix_world.to_quaternion() @ Vector((0, 1, 0))
z_axis = obj.matrix_world.to_quaternion() @ Vector((-1, 0, 0))
else:
assert False, plane
return self.create_matrix(p, x_axis, y_axis, z_axis)
def create_matrix(self, p: Vector, x: Vector, y: Vector, z: Vector) -> Matrix:
@@ -508,7 +508,6 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
converter.run()
profile = tool.Ifc.get().createIfcArbitraryClosedProfileDef("AREA")
curve = None
for path in converter.paths:
points = []
lines = path[0]
@@ -518,7 +517,6 @@ class EditSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator):
points.append(tool.Ifc.get().createIfcCartesianPoint(local_point))
points.append(points[0])
curve = tool.Ifc.get().createIfcPolyline(points)
assert curve
profile.OuterCurve = curve
old_profile = extrusion.SweptArea
@@ -1577,7 +1577,6 @@ class DumbWallJoiner:
# Get the ATEND connection from wall1 to use it in wall2
relating_element = None
connections = element1.ConnectedTo
relating_connection, description = ..., ...
for conn in connections:
if conn.is_a("IfcRelConnectsPathElements") and conn.RelatingConnectionType == "ATEND":
relating_element = conn.RelatedElement
@@ -1592,7 +1591,6 @@ class DumbWallJoiner:
description = conn.Description
bonsai.core.geometry.remove_connection(tool.Geometry, connection=conn)
if relating_element:
assert relating_connection is not ... and description is not ...
ifcopenshell.api.geometry.connect_path(
tool.Ifc.get(),
relating_element=relating_element,
@@ -18,7 +18,7 @@
import json
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast
import bpy
import ifcopenshell
@@ -122,8 +122,8 @@ class ExecuteIfcPatch(bpy.types.Operator):
if props.should_load_from_memory and tool.Ifc.get():
args["file"] = tool.Ifc.get()
else:
args["input"] = props.ifc_patch_input
args["file"] = ifcopenshell.open(props.ifc_patch_input)
args["input"] = cast(str, props.ifc_patch_input)
args["file"] = cast(ifcopenshell.file, ifcopenshell.open(props.ifc_patch_input))
# Store this in case the patch recipe resets the Blender session, such as by loading a new project.
ifc_patch_output = props.ifc_patch_output or props.ifc_patch_input
+1 -1
View File
@@ -162,7 +162,7 @@ class ProjectLibraryData:
library_file = IfcStore.library_file
if library_file is None or library_file.schema == "IFC2X3":
return results
root = library_file.by_type("IfcProject")[0]
root = tool.Project.get_root_context(library_file)
results.append((str(root.id()), f"{root.is_a()} {root.Name or 'Unnamed'}", root.Description or ""))
for library_id, data in cls.data["project_libraries"].items():
results.append((str(library_id), data["Name"] or "Unnamed", data["Description"] or ""))
@@ -281,7 +281,7 @@ class RefreshLibrary(bpy.types.Operator):
elements = {e for e in elements if not tool.Project.is_element_assigned_to_project_library(e, rels)}
self.props.add_library_project_library("Unassigned", len(elements), 0, False)
root_context = library_file.by_type("IfcProject")[0]
root_context = tool.Project.get_root_context(library_file)
hierarchy = tool.Project.get_project_hierarchy(library_file)
tool.Project.load_project_libraries_to_ui(root_context, hierarchy)
return {"FINISHED"}
@@ -714,8 +714,6 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
representations = element.RepresentationMaps or []
elif element.is_a("IfcProduct"):
representations = [element.Representation] if element.Representation else []
else:
assert False, element
for representation in representations or []:
for element in self.file.traverse(representation):
if not element.is_a("IfcRepresentationItem") or not element.StyledByItem:
@@ -761,22 +759,21 @@ class EditProjectLibrary(bpy.types.Operator):
attributes = bonsai.bim.helper.export_attributes(props.project_library_attributes)
ifcopenshell.api.attribute.edit_attributes(library_file, project_library, attributes)
# Update parent library. Tear down the old IfcRelDeclares/IfcRelNests before
# creating the new one; a library must have exactly one of the two, never both.
# 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 not None:
if previous_parent_library.is_a("IfcProject"):
ifcopenshell.api.project.unassign_declaration(
library_file, [project_library], previous_parent_library
)
else:
ifcopenshell.api.nest.unassign_object(library_file, [project_library])
if new_parent_library.is_a("IfcProject"):
ifcopenshell.api.project.assign_declaration(library_file, [project_library], new_parent_library)
else:
if previous_parent_library is None:
# Edited library was a root in a library-only file; nest it under the new parent.
ifcopenshell.api.nest.assign_object(library_file, [project_library], new_parent_library)
elif 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
bpy.ops.bim.refresh_library()
@@ -810,9 +807,12 @@ class AddProjectLibrary(bpy.types.Operator):
props = tool.Project.get_project_props()
library_file = IfcStore.library_file
assert library_file
root_context = library_file.by_type("IfcProject")[0]
root_context = tool.Project.get_root_context(library_file)
project_library = ifcopenshell.api.root.create_entity(library_file, "IfcProjectLibrary")
ifcopenshell.api.project.assign_declaration(library_file, [project_library], root_context)
if root_context.is_a("IfcProject"):
ifcopenshell.api.project.assign_declaration(library_file, [project_library], root_context)
else:
ifcopenshell.api.nest.assign_object(library_file, [project_library], root_context)
ProjectLibraryData.load() # Update enum.
props.selected_project_library = str(project_library.id())
props.is_editing_project_library = True
@@ -1299,10 +1299,6 @@ class LoadProjectElements(bpy.types.Operator):
tool.Project.set_default_modeling_dimensions()
tool.Root.reload_grid_decorator()
bonsai.bim.handler.refresh_ui_data()
for screen in bpy.data.screens:
for area in screen.areas:
if area.type == "VIEW_3D":
bonsai.bim.handler.viewport_shading_changed_callback(area)
return {"FINISHED"}
def get_decomposition_elements(self) -> set[ifcopenshell.entity_instance]:
@@ -1580,13 +1576,10 @@ class LoadLink(bpy.types.Operator, tool.Ifc.Operator):
json_filepath = self.filepath_.with_suffix(".ifc.cache.json")
def should_clear_cache() -> bool:
# Nothing to clear if the cache file was never created (e.g. a
# fresh link). Check this first so os.remove below is never
# called on a non-existent path, regardless of use_cache.
if not blend_filepath.exists():
return False
if not self.use_cache:
return True
if not blend_filepath.exists():
return False
data = json.loads(json_filepath.read_text())
# Empty 'query' - model loaded without custom query.
# Missing 'query' - model was loaded before custom queries were introduced in Bonsai.
@@ -2036,7 +2029,6 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
project_props = tool.Project.get_project_props()
prefs = tool.Blender.get_addon_preferences()
project_props.use_relative_project_path = self.use_relative_path
old_history_size, old_undo_steps = None, None
if prefs.should_disable_undo_on_save:
old_history_size = tool.Ifc.get().history_size
old_undo_steps = context.preferences.edit.undo_steps
@@ -2044,7 +2036,6 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
context.preferences.edit.undo_steps = 0
IfcStore.execute_ifc_operator(self, context)
if prefs.should_disable_undo_on_save:
assert old_history_size is not None and old_undo_steps is not None
tool.Ifc.get().history_size = old_history_size
context.preferences.edit.undo_steps = old_undo_steps
return {"FINISHED"}
@@ -113,8 +113,6 @@ class EditPset(bpy.types.Operator, tool.Ifc.Operator):
elif props.active_pset_type == "QTO":
pset = ifcopenshell.api.pset.add_qto(self.file, product=element, name=props.active_pset_name)
props.active_pset_id = pset.id()
else:
assert False
if self.properties:
properties = json.loads(self.properties)

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