mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Merge branch 'v0.8.0' into ifcmax/initial-refresh
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import TypedDict
|
||||
|
||||
import github_action_utils as gha_utils
|
||||
|
||||
|
||||
class Entry(TypedDict):
|
||||
location: Location
|
||||
|
||||
|
||||
class Location(TypedDict):
|
||||
path: str
|
||||
lines: Lines
|
||||
|
||||
|
||||
class Lines(TypedDict):
|
||||
begin: int
|
||||
end: int
|
||||
|
||||
|
||||
json_data: list[Entry] = json.load(sys.stdin)
|
||||
|
||||
if os.getenv("RUNNER_DEBUG"):
|
||||
print("Debug: Black formatting JSON data:")
|
||||
print(json.dumps(json_data, indent=2))
|
||||
|
||||
for change in json_data:
|
||||
location = change["location"]
|
||||
path = location["path"]
|
||||
lines = location["lines"]
|
||||
gha_utils.error(
|
||||
f"Black formatting issue in {path}",
|
||||
title="Black Format Issue",
|
||||
file=path,
|
||||
line=lines["begin"],
|
||||
end_line=lines["end"],
|
||||
)
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-logs-osx-${{ matrix.arch }}
|
||||
path: |
|
||||
|
||||
@@ -9,13 +9,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: IfcOpenShell
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ifcopenshell_build
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-logs-pyodide
|
||||
path: |
|
||||
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
aws --version
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-logs-rocky
|
||||
path: |
|
||||
|
||||
@@ -29,12 +29,12 @@ jobs:
|
||||
aws --version
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ./build
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
|
||||
- name: Upload Build Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: build-logs-rocky-arm64
|
||||
path: |
|
||||
|
||||
@@ -12,12 +12,12 @@ jobs:
|
||||
arch: ['x64']
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Checkout Build Repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: _deps-vs2022-x64-installed
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
import requests
|
||||
import zipfile
|
||||
import os
|
||||
|
||||
# To test this locally, set these environment variables
|
||||
REPO_OWNER = os.environ.get("REPO_OWNER", "IfcOpenShell/IfcOpenShell")
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5 # https://github.com/actions/checkout
|
||||
- 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
|
||||
|
||||
@@ -9,12 +9,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Action - checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.9"
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Action - install python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -33,23 +33,58 @@ jobs:
|
||||
id: syntax-errors
|
||||
run: |
|
||||
ERROR=0
|
||||
python3.9 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3.10 -W error -m compileall -q src/ifcopenshell-python || ERROR=1
|
||||
python3.11 -W error -m compileall -q src/bonsai || ERROR=1
|
||||
exit $ERROR
|
||||
continue-on-error: true
|
||||
|
||||
- name: Black formatter
|
||||
id: black
|
||||
uses: psf/black@stable
|
||||
continue-on-error: true
|
||||
|
||||
# Same check as above, but just for creating github annotations.
|
||||
- name: Black formatter annotations
|
||||
id: black-annotations
|
||||
run: |
|
||||
black --diff --check .
|
||||
uv tool install black-codeclimate
|
||||
pip install github_action_utils
|
||||
black --diff --check . | black-codeclimate | python .github/workflows/black_to_github_annotations.py
|
||||
continue-on-error: true
|
||||
|
||||
- name: Ruff check
|
||||
id: ruff
|
||||
run: |
|
||||
# Ensure execution continues, since we need to cache the output.
|
||||
set +e
|
||||
|
||||
ERROR=0
|
||||
poe ruff-main || ERROR=1
|
||||
poe ruff-old || ERROR=1
|
||||
# Keep colored output inside action logs, strip it from color codes for summary.
|
||||
uv tool install ansi2txt
|
||||
# `ruff` disables color output in CI by default.
|
||||
export FORCE_COLOR="1"
|
||||
|
||||
run_check() {
|
||||
local out
|
||||
out="$("$@" 2>&1)"
|
||||
local exit_code=$?
|
||||
|
||||
if [ "$exit_code" -ne 0 ]; then
|
||||
ERROR=1
|
||||
fi
|
||||
|
||||
# Rerun just for GitHub annotations.
|
||||
"$@" --output-format=github || true
|
||||
|
||||
echo "$out"
|
||||
echo "\`\`\`python" >> $GITHUB_STEP_SUMMARY
|
||||
echo "$out" | ansi2txt >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
}
|
||||
|
||||
run_check poe ruff-main
|
||||
run_check poe ruff-old
|
||||
|
||||
exit $ERROR
|
||||
continue-on-error: true
|
||||
|
||||
@@ -60,9 +95,9 @@ jobs:
|
||||
echo "::error::Syntax errors check failed, see 'syntax-errors' step for the details." && ERROR=1
|
||||
fi
|
||||
if [ "${{ steps.black.outcome }}" != "success" ]; then
|
||||
echo "::error::Black formatting check failed, see 'black' step for the details." && ERROR=1
|
||||
echo "::error::Black formatting check failed, see Summary or 'black' step for the details." && ERROR=1
|
||||
fi
|
||||
if [ "${{ steps.ruff.outcome }}" != "success" ]; then
|
||||
echo "::error::Ruff check failed, see 'ruff' step for the details." && ERROR=1
|
||||
echo "::error::Ruff check failed, see Summary or 'ruff' step for the details." && ERROR=1
|
||||
fi
|
||||
exit $ERROR
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout bonsai_unstable_repo repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/bonsai_unstable_repo
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
short_name: macosm1,
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
date: ${{ steps.date.outputs.date }}
|
||||
verdate: ${{ steps.verdate.outputs.verdate }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set env
|
||||
run: echo ok go
|
||||
|
||||
@@ -54,8 +54,7 @@ jobs:
|
||||
platform: [
|
||||
{ name: win, distver: windows-latest, pkg_dir: 'win-64' },
|
||||
{ name: linux, distver: ubuntu-latest, pkg_dir: 'linux-64' },
|
||||
{ name: macOS-arm, distver: macos-latest, pkg_dir: 'osx-arm64' },
|
||||
{ name: macOS-x86, distver: macos-13, pkg_dir: 'osx-64' }
|
||||
{ name: macOS-arm, distver: macos-latest, pkg_dir: 'osx-arm64' }
|
||||
]
|
||||
steps:
|
||||
- name: Set Swap Space
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
echo "ARTIFACTS_DIR=/home/runner/work/artifacts" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
make package
|
||||
working-directory: build
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
@@ -86,12 +86,12 @@ jobs:
|
||||
name: Docker Build, Tag, Push
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v6.0.0
|
||||
uses: actions/download-artifact@v7.0.0
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
short_name: macosm164
|
||||
}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions/setup-python@v6 # https://github.com/actions/setup-python
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -10,9 +10,9 @@ jobs:
|
||||
publish_website:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout ifctester_org_static_html
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: IfcOpenShell/ifctester_org_static_html
|
||||
token: ${{ secrets.IFCOPENBOT_TOKEN }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- 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
|
||||
|
||||
@@ -34,8 +34,12 @@ jobs:
|
||||
compile-and-test:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: activate
|
||||
env:
|
||||
# Colored output for cmake.
|
||||
CLICOLOR_FORCE: "1"
|
||||
CMAKE_COLOR_DIAGNOSTICS: "ON"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (recursive)
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install C++ dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import boto3
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import boto3
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ import hashlib
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
from urllib import request
|
||||
from github import Github
|
||||
from typing import NoReturn
|
||||
from urllib import request
|
||||
|
||||
from github import Github
|
||||
|
||||
|
||||
def get_repo_tag_names() -> list[str]:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import bpy
|
||||
|
||||
|
||||
bpy.ops.preferences.addon_disable(module='blenderbim')
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import bpy
|
||||
|
||||
|
||||
bpy.ops.preferences.addon_enable(module='blenderbim')
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ option(USERSPACE_PYTHON_PREFIX "Installs IfcPython for the current user only ins
|
||||
option(USE_DEBUG_PYTHON "Use debug binaries when building Debug IfcPython on Windows." OFF)
|
||||
option(ADD_COMMIT_SHA "Add commit sha and branch in version number, requires git" OFF)
|
||||
option(VERSION_OVERRIDE "Override the version defined in buildinfo.cpp with the file VERSION in the repository root" OFF)
|
||||
option(USE_CCACHE "Enable use of ccache if it's available from PATH." ON)
|
||||
|
||||
set(
|
||||
PYTHON_MODULE_INSTALL_DIR
|
||||
@@ -132,7 +133,7 @@ if((BUILD_CONVERT OR BUILD_GEOMSERVER OR BUILD_IFCPYTHON) AND(NOT BUILD_IFCGEOM)
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
if(USE_CCACHE AND CCACHE_FOUND)
|
||||
message(STATUS "`ccache` is found, using it as a compiler launcher.")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}")
|
||||
if(MSVC)
|
||||
|
||||
@@ -4,7 +4,42 @@
|
||||
set(IFCOPENSHELL_SCHEMA_VERSIONS @SCHEMA_VERSIONS@)
|
||||
|
||||
set(IFCOPENSHELL_WITH_OPENCASCADE @WITH_OPENCASCADE@)
|
||||
set(IFCOPENSHELL_WITH_CGAL @WITH_CGAL@)
|
||||
set(IFCOPENSHELL_IFCXML @IFCXML_SUPPORT@)
|
||||
set(IFCOPENSHELL_WITH_ROCKSDB @WITH_ROCKSDB@)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
set(Boost_COMPONENTS system program_options regex thread date_time iostreams)
|
||||
find_dependency(Boost CONFIG COMPONENTS ${Boost_COMPONENTS})
|
||||
find_dependency(Eigen3 CONFIG)
|
||||
|
||||
if(IFCOPENSHELL_WITH_ROCKSDB)
|
||||
find_dependency(zstd CONFIG)
|
||||
find_dependency(RocksDB CONFIG)
|
||||
endif()
|
||||
|
||||
if(IFCOPENSHELL_IFCXML)
|
||||
find_dependency(LibXml2 CONFIG)
|
||||
endif()
|
||||
|
||||
|
||||
if(IFCOPENSHELL_WITH_CGAL)
|
||||
find_dependency(CGAL CONFIG)
|
||||
endif()
|
||||
|
||||
if(IFCOPENSHELL_WITH_OPENCASCADE)
|
||||
find_dependency(OpenCASCADE CONFIG)
|
||||
if(OpenCASCADE_VERSION VERSION_LESS "7.7.0")
|
||||
# cmake configs < 7.7.0 were not adding include directories to targets automatically.
|
||||
set_target_properties(TKernel PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${OpenCASCADE_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@CONFIG_TARGETS_FILENAME@")
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import re
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def update_version(file_path: str, version: str) -> None:
|
||||
"""Update the version string in the given __init__.py file."""
|
||||
file_path = Path(file_path)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import textwrap
|
||||
|
||||
# The `extensions` list should already be in here from `sphinx-quickstart`
|
||||
extensions = [
|
||||
# there may be others here already, e.g. 'sphinx.ext.mathjax'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# This program requires doxygen, sphinx, breathe and exhale.
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
# some extra check to see if we can find sphinx in pypy bin dir
|
||||
sphinx_build = os.path.join(os.path.dirname(sys.executable), 'sphinx-build')
|
||||
|
||||
+14
-13
@@ -99,35 +99,36 @@ Used environment variables:
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
import subprocess as sp
|
||||
import shutil
|
||||
import tarfile
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import threading
|
||||
import sysconfig
|
||||
from datetime import datetime
|
||||
import re
|
||||
import shutil
|
||||
|
||||
# @todo temporary for expired mpfr.org certificate on 2023-04-08
|
||||
import ssl
|
||||
import subprocess as sp
|
||||
import sys
|
||||
import sysconfig
|
||||
import tarfile
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
import time
|
||||
from urllib.request import urlretrieve
|
||||
from collections.abc import Generator, Sequence
|
||||
from pathlib import Path
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
try:
|
||||
from typing import Union, Literal
|
||||
from typing import Literal, Union
|
||||
except:
|
||||
# python 3.6 compatibility for rocky 8
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import Literal
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -139,7 +140,7 @@ PROJECT_NAME = "IfcOpenShell"
|
||||
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
|
||||
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")
|
||||
|
||||
PYTHON_VERSIONS = ["3.9.11", "3.10.3", "3.11.8", "3.12.1", "3.13.6", "3.14.0"]
|
||||
PYTHON_VERSIONS = ["3.10.3", "3.11.8", "3.12.1", "3.13.6", "3.14.0"]
|
||||
JSON_VERSION = "3.11.3"
|
||||
OCE_VERSION = "0.18.3"
|
||||
OCCT_VERSION = "7.8.1"
|
||||
|
||||
@@ -8,12 +8,11 @@ or unpacks existing archives.
|
||||
Usage: python cache_dependencies.py [pack|unpack]
|
||||
"""
|
||||
|
||||
import tarfile
|
||||
import sys
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
|
||||
CACHE_PREFIX = "cache-"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
WHEEL_FILENAME = next(
|
||||
p.name for p in (Path.cwd() / "pyodide").iterdir() if p.name.startswith("ifcopenshell-") and p.suffix == ".whl"
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ select = [
|
||||
"UP", # pyupgrade
|
||||
"RUF015", # next() > list_comprehension[0]
|
||||
"RUF022", # sort __all__
|
||||
"I", # import sorting
|
||||
]
|
||||
ignore = [
|
||||
"FA100", # Conflicts with Blender using annotations for props definitions.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from dataclasses import fields
|
||||
from typing import NamedTuple, Union
|
||||
|
||||
import bcf.v2.model.extensions
|
||||
import bcf.v3.model.extensions
|
||||
from typing import NamedTuple, Union
|
||||
from dataclasses import fields
|
||||
|
||||
|
||||
class AttributeData(NamedTuple):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Union
|
||||
|
||||
import bcf.v2.model
|
||||
import bcf.v3.model
|
||||
from typing import Union
|
||||
|
||||
BimSnippet = Union[bcf.v2.model.BimSnippet, bcf.v3.model.BimSnippet]
|
||||
BitMap = Union[bcf.v2.model.VisualizationInfoBitmap, bcf.v3.model.Bitmap]
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
import bcf.agnostic.model as mdl
|
||||
import bcf.v2.bcfxml
|
||||
import bcf.v2.model
|
||||
import bcf.v2.topic
|
||||
import bcf.v3.bcfxml
|
||||
import bcf.v3.model
|
||||
import bcf.v3.topic
|
||||
import bcf.agnostic.model as mdl
|
||||
from pathlib import Path
|
||||
from typing import Union, Optional
|
||||
from typing_extensions import assert_never
|
||||
|
||||
TopicHandler = Union[bcf.v2.topic.TopicHandler, bcf.v3.topic.TopicHandler]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bcf.v2.visinfo
|
||||
import bcf.v3.visinfo
|
||||
from typing import Union
|
||||
|
||||
import bcf.v2.visinfo
|
||||
import bcf.v3.visinfo
|
||||
|
||||
VisualizationInfoHandler = Union[bcf.v2.visinfo.VisualizationInfoHandler, bcf.v3.visinfo.VisualizationInfoHandler]
|
||||
|
||||
@@ -29,7 +29,6 @@ from bcf.v3.bcfxml import BcfXml as BcfXml3
|
||||
from bcf.v3.model import Version as Version3
|
||||
from bcf.xml_parser import AbstractXmlParserSerializer, XmlParserSerializer
|
||||
|
||||
|
||||
BcfXml = Union[BcfXml2, BcfXml3]
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ original idea from https://stackoverflow.com/a/19722365/1307905
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from os import PathLike
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""BCF XML V2 handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
# Currently extensions support for v2 is only read-only.
|
||||
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -39,7 +35,7 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -56,7 +52,7 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -73,7 +69,7 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -90,7 +86,7 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -107,7 +103,7 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -124,7 +120,7 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -141,7 +137,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -41,7 +38,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -56,7 +53,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFile:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -112,7 +109,7 @@ class HeaderFile:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReference:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -150,7 +147,7 @@ class TopicDocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -165,7 +162,7 @@ class TopicRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -201,7 +198,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -261,7 +258,7 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
file: list[HeaderFile] = field(
|
||||
default_factory=list,
|
||||
@@ -274,7 +271,7 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_link: list[str] = field(
|
||||
default_factory=list,
|
||||
@@ -428,7 +425,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -24,7 +21,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectExtension:
|
||||
project: Optional[Project] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
detailed_version: Optional[str] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
PNG = "PNG"
|
||||
JPG = "JPG"
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -38,7 +35,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -63,7 +60,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -88,7 +85,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: Optional[bool] = field(
|
||||
default=None,
|
||||
@@ -113,7 +110,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -131,7 +128,7 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -154,7 +151,7 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
@@ -166,7 +163,7 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -181,7 +178,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -199,7 +196,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -239,7 +236,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -284,7 +281,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmap:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -333,7 +330,7 @@ class VisualizationInfoBitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: list[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
@@ -345,7 +342,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
exceptions: Optional[ComponentVisibilityExceptions] = field(
|
||||
default=None,
|
||||
@@ -363,7 +360,7 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -377,7 +374,7 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -392,7 +389,7 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -424,7 +421,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""BCF XML V2 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""BCF XML V3 handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Document:
|
||||
filename: str = field(
|
||||
metadata={
|
||||
@@ -37,7 +34,7 @@ class Document:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfoDocuments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -52,7 +49,7 @@ class DocumentInfoDocuments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentInfo:
|
||||
documents: Optional[DocumentInfoDocuments] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsPriorities:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -22,7 +19,7 @@ class ExtensionsPriorities:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsSnippetTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -39,7 +36,7 @@ class ExtensionsSnippetTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsStages:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -56,7 +53,7 @@ class ExtensionsStages:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -73,7 +70,7 @@ class ExtensionsTopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicStatuses:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -90,7 +87,7 @@ class ExtensionsTopicStatuses:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsTopicTypes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -107,7 +104,7 @@ class ExtensionsTopicTypes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ExtensionsUsers:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -124,7 +121,7 @@ class ExtensionsUsers:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Extensions:
|
||||
topic_types: Optional[ExtensionsTopicTypes] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class BimSnippet:
|
||||
reference: str = field(
|
||||
metadata={
|
||||
@@ -47,7 +44,7 @@ class BimSnippet:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class CommentViewpoint:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -62,7 +59,7 @@ class CommentViewpoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class DocumentReference:
|
||||
document_guid: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -103,7 +100,7 @@ class DocumentReference:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class File:
|
||||
filename: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -160,7 +157,7 @@ class File:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicLabels:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -177,7 +174,7 @@ class TopicLabels:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicReferenceLinks:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -194,7 +191,7 @@ class TopicReferenceLinks:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopicsRelatedTopic:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -209,7 +206,7 @@ class TopicRelatedTopicsRelatedTopic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewPoint:
|
||||
viewpoint: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -249,7 +246,7 @@ class ViewPoint:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Comment:
|
||||
date: XmlDateTime = field(
|
||||
metadata={
|
||||
@@ -315,7 +312,7 @@ class Comment:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class HeaderFiles:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -330,7 +327,7 @@ class HeaderFiles:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicDocumentReferences:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -345,7 +342,7 @@ class TopicDocumentReferences:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicRelatedTopics:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -360,7 +357,7 @@ class TopicRelatedTopics:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicViewpoints:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -375,7 +372,7 @@ class TopicViewpoints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Header:
|
||||
files: Optional[HeaderFiles] = field(
|
||||
default=None,
|
||||
@@ -387,7 +384,7 @@ class Header:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class TopicComments:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -402,7 +399,7 @@ class TopicComments:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Topic:
|
||||
reference_links: Optional[TopicReferenceLinks] = field(
|
||||
default=None,
|
||||
@@ -599,7 +596,7 @@ class Topic:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Markup:
|
||||
header: Optional[Header] = field(
|
||||
default=None,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Project:
|
||||
name: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -28,7 +25,7 @@ class Project:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ProjectInfo:
|
||||
project: Project = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Version:
|
||||
version_id: str = field(
|
||||
metadata={
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
|
||||
DATACLASS_KWARGS = {} if sys.version_info < (3, 10) else {"slots": True, "kw_only": True}
|
||||
|
||||
|
||||
class BitmapFormat(Enum):
|
||||
PNG = "png"
|
||||
JPG = "jpg"
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Component:
|
||||
originating_system: Optional[str] = field(
|
||||
default=None,
|
||||
@@ -43,7 +39,7 @@ class Component:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Direction:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -68,7 +64,7 @@ class Direction:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Point:
|
||||
x: float = field(
|
||||
metadata={
|
||||
@@ -93,7 +89,7 @@ class Point:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ViewSetupHints:
|
||||
spaces_visible: bool = field(
|
||||
default=False,
|
||||
@@ -118,7 +114,7 @@ class ViewSetupHints:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Bitmap:
|
||||
format: BitmapFormat = field(
|
||||
metadata={
|
||||
@@ -166,7 +162,7 @@ class Bitmap:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ClippingPlane:
|
||||
location: Point = field(
|
||||
metadata={
|
||||
@@ -184,7 +180,7 @@ class ClippingPlane:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColorComponents:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -199,7 +195,7 @@ class ComponentColoringColorComponents:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentSelection:
|
||||
component: list[Component] = field(
|
||||
default_factory=list,
|
||||
@@ -210,7 +206,7 @@ class ComponentSelection:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibilityExceptions:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -224,7 +220,7 @@ class ComponentVisibilityExceptions:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Line:
|
||||
start_point: Point = field(
|
||||
metadata={
|
||||
@@ -242,7 +238,7 @@ class Line:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class OrthogonalCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -292,7 +288,7 @@ class OrthogonalCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class PerspectiveCamera:
|
||||
"""
|
||||
Attributes
|
||||
@@ -348,7 +344,7 @@ class PerspectiveCamera:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoringColor:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -370,7 +366,7 @@ class ComponentColoringColor:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentVisibility:
|
||||
view_setup_hints: Optional[ViewSetupHints] = field(
|
||||
default=None,
|
||||
@@ -395,7 +391,7 @@ class ComponentVisibility:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoBitmaps:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -409,7 +405,7 @@ class VisualizationInfoBitmaps:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoClippingPlanes:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -423,7 +419,7 @@ class VisualizationInfoClippingPlanes:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfoLines:
|
||||
class Meta:
|
||||
global_type = False
|
||||
@@ -437,7 +433,7 @@ class VisualizationInfoLines:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class ComponentColoring:
|
||||
color: list[ComponentColoringColor] = field(
|
||||
default_factory=list,
|
||||
@@ -448,7 +444,7 @@ class ComponentColoring:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class Components:
|
||||
selection: Optional[ComponentSelection] = field(
|
||||
default=None,
|
||||
@@ -473,7 +469,7 @@ class Components:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(**DATACLASS_KWARGS)
|
||||
@dataclass(slots=True, kw_only=True)
|
||||
class VisualizationInfo:
|
||||
"""
|
||||
VisualizationInfo documentation.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""BCF XML V3 Topic handler."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import uuid
|
||||
import zipfile
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Optional, Literal, Union
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
from ifcopenshell import entity_instance
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from numpy.typing import NDArray
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
|
||||
from bcf.xml_parser import XmlParserSerializer
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
import pytest
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from bcf.v2.visinfo import (
|
||||
|
||||
@@ -3,11 +3,10 @@ import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v2.model as mdl
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
from bcf.v2.topic import TopicHandler
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
def test_maximum_information() -> None:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v2.bcfxml import BcfXml
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import uuid
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
import pytest
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
from bcf.v3.visinfo import (
|
||||
|
||||
@@ -3,11 +3,10 @@ import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
import bcf.v3.model as mdl
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
from bcf.v3.topic import TopicHandler
|
||||
from xsdata.models.datatype import XmlDateTime
|
||||
|
||||
|
||||
def test_doc_ref_internal() -> None:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from bcf.v3.bcfxml import BcfXml
|
||||
|
||||
|
||||
|
||||
@@ -115,11 +115,13 @@ classes = [
|
||||
operator.EditBlenderCollection,
|
||||
operator.FileAssociate,
|
||||
operator.FileUnassociate,
|
||||
operator.LoadBlendMetadataAndIFC,
|
||||
operator.OpenPath,
|
||||
operator.OpenUpstream,
|
||||
operator.OpenUri,
|
||||
operator.ReloadIfcFile,
|
||||
operator.RevertClippingPlaneCut,
|
||||
operator.SaveBlendMetadataFile,
|
||||
operator.SelectDir,
|
||||
operator.SelectIfcFile,
|
||||
operator.SelectURIAttribute,
|
||||
@@ -129,12 +131,21 @@ classes = [
|
||||
prop.StrProperty,
|
||||
operator.BIM_OT_enum_property_search, # /!\ Register AFTER prop.StrProperty
|
||||
operator.BIM_OT_attribute_search_values,
|
||||
operator.BIM_UL_tab_panels,
|
||||
operator.BIM_OT_toggle_panel_visibility,
|
||||
operator.BIM_OT_bookmark_panel,
|
||||
operator.BIM_OT_manage_tab_panels,
|
||||
operator.BIM_OT_manage_tab_visibility,
|
||||
operator.BIM_OT_toggle_tab_visibility,
|
||||
operator.BIM_OT_reset_ui_layout,
|
||||
prop.ObjProperty,
|
||||
prop.MultipleFileSelect,
|
||||
prop.Attribute,
|
||||
prop.ISODuration,
|
||||
prop.BIMAreaProperties,
|
||||
prop.BIMTabProperties,
|
||||
prop.BIMTabVisibility, # Must be registered before BIMProperties
|
||||
prop.BIMPanelProperties, # Must be registered before BIMProperties
|
||||
prop.BIMProperties,
|
||||
prop.IfcParameter,
|
||||
prop.PsetQto,
|
||||
@@ -272,6 +283,7 @@ def register():
|
||||
bpy.types.Curve.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.Camera.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
bpy.types.PointLight.BIMMeshProperties = bpy.props.PointerProperty(type=prop.BIMMeshProperties)
|
||||
|
||||
if hasattr(bpy.types, "UI_MT_button_context_menu"):
|
||||
bpy.types.UI_MT_button_context_menu.append(ui.draw_custom_context_menu)
|
||||
bpy.types.STATUSBAR_HT_header.append(ui.draw_statusbar)
|
||||
@@ -307,6 +319,10 @@ def register():
|
||||
# RestrictedContext doesn't allow accessing scene attribute, postpone it for a bit.
|
||||
bpy.app.timers.register(tool.Blender.setup_user_data_dir, first_interval=0.1)
|
||||
|
||||
bpy.types.Scene.active_tab_name = bpy.props.StringProperty()
|
||||
bpy.types.Scene.tab_panels = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup)
|
||||
bpy.types.Scene.active_tab_panel_index = bpy.props.IntProperty()
|
||||
|
||||
|
||||
def unregister():
|
||||
global icons
|
||||
@@ -348,3 +364,7 @@ def unregister():
|
||||
tool.Blender.remove_scene_panel_override(panel)
|
||||
|
||||
bpy.app.translations.unregister("bonsai")
|
||||
|
||||
del bpy.types.Scene.active_tab_name
|
||||
del bpy.types.Scene.tab_panels
|
||||
del bpy.types.Scene.active_tab_panel_index
|
||||
|
||||
@@ -498,6 +498,8 @@ def draw_filter(
|
||||
if data.data["saved_searches"]:
|
||||
row.operator("bim.load_search", text="", icon="IMPORT").module = module
|
||||
row.operator("bim.save_search", text="", icon="EXPORT").module = module
|
||||
if data.data["saved_searches"]:
|
||||
row.operator("bim.remove_search", text="", icon="REMOVE").module = module
|
||||
if module != "search":
|
||||
if module == "drawing_include":
|
||||
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "INCLUDE"
|
||||
@@ -505,8 +507,17 @@ def draw_filter(
|
||||
row.operator("bim.edit_element_filter", icon="CHECKMARK", text="").filter_mode = "EXCLUDE"
|
||||
row.operator("bim.enable_editing_element_filter", icon="CANCEL", text="").filter_mode = "NONE"
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
|
||||
row.operator("bim.edit_filter_query", text="", icon="FILTER").module = module
|
||||
if not tool.Blender.get_addon_preferences().chain_filter_with_set_operations:
|
||||
row.operator("bim.add_filter_group", text="Add Search Group", icon="ADD").module = module
|
||||
else:
|
||||
if not filter_groups or not any(fg.filters for fg in filter_groups):
|
||||
op = row.operator("bim.add_filter", text="Add Filter", icon="ADD")
|
||||
op.type = "entity"
|
||||
op.index = 0
|
||||
op.module = module
|
||||
op = row.operator("bim.edit_filter_query", text="", icon="FILTER")
|
||||
if "module" in op.bl_rna.properties:
|
||||
op.module = module
|
||||
|
||||
for i, filter_group in enumerate(filter_groups):
|
||||
box = layout.box()
|
||||
@@ -524,43 +535,251 @@ def draw_filter(
|
||||
for j, ifc_filter in enumerate(filter_group.filters):
|
||||
if ifc_filter.type == "entity":
|
||||
row = box.row(align=True)
|
||||
preferences = tool.Blender.get_addon_preferences()
|
||||
if preferences.chain_filter_with_set_operations:
|
||||
show_mode_toggle = j > 0
|
||||
else:
|
||||
show_mode_toggle = (
|
||||
preferences.default_filter_with_set_operations_for_globalid_and_class and j > 0
|
||||
) # PR 7315 mode
|
||||
if show_mode_toggle:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_3D")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "attribute":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "name", text="", icon="COPY_ID")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op.suggestion_type = "attribute_name"
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
if ifc_filter.name:
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op.suggestion_type = "attribute_value"
|
||||
elif ifc_filter.type == "type":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_VOLUME")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "material":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="MATERIAL")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "property":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "pset", text="", icon="PROPERTIES")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op.suggestion_type = "pset"
|
||||
row.prop(ifc_filter, "name", text="")
|
||||
if ifc_filter.pset:
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op.suggestion_type = "property_name"
|
||||
row.prop(ifc_filter, "comparison", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
if ifc_filter.pset and ifc_filter.name:
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op.suggestion_type = "property_value"
|
||||
elif ifc_filter.type == "classification":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="OUTLINER")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "location":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="PACKAGE")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "group":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="OUTLINER_COLLECTION")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "parent":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="FILE_PARENT")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
elif ifc_filter.type == "query":
|
||||
row = box.row(align=True)
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and j > 0:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "name", text="", icon="POINTCLOUD_DATA")
|
||||
row.prop(ifc_filter, "comparison", text="")
|
||||
row.prop(ifc_filter, "value", text="")
|
||||
elif ifc_filter.type == "instance":
|
||||
row = box.row(align=True)
|
||||
preferences = tool.Blender.get_addon_preferences()
|
||||
if preferences.chain_filter_with_set_operations:
|
||||
show_mode_toggle = j > 0
|
||||
else:
|
||||
show_mode_toggle = (
|
||||
preferences.default_filter_with_set_operations_for_globalid_and_class and j > 0
|
||||
) # PR 7315 mode
|
||||
if show_mode_toggle:
|
||||
mode_icons = {"ADD": "ADD", "SUBTRACT": "REMOVE", "FILTER": "FILTER"}
|
||||
op = row.operator(
|
||||
"bim.toggle_filter_inclusion",
|
||||
icon=mode_icons.get(ifc_filter.filter_mode, "ADD"),
|
||||
text="",
|
||||
depress=ifc_filter.filter_mode != "ADD",
|
||||
)
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
row.prop(ifc_filter, "value", text="", icon="GRIP")
|
||||
op = row.operator("bim.filter_value_suggestions", text="", icon="VIEWZOOM")
|
||||
op.group_index = i
|
||||
op.filter_index = j
|
||||
op.module = module
|
||||
op.filter_type = ifc_filter.type
|
||||
op = row.operator("bim.select_filter_elements", text="", icon="EYEDROPPER")
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
@@ -569,3 +788,149 @@ def draw_filter(
|
||||
op.group_index = i
|
||||
op.index = j
|
||||
op.module = module
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# UI Panel Visibility Helpers
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def get_tab_names():
|
||||
from bonsai.bim.prop import get_tab
|
||||
|
||||
enum_items = get_tab(None, None)
|
||||
# Exclude None separators and the BLENDER tab (not part of BIM tab system)
|
||||
return [item[0] for item in enum_items if item is not None and item[0] != "BLENDER"]
|
||||
|
||||
|
||||
def get_panel_tab_name(panel_class):
|
||||
if hasattr(panel_class, "bim_tab_name"):
|
||||
return panel_class.bim_tab_name
|
||||
return "PROJECT" # Default fallback
|
||||
|
||||
|
||||
def should_show_panel(panel_id, panel_tab_name, context):
|
||||
if tool.Blender.is_tab(context, "BOOKMARK"):
|
||||
return is_panel_bookmarked(panel_id) and get_panel_visibility(panel_id, "BOOKMARK")
|
||||
|
||||
if tool.Blender.is_tab(context, panel_tab_name):
|
||||
return get_tab_visibility(panel_tab_name) and get_panel_visibility(panel_id, panel_tab_name)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_tab_visibility(tab_name):
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
tab_vis = bim_props.tab_visibilities.get(tab_name)
|
||||
return tab_vis.is_visible if tab_vis else True
|
||||
|
||||
|
||||
def set_tab_visibility(tab_name, visible):
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
tab_vis = bim_props.tab_visibilities.get(tab_name)
|
||||
if tab_vis:
|
||||
tab_vis.is_visible = visible
|
||||
else:
|
||||
new_tab = bim_props.tab_visibilities.add()
|
||||
new_tab.name = tab_name
|
||||
new_tab.is_visible = visible
|
||||
|
||||
|
||||
def get_panel_visibility(panel_id, current_tab=None):
|
||||
panel_config = get_panel_config(panel_id)
|
||||
if panel_config:
|
||||
if current_tab == "BOOKMARK":
|
||||
return panel_config.is_visible_in_bookmarks
|
||||
else:
|
||||
return panel_config.is_visible_in_tab
|
||||
return True
|
||||
|
||||
|
||||
def is_panel_bookmarked(panel_id):
|
||||
panel_config = get_panel_config(panel_id)
|
||||
if panel_config:
|
||||
return panel_config.is_bookmarked
|
||||
return False
|
||||
|
||||
|
||||
def get_panel_config(panel_id, create_if_missing=False):
|
||||
try:
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
except (AttributeError, AssertionError):
|
||||
return None
|
||||
|
||||
for prop in bim_props.panel_properties:
|
||||
if prop.name == panel_id:
|
||||
return prop
|
||||
|
||||
if create_if_missing:
|
||||
try:
|
||||
prop = bim_props.panel_properties.add()
|
||||
prop.name = panel_id
|
||||
prop.is_visible_in_tab = True
|
||||
prop.is_visible_in_bookmarks = True
|
||||
prop.is_bookmarked = False
|
||||
return prop
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_all_tab_panels(force_refresh=False):
|
||||
panels = {tab_name: [] for tab_name in get_tab_names() if tab_name != "BOOKMARK"}
|
||||
panels["BOOKMARK"] = []
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
for prop in bim_props.panel_properties:
|
||||
panel_class = getattr(bpy.types, prop.name, None)
|
||||
if panel_class:
|
||||
tab_name = get_panel_tab_name(panel_class)
|
||||
if tab_name and tab_name != "BOOKMARK":
|
||||
bl_label = getattr(panel_class, "bl_label", prop.name)
|
||||
panels[tab_name].append({"bl_idname": prop.name, "bl_label": bl_label})
|
||||
|
||||
if prop.is_bookmarked:
|
||||
panel_class = getattr(bpy.types, prop.name, None)
|
||||
if panel_class:
|
||||
bl_label = getattr(panel_class, "bl_label", prop.name)
|
||||
panels["BOOKMARK"].append({"bl_idname": prop.name, "bl_label": bl_label})
|
||||
|
||||
if not panels["BOOKMARK"]:
|
||||
panels["BOOKMARK"] = [{}]
|
||||
|
||||
return panels
|
||||
|
||||
|
||||
def initialize_tab_visibilities():
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
|
||||
if len(bim_props.tab_visibilities) > 0:
|
||||
return
|
||||
|
||||
for tab_name in get_tab_names():
|
||||
tab_vis = bim_props.tab_visibilities.add()
|
||||
tab_vis.name = tab_name
|
||||
tab_vis.is_visible = True
|
||||
|
||||
|
||||
def initialize_panel_properties():
|
||||
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
|
||||
if len(bim_props.panel_properties) > 0:
|
||||
return
|
||||
|
||||
for attr_name in dir(bpy.types):
|
||||
if attr_name.startswith("BIM_PT_tab_"):
|
||||
panel_class = getattr(bpy.types, attr_name)
|
||||
if not hasattr(panel_class, "bl_idname"):
|
||||
continue
|
||||
|
||||
panel_id = panel_class.bl_idname
|
||||
|
||||
prop = bim_props.panel_properties.add()
|
||||
prop.name = panel_id
|
||||
prop.is_visible_in_tab = True
|
||||
prop.is_visible_in_bookmarks = True
|
||||
prop.is_bookmarked = False
|
||||
|
||||
@@ -143,6 +143,7 @@ def menu_func(self, context):
|
||||
if element and element.is_a("IfcAnnotation") and element.ObjectType in ["SECTION", "ELEVATION"]:
|
||||
self.layout.operator("bim.activate_drawing_by_annotation", text="Go to Drawing")
|
||||
|
||||
|
||||
def register():
|
||||
if not bpy.app.background:
|
||||
bpy.utils.register_tool(workspace.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False)
|
||||
@@ -155,7 +156,7 @@ def register():
|
||||
bpy.app.handlers.load_post.append(handler.load_post)
|
||||
bpy.app.handlers.depsgraph_update_pre.append(handler.depsgraph_update_pre_handler)
|
||||
bpy.types.VIEW3D_MT_image_add.append(ui.add_object_button)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func)
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
|
||||
@@ -1655,7 +1655,7 @@ class CutDecorator:
|
||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||
in_local_view = True
|
||||
break
|
||||
|
||||
|
||||
# If just entering local view (transition from False to True)
|
||||
if in_local_view and not self.__class__.was_in_local_view:
|
||||
self.__class__.local_view_has_annotation = False
|
||||
@@ -1664,10 +1664,10 @@ class CutDecorator:
|
||||
if element and element.is_a("IfcAnnotation"):
|
||||
self.__class__.local_view_has_annotation = True
|
||||
break
|
||||
|
||||
|
||||
# Update the state for next time
|
||||
self.__class__.was_in_local_view = in_local_view
|
||||
|
||||
|
||||
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
||||
if in_local_view and not self.__class__.local_view_has_annotation:
|
||||
return
|
||||
@@ -2066,7 +2066,7 @@ class DecorationsHandler:
|
||||
if isinstance(space, bpy.types.SpaceView3D) and space.local_view:
|
||||
in_local_view = True
|
||||
break
|
||||
|
||||
|
||||
# If just entering local view (transition from False to True)
|
||||
if in_local_view and not self.__class__.was_in_local_view:
|
||||
self.__class__.local_view_has_annotation = False
|
||||
@@ -2075,10 +2075,10 @@ class DecorationsHandler:
|
||||
if element and element.is_a("IfcAnnotation"):
|
||||
self.__class__.local_view_has_annotation = True
|
||||
break
|
||||
|
||||
|
||||
# Update the state for next time
|
||||
self.__class__.was_in_local_view = in_local_view
|
||||
|
||||
|
||||
# Skip decorations if in local view and no IfcAnnotation was selected when entering
|
||||
if in_local_view and not self.__class__.local_view_has_annotation:
|
||||
return
|
||||
@@ -2093,5 +2093,6 @@ class DecorationsHandler:
|
||||
if not DecoratorData.is_loaded:
|
||||
DecoratorData.load(self)
|
||||
|
||||
for obj, decorator in DecoratorData.data["object_decorators"]:
|
||||
object_decorators = DecoratorData.data.get("object_decorators", [])
|
||||
for obj, decorator in object_decorators:
|
||||
decorator.decorate(context, obj)
|
||||
|
||||
@@ -86,7 +86,7 @@ overlapping with geometry. The get_local_view_direction() helper determines if t
|
||||
camera is viewing from the positive or negative side of each axis.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
__all__ = [ # noqa: RUF022 (unsorted `__all__`)
|
||||
"GizmoColor",
|
||||
"GizmoAxis",
|
||||
"TextAlignment",
|
||||
|
||||
@@ -296,6 +296,15 @@ class CreateDrawing(bpy.types.Operator):
|
||||
self.drawing_index = drawing_i
|
||||
if self.print_all:
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_id, should_view_from_camera=False)
|
||||
original_cache_setting = self.props.should_use_underlay_cache
|
||||
self.props.should_use_underlay_cache = False
|
||||
|
||||
# Force Blender to process all pending operations
|
||||
for area in context.screen.areas:
|
||||
area.tag_redraw()
|
||||
|
||||
# Process events to let Blender finish internal cleanup
|
||||
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
|
||||
|
||||
self.camera = context.scene.camera
|
||||
assert (camera_element := tool.Ifc.get_entity(self.camera))
|
||||
@@ -339,6 +348,23 @@ class CreateDrawing(bpy.types.Operator):
|
||||
f"Failed to create drawing '{self.drawing.Name}' - drawing has underlay but there's no active drawing underlay style.",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
# Clear any local camera setup and force viewport to use scene camera
|
||||
for area in context.screen.areas:
|
||||
if area.type == "VIEW_3D":
|
||||
for space in area.spaces:
|
||||
if space.type == "VIEW_3D":
|
||||
# Clear local camera to ensure we use scene.camera
|
||||
space.use_local_camera = False
|
||||
space.camera = context.scene.camera
|
||||
space.region_3d.view_perspective = "CAMERA"
|
||||
print(f"Set viewport camera to: {context.scene.camera.name}")
|
||||
break
|
||||
|
||||
# Force complete scene update
|
||||
context.view_layer.update()
|
||||
context.evaluated_depsgraph_get()
|
||||
|
||||
underlay_svg = self.generate_underlay(context)
|
||||
|
||||
with profile("Generate linework"):
|
||||
@@ -3050,9 +3076,9 @@ class AddReference(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
|
||||
filter_glob: bpy.props.StringProperty(default="*.svg", options={"HIDDEN"})
|
||||
use_relative_path: bpy.props.BoolProperty(name="Use Relative Path", default=True)
|
||||
filename_ext = ".svg"
|
||||
|
||||
|
||||
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)
|
||||
directory: bpy.props.StringProperty(subtype='DIR_PATH')
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH")
|
||||
|
||||
def _execute(self, context):
|
||||
# Handle both single and multiple file selection
|
||||
@@ -3661,8 +3687,12 @@ class EnableEditingElementFilter(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if query := ifcopenshell.util.element.get_pset(element, "EPset_Drawing", self.filter_mode.title()):
|
||||
filter_groups = tool.Search.get_filter_groups(f"drawing_{self.filter_mode.lower()}")
|
||||
try:
|
||||
tool.Search.import_filter_query(query, filter_groups)
|
||||
except:
|
||||
data = json.loads(query)
|
||||
if isinstance(data, dict) and "filter_structure" in data:
|
||||
tool.Search.import_filter_structure(data["filter_structure"], filter_groups)
|
||||
else:
|
||||
tool.Search.import_filter_query(query, filter_groups)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -3682,12 +3712,41 @@ class EditElementFilter(bpy.types.Operator, tool.Ifc.Operator):
|
||||
assert element
|
||||
pset = tool.Pset.get_element_pset(element, "EPset_Drawing")
|
||||
assert pset
|
||||
|
||||
if self.filter_mode == "INCLUDE":
|
||||
query = tool.Search.export_filter_query(props.include_filter_groups) or None
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Include": query})
|
||||
filter_groups = props.include_filter_groups
|
||||
elif self.filter_mode == "EXCLUDE":
|
||||
query = tool.Search.export_filter_query(props.exclude_filter_groups) or None
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Exclude": query})
|
||||
filter_groups = props.exclude_filter_groups
|
||||
else:
|
||||
return
|
||||
|
||||
query = tool.Search.export_filter_query(filter_groups) or None
|
||||
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations and query:
|
||||
filter_structure = []
|
||||
for filter_group in filter_groups:
|
||||
group_data = []
|
||||
for ifc_filter in filter_group.filters:
|
||||
filter_data = {
|
||||
"type": ifc_filter.type,
|
||||
"name": ifc_filter.name,
|
||||
"value": ifc_filter.value,
|
||||
"pset": ifc_filter.pset,
|
||||
"comparison": ifc_filter.comparison,
|
||||
"filter_mode": ifc_filter.filter_mode,
|
||||
}
|
||||
group_data.append(filter_data)
|
||||
filter_structure.append(group_data)
|
||||
|
||||
value = json.dumps({"type": "BBIM_Search", "query": query, "filter_structure": filter_structure})
|
||||
else:
|
||||
value = query
|
||||
|
||||
if self.filter_mode == "INCLUDE":
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Include": value})
|
||||
elif self.filter_mode == "EXCLUDE":
|
||||
ifcopenshell.api.pset.edit_pset(tool.Ifc.get(), pset=pset, properties={"Exclude": value})
|
||||
|
||||
props.filter_mode = "NONE"
|
||||
bpy.ops.bim.activate_drawing(drawing=element.id(), should_view_from_camera=False)
|
||||
|
||||
@@ -4015,66 +4074,66 @@ class ExcludeAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
tool.Drawing.exclude_annotation_from_drawing(referenced_element, drawing)
|
||||
core.sync_references(tool.Ifc, tool.Collector, tool.Drawing, drawing=drawing)
|
||||
|
||||
|
||||
class ActivateDrawingByAnnotation(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.activate_drawing_by_annotation"
|
||||
bl_label = "Activate Drawing"
|
||||
bl_description = "Activate the drawing corresponding to the selected annotation"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# Check if an annotation object is selected
|
||||
if not context.selected_objects:
|
||||
cls.poll_message_set("No object selected")
|
||||
return False
|
||||
|
||||
|
||||
active_obj = context.active_object
|
||||
if not active_obj:
|
||||
cls.poll_message_set("No active object")
|
||||
return False
|
||||
|
||||
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
if not element:
|
||||
cls.poll_message_set("Selected object is not an IFC element")
|
||||
return False
|
||||
|
||||
|
||||
# Check if it's an IfcAnnotation with ObjectType = "SECTION" or "ELEVATION"
|
||||
if not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
||||
cls.poll_message_set("Selected object is not a drawing annotation")
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
active_obj = context.active_object
|
||||
element = tool.Ifc.get_entity(active_obj)
|
||||
|
||||
|
||||
if not element or not element.is_a("IfcAnnotation") or element.ObjectType not in ["SECTION", "ELEVATION"]:
|
||||
self.report({"ERROR"}, "Selected object is not a drawing annotation")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
# Find the drawing/camera element that this annotation references
|
||||
drawing_element = self.find_drawing_from_annotation(element)
|
||||
|
||||
|
||||
if not drawing_element:
|
||||
self.report({"ERROR"}, "Could not find drawing element for this annotation")
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
# Use the existing ActivateDrawing operator with the drawing element's ID
|
||||
bpy.ops.bim.activate_drawing(drawing=drawing_element.id())
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def find_drawing_from_annotation(self, annotation_element):
|
||||
"""Find the drawing/camera element that this annotation references."""
|
||||
ifc = tool.Ifc.get()
|
||||
|
||||
|
||||
# Check IfcRelAssignsToProduct relationships
|
||||
for rel in ifc.get_inverse(annotation_element):
|
||||
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct:
|
||||
if rel.RelatingProduct.is_a("IfcAnnotation"):
|
||||
# Found the drawing element!
|
||||
return rel.RelatingProduct
|
||||
|
||||
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
@@ -479,10 +479,12 @@ class BIM_PT_sheets(Panel):
|
||||
drawingnamesvg = active_sheet.name
|
||||
drawingname = drawingnamesvg.split(".svg")[0]
|
||||
ifc_file = tool.Ifc.get()
|
||||
ifc_annotations = ifc_file.by_type("IfcAnnotation")
|
||||
drawingid = None
|
||||
|
||||
for annotation in ifc_annotations:
|
||||
for annotation in ifc_file.by_type("IfcAnnotation"):
|
||||
if annotation.ObjectType != "DRAWING":
|
||||
continue
|
||||
|
||||
Annotation_Name = annotation.Name.replace(",", "") # Remove commas
|
||||
if Annotation_Name == drawingname:
|
||||
drawingid = annotation.id()
|
||||
|
||||
@@ -151,23 +151,23 @@ class FilledOpeningGenerator:
|
||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
assert representation
|
||||
|
||||
|
||||
# Check if mapped representation - preserve it
|
||||
if (representation.RepresentationType == 'MappedRepresentation' and
|
||||
len(representation.Items) == 1 and
|
||||
representation.Items[0].is_a("IfcMappedItem")):
|
||||
if (
|
||||
representation.RepresentationType == "MappedRepresentation"
|
||||
and len(representation.Items) == 1
|
||||
and representation.Items[0].is_a("IfcMappedItem")
|
||||
):
|
||||
source_rep = representation.Items[0].MappingSource.MappedRepresentation
|
||||
representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
source_rep,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||
)
|
||||
else:
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
else:
|
||||
# Check for library template before generating from filling
|
||||
template_rep = self.get_opening_template_from_type(filling)
|
||||
|
||||
|
||||
if template_rep:
|
||||
representation = template_rep
|
||||
else:
|
||||
@@ -222,68 +222,68 @@ class FilledOpeningGenerator:
|
||||
voided_element = opening.VoidsElements[0].RelatingBuildingElement
|
||||
|
||||
opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
|
||||
# ALWAYS preserve the existing opening representation (Tessellation, SweptSolid, etc.)
|
||||
preserved_representation = None
|
||||
if opening_rep:
|
||||
if (opening_rep.RepresentationType == 'MappedRepresentation' and
|
||||
len(opening_rep.Items) == 1 and
|
||||
opening_rep.Items[0].is_a("IfcMappedItem")):
|
||||
if (
|
||||
opening_rep.RepresentationType == "MappedRepresentation"
|
||||
and len(opening_rep.Items) == 1
|
||||
and opening_rep.Items[0].is_a("IfcMappedItem")
|
||||
):
|
||||
# For mapped representations, copy the underlying representation
|
||||
preserved_representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
opening_rep.Items[0].MappingSource.MappedRepresentation,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
exclude=["IfcGeometricRepresentationContext"],
|
||||
)
|
||||
else:
|
||||
# For direct representations (non-mapped), copy them too
|
||||
preserved_representation = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
opening_rep,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
tool.Ifc.get(), opening_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||
)
|
||||
|
||||
|
||||
ifcopenshell.api.geometry.unassign_representation(tool.Ifc.get(), product=opening, representation=opening_rep)
|
||||
ifcopenshell.api.geometry.remove_representation(tool.Ifc.get(), representation=opening_rep)
|
||||
|
||||
existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling)
|
||||
|
||||
|
||||
# Priority order for choosing representation:
|
||||
# 1. Existing occurrence with Tessellation (best quality)
|
||||
# 2. Library template with Tessellation
|
||||
# 3. Preserved representation from old opening (maintain user's work)
|
||||
# 4. Generate from filling (last resort)
|
||||
|
||||
|
||||
representation_to_use = None
|
||||
|
||||
|
||||
if existing_opening_occurrence:
|
||||
representation = ifcopenshell.util.representation.get_representation(
|
||||
existing_opening_occurrence, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
|
||||
if (representation and
|
||||
representation.RepresentationType == 'MappedRepresentation' and
|
||||
len(representation.Items) == 1 and
|
||||
representation.Items[0].is_a("IfcMappedItem")):
|
||||
|
||||
if (
|
||||
representation
|
||||
and representation.RepresentationType == "MappedRepresentation"
|
||||
and len(representation.Items) == 1
|
||||
and representation.Items[0].is_a("IfcMappedItem")
|
||||
):
|
||||
source_rep = representation.Items[0].MappingSource.MappedRepresentation
|
||||
# Prefer Tessellation from existing occurrence over preserved representation
|
||||
if source_rep.RepresentationType == 'Tessellation':
|
||||
if source_rep.RepresentationType == "Tessellation":
|
||||
representation_to_use = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
source_rep,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||
)
|
||||
else:
|
||||
representation_to_use = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
|
||||
|
||||
if not representation_to_use:
|
||||
template_rep = self.get_opening_template_from_type(filling)
|
||||
if template_rep and template_rep.RepresentationType == 'Tessellation':
|
||||
if template_rep and template_rep.RepresentationType == "Tessellation":
|
||||
representation_to_use = template_rep
|
||||
|
||||
|
||||
if not representation_to_use and preserved_representation:
|
||||
representation_to_use = preserved_representation
|
||||
|
||||
|
||||
if not representation_to_use:
|
||||
opening_obj = tool.Ifc.get_object(opening)
|
||||
if opening_obj:
|
||||
@@ -299,7 +299,7 @@ class FilledOpeningGenerator:
|
||||
ifcopenshell.api.geometry.assign_representation(
|
||||
tool.Ifc.get(), product=opening, representation=mapped_representation
|
||||
)
|
||||
|
||||
|
||||
# update voided object representation...
|
||||
voided_elements = ifcopenshell.util.element.get_parts(voided_element) or [voided_element]
|
||||
for voided_element in voided_elements:
|
||||
@@ -314,31 +314,31 @@ class FilledOpeningGenerator:
|
||||
representation=representation,
|
||||
)
|
||||
|
||||
def get_opening_template_from_type(self, filling: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def get_opening_template_from_type(
|
||||
self, filling: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""
|
||||
Check if the filling's type has a stored opening template from library import.
|
||||
"""
|
||||
element_type = ifcopenshell.util.element.get_type(filling)
|
||||
|
||||
|
||||
if not element_type:
|
||||
return None
|
||||
|
||||
|
||||
desc = element_type.Description
|
||||
|
||||
|
||||
if not desc or "||BonsaiOpeningTemplate:" not in desc:
|
||||
return None
|
||||
|
||||
|
||||
# Extract template ID
|
||||
marker = desc.split("||BonsaiOpeningTemplate:")[-1]
|
||||
template_id = int(marker.split("||")[0])
|
||||
|
||||
|
||||
try:
|
||||
template_rep = tool.Ifc.get().by_id(template_id)
|
||||
# Make a copy so we don't reuse the same representation instance
|
||||
copied = ifcopenshell.util.element.copy_deep(
|
||||
tool.Ifc.get(),
|
||||
template_rep,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
tool.Ifc.get(), template_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||
)
|
||||
return copied
|
||||
except:
|
||||
|
||||
@@ -715,11 +715,8 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
if element.is_a("IfcSurfaceStyle") and not tool.Ifc.get_object_by_identifier(element.id()):
|
||||
ifc_importer.create_style(element)
|
||||
|
||||
|
||||
def store_opening_template_from_library(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
library_file: ifcopenshell.file
|
||||
self, element: ifcopenshell.entity_instance, library_file: ifcopenshell.file
|
||||
) -> None:
|
||||
"""
|
||||
Find an opening representation in the library and copy it to the current file
|
||||
@@ -729,36 +726,36 @@ class AppendLibraryElement(bpy.types.Operator, tool.Ifc.Operator):
|
||||
library_element = library_file.by_guid(element.GlobalId)
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
# Find occurrences with openings in the library
|
||||
library_occurrences = ifcopenshell.util.element.get_types(library_element)
|
||||
|
||||
|
||||
for occurrence in library_occurrences:
|
||||
if not getattr(occurrence, "FillsVoids", None):
|
||||
continue
|
||||
|
||||
|
||||
library_opening = occurrence.FillsVoids[0].RelatingOpeningElement
|
||||
library_opening_rep = ifcopenshell.util.representation.get_representation(
|
||||
library_opening, "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
|
||||
|
||||
if not library_opening_rep:
|
||||
continue
|
||||
|
||||
|
||||
# Check if mapped representation
|
||||
if (library_opening_rep.RepresentationType == 'MappedRepresentation' and
|
||||
len(library_opening_rep.Items) == 1 and
|
||||
library_opening_rep.Items[0].is_a("IfcMappedItem")):
|
||||
|
||||
if (
|
||||
library_opening_rep.RepresentationType == "MappedRepresentation"
|
||||
and len(library_opening_rep.Items) == 1
|
||||
and library_opening_rep.Items[0].is_a("IfcMappedItem")
|
||||
):
|
||||
|
||||
mapped_rep = library_opening_rep.Items[0].MappingSource.MappedRepresentation
|
||||
|
||||
|
||||
# Store ALL representation types (Tessellation, SweptSolid, etc.)
|
||||
template_rep = ifcopenshell.util.element.copy_deep(
|
||||
self.file,
|
||||
mapped_rep,
|
||||
exclude=["IfcGeometricRepresentationContext"]
|
||||
self.file, mapped_rep, exclude=["IfcGeometricRepresentationContext"]
|
||||
)
|
||||
|
||||
|
||||
# Store reference in type's Description
|
||||
current_desc = element.Description or ""
|
||||
element.Description = f"{current_desc}||BonsaiOpeningTemplate:{template_rep.id()}"
|
||||
@@ -1059,6 +1056,20 @@ class LoadProject(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
return tooltip
|
||||
|
||||
def execute(self, context):
|
||||
if (
|
||||
tool.Blender.get_addon_preferences().save_metadata_blend_file
|
||||
and self.should_start_fresh_session
|
||||
and not self.is_advanced
|
||||
):
|
||||
filepath = self.get_filepath()
|
||||
metadata_path = Path(str(filepath) + ".metadata.blend")
|
||||
if metadata_path.exists() and metadata_path.is_file():
|
||||
try:
|
||||
bpy.ops.bim.load_blend_metadata_and_ifc(filepath=filepath)
|
||||
return {"FINISHED"}
|
||||
except Exception as e:
|
||||
self.report({"WARNING"}, f"Failed to load metadata file, using regular load: {e}")
|
||||
|
||||
@persistent
|
||||
def load_handler(*args):
|
||||
bpy.app.handlers.load_post.remove(load_handler)
|
||||
@@ -1747,15 +1758,28 @@ class ExportIFC(bpy.types.Operator, ExportHelper):
|
||||
bim_props = tool.Blender.get_bim_props()
|
||||
if bim_props.ifc_file != output_file and extension not in ("ifczip", "ifcjson"):
|
||||
tool.Ifc.set_path(output_file)
|
||||
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
|
||||
if save_blend_file:
|
||||
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
||||
bim_props.is_dirty = False
|
||||
|
||||
if tool.Blender.get_addon_preferences().save_metadata_blend_file:
|
||||
try:
|
||||
bpy.ops.bim.save_blend_metadata_file()
|
||||
blendmetadata_path = output_file + ".metadata.blend"
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'IFC Project "{os.path.basename(output_file)}" And Metadata File Saved to: {os.path.basename(blendmetadata_path)}',
|
||||
)
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to save blend metadata file: {e}")
|
||||
else:
|
||||
save_blend_file = bool(bpy.data.is_saved and bpy.data.is_dirty and bpy.data.filepath)
|
||||
if save_blend_file:
|
||||
bpy.ops.wm.save_mainfile(filepath=bpy.data.filepath)
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'IFC Project "{os.path.basename(output_file)}" {"" if not save_blend_file else "And Current Blend File Are"} Saved',
|
||||
)
|
||||
|
||||
bonsai.bim.handler.refresh_ui_data()
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f'IFC Project "{os.path.basename(output_file)}" {"" if not save_blend_file else "And Current Blend File Are"} Saved',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
|
||||
@@ -331,6 +331,13 @@ class BIM_PT_project(Panel):
|
||||
col.prop(props, "ifc_file", text="")
|
||||
row.operator("bim.select_ifc_file", icon="FILE_FOLDER", text="")
|
||||
|
||||
if tool.Blender.get_addon_preferences().save_metadata_blend_file:
|
||||
row = self.layout.row(align=True)
|
||||
col = row.column()
|
||||
col.enabled = False
|
||||
metadata_filename = os.path.basename(props.ifc_file) + ".metadata.blend"
|
||||
col.label(text=f"Saving session data to: {metadata_filename}")
|
||||
|
||||
|
||||
class BIM_PT_new_project_wizard(Panel):
|
||||
bl_label = "New Project Wizard"
|
||||
|
||||
@@ -24,12 +24,15 @@ classes = (
|
||||
operator.ActivateIfcClassFilter,
|
||||
operator.AddFilter,
|
||||
operator.AddFilterGroup,
|
||||
operator.ApplyFilterFromText,
|
||||
operator.ColourByProperty,
|
||||
operator.EditFilterQuery,
|
||||
operator.FilterValueSuggestions,
|
||||
operator.LoadColourscheme,
|
||||
operator.LoadSearch,
|
||||
operator.RemoveFilter,
|
||||
operator.RemoveFilterGroup,
|
||||
operator.RemoveSearch,
|
||||
operator.ResetObjectColours,
|
||||
operator.SaveColourscheme,
|
||||
operator.SaveSearch,
|
||||
@@ -40,6 +43,7 @@ classes = (
|
||||
operator.SelectIfcClass,
|
||||
operator.SelectSimilar,
|
||||
operator.ShowAllElements,
|
||||
operator.ToggleFilterInclusion,
|
||||
operator.ToggleFilterSelection,
|
||||
prop.BIMColour,
|
||||
prop.BIMFilterItem,
|
||||
@@ -55,7 +59,9 @@ classes = (
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.BIMSearchProperties = bpy.props.PointerProperty(type=prop.BIMSearchProperties)
|
||||
bpy.types.TEXT_HT_header.append(operator.draw_text_editor_header)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.BIMSearchProperties
|
||||
bpy.types.TEXT_HT_header.remove(operator.draw_text_editor_header)
|
||||
|
||||
@@ -40,9 +40,476 @@ from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
CollectionProperty,
|
||||
)
|
||||
from bonsai.bim.prop import StrProperty
|
||||
from typing import TYPE_CHECKING, Literal, get_args, assert_never
|
||||
|
||||
|
||||
def draw_text_editor_header(self, context):
|
||||
if context.space_data.text and context.space_data.text.name.startswith("FilterQuery_"):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
op = layout.operator("bim.apply_filter_from_text", text="Apply Filter Configuration", icon="CHECKMARK")
|
||||
|
||||
|
||||
def update_filter_search_value(self: "FilterValueSuggestions", context: bpy.types.Context) -> None:
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
ifc_filter = filter_groups[self.group_index].filters[self.filter_index]
|
||||
|
||||
value = self.search_value
|
||||
|
||||
if " < " in value:
|
||||
value = value.split(" < ")[-1]
|
||||
|
||||
if ifc_filter.type == "entity":
|
||||
if " (superclass)" in value:
|
||||
value = value.replace(" (superclass)", "")
|
||||
if " > " in value:
|
||||
value = value.split(" > ")[-1]
|
||||
|
||||
elif ifc_filter.type == "instance":
|
||||
if ": " in value:
|
||||
value = value.split(": ")[-1]
|
||||
elif " (" in value:
|
||||
hierarchy_class = value.split(" (")
|
||||
element_class = hierarchy_class[-1].rstrip(")")
|
||||
element_name = hierarchy_class[0] if len(hierarchy_class) > 1 else None
|
||||
|
||||
ifc_file = tool.Ifc.get()
|
||||
if ifc_file:
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
if element.is_a() == element_class:
|
||||
if element_name is None or (hasattr(element, "Name") and element.Name == element_name):
|
||||
value = element.GlobalId
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
elif ifc_filter.type == "parent":
|
||||
if " (" in value:
|
||||
value = value.split(" (")[0]
|
||||
|
||||
if ifc_filter.type == "property":
|
||||
if self.suggestion_type == "pset":
|
||||
ifc_filter.pset = value
|
||||
elif self.suggestion_type == "property_name":
|
||||
ifc_filter.name = value
|
||||
else:
|
||||
ifc_filter.value = value
|
||||
elif ifc_filter.type == "attribute":
|
||||
if self.suggestion_type == "attribute_name":
|
||||
ifc_filter.name = value
|
||||
else:
|
||||
ifc_filter.value = value
|
||||
else:
|
||||
ifc_filter.value = value
|
||||
|
||||
if self.first_launch:
|
||||
self.first_launch = False
|
||||
else:
|
||||
context.window.screen = context.window.screen
|
||||
|
||||
|
||||
class FilterValueSuggestions(Operator):
|
||||
bl_idname = "bim.filter_value_suggestions"
|
||||
bl_label = "Filter Value Suggestions"
|
||||
bl_description = "Get suggestions for filter values from the current IFC file"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
group_index: IntProperty()
|
||||
filter_index: IntProperty()
|
||||
module: StringProperty(default="search")
|
||||
filter_type: StringProperty()
|
||||
suggestion_type: StringProperty(default="value")
|
||||
|
||||
first_launch: BoolProperty(default=True, options={"SKIP_SAVE"})
|
||||
search_value: StringProperty(
|
||||
name="Search",
|
||||
description="Search for filter values",
|
||||
update=update_filter_search_value,
|
||||
default="",
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
collection_values: CollectionProperty(type=StrProperty, options={"SKIP_SAVE"})
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
ifc_file = tool.Ifc.get()
|
||||
if not ifc_file:
|
||||
self.report({"WARNING"}, "No IFC file loaded")
|
||||
return {"CANCELLED"}
|
||||
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
ifc_filter = filter_groups[self.group_index].filters[self.filter_index]
|
||||
|
||||
string_suggestions = self.get_suggestions(ifc_file, ifc_filter)
|
||||
if not string_suggestions:
|
||||
self.report({"INFO"}, f"No suggestions available")
|
||||
return {"CANCELLED"}
|
||||
|
||||
self.collection_values.clear()
|
||||
for suggestion in natsorted(string_suggestions):
|
||||
self.collection_values.add().name = suggestion
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self, width=800)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
ifc_filter = filter_groups[self.group_index].filters[self.filter_index]
|
||||
|
||||
label_map = {
|
||||
"entity": "Select Class",
|
||||
"type": "Select Type",
|
||||
"material": "Select Material",
|
||||
"location": "Select Location",
|
||||
"group": "Select Group",
|
||||
"classification": "Select Classification",
|
||||
"parent": "Select Parent",
|
||||
"instance": "Select GlobalId",
|
||||
}
|
||||
|
||||
if ifc_filter.type == "attribute":
|
||||
if self.suggestion_type == "attribute_value":
|
||||
label = f"Select Value for {ifc_filter.name}"
|
||||
else:
|
||||
label = "Select Attribute Name"
|
||||
elif ifc_filter.type == "property":
|
||||
if self.suggestion_type == "property_value":
|
||||
label = f"Select Value for {ifc_filter.pset}.{ifc_filter.name}"
|
||||
elif self.suggestion_type == "property_name":
|
||||
label = f"Select Property from {ifc_filter.pset}"
|
||||
else:
|
||||
label = "Select Property Set"
|
||||
else:
|
||||
label = label_map.get(ifc_filter.type, "Select Value")
|
||||
|
||||
row = layout.row()
|
||||
row.label(text=label)
|
||||
row = layout.row()
|
||||
row.prop_search(
|
||||
self,
|
||||
"search_value",
|
||||
self,
|
||||
"collection_values",
|
||||
text="",
|
||||
results_are_suggestions=True,
|
||||
)
|
||||
|
||||
def get_suggestions(self, ifc_file, ifc_filter):
|
||||
suggestions = set()
|
||||
|
||||
if ifc_filter.type == "entity":
|
||||
suggestions = self.get_entity_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "type":
|
||||
suggestions = self.get_type_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "material":
|
||||
suggestions = self.get_material_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "location":
|
||||
suggestions = self.get_location_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "group":
|
||||
suggestions = self.get_group_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "classification":
|
||||
suggestions = self.get_classification_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "parent":
|
||||
suggestions = self.get_parent_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "instance":
|
||||
suggestions = self.get_instance_suggestions(ifc_file)
|
||||
elif ifc_filter.type == "attribute":
|
||||
if self.suggestion_type == "attribute_name":
|
||||
suggestions = self.get_attribute_names(ifc_file)
|
||||
else:
|
||||
suggestions = self.get_attribute_values(ifc_file, ifc_filter.name)
|
||||
elif ifc_filter.type == "property":
|
||||
if self.suggestion_type == "pset":
|
||||
suggestions = self.get_property_sets(ifc_file)
|
||||
elif self.suggestion_type == "property_name":
|
||||
suggestions = self.get_property_names(ifc_file, ifc_filter.pset)
|
||||
else:
|
||||
suggestions = self.get_property_values(ifc_file, ifc_filter.pset, ifc_filter.name)
|
||||
|
||||
return suggestions
|
||||
|
||||
def build_hierarchy_path(self, element):
|
||||
path = []
|
||||
current = element
|
||||
|
||||
while current:
|
||||
if hasattr(current, "Name") and current.Name:
|
||||
path.insert(0, current.Name)
|
||||
|
||||
parent = None
|
||||
|
||||
if hasattr(current, "Decomposes") and current.Decomposes:
|
||||
for rel in current.Decomposes:
|
||||
if hasattr(rel, "RelatingObject"):
|
||||
parent = rel.RelatingObject
|
||||
break
|
||||
|
||||
if not parent and hasattr(current, "ContainedInStructure") and current.ContainedInStructure:
|
||||
for rel in current.ContainedInStructure:
|
||||
if hasattr(rel, "RelatingStructure"):
|
||||
parent = rel.RelatingStructure
|
||||
break
|
||||
|
||||
current = parent
|
||||
|
||||
return path
|
||||
|
||||
def get_entity_suggestions(self, ifc_file):
|
||||
all_classes = set()
|
||||
schema = tool.Ifc.schema()
|
||||
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
class_name = element.is_a()
|
||||
|
||||
try:
|
||||
entity = schema.declaration_by_name(class_name).as_entity()
|
||||
current = entity
|
||||
|
||||
chain_names = [class_name]
|
||||
while current.supertype():
|
||||
supertype = current.supertype()
|
||||
chain_names.insert(0, supertype.name())
|
||||
current = supertype
|
||||
|
||||
if len(chain_names) > 1:
|
||||
all_classes.add(" > ".join(chain_names))
|
||||
for i in range(len(chain_names) - 1):
|
||||
superclass_chain = " > ".join(chain_names[: i + 1])
|
||||
all_classes.add(f"{superclass_chain} (superclass)")
|
||||
else:
|
||||
all_classes.add(class_name)
|
||||
except:
|
||||
all_classes.add(class_name)
|
||||
except:
|
||||
continue
|
||||
|
||||
return all_classes
|
||||
|
||||
def get_type_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for element_type in ifc_file.by_type("IfcTypeObject"):
|
||||
if element_type.Name:
|
||||
hierarchy_path = self.build_hierarchy_path(element_type)
|
||||
if len(hierarchy_path) > 1:
|
||||
suggestions.add(" < ".join(hierarchy_path))
|
||||
else:
|
||||
suggestions.add(element_type.Name)
|
||||
return suggestions
|
||||
|
||||
def get_material_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for material in ifc_file.by_type("IfcMaterial"):
|
||||
if material.Name:
|
||||
suggestions.add(material.Name)
|
||||
return suggestions
|
||||
|
||||
def get_location_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for spatial in ifc_file.by_type("IfcSpatialStructureElement"):
|
||||
if spatial.Name:
|
||||
hierarchy_path = self.build_hierarchy_path(spatial)
|
||||
if len(hierarchy_path) > 1:
|
||||
suggestions.add(" < ".join(hierarchy_path))
|
||||
else:
|
||||
suggestions.add(spatial.Name)
|
||||
return suggestions
|
||||
|
||||
def get_group_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for group in ifc_file.by_type("IfcGroup"):
|
||||
if group.Name:
|
||||
hierarchy_path = self.build_hierarchy_path(group)
|
||||
if len(hierarchy_path) > 1:
|
||||
suggestions.add(" < ".join(hierarchy_path))
|
||||
else:
|
||||
suggestions.add(group.Name)
|
||||
return suggestions
|
||||
|
||||
def get_classification_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for ref in ifc_file.by_type("IfcClassificationReference"):
|
||||
if ref.Identification:
|
||||
suggestions.add(ref.Identification)
|
||||
return suggestions
|
||||
|
||||
def get_parent_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
has_children = False
|
||||
|
||||
if hasattr(element, "IsDecomposedBy") and element.IsDecomposedBy:
|
||||
has_children = True
|
||||
elif hasattr(element, "ContainsElements") and element.ContainsElements:
|
||||
has_children = True
|
||||
elif hasattr(element, "HasOpenings") and element.HasOpenings:
|
||||
has_children = True
|
||||
|
||||
if has_children:
|
||||
hierarchy_path = self.build_hierarchy_path(element)
|
||||
element_class = element.is_a()
|
||||
|
||||
if len(hierarchy_path) > 0:
|
||||
hierarchy_str = " < ".join(hierarchy_path)
|
||||
suggestions.add(f"{hierarchy_str} ({element_class})")
|
||||
else:
|
||||
element_name = element.Name if hasattr(element, "Name") and element.Name else element_class
|
||||
suggestions.add(f"{element_name} ({element_class})")
|
||||
except:
|
||||
continue
|
||||
|
||||
return suggestions
|
||||
|
||||
def get_instance_suggestions(self, ifc_file):
|
||||
suggestions = set()
|
||||
element_data = []
|
||||
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
if hasattr(element, "GlobalId") and element.GlobalId:
|
||||
hierarchy_path = self.build_hierarchy_path(element)
|
||||
element_class = element.is_a()
|
||||
|
||||
if len(hierarchy_path) > 0:
|
||||
hierarchy_str = " < ".join(hierarchy_path)
|
||||
display_str = f"{hierarchy_str} ({element_class})"
|
||||
else:
|
||||
display_str = f"({element_class})"
|
||||
|
||||
element_data.append((display_str, element.GlobalId))
|
||||
except:
|
||||
continue
|
||||
|
||||
display_counts = {}
|
||||
for display_str, global_id in element_data:
|
||||
display_counts[display_str] = display_counts.get(display_str, 0) + 1
|
||||
|
||||
for display_str, global_id in element_data:
|
||||
if display_counts[display_str] > 1:
|
||||
suggestions.add(f"{display_str}: {global_id}")
|
||||
else:
|
||||
suggestions.add(display_str)
|
||||
|
||||
return suggestions
|
||||
|
||||
def get_property_sets(self, ifc_file):
|
||||
psets = set()
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
for definition in getattr(element, "IsDefinedBy", []):
|
||||
if definition.is_a("IfcRelDefinesByProperties"):
|
||||
pset = definition.RelatingPropertyDefinition
|
||||
if pset.is_a("IfcPropertySet") and pset.Name:
|
||||
psets.add(pset.Name)
|
||||
except:
|
||||
continue
|
||||
return psets
|
||||
|
||||
def get_property_names(self, ifc_file, pset_name):
|
||||
property_names = set()
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
for definition in getattr(element, "IsDefinedBy", []):
|
||||
if definition.is_a("IfcRelDefinesByProperties"):
|
||||
pset = definition.RelatingPropertyDefinition
|
||||
if pset.is_a("IfcPropertySet") and pset.Name == pset_name:
|
||||
if pset.HasProperties:
|
||||
for prop in pset.HasProperties:
|
||||
if hasattr(prop, "Name") and prop.Name:
|
||||
property_names.add(prop.Name)
|
||||
except:
|
||||
continue
|
||||
return property_names
|
||||
|
||||
def get_property_values(self, ifc_file, pset_name, property_name):
|
||||
property_values = set()
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
for definition in getattr(element, "IsDefinedBy", []):
|
||||
if definition.is_a("IfcRelDefinesByProperties"):
|
||||
pset = definition.RelatingPropertyDefinition
|
||||
if pset.is_a("IfcPropertySet") and pset.Name == pset_name:
|
||||
if pset.HasProperties:
|
||||
for prop in pset.HasProperties:
|
||||
if hasattr(prop, "Name") and prop.Name == property_name:
|
||||
if hasattr(prop, "NominalValue") and prop.NominalValue:
|
||||
try:
|
||||
value = prop.NominalValue.wrappedValue
|
||||
if value is not None and value != "":
|
||||
if not hasattr(value, "is_a") and not isinstance(
|
||||
value, (tuple, list)
|
||||
):
|
||||
str_value = str(value)
|
||||
if not str_value.startswith("#") and not str_value.startswith(
|
||||
"("
|
||||
):
|
||||
property_values.add(str_value)
|
||||
except:
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
return property_values
|
||||
|
||||
def get_attribute_names(self, ifc_file):
|
||||
attribute_names = set()
|
||||
schema = tool.Ifc.schema()
|
||||
|
||||
ifc_classes = set()
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
ifc_classes.add(element.is_a())
|
||||
except:
|
||||
continue
|
||||
|
||||
for ifc_class in ifc_classes:
|
||||
try:
|
||||
entity = schema.declaration_by_name(ifc_class).as_entity()
|
||||
attributes = entity.all_attributes()
|
||||
for attr in attributes:
|
||||
attribute_names.add(attr.name())
|
||||
except:
|
||||
continue
|
||||
|
||||
return attribute_names
|
||||
|
||||
def get_attribute_values(self, ifc_file, attribute_name):
|
||||
attribute_values = set()
|
||||
|
||||
for element_id in IfcStore.id_map.keys():
|
||||
try:
|
||||
element = ifc_file.by_id(element_id)
|
||||
|
||||
if element.is_a("IfcRelationship") or element.is_a("IfcTypeObject"):
|
||||
continue
|
||||
|
||||
if hasattr(element, attribute_name):
|
||||
value = getattr(element, attribute_name, None)
|
||||
|
||||
if value is not None and value != "":
|
||||
if not hasattr(value, "is_a") and not isinstance(value, (tuple, list)):
|
||||
str_value = str(value)
|
||||
if not str_value.startswith("#") and not str_value.startswith("("):
|
||||
attribute_values.add(str_value)
|
||||
except:
|
||||
continue
|
||||
|
||||
return attribute_values
|
||||
|
||||
|
||||
class AddFilterGroup(Operator):
|
||||
bl_idname = "bim.add_filter_group"
|
||||
bl_label = "Add Filter Group"
|
||||
@@ -96,11 +563,38 @@ class AddFilter(Operator):
|
||||
|
||||
def execute(self, context):
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
if self.index >= len(filter_groups):
|
||||
filter_groups.add()
|
||||
new = filter_groups[self.index].filters.add()
|
||||
new.type = self.type
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ToggleFilterInclusion(Operator):
|
||||
bl_idname = "bim.toggle_filter_inclusion"
|
||||
bl_label = "Toggle Filter Mode"
|
||||
bl_description = "Cycle between Add (+), Subtract (-), and Filter modes for this filter"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
group_index: IntProperty()
|
||||
filter_index: IntProperty()
|
||||
module: StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
filter_group = filter_groups[self.group_index]
|
||||
ifc_filter = filter_group.filters[self.filter_index]
|
||||
|
||||
if ifc_filter.filter_mode == "ADD":
|
||||
ifc_filter.filter_mode = "SUBTRACT"
|
||||
elif ifc_filter.filter_mode == "SUBTRACT":
|
||||
ifc_filter.filter_mode = "FILTER"
|
||||
else:
|
||||
ifc_filter.filter_mode = "ADD"
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SelectFilterElements(bpy.types.Operator):
|
||||
bl_idname = "bim.select_filter_elements"
|
||||
bl_label = "Select Filter Elements"
|
||||
@@ -120,6 +614,47 @@ class SelectFilterElements(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class ApplyFilterFromText(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.apply_filter_from_text"
|
||||
bl_label = "Apply Filter Configuration"
|
||||
bl_description = "Apply the JSON filter configuration from the current text block"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.area and context.area.type == "TEXT_EDITOR":
|
||||
space = context.space_data
|
||||
if space.text and space.text.name.startswith("FilterQuery_"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
space = context.space_data
|
||||
text = space.text
|
||||
|
||||
if not text or not text.name.startswith("FilterQuery_"):
|
||||
self.report({"ERROR"}, "No valid filter configuration text block")
|
||||
return {"CANCELLED"}
|
||||
|
||||
module = text.name.replace("FilterQuery_", "")
|
||||
|
||||
try:
|
||||
json_data = json.loads(text.as_string())
|
||||
filter_structure = json_data.get("filter_structure", [])
|
||||
filter_groups = tool.Search.get_filter_groups(module)
|
||||
tool.Search.import_filter_structure(filter_structure, filter_groups)
|
||||
self.report({"INFO"}, "Filter configuration applied successfully")
|
||||
|
||||
if len(context.window_manager.windows) > 1:
|
||||
bpy.ops.wm.window_close()
|
||||
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Invalid JSON: {str(e)}")
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class EditFilterQuery(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.edit_filter_query"
|
||||
bl_label = "Edit Filter Query"
|
||||
@@ -127,29 +662,82 @@ class EditFilterQuery(Operator, tool.Ifc.Operator):
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
query: StringProperty(name="Query")
|
||||
old_query: StringProperty(name="Old Query")
|
||||
module: StringProperty()
|
||||
module: StringProperty(default="search")
|
||||
|
||||
def _execute(self, context):
|
||||
if self.query == self.old_query:
|
||||
return
|
||||
module = getattr(self, "module", "search")
|
||||
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
try:
|
||||
tool.Search.import_filter_query(self.query, filter_groups)
|
||||
except:
|
||||
return
|
||||
if not tool.Blender.get_addon_preferences().chain_filter_with_set_operations:
|
||||
if self.query == self.old_query:
|
||||
return
|
||||
|
||||
filter_groups = tool.Search.get_filter_groups(module)
|
||||
try:
|
||||
tool.Search.import_filter_query(self.query, filter_groups)
|
||||
except:
|
||||
return
|
||||
|
||||
def draw(self, context):
|
||||
row = self.layout.row()
|
||||
row.prop(self, "query", text="")
|
||||
|
||||
if not tool.Blender.get_addon_preferences().chain_filter_with_set_operations:
|
||||
row = self.layout.row()
|
||||
row.prop(self, "query", text="")
|
||||
|
||||
def invoke(self, context, event):
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
module = getattr(self, "module", "search")
|
||||
filter_groups = tool.Search.get_filter_groups(module)
|
||||
|
||||
self.query = tool.Search.export_filter_query(filter_groups)
|
||||
self.old_query = self.query
|
||||
if tool.Blender.get_addon_preferences().chain_filter_with_set_operations:
|
||||
filter_structure = []
|
||||
for filter_group in filter_groups:
|
||||
group_data = []
|
||||
for ifc_filter in filter_group.filters:
|
||||
filter_data = {
|
||||
"type": ifc_filter.type,
|
||||
"name": ifc_filter.name,
|
||||
"value": ifc_filter.value,
|
||||
"pset": ifc_filter.pset,
|
||||
"comparison": ifc_filter.comparison,
|
||||
"filter_mode": ifc_filter.filter_mode,
|
||||
}
|
||||
group_data.append(filter_data)
|
||||
filter_structure.append(group_data)
|
||||
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
query = tool.Search.export_filter_query(filter_groups)
|
||||
json_data = {"type": "BBIM_Search", "query": query, "filter_structure": filter_structure}
|
||||
|
||||
text_block_name = f"FilterQuery_{module}"
|
||||
text = bpy.data.texts.get(text_block_name)
|
||||
if not text:
|
||||
text = bpy.data.texts.new(text_block_name)
|
||||
|
||||
text.clear()
|
||||
text.write(json.dumps(json_data, indent=2))
|
||||
|
||||
bpy.ops.wm.window_new()
|
||||
new_window = context.window_manager.windows[-1]
|
||||
|
||||
new_area = new_window.screen.areas[0]
|
||||
new_area.type = "TEXT_EDITOR"
|
||||
|
||||
text_space = None
|
||||
for space in new_area.spaces:
|
||||
if space.type == "TEXT_EDITOR":
|
||||
text_space = space
|
||||
break
|
||||
|
||||
if text_space:
|
||||
text_space.text = text
|
||||
|
||||
self.report(
|
||||
{"INFO"}, "Compact text editor opened. Edit JSON and click 'Apply Filter Configuration' in header"
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
else:
|
||||
self.query = tool.Search.export_filter_query(filter_groups)
|
||||
self.old_query = self.query
|
||||
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||
|
||||
|
||||
class Search(Operator):
|
||||
@@ -174,9 +762,26 @@ class Search(Operator):
|
||||
else:
|
||||
assert_never(self.property_group)
|
||||
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
tool.Ifc.get(), tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
preferences = tool.Blender.get_addon_preferences()
|
||||
|
||||
# Migrate old ! prefix filters to new filter_mode system when preferences are enabled
|
||||
if (
|
||||
preferences.chain_filter_with_set_operations
|
||||
or preferences.default_filter_with_set_operations_for_globalid_and_class
|
||||
):
|
||||
for filter_group in props.filter_groups:
|
||||
for ifc_filter in filter_group.filters:
|
||||
if ifc_filter.type not in ["entity", "instance"]:
|
||||
continue
|
||||
if ifc_filter.value.startswith("!"):
|
||||
ifc_filter.value = ifc_filter.value[1:]
|
||||
ifc_filter.filter_mode = "SUBTRACT"
|
||||
|
||||
results = tool.Search.execute_filter_groups(props.filter_groups)
|
||||
else:
|
||||
results = ifcopenshell.util.selector.filter_elements(
|
||||
tool.Ifc.get(), tool.Search.export_filter_query(props.filter_groups)
|
||||
)
|
||||
|
||||
objs = [obj for e in results if isinstance(obj := tool.Ifc.get_object(e), bpy.types.Object)]
|
||||
for obj in objs:
|
||||
@@ -242,13 +847,28 @@ class SaveSearch(Operator, tool.Ifc.Operator):
|
||||
|
||||
try:
|
||||
query = tool.Search.export_filter_query(filter_groups)
|
||||
results = ifcopenshell.util.selector.filter_elements(tool.Ifc.get(), query)
|
||||
results = tool.Search.execute_filter_groups(filter_groups)
|
||||
|
||||
filter_structure = []
|
||||
for filter_group in filter_groups:
|
||||
group_data = []
|
||||
for ifc_filter in filter_group.filters:
|
||||
filter_data = {
|
||||
"type": ifc_filter.type,
|
||||
"name": ifc_filter.name,
|
||||
"value": ifc_filter.value,
|
||||
"pset": ifc_filter.pset,
|
||||
"comparison": ifc_filter.comparison,
|
||||
"filter_mode": ifc_filter.filter_mode,
|
||||
}
|
||||
group_data.append(filter_data)
|
||||
filter_structure.append(group_data)
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
self.report({"ERROR"}, "Error occurred trying save search.")
|
||||
return
|
||||
|
||||
description = json.dumps({"type": "BBIM_Search", "query": query})
|
||||
description = json.dumps({"type": "BBIM_Search", "query": query, "filter_structure": filter_structure})
|
||||
ifc_file = tool.Ifc.get()
|
||||
group = next(
|
||||
(
|
||||
@@ -289,7 +909,12 @@ class LoadSearch(Operator, tool.Ifc.Operator):
|
||||
filter_groups = tool.Search.get_filter_groups(self.module)
|
||||
props = tool.Search.get_search_props()
|
||||
group = tool.Ifc.get().by_id(int(props.saved_searches))
|
||||
tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
|
||||
|
||||
group_data = tool.Search.get_group_data(group)
|
||||
if group_data and "filter_structure" in group_data:
|
||||
tool.Search.import_filter_structure(group_data["filter_structure"], filter_groups)
|
||||
else:
|
||||
tool.Search.import_filter_query(tool.Search.get_group_query(group), filter_groups)
|
||||
|
||||
def draw(self, context):
|
||||
assert self.layout
|
||||
@@ -302,6 +927,41 @@ class LoadSearch(Operator, tool.Ifc.Operator):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class RemoveSearch(Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_search"
|
||||
bl_label = "Remove Search"
|
||||
bl_description = "Remove a saved search filter"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
module: StringProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
props = tool.Search.get_search_props()
|
||||
group_id = props.saved_searches
|
||||
if not group_id:
|
||||
self.report({"ERROR"}, "No search selected for removal")
|
||||
return
|
||||
|
||||
group = tool.Ifc.get().by_id(int(group_id))
|
||||
group_name = group.Name or "Unnamed"
|
||||
ifcopenshell.api.group.remove_group(tool.Ifc.get(), group=group)
|
||||
tool.Search.patch_search_ifcgroups()
|
||||
self.report({"INFO"}, f"Removed saved search: {group_name}")
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text="Select search to remove:", icon="ERROR")
|
||||
row = self.layout.row()
|
||||
props = tool.Search.get_search_props()
|
||||
row.prop(props, "saved_searches", text="")
|
||||
|
||||
def invoke(self, context, event):
|
||||
tool.Search.patch_search_ifcgroups()
|
||||
from bonsai.bim.module.search.data import SearchData
|
||||
|
||||
if not SearchData.is_loaded:
|
||||
SearchData.load()
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class ColourByProperty(Operator):
|
||||
bl_idname = "bim.colour_by_property"
|
||||
bl_label = "Colour by Property"
|
||||
|
||||
@@ -101,7 +101,7 @@ class TypeData:
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
return results
|
||||
|
||||
|
||||
data = element_type.get_info()
|
||||
if "GlobalId" in data:
|
||||
excluded_keys = ["id", "type"]
|
||||
|
||||
@@ -429,18 +429,18 @@ class EnableEditingTypeAttributes(bpy.types.Operator):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
props = tool.Type.get_object_type_props(obj)
|
||||
props.type_attributes.clear()
|
||||
|
||||
|
||||
bonsai.bim.helper.import_attributes(element_type, props.type_attributes)
|
||||
props.is_editing_type_attributes = True
|
||||
return {"FINISHED"}
|
||||
@@ -456,7 +456,7 @@ class DisableEditingTypeAttributes(bpy.types.Operator):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
props = tool.Type.get_object_type_props(obj)
|
||||
props.type_attributes.clear()
|
||||
props.property_unset("is_editing_type_attributes")
|
||||
@@ -473,24 +473,24 @@ class EditTypeAttributes(bpy.types.Operator, tool.Ifc.Operator):
|
||||
obj = context.active_object
|
||||
if not obj:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
return {"CANCELLED"}
|
||||
|
||||
|
||||
props = tool.Type.get_object_type_props(obj)
|
||||
attributes = bonsai.bim.helper.export_attributes(props.type_attributes)
|
||||
|
||||
|
||||
ifcopenshell.api.attribute.edit_attributes(tool.Ifc.get(), product=element_type, attributes=attributes)
|
||||
|
||||
|
||||
type_obj = tool.Ifc.get_object(element_type)
|
||||
if type_obj:
|
||||
tool.Root.set_object_name(type_obj, element_type)
|
||||
|
||||
|
||||
bpy.ops.bim.disable_editing_type_attributes()
|
||||
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -124,27 +124,28 @@ class BIM_PT_type_attributes(Panel):
|
||||
def draw(self, context):
|
||||
if not TypeData.is_loaded:
|
||||
TypeData.load()
|
||||
|
||||
|
||||
assert (layout := self.layout)
|
||||
assert (obj := context.active_object)
|
||||
|
||||
|
||||
if not TypeData.data.get("relating_type"):
|
||||
layout.label(text="No Relating Type", icon="INFO")
|
||||
return
|
||||
|
||||
|
||||
props = tool.Type.get_object_type_props(obj)
|
||||
|
||||
|
||||
if props.is_editing_type_attributes:
|
||||
row = layout.row(align=True)
|
||||
row.operator("bim.edit_type_attributes", icon="CHECKMARK", text="Save Attributes")
|
||||
row.operator("bim.disable_editing_type_attributes", icon="CANCEL", text="")
|
||||
|
||||
|
||||
import bonsai.bim.helper
|
||||
|
||||
bonsai.bim.helper.draw_attributes(props.type_attributes, layout)
|
||||
else:
|
||||
row = layout.row()
|
||||
row.operator("bim.enable_editing_type_attributes", icon="GREASEPENCIL", text="Edit")
|
||||
|
||||
|
||||
for attribute in TypeData.data["relating_type_attributes"]:
|
||||
row = layout.row(align=True)
|
||||
row.label(text=attribute["name"])
|
||||
|
||||
@@ -33,6 +33,15 @@ import bonsai.bim
|
||||
import bonsai.tool as tool
|
||||
import bonsai.bim.handler
|
||||
from enum import Enum
|
||||
from bonsai.bim.helper import (
|
||||
get_all_tab_panels,
|
||||
get_tab_visibility,
|
||||
set_tab_visibility,
|
||||
get_tab_names,
|
||||
get_panel_config,
|
||||
initialize_panel_properties,
|
||||
initialize_tab_visibilities,
|
||||
)
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from bonsai.bim import import_ifc
|
||||
from bonsai.bim.prop import StrProperty
|
||||
@@ -236,6 +245,147 @@ class SelectIfcFile(bpy.types.Operator, IFCFileSelector, ImportHelper):
|
||||
return ImportHelper.invoke(self, context, event)
|
||||
|
||||
|
||||
class SaveBlendMetadataFile(bpy.types.Operator):
|
||||
bl_idname = "bim.save_blend_metadata_file"
|
||||
bl_label = "Save Blend Metadata File"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
"""
|
||||
Save the current blend file as a metadata-only file (no geometry), preserving settings, window arrangement, geometry nodes, etc.
|
||||
"""
|
||||
props = tool.Blender.get_bim_props()
|
||||
ifc_file = getattr(props, "ifc_file", None)
|
||||
if not ifc_file:
|
||||
self.report({"WARNING"}, "No IFC file path set.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
blendmetadata_path = ifc_file + ".metadata.blend"
|
||||
|
||||
# Save a temporary copy of the current blend file
|
||||
temp_path = bpy.path.abspath("//__temp_blendmetadata.blend")
|
||||
bpy.ops.wm.save_as_mainfile(filepath=temp_path, copy=True)
|
||||
|
||||
cleanup_script = f"""
|
||||
import bpy
|
||||
|
||||
# Ensure all styles are loaded before attempting to remove them
|
||||
try:
|
||||
bpy.ops.bim.load_styles()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 1. Collect all IfcStyle material names
|
||||
ifcstyle_material_names = []
|
||||
try:
|
||||
styles_props = getattr(bpy.context.scene, "BIMStylesProperties", None)
|
||||
if styles_props is None and bpy.data.scenes:
|
||||
styles_props = getattr(bpy.data.scenes[0], "BIMStylesProperties", None)
|
||||
if styles_props:
|
||||
for style in list(styles_props.styles):
|
||||
material = getattr(style, "blender_material", None)
|
||||
if material and material.name:
|
||||
ifcstyle_material_names.append(material.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Purge IfcStore
|
||||
try:
|
||||
from bonsai.bim.ifc import IfcStore
|
||||
except ImportError:
|
||||
IfcStore = None
|
||||
|
||||
if IfcStore:
|
||||
try:
|
||||
IfcStore.purge()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 3. Remove all collections named IfcProject*
|
||||
for collection in list(bpy.data.collections):
|
||||
if collection.name.startswith('IfcProject'):
|
||||
try:
|
||||
bpy.data.collections.remove(collection, do_unlink=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 4. Purge orphaned data blocks after removing IfcProject collections
|
||||
try:
|
||||
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 5. Remove all materials corresponding to the IfcStyles we collected
|
||||
materials_removed = 0
|
||||
try:
|
||||
for mat_name in ifcstyle_material_names:
|
||||
if mat_name in bpy.data.materials:
|
||||
try:
|
||||
bpy.data.materials.remove(bpy.data.materials[mat_name], do_unlink=True)
|
||||
materials_removed += 1
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bpy.ops.wm.save_as_mainfile(filepath=r'{blendmetadata_path}')
|
||||
"""
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as script_file:
|
||||
script_file.write(cleanup_script)
|
||||
script_path = script_file.name
|
||||
|
||||
blender_exe = bpy.app.binary_path
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
[blender_exe, temp_path, "--background", "--python", script_path], capture_output=True, text=True
|
||||
)
|
||||
|
||||
# Print the output from the background process (includes debug info)
|
||||
if result.stdout:
|
||||
print("\n=== Background Blender Output ===")
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print("\n=== Background Blender Errors ===")
|
||||
print(result.stderr)
|
||||
|
||||
try:
|
||||
os.remove(temp_path)
|
||||
os.remove(script_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LoadBlendMetadataAndIFC(bpy.types.Operator):
|
||||
bl_idname = "bim.load_blend_metadata_and_ifc"
|
||||
bl_label = "Load Blend Metadata and IFC"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
filepath: bpy.props.StringProperty(name="IFC File Path", default="")
|
||||
|
||||
def execute(self, context):
|
||||
ifc_file = self.filepath
|
||||
if not ifc_file:
|
||||
props = tool.Blender.get_bim_props()
|
||||
ifc_file = getattr(props, "ifc_file", None)
|
||||
|
||||
if not ifc_file:
|
||||
self.report({"WARNING"}, "No IFC file path set.")
|
||||
return {"CANCELLED"}
|
||||
|
||||
metadata_path = ifc_file + ".metadata.blend"
|
||||
# Open the metadata blend file
|
||||
bpy.ops.wm.open_mainfile(filepath=metadata_path)
|
||||
# After loading metadata, clear blend warning (no geometry loaded yet)
|
||||
props = tool.Blender.get_bim_props()
|
||||
props.has_blend_warning = False
|
||||
# Load the IFC file into the current session (preserve layout)
|
||||
bpy.ops.bim.load_project(filepath=ifc_file, should_start_fresh_session=False)
|
||||
self.report({"INFO"}, f"Loaded metadata and IFC: {metadata_path}, {ifc_file}")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
# TODO: Unused operator.
|
||||
# Is there a need for this or 'DIR_PATH' propety subtype does almost the same,
|
||||
# but also has alt+click?
|
||||
@@ -1605,3 +1755,244 @@ class BIM_OT_attribute_remove_subitem(bpy.types.Operator):
|
||||
attr.is_null = True
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_UL_tab_panels(bpy.types.UIList):
|
||||
"""UIList for Tab Panels"""
|
||||
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
row = layout.row(align=True)
|
||||
row.label(text=item["bl_label"])
|
||||
|
||||
row.operator(
|
||||
"bim.toggle_panel_visibility",
|
||||
text="",
|
||||
icon="HIDE_OFF" if item.get("visible", True) else "HIDE_ON",
|
||||
).action = f"TOGGLE_VISIBILITY_{item.name}"
|
||||
|
||||
row.operator(
|
||||
"bim.bookmark_panel",
|
||||
text="",
|
||||
icon="SOLO_ON" if item.get("bookmarked", False) else "SOLO_OFF",
|
||||
).action = f"BOOKMARK_{item.name}"
|
||||
|
||||
|
||||
class BIM_OT_toggle_panel_visibility(bpy.types.Operator):
|
||||
"""Toggle Panel Visibility"""
|
||||
|
||||
bl_idname = "bim.toggle_panel_visibility"
|
||||
bl_label = "Toggle Panel Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
action: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
panel_name = self.action.replace("TOGGLE_VISIBILITY_", "")
|
||||
active_tab = getattr(context.scene, "active_tab_name", None) or getattr(
|
||||
tool.Blender.get_bim_props(), "tab", None
|
||||
)
|
||||
is_bookmark_tab = active_tab == "BOOKMARK"
|
||||
|
||||
panel_config = get_panel_config(panel_name, create_if_missing=True)
|
||||
if panel_config:
|
||||
if is_bookmark_tab:
|
||||
panel_config.is_visible_in_bookmarks = not panel_config.is_visible_in_bookmarks
|
||||
new_value = panel_config.is_visible_in_bookmarks
|
||||
else:
|
||||
panel_config.is_visible_in_tab = not panel_config.is_visible_in_tab
|
||||
new_value = panel_config.is_visible_in_tab
|
||||
|
||||
for item in context.scene.tab_panels:
|
||||
if item.name == panel_name:
|
||||
item["visible"] = new_value
|
||||
break
|
||||
|
||||
for area in bpy.context.window.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
area.tag_redraw()
|
||||
|
||||
tab_context = "Bookmarks" if is_bookmark_tab else "Tab"
|
||||
self.report({"INFO"}, f"Toggled visibility for {panel_name} in {tab_context}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_bookmark_panel(bpy.types.Operator):
|
||||
"""Bookmark Panel"""
|
||||
|
||||
bl_idname = "bim.bookmark_panel"
|
||||
bl_label = "Bookmark Panel"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
action: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
panel_name = self.action.replace("BOOKMARK_", "")
|
||||
panel_config = get_panel_config(panel_name, create_if_missing=True)
|
||||
|
||||
if panel_config:
|
||||
panel_config.is_bookmarked = not panel_config.is_bookmarked
|
||||
|
||||
for item in context.scene.tab_panels:
|
||||
if item.name == panel_name:
|
||||
item["bookmarked"] = panel_config.is_bookmarked
|
||||
break
|
||||
|
||||
for area in bpy.context.window.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
area.tag_redraw()
|
||||
|
||||
self.report({"INFO"}, f"Toggled bookmark for {panel_name}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_manage_tab_panels(bpy.types.Operator):
|
||||
"""Manage Tab Panels"""
|
||||
|
||||
bl_idname = "bim.manage_tab_panels"
|
||||
bl_label = "Manage Tab Panels"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
tab_name: bpy.props.StringProperty()
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
context.scene.active_tab_name = self.tab_name
|
||||
context.scene.tab_panels.clear()
|
||||
|
||||
initialize_tab_visibilities()
|
||||
initialize_panel_properties()
|
||||
all_panels = get_all_tab_panels(force_refresh=True)
|
||||
|
||||
for panel_data in all_panels.get(self.tab_name, []):
|
||||
panel_name = panel_data.get("bl_idname", "")
|
||||
panel_label = panel_data.get("bl_label", "")
|
||||
if not panel_name or not panel_label:
|
||||
continue
|
||||
|
||||
item = context.scene.tab_panels.add()
|
||||
item.name = panel_name
|
||||
item["bl_label"] = panel_label
|
||||
|
||||
panel_config = get_panel_config(panel_name, create_if_missing=True)
|
||||
if panel_config:
|
||||
if self.tab_name == "BOOKMARK":
|
||||
item["visible"] = panel_config.is_visible_in_bookmarks
|
||||
else:
|
||||
item["visible"] = panel_config.is_visible_in_tab
|
||||
item["bookmarked"] = panel_config.is_bookmarked
|
||||
else:
|
||||
item["visible"] = True
|
||||
item["bookmarked"] = False
|
||||
|
||||
return context.window_manager.invoke_popup(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text=f"Manage Panels for {self.tab_name} Tab")
|
||||
|
||||
row = layout.row()
|
||||
row.template_list("BIM_UL_tab_panels", "", context.scene, "tab_panels", context.scene, "active_tab_panel_index")
|
||||
|
||||
def execute(self, context):
|
||||
for item in context.scene.tab_panels:
|
||||
panel_config = get_panel_config(item.name, create_if_missing=True)
|
||||
if panel_config:
|
||||
if self.tab_name == "BOOKMARK":
|
||||
panel_config.is_visible_in_bookmarks = item["visible"]
|
||||
else:
|
||||
panel_config.is_visible_in_tab = item["visible"]
|
||||
panel_config.is_bookmarked = item["bookmarked"]
|
||||
|
||||
self.report({"INFO"}, f"Panels for {self.tab_name} managed successfully.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_manage_tab_visibility(bpy.types.Operator):
|
||||
"""Manage Tab Visibility"""
|
||||
|
||||
bl_idname = "bim.manage_tab_visibility"
|
||||
bl_label = "Manage Tab Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "RIGHT"
|
||||
|
||||
row.operator("bim.reset_ui_layout", icon="FILE_REFRESH", text="")
|
||||
row = layout.row()
|
||||
row = self.layout.row(align=True)
|
||||
row.alignment = "CENTER"
|
||||
|
||||
for tab_name in get_tab_names():
|
||||
row = layout.row()
|
||||
row.label(text=tab_name)
|
||||
is_visible = get_tab_visibility(tab_name)
|
||||
icon = "HIDE_OFF" if is_visible else "HIDE_ON"
|
||||
op = row.operator("bim.toggle_tab_visibility", text="", icon=icon)
|
||||
op.tab_name = tab_name
|
||||
|
||||
def execute(self, context):
|
||||
return {"FINISHED"}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_popup(self)
|
||||
|
||||
|
||||
class BIM_OT_toggle_tab_visibility(bpy.types.Operator):
|
||||
"""Toggle Tab Visibility"""
|
||||
|
||||
bl_idname = "bim.toggle_tab_visibility"
|
||||
bl_label = "Toggle Tab Visibility"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
tab_name: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
if self.tab_name in get_tab_names():
|
||||
current_visibility = get_tab_visibility(self.tab_name)
|
||||
set_tab_visibility(self.tab_name, not current_visibility)
|
||||
|
||||
for area in bpy.context.window.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
area.tag_redraw()
|
||||
|
||||
self.report({"INFO"}, f"Toggled visibility for {self.tab_name}.")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class BIM_OT_reset_ui_layout(bpy.types.Operator):
|
||||
"""Reset UI Layout to Default"""
|
||||
|
||||
bl_idname = "bim.reset_ui_layout"
|
||||
bl_label = "Reset UI Layout"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
for tab_name in get_tab_names():
|
||||
set_tab_visibility(tab_name, True)
|
||||
|
||||
get_all_tab_panels()["BOOKMARK"] = [{}]
|
||||
|
||||
for tab_name, panels in get_all_tab_panels().items():
|
||||
for panel in panels:
|
||||
panel_name = panel.get("bl_idname", "")
|
||||
if not panel_name:
|
||||
continue
|
||||
|
||||
show_prop_name = f"show_{panel_name.lower()}"
|
||||
if hasattr(context.scene, show_prop_name):
|
||||
setattr(context.scene, show_prop_name, True)
|
||||
|
||||
bookmark_prop_name = f"bookmark_{panel_name.lower()}"
|
||||
if hasattr(context.scene, bookmark_prop_name):
|
||||
setattr(context.scene, bookmark_prop_name, False)
|
||||
|
||||
for area in bpy.context.window.screen.areas:
|
||||
if area.type == "PROPERTIES":
|
||||
area.tag_redraw()
|
||||
|
||||
self.report({"INFO"}, "UI layout reset to default.")
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -493,8 +493,9 @@ def get_tab(
|
||||
("SCHEDULING", "Costing and Scheduling", "", "NLA", 6),
|
||||
("FM", "Facility Management", "", "PACKAGE", 7),
|
||||
("QUALITY", "Quality and Coordination", "", "COMMUNITY", 8),
|
||||
("BOOKMARK", "Bookmark", "", "SOLO_ON", 9),
|
||||
None,
|
||||
("BLENDER", "Blender Properties", "", "BLENDER", 9),
|
||||
("BLENDER", "Blender Properties", "", "BLENDER", 10),
|
||||
]
|
||||
return get_tab.enum_items
|
||||
|
||||
@@ -529,6 +530,26 @@ class BIMTabProperties(PropertyGroup):
|
||||
inactive_tab: bool
|
||||
|
||||
|
||||
class BIMTabVisibility(PropertyGroup):
|
||||
name: StringProperty(name="Tab Name")
|
||||
is_visible: BoolProperty(name="Is Visible", default=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
name: str
|
||||
is_visible: bool
|
||||
|
||||
|
||||
class BIMPanelProperties(PropertyGroup):
|
||||
is_visible_in_tab: BoolProperty(name="Is Visible in Tab", default=True)
|
||||
is_visible_in_bookmarks: BoolProperty(name="Is Visible in Bookmarks", default=True)
|
||||
is_bookmarked: BoolProperty(name="Is Bookmarked", default=False)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_visible_in_tab: bool
|
||||
is_visible_in_bookmarks: bool
|
||||
is_bookmarked: bool
|
||||
|
||||
|
||||
class BIMProperties(PropertyGroup):
|
||||
is_dirty: BoolProperty(name="Is Dirty", default=False)
|
||||
schema_dir: StringProperty(
|
||||
@@ -613,6 +634,9 @@ class BIMProperties(PropertyGroup):
|
||||
name="Time Unit",
|
||||
default="HOUR",
|
||||
)
|
||||
tab_visibilities: CollectionProperty(type=BIMTabVisibility, name="Tab Visibilities")
|
||||
panel_properties: CollectionProperty(type=BIMPanelProperties, name="Panel Properties")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
is_dirty: bool
|
||||
schema_dir: str
|
||||
@@ -627,6 +651,8 @@ class BIMProperties(PropertyGroup):
|
||||
volume_unit: str
|
||||
mass_unit: str
|
||||
time_unit: str
|
||||
tab_visibilities: bpy.types.bpy_prop_collection[BIMTabVisibility]
|
||||
panel_properties: bpy.types.bpy_prop_collection[BIMPanelProperties]
|
||||
|
||||
|
||||
class IfcParameter(PropertyGroup):
|
||||
@@ -748,6 +774,15 @@ class BIMFacet(PropertyGroup):
|
||||
pset: StringProperty(name="Pset")
|
||||
value: StringProperty(name="Value")
|
||||
type: StringProperty(name="Type")
|
||||
filter_mode: EnumProperty(
|
||||
name="Filter Mode",
|
||||
items=[
|
||||
("ADD", "Add", "Add elements to the result set (query entire IFC file)", "ADD", 0),
|
||||
("SUBTRACT", "Subtract", "Subtract matching elements from previous results", "REMOVE", 1),
|
||||
("FILTER", "Filter", "Filter down previous results to matching elements", "FILTER", 2),
|
||||
],
|
||||
default="ADD",
|
||||
)
|
||||
comparison: EnumProperty(
|
||||
items=[
|
||||
("=", "equal to", ""),
|
||||
@@ -765,6 +800,7 @@ class BIMFacet(PropertyGroup):
|
||||
pset: str
|
||||
value: str
|
||||
type: str
|
||||
filter_mode: Literal["ADD", "SUBTRACT", "FILTER"]
|
||||
comparison: Literal["=", "!=", ">=", "<=", ">", "<", "*=", "!*="]
|
||||
|
||||
|
||||
|
||||
+380
-104
File diff suppressed because it is too large
Load Diff
@@ -487,12 +487,29 @@ def sync_references(
|
||||
potential_reference_elements = drawing_tool.get_potential_reference_elements(drawing)
|
||||
|
||||
for element in potential_reference_elements:
|
||||
# Skip spatial elements - their IFC placement is the source of truth
|
||||
if element.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
|
||||
# Skip grids - their IFC placement is the source of truth
|
||||
if element.is_a("IfcGrid") or element.is_a("IfcGridAxis"):
|
||||
continue
|
||||
|
||||
if (obj := ifc.get_object(element)) and ifc.is_moved(obj):
|
||||
drawing_tool.sync_object_placement(obj)
|
||||
|
||||
for element in drawing_tool.get_group_elements(group):
|
||||
if not drawing_tool.is_auto_annotation(element):
|
||||
continue
|
||||
|
||||
# Skip spatial elements - should never sync their placement
|
||||
if element.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
|
||||
# Skip grids
|
||||
if element.is_a("IfcGrid") or element.is_a("IfcGridAxis"):
|
||||
continue
|
||||
|
||||
if (obj := ifc.get_object(element)) and ifc.is_moved(obj):
|
||||
drawing_tool.sync_object_placement(obj)
|
||||
if not (reference_element := drawing_tool.get_assigned_product(element)):
|
||||
|
||||
@@ -28,9 +28,13 @@ if TYPE_CHECKING:
|
||||
|
||||
def assign_scene_units(ifc: type[tool.Ifc], unit: type[tool.Unit]) -> None:
|
||||
if unit.is_scene_unit_metric():
|
||||
lengthunit = ifc.run("unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT"))
|
||||
lengthunit = ifc.run(
|
||||
"unit.add_si_unit", unit_type="LENGTHUNIT", prefix=unit.get_scene_unit_si_prefix("LENGTHUNIT")
|
||||
)
|
||||
areaunit = ifc.run("unit.add_si_unit", unit_type="AREAUNIT", prefix=unit.get_scene_unit_si_prefix("AREAUNIT"))
|
||||
volumeunit = ifc.run("unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT"))
|
||||
volumeunit = ifc.run(
|
||||
"unit.add_si_unit", unit_type="VOLUMEUNIT", prefix=unit.get_scene_unit_si_prefix("VOLUMEUNIT")
|
||||
)
|
||||
planeangleunit = ifc.run("unit.add_conversion_based_unit", name="degree")
|
||||
units = [lengthunit, areaunit, volumeunit, planeangleunit]
|
||||
|
||||
|
||||
@@ -2123,7 +2123,15 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
|
||||
value = '"' + str(value).replace('"', '\\"') + '"'
|
||||
command = command.replace(variable, value)
|
||||
text = text.replace(original_command, ifcopenshell.util.selector.format(command[2:-2]))
|
||||
# Defensive: skip if command[2:-2] is None or 'None'
|
||||
command_content = command[2:-2]
|
||||
if command_content is None or str(command_content).strip().lower() == "none":
|
||||
text = text.replace(original_command, "")
|
||||
else:
|
||||
try:
|
||||
text = text.replace(original_command, ifcopenshell.util.selector.format(command_content))
|
||||
except Exception:
|
||||
text = text.replace(original_command, "")
|
||||
for variable in re.findall("{{.*?}}", text):
|
||||
value = ifcopenshell.util.selector.get_element_value(product, variable[2:-2])
|
||||
if isinstance(value, (list, tuple)):
|
||||
@@ -2277,7 +2285,16 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
pset = ifcopenshell.util.element.get_psets(drawing).get("EPset_Drawing", {})
|
||||
include = pset.get("Include", None)
|
||||
if include:
|
||||
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include)
|
||||
try:
|
||||
data = json.loads(include)
|
||||
if isinstance(data, dict) and "filter_structure" in data:
|
||||
elements = tool.Search.execute_filter_groups_from_json(data, ifc_file)
|
||||
elif isinstance(data, dict) and "query" in data:
|
||||
elements = ifcopenshell.util.selector.filter_elements(ifc_file, data["query"])
|
||||
else:
|
||||
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
elements = ifcopenshell.util.selector.filter_elements(ifc_file, include)
|
||||
else:
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
base_elements = set(ifc_file.by_type("IfcElement") + ifc_file.by_type("IfcSpatialStructureElement"))
|
||||
@@ -2291,7 +2308,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
if not i.is_a("IfcAnnotation"):
|
||||
updated_set.add(i)
|
||||
# add aggregate too, if element is host by one
|
||||
if decomposes := i.Decomposes:
|
||||
if hasattr(i, "Decomposes") and (decomposes := i.Decomposes):
|
||||
aggregate = decomposes[0].RelatingObject
|
||||
# remove IfcProject for class iterator. See https://github.com/IfcOpenShell/IfcOpenShell/issues/4361#issuecomment-2081223615
|
||||
if aggregate.is_a("IfcProduct"):
|
||||
@@ -2304,7 +2321,18 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
exclude = pset.get("Exclude", None)
|
||||
if exclude:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
try:
|
||||
data = json.loads(exclude)
|
||||
if isinstance(data, dict) and "filter_structure" in data:
|
||||
exclude_elements = tool.Search.execute_filter_groups_from_json(data, ifc_file)
|
||||
elements -= exclude_elements
|
||||
elif isinstance(data, dict) and "query" in data:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, data["query"])
|
||||
else:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
elements -= set(ifc_file.by_type("IfcOpeningElement"))
|
||||
return elements
|
||||
|
||||
@@ -2318,7 +2346,18 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
# NOTE: EPset_Drawing.Include is not used to avoid adding other elements besides spaces
|
||||
exclude = pset.get("Exclude", None)
|
||||
if exclude:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
try:
|
||||
data = json.loads(exclude)
|
||||
if isinstance(data, dict) and "filter_structure" in data:
|
||||
exclude_elements = tool.Search.execute_filter_groups_from_json(data, ifc_file)
|
||||
elements -= exclude_elements
|
||||
elif isinstance(data, dict) and "query" in data:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, data["query"])
|
||||
else:
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
elements -= ifcopenshell.util.selector.filter_elements(ifc_file, exclude)
|
||||
return elements
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -91,20 +91,21 @@ class Root(bonsai.core.tool.Root):
|
||||
elif dest.is_a("IfcTypeProduct"):
|
||||
if not source.RepresentationMaps:
|
||||
return copied_entities
|
||||
|
||||
|
||||
# Copy representation maps while preserving mapped representation structures
|
||||
new_maps = []
|
||||
for i, rep_map in enumerate(source.RepresentationMaps):
|
||||
source_rep = rep_map.MappedRepresentation
|
||||
|
||||
|
||||
# Copy the map itself
|
||||
new_map = ifcopenshell.util.element.copy(tool.Ifc.get(), rep_map)
|
||||
|
||||
|
||||
# Handle the mapped representation - preserve mapping structure if present
|
||||
if (source_rep.RepresentationType == 'MappedRepresentation' and
|
||||
len(source_rep.Items) == 1 and
|
||||
source_rep.Items[0].is_a("IfcMappedItem")):
|
||||
if (
|
||||
source_rep.RepresentationType == "MappedRepresentation"
|
||||
and len(source_rep.Items) == 1
|
||||
and source_rep.Items[0].is_a("IfcMappedItem")
|
||||
):
|
||||
# This is a mapped representation - preserve the structure
|
||||
new_rep = ifcopenshell.util.element.copy(tool.Ifc.get(), source_rep)
|
||||
new_rep.Items = [ifcopenshell.util.element.copy(tool.Ifc.get(), item) for item in source_rep.Items]
|
||||
@@ -118,9 +119,9 @@ class Root(bonsai.core.tool.Root):
|
||||
exclude_callback=exclude_callback,
|
||||
copied_entities=copied_entities,
|
||||
)
|
||||
|
||||
|
||||
new_maps.append(new_map)
|
||||
|
||||
|
||||
dest.RepresentationMaps = new_maps
|
||||
return copied_entities
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user